<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Tools on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/tools/</link><description>Recent content in Tools on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Mon, 17 Mar 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/tools/index.xml" rel="self" type="application/rss+xml"/><item><title>Node.js built in test runner</title><link>https://blog.iankulin.com/node-js-built-in-test-runner/</link><pubDate>Mon, 17 Mar 2025 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/node-js-built-in-test-runner/</guid><description>&lt;p&gt;For the longest time, I&amp;rsquo;ve been using &lt;a href="https://mochajs.org/"&gt;Mocha&lt;/a&gt; (test runner) and &lt;a href="https://www.chaijs.com/"&gt;Chai&lt;/a&gt; (assertion library) for my JS testing. They are reliable old friends.&lt;/p&gt;
&lt;p&gt;One of the effects of the existence of &lt;a href="https://bun.sh/"&gt;Bun&lt;/a&gt; and &lt;a href="https://deno.com/"&gt;Deno&lt;/a&gt; has been to spur Node onto adding some new features, so after appearing as an experimental feature in 18, the Node test runner dropped in Node 20.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not sure if the familiar unit test layout of Mocha and Node is inherited from Jest, or comes from older testing frameworks of which JUnit and NUnit were the first ones I&amp;rsquo;d ever used. Before that I just used to write tests as lumps of assertions in regular code - which worked but wasn&amp;rsquo;t as pleasant to use as a proper unit test setup. Regardless, the system of bundling a few tests together and having them all run and spit out green ticks is not a new one.&lt;/p&gt;
&lt;p&gt;If you are coming from Mocha, there are very few changes to your practice to make. I didn&amp;rsquo;t read &lt;a href="https://nodejs.org/api/test.html#skipping-tests"&gt;the docs&lt;/a&gt; to check that I had to begin my test files with &amp;rsquo;test&amp;rsquo; or to put them into a &amp;rsquo;test&amp;rsquo; directory. But I discovered that dragging them into a &amp;rsquo;test-dont&amp;rsquo; directory didn&amp;rsquo;t stop them from running.&lt;/p&gt;
&lt;h3 id="testing"&gt;Testing&lt;/h3&gt;
&lt;p&gt;As well as what ever you&amp;rsquo;re testing, you need to import a couple of things from node:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;import { isEven } from &amp;#34;../index.js&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;import { describe, it } from &amp;#34;node:test&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;import assert from &amp;#34;node:assert&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then we can write our tests, grouping them in a describe:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;describe(&amp;#34;isEven&amp;#34;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it(&amp;#34;returns true for even numbers&amp;#34;, (t) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assert.strictEqual(isEven(4), true);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assert.strictEqual(isEven(0), true);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assert.strictEqual(isEven(-2), true);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it(&amp;#34;returns false for odd numbers&amp;#34;, (t) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assert.strictEqual(isEven(7), false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assert.strictEqual(isEven(-3), false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it(&amp;#34;returns false for most floating point numbers&amp;#34;, (t) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assert.strictEqual(isEven(3.5), false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // sadly, this is true because JavaScript
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // assert.strictEqual(isEven(4.0000000000000000000001), false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it(&amp;#34;returns false for non-numbers&amp;#34;, (t) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assert.strictEqual(isEven(&amp;#34;a&amp;#34;), false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assert.strictEqual(isEven(null), false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assert.strictEqual(isEven(undefined), false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then to run them, just &lt;code&gt;node --test&lt;/code&gt; and we&amp;rsquo;ll get a nice summary&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-03-07-at-20.15.43.png" width="800" alt=""&gt;
&lt;p&gt;Of course there are a heap of other &lt;a href="https://nodejs.org/api/assert.html"&gt;assertions&lt;/a&gt;, as well as stuff to set-up and tear-down and to &lt;a href="https://nodejs.org/en/learn/test-runner/mocking"&gt;mock things such as times&lt;/a&gt;. What I&amp;rsquo;ve shown here is very much a getting started, but it also deals with about 80% of my testing needs.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not entirely sure how I feel about moving to a built in test runner. The small convenience I gain has to be weighed up against a small amount of lock in to a run time. I haven&amp;rsquo;t yet been tempted to Deno or Bun, but I love that they exist and are spurring on innovation. Possibly I&amp;rsquo;ll continue with portable testing tools for big projects, but for little ones use the built in.&lt;/p&gt;</description></item><item><title>Testing Node.js apps - Mocha, Chai, and Supertest</title><link>https://blog.iankulin.com/testing-node-js-apps-mocha-chai-and-supertest/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/testing-node-js-apps-mocha-chai-and-supertest/</guid><description>&lt;p&gt;Bruno is a great open source Postman/Insomnia replacement, and I&amp;rsquo;ve been using it for basic tests of my node servers using the built in asserts and loving it. This is pretty great, and I gather it&amp;rsquo;s also possible to go beyond this and &lt;a href="https://docs.usebruno.com/testing/introduction.html"&gt;write tests in JS in Bruno&lt;/a&gt;. I believe it also has the hooks needed to build it into your CI/CD systems.&lt;/p&gt;
&lt;p&gt;Any large project is probably going to benefit from a more comprehensive suit of testing tools, and while I&amp;rsquo;ll still be using Bruno, my serious tests will be managed with these other tools.&lt;/p&gt;
&lt;p&gt;I admit I&amp;rsquo;ve probably put this off a bit longer than I should have - I didn&amp;rsquo;t really want to install four dependencies and learn four different things just to test my endpoints. It turns out that using the tools together is seamless, and setting it all up was trivial.&lt;/p&gt;
&lt;p&gt;Speaking of trivial, here&amp;rsquo;s my brilliant Node app. It has two endpoints, both of which do a bit of maths.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; express &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; require&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;express&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; app &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; express&lt;span style="color:#eceff4"&gt;();&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; port &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;3000&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;app&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;use&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;express&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;json&lt;span style="color:#eceff4"&gt;());&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; endpoint that takes two numbers &lt;span style="color:#81a1c1;font-weight:bold"&gt;and&lt;/span&gt; returns their sumapp&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;post&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/sum&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; a&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; b &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;json&lt;span style="color:#eceff4"&gt;({&lt;/span&gt; sum&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; a &lt;span style="color:#81a1c1"&gt;+&lt;/span&gt; b &lt;span style="color:#eceff4"&gt;});});&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; endpoint that takes two numbers &lt;span style="color:#81a1c1;font-weight:bold"&gt;and&lt;/span&gt; multiplies themapp&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;post&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/multiply&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; a&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; b &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;json&lt;span style="color:#eceff4"&gt;({&lt;/span&gt; product&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; a &lt;span style="color:#81a1c1"&gt;*&lt;/span&gt; b &lt;span style="color:#eceff4"&gt;});});&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; start the serverapp&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;listen&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;port&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; console&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;log&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;Maths server is running at http&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt;localhost&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;&lt;span style="color:#eceff4"&gt;{&lt;/span&gt;port&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="setting-up-your-project-for-testing"&gt;Setting up your project for testing&lt;/h3&gt;
&lt;h4 id="install-the-tools"&gt;Install the tools&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm install --save-dev mocha chai supertest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;--save-dev&lt;/code&gt; bit installs them as a development dependencies - they will go in your &lt;code&gt;package.json&lt;/code&gt; and everyone who clones the repo will be working with the same version. Additionally, they won&amp;rsquo;t needlessly be installed when deployed to production.&lt;/p&gt;
&lt;h4 id="export-the-app"&gt;Export the app&lt;/h4&gt;
&lt;p&gt;The testing system needs to be able to control the app a little bit - start it, stop it, and hook into it. To do that, we&amp;rsquo;ll complicate our &lt;code&gt;app.listen&lt;/code&gt; code a bit so that we&amp;rsquo;ve also got a server variable, then we&amp;rsquo;ll export the app and server so out test files can import them. It will end up looking something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;let server&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;process&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;env&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;NODE_ENV &lt;span style="color:#81a1c1"&gt;!==&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#39;test&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; server &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; app&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;listen&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;3000&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; console&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;log&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;Maths server is running at http&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt;localhost&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;&lt;span style="color:#eceff4"&gt;{&lt;/span&gt;port&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#eceff4"&gt;));}&lt;/span&gt;module&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;exports &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; app&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; server &lt;span style="color:#eceff4"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This stops the server being started here if we&amp;rsquo;re in test mode, but exports the bits the test framework needs to manage things.&lt;/p&gt;
&lt;h4 id="create-the-test-files"&gt;Create the test files&lt;/h4&gt;
&lt;p&gt;Our JS test code is all going in a &lt;code&gt;test/&lt;/code&gt; directory in our project, and they will all be named &lt;code&gt;&amp;lt;something&amp;gt;.test.js&lt;/code&gt; I usually use the file name of the file I&amp;rsquo;m testing. So today I&amp;rsquo;m writing tests for &lt;code&gt;app.js&lt;/code&gt; my tests will be in &lt;code&gt;apps.test.js&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Each test file will need to pull in our tools (supertest and chai) and the server and app variables.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s followed by one or more &lt;em&gt;test suites&lt;/em&gt;; each test suite contains one or more &lt;em&gt;test cases&lt;/em&gt;. This might be easier to explain if we look at a real file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; supertest &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; require&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;supertest&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; chai &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; require&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;chai&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; app&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; server &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; require&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;../app&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; expect &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; chai&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;expect&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;describe&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;POST / add&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; it&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;should return the correct sum&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; supertest&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;app&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;post&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/sum&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;({&lt;/span&gt; a&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;5&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; b&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;5&lt;/span&gt; &lt;span style="color:#eceff4"&gt;})&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;expect&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;200&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;then&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;res &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; expect&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;sum&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;to&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;equal&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;10&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});&lt;/span&gt; it&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;should return the correct sum with negative numbers&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; supertest&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;app&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;post&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/sum&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;({&lt;/span&gt; a&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;&lt;span style="color:#b48ead"&gt;5&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; b&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;&lt;span style="color:#b48ead"&gt;5&lt;/span&gt; &lt;span style="color:#eceff4"&gt;})&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;expect&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;200&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;then&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;res &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; expect&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;sum&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;to&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;equal&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;&lt;span style="color:#b48ead"&gt;10&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});});&lt;/span&gt;describe&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;POST / multiply&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; it&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;should return the correct product&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; supertest&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;app&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;post&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/multiply&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;({&lt;/span&gt; a&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;5&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; b&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;5&lt;/span&gt; &lt;span style="color:#eceff4"&gt;})&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;expect&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;200&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;then&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;res &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; expect&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;product&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;to&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;equal&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;25&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});});&lt;/span&gt;server&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;close&lt;span style="color:#eceff4"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This file contains two test suites - &amp;lsquo;POST/add&amp;rsquo; and &amp;lsquo;POST/multiply&amp;rsquo;. POST/add contains two test cases (each begins with &lt;code&gt;it&amp;lt;statement of what the test subject should do&amp;gt;&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s no end to the tests you can write. I normally do basic functionality as I&amp;rsquo;m writing code, and I also add in tests for anything that emerges as a bug. If you get into the rhythm of bug -&amp;gt; write failing test -&amp;gt; fix bug -&amp;gt; test passes you can have your day punctuated by little doses of dopamine. I often write a timed test - that an endpoint should respond in 10ms. These don&amp;rsquo;t help you when you are developing, but sure will later. You should also check that all of the wrong inputs users will eventually try have been handled. If an API expects a number, check for errors being thrown for strings, for negative numbers, for huge numbers, for decimals, for booleans, for objects etc etc.&lt;/p&gt;
&lt;p&gt;Another thing I will do is use a code coverage tool to check my test covers all the branches and error conditions. I plan to talk about that another day. First I need to show you how to run the tests.&lt;/p&gt;
&lt;h4 id="add-test-script"&gt;Add test script&lt;/h4&gt;
&lt;p&gt;If we had installed mocha globally, we could just call it from the command line with something like:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mocha ./test/app.test.js&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;But we didn&amp;rsquo;t do that, so we need npm to start it up for us. I know this seems like another time wasting step, but it&amp;rsquo;s one of those do it once, benefit from it thousands of times things.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;package.json&lt;/code&gt; file, we can add a section called scripts. If you started you project with &lt;code&gt;npm init&lt;/code&gt; you may already have this section, if not, just add it in. It&amp;rsquo;s common to have a &lt;code&gt;run&lt;/code&gt; and a &lt;code&gt;test&lt;/code&gt; script, and I often have one or two others. Here&amp;rsquo;s the sort of thing you want.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{ &amp;#34;name&amp;#34;: &amp;#34;test-demo&amp;#34;, &amp;#34;version&amp;#34;: &amp;#34;0.1.0&amp;#34;, &amp;#34;description&amp;#34;: &amp;#34;Simple Maths API&amp;#34;, &amp;#34;main&amp;#34;: &amp;#34;app.js&amp;#34;, &amp;#34;scripts&amp;#34;: { &amp;#34;start&amp;#34;: &amp;#34;node app.js&amp;#34;, &amp;#34;test&amp;#34;: &amp;#34;mocha &amp;#39;./test/*.test.js&amp;#39;&amp;#34; }, &amp;#34;dependencies&amp;#34;: { &amp;#34;express&amp;#34;: &amp;#34;^4.18.2&amp;#34; }, &amp;#34;devDependencies&amp;#34;: { &amp;#34;chai&amp;#34;: &amp;#34;^4.3.10&amp;#34;, &amp;#34;mocha&amp;#34;: &amp;#34;^10.2.0&amp;#34;, &amp;#34;nyc&amp;#34;: &amp;#34;^15.1.0&amp;#34;, &amp;#34;supertest&amp;#34;: &amp;#34;^6.3.3&amp;#34; }}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;npm&lt;/code&gt; does the magic to make the correct version of the library available when this script is run. The end effect of these is that you can type &lt;code&gt;npm test&lt;/code&gt; at the command line, and mocha will run your tests. Let&amp;rsquo;s try it would our tests.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-12-16-at-8.18.28-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-12-16-at-8.18.28-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s what we like to see, passing tests. I&amp;rsquo;ll make one fail by telling it to expect 5x5=26.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-12-16-at-8.22.53-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-12-16-at-8.22.53-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s it, you&amp;rsquo;re all set up to write tests against your node apps.&lt;/p&gt;
&lt;h3 id="what-do-the-different-bits-do"&gt;What do the different bits do?&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s a lot of moving parts here, lets tease those out a little.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://mochajs.org/#getting-started"&gt;mocha&lt;/a&gt; - this is the test framework. As we&amp;rsquo;ve discussed, it&amp;rsquo;s the command line tool that runs the tests and produces the output.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.npmjs.com/package/supertest"&gt;supertest&lt;/a&gt; - manages the connections between the test runner/framework and the code being tested. When I&amp;rsquo;m pressing a button in Bruno, it&amp;rsquo;s actually hitting localhost:3000 to exercise the server which I&amp;rsquo;ve previously started. supertest is doing magic to make that connection without going through the network layers.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.chaijs.com"&gt;chai&lt;/a&gt; - it provides the assert()s, expect()s and should()s that we use in the test cases. You could, in theory make do with the assert() library built into node - especially for our toy demo app - but it&amp;rsquo;s no where near as nice, and in particular chai has a massive set of plugins that both extend it&amp;rsquo;s use generally, but also into working at a detailed level with other vendor packages.&lt;/p&gt;</description></item><item><title>Bruno asserts</title><link>https://blog.iankulin.com/bruno-asserts/</link><pubDate>Sat, 11 Nov 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/bruno-asserts/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-22-at-12.11.09-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-22-at-12.11.09-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I mentioned &lt;a href="https://www.usebruno.com/"&gt;Bruno&lt;/a&gt; the other day. Although it&amp;rsquo;s still very much under development, it is shaping up as a great Postman/Insomnia replacement.&lt;/p&gt;
&lt;p&gt;One of the aspects I&amp;rsquo;ve been using today is asserts. As part of a request, you can add some asserts - so when you&amp;rsquo;re hitting an endpoint it will check what status should it be returning, or given the data you&amp;rsquo;re passing in, what should be in the response body.&lt;/p&gt;
&lt;p&gt;When I&amp;rsquo;d asked ChatGPT to to review the mdserver code, it had suggested that I should be sanitising URL inputs better to prevent users transversing out of the &amp;lsquo;public&amp;rsquo; file directory to other places in the file system. I thought Express had already taken care of this for me, but wanted to check. I had ChatGPT generate a bunch of pass and fail URL examples, then just created asserts for each one in Bruno.&lt;/p&gt;
&lt;p&gt;Once that&amp;rsquo;s done, you can just right click on the collection and have it run all of those.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-22-at-12.19.59-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;An extra benefit of Bruno is that all these requests are stored as JSON-like, version-controllable text. I store them in my project and commit them along with the rest of my code.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-22-at-12.25.43-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-22-at-12.25.43-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>We need to talk about Bruno</title><link>https://blog.iankulin.com/we-need-to-talk-about-bruno/</link><pubDate>Fri, 27 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/we-need-to-talk-about-bruno/</guid><description>&lt;p&gt;&lt;a href="https://www.usebruno.com/"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-01-at-6.01.17-pm.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve &lt;a href="https://blog.iankulin.com/how-to-deploy-a-node-js-app/"&gt;mentioned before&lt;/a&gt; that I was using Insomnia as a tool to check my REST APIs as I was developing them, and that I was avoiding Postman (which I guess is more widely used since it&amp;rsquo;s worth &lt;a href="https://techcrunch.com/2021/08/18/api-platform-postman-valued-at-5-6-billion-in-225-million-fundraise/"&gt;USD5.6 billion&lt;/a&gt;) because&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The only reason I&amp;rsquo;m using Insomnia instead of Postman is that when I tried Postman, it straight away wanted some of my data to make it work. Insomnia hasn&amp;rsquo;t forced me to do that yet.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sadly that was prophetic. I saw &lt;a href="https://vmst.io/@wtpisaac"&gt;@wtpisaac@vmst.io&lt;/a&gt; &lt;a href="https://vmst.io/@wtpisaac/111150369449470670"&gt;mention this exact thing&lt;/a&gt; had happened.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-01-at-5.43.52-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The following day when I opened up Insomnia, it asked to upgrade and I said no, and was able to write and save a handful of new calls to test the API I was building. Sadly, I closed it, and of course on the next run, it demanded an account be created. I skipped that, and it presented me with a &amp;ldquo;sandbox&amp;rdquo; and all my saved requests were gone.&lt;/p&gt;
&lt;p&gt;Luckily, Isaac also suggests a solution - &lt;a href="https://www.usebruno.com/"&gt;Bruno&lt;/a&gt;. This is a 1K star FOSS project by &lt;a href="https://github.com/helloanoop"&gt;helloanoop&lt;/a&gt;. There are Mac, Windows and Linux clients, as well as a CLI and VSCode plugins. One of it&amp;rsquo;s selling points (apart from it&amp;rsquo;s not Postman or Insomnia) is that the collections of requests are saved in very simple human readable text (a language called &lt;em&gt;bru&lt;/em&gt;) so it&amp;rsquo;s straightforward and sensible to commit them to source control along with your code.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-01-at-6.14.09-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-01-at-6.14.09-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This video from Anoop (with a very clickbait-y title) does a good job of explaining his project.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/b_ctmKlEOXg?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;I&amp;rsquo;ve only played around with Bruno for an afternoon, but I&amp;rsquo;m loving it so far. Seems like it will do everything I need, and the diffable files for the requests are a bonus. This is a project that deserves to be better known.&lt;/p&gt;</description></item><item><title>Hide 'Problems' for a file in VS Code</title><link>https://blog.iankulin.com/hide-problems-for-a-file-in-vs-code/</link><pubDate>Fri, 25 Aug 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/hide-problems-for-a-file-in-vs-code/</guid><description>&lt;p&gt;I&amp;rsquo;m interested in trying out &lt;a href="https://picocss.com/"&gt;Pico CSS&lt;/a&gt; - a lightweight CSS library, but when I tossed it into my project, the linter found and reported 29 problems. One of my processes is to just keep that problems tab clear as I work, so I&amp;rsquo;d like that to go away.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-20-at-6.54.06-am.jpg" alt="Screenshot of VS Code showing 29 problems detected."&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s possible, but only by &amp;rsquo;excluding&amp;rsquo; the file from your project - it won&amp;rsquo;t show up in the file view either. That&amp;rsquo;s fine with me, I never want to deal with the file so we&amp;rsquo;ll do that, although it might confuse me in seven years if I come back to this project, so I&amp;rsquo;ll drop a link in my .git_ignore as a clue for future me (excluding the file in VS Code doesn&amp;rsquo;t affect git finding it).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-20-at-7.03.25-am.png" alt="screenshot of .gitignore file including public/pico.min.css"&gt;&lt;/p&gt;
&lt;p&gt;Workspace settings (such as excluding a file) are stored in &lt;code&gt;./vscode/settings.json&lt;/code&gt; - this has some other bits and pieces such as spelling corrections etc. It&amp;rsquo;s worth letting this into your repository so your workspace is recreated when you clone the repo. The fragment you need to add is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;files.exclude&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;**/pico.min.css&amp;#34;: true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will remove it from the files, and it will stop it being processed by your extensions including any linters. If the problems don&amp;rsquo;t disappear instantly, click into a different file for a second and they should go, or occasionally, you&amp;rsquo;ll need to close and open VSCode.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-20-at-6.54.47-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-20-at-6.54.47-am.png" width="800" alt="Screenshot of VS Code showing settings.json and zero problems in the problems tab."&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>CodePen</title><link>https://blog.iankulin.com/codepen/</link><pubDate>Sun, 29 Jan 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/codepen/</guid><description>&lt;p&gt;I think I&amp;rsquo;ve written about CodePen before, its a site that allows users to quickly put together HTML, CSS &amp;amp; JS and see the results as they edit. Users &amp;lsquo;pens&amp;rsquo; are public and can be tagged, so it also serves as a repository of examples.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s possible to host incredibly complex artefacts, such as this &lt;a href="https://codepen.io/ricardoolivaalonso/pen/RwBZMGB"&gt;3D Sony Walkman&lt;/a&gt;, but what I mostly use it for is to work out simple things - like how to &lt;a href="https://codepen.io/IanKulin/pen/wvxrZxW"&gt;collapse a row of text into a column&lt;/a&gt; with a media query.&lt;/p&gt;
&lt;p&gt;The alternative to a site like this for those jobs would be to create a couple of scratch files and open up another instance of VS Code, but this is quicker, the results are immediately available, and are saved if I ever need to go back to them.&lt;/p&gt;
&lt;p&gt;The free tier more than covers my needs, but the paid tier (about $10 a month) includes being able to make your pens private and hosting image assets there. The private options would be nice to hide your tests if you were going to use it as a portfolio - which I don&amp;rsquo;t doubt some designers would.&lt;/p&gt;
&lt;p&gt;As a bonus, pens can easily be embedded in blog posts on WordPress.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://codepen.io/IanKulin/pen/BaPjmNv"&gt;View embed&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Digital Color Meter</title><link>https://blog.iankulin.com/digital-color-meter/</link><pubDate>Wed, 18 Jan 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/digital-color-meter/</guid><description>&lt;p&gt;For the Calculator project, I needed to know the exact RGB values for the colours on the iOS calculator buttons so I could reproduce them. Assuming a tool for reading colours from the screen exisited, I googled it, and was surprised to find this exact tool is already installed by default on MacOS.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s called Digital Color Meter and just shows the RGB values for anything on the screen under the cursor.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-08-at-2.26.12-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;In order to copy the values, hit &lt;code&gt;Command|L&lt;/code&gt; to freeze the current colour, then copy them from the Colour menu. Also in that menu you can choose to have the values shown as hex.&lt;/p&gt;
&lt;h4 id="cursor-in-screenshots"&gt;Cursor in screenshots&lt;/h4&gt;
&lt;p&gt;While I&amp;rsquo;m doing tips and tricks, to have the cursor showing in a Mac screenshot (which I needed for the image above), do &lt;code&gt;shift|command|5&lt;/code&gt; (which is the time delay whole screenshot) then turn it on in the option bar that appears before starting the timer.&lt;/p&gt;</description></item><item><title>Lost in Translation</title><link>https://blog.iankulin.com/lost-in-translation/</link><pubDate>Wed, 11 Jan 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/lost-in-translation/</guid><description>&lt;p&gt;We&amp;rsquo;re in a pretty good place now (compared to a few years ago) in terms of being able to rely on JavaScript behaving the same on different platforms. There&amp;rsquo;s still some differences (mostly in when things are implemented) but overall, not to bad once you decide to no longer support Internet Explorer.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript"&gt;In times past, it was a lot more painful&lt;/a&gt;. A few of approaches to deal with this arose. One is to let a library, such as &lt;a href="https://jquery.com/"&gt;jQuery&lt;/a&gt; or a &lt;a href="https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills"&gt;polyfill&lt;/a&gt; deal with it, and the other is use a translation utility such as Babel to down convert (transpile) your modern JavaScript to something that will run in more browsers.&lt;/p&gt;
&lt;p&gt;Babel can be run from the command line, and therefore integrated into a toolchain, but if you want to have a play with it, they have an interactive version on their web site. Here&amp;rsquo;s a couple of examples of how arrow functions and backtick templates get converted to run on IE 6.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-01-02-at-3.09.56-pm.png" alt=""&gt;&lt;/p&gt;</description></item><item><title>CodePen.io</title><link>https://blog.iankulin.com/codepen-io/</link><pubDate>Thu, 05 Jan 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/codepen-io/</guid><description>&lt;p&gt;I quite often leave a link to a GitHub repo to share my source in these posts, and on a few recent ones, a link to a live version of a page on my github.io. In a recent installment of &lt;a href="https://www.udemy.com/course/the-complete-web-developer-zero-to-mastery/"&gt;CWD&lt;/a&gt;, Andrei shared some previous students&amp;rsquo; solutions, and some were hosted on CodePen.io which I hadn&amp;rsquo;t seen before.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-12-28-at-10.20.02-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a cute concept, you can enter HTML, CSS &amp;amp; JS and see a live view of the page below. It looks super extensible - there&amp;rsquo;s mentions of SCSS, Typescript and preprocessors for JS in the settings.&lt;/p&gt;
&lt;p&gt;There are a few quirks - you don&amp;rsquo;t enter all the DOCTYPE/&lt;html&gt;&lt;body&gt; etc in your HTML- presumably that&amp;rsquo;s because they already need that for their page. There are settings for some of these for example if you need to add a clss to the &lt;html&gt;. I couldn&amp;rsquo;t see how to do that for the &lt;body&gt; though so I needed to change the JS for this project a bit.&lt;/p&gt;
&lt;p&gt;It seems pretty great - sort of like Playgrounds for web dev. It&amp;rsquo;s not quite a full debugging IDE - that doesn&amp;rsquo;t seem to exist for web dev. There&amp;rsquo;s no intellisense, but it was flagging problems in my JavaScript, so I&amp;rsquo;ll use it for a few of these tutorial pages and see how it goes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://codepen.io/IanKulin/pen/BaPjmNv"&gt;Here&amp;rsquo;s a link&lt;/a&gt; to the tutorial app above on CodePen.&lt;/p&gt;</description></item><item><title>Running Javascript in VS Code</title><link>https://blog.iankulin.com/running-javascript-in-vs-code/</link><pubDate>Tue, 27 Dec 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/running-javascript-in-vs-code/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-12-21-at-11.08.17-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer"&gt;Live Server&lt;/a&gt; plugin to see HTML &amp;amp; CSS updated as I edit, and that will also be useful when I start using Javascript for web development, but as you can see above, I&amp;rsquo;m not quite up to that. It seemed there should be a way to run JS in VS Code, and it turns out it&amp;rsquo;s easy.&lt;/p&gt;
&lt;p&gt;You just need something installed that can run Javascript. Node.js is the obvious choice, and you&amp;rsquo;re going to need it later in your development journey. Just i&lt;a href="https://nodejs.org/en/download/"&gt;nstall Node.js&lt;/a&gt; then the first time you try to run some JS in VS code, it will ask you what to use, select Node and you&amp;rsquo;re in business.&lt;/p&gt;
&lt;p&gt;I found out about this &lt;a href="https://linuxhint.com/javascript-visual-studio-code/"&gt;from here&lt;/a&gt;. I didn&amp;rsquo;t worry about Code Runner - just using Node.js worked for me without any fiddling beyond installing it (this is on Mac though - your Windows mileage may vary).&lt;/p&gt;
&lt;p&gt;While I&amp;rsquo;m doing handy hints for dev tools, I discovered last night that the baby webserver that Live Server is running, isn&amp;rsquo;t just available on the local machine, it&amp;rsquo;s available to anyone on your network. Instead of using 127.0.0.1:5500, use the IP address of your development machine (but still port 5500 if that&amp;rsquo;s what you&amp;rsquo;re using). It&amp;rsquo;s an excellent way to look at your layout on phones etc, or, I guess, to see what other devs at your company are working on :- )&lt;/p&gt;</description></item><item><title>Who is Emmet?</title><link>https://blog.iankulin.com/who-is-emmet/</link><pubDate>Wed, 14 Dec 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/who-is-emmet/</guid><description>&lt;p&gt;&lt;a href="https://www.piqsels.com/en/public-domain-photo-ircsa"&gt;&lt;img src="https://blog.iankulin.com/images/css-hacks.jpg" width="728" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I knew there was some magical way of entering all the the &lt;HTML&gt; boilerplate in Visual Studio Code as I&amp;rsquo;d seen it happen in several videos, and assumed is was some sort of macro expansion thing in the editor. Fast forward a few blog post readings and youtube viewings and I keep seeing tangential references to someone called Emmet. Turns out they&amp;rsquo;re the same thing, and it&amp;rsquo;s pretty cool.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not a new idea to have functionality in code editors to insert snippets of code. &lt;a href="https://docs.emmet.io/"&gt;Emmet&lt;/a&gt; goes a bit further than that - and like many tools made by programmers for programmers it goes way to technical to the point where you need to memorise ridiculous amounts of combos to to some awesome stuff (I&amp;rsquo;m looking at you whoever made it possible to use vi commands in VS Code). Nevertheless, Emmet is extremely handy even at my n00b level.&lt;/p&gt;
&lt;p&gt;The key thing to know, is that it borrows from the CSS selector syntax. So if you want to insert &lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; you enter &lt;code&gt;div&lt;/code&gt; and press tab.&lt;/p&gt;
&lt;p&gt;Want a div with a class named &amp;ldquo;container&amp;rdquo;?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;div.container&lt;/code&gt; becomes &lt;code&gt;&amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The same trick works for an id - Enter&lt;/p&gt;
&lt;p&gt;&lt;code&gt;div#emmet&lt;/code&gt; becomes &lt;code&gt;&amp;lt;div id=&amp;quot;emmet&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Would you like a div, with a heading inside? The greater than sign nests elements, so &lt;code&gt;div&amp;gt;h4&lt;/code&gt; becomes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;h4&amp;gt;&amp;lt;/h4&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;d like some text up in there, try div&amp;gt;h4{Hello world}&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;h4&amp;gt;Hello world&amp;lt;/h4&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can repeat things numbers of times, so to create a list with three items, try &lt;code&gt;ul&amp;gt;li*3&lt;/code&gt; to get:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;ul&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/ul&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s about as complex as I&amp;rsquo;d want to get, though of course it gets more complex. It&amp;rsquo;s a super handy feature that quickly becomes second nature.&lt;/p&gt;</description></item><item><title>ChatGPT's code writing</title><link>https://blog.iankulin.com/chatgpts-code-writing/</link><pubDate>Sun, 11 Dec 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/chatgpts-code-writing/</guid><description>&lt;p&gt;This week, the internet has been all about &lt;a href="https://chat.openai.com/chat"&gt;ChatGPT&lt;/a&gt;, the rather remarkable natural language AI with a very large model. If you&amp;rsquo;re a twitter user, you were probably amazed, but now eventually tired of seeing examples of it&amp;rsquo;s output. I&amp;rsquo;ll add to that with an example of a SwifUI CoreData based todo app it wrote for me from a single sentence prompt below. Rather than look at other people&amp;rsquo;s examples you should definitely go and play with it yourself - it is very impressive. Along with the image based AI&amp;rsquo;s it&amp;rsquo;s made 2022 into a historical year for AI.&lt;/p&gt;
&lt;p&gt;ChatGPT will write software. I can&amp;rsquo;t see it replacing developers, but it seems likely there&amp;rsquo;ll be a bunch of interesting tools to come over the next few years. I feel like the first big effect will be the volume of mediocre internet content will increase dramatically as it is pretty great at run of the mill copy-writing. I&amp;rsquo;d like to assure my readers that all my mediocre internet content will continue to be human written - with the exception of most of the art, and the code example below :- )&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;write swiftui code for a todo application where the items reoccur after a certain number of days. The items are shown in a list and the user can navigate to a detail view.&lt;/p&gt;
&lt;p&gt;me&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Here is an example of SwiftUI code for a todo application where the items reoccur after a certain number of days:&lt;/p&gt;
&lt;p&gt;ChatGPT&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;TodoItem&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Identifiable &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; id &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; UUID&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; task&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; dueDate&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Date
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; repeatInterval&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Int&lt;/span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// number of days until the task reoccurs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;TodoList&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;TodoItem&lt;span style="color:#eceff4"&gt;]()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; item &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NavigationLink&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;destination&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; TodoDetail&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; item&lt;span style="color:#eceff4"&gt;))&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;task&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;TodoDetail&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; item&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; TodoItem
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;task&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Due: &lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;dueDate&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; formatter&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; DateFormatter&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;mediumDate&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Reoccurs every: &lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;repeatInterval&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt; days&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;In the &lt;code&gt;TodoList&lt;/code&gt; view, a &lt;code&gt;List&lt;/code&gt; is used to display the todo items. Each item in the list is wrapped in a &lt;code&gt;NavigationLink&lt;/code&gt; that takes the user to a &lt;code&gt;TodoDetail&lt;/code&gt; view when tapped. The &lt;code&gt;TodoDetail&lt;/code&gt; view displays the task, the due date, and the repeat interval for the selected todo item.&lt;/p&gt;
&lt;p&gt;You can modify this code to suit your specific needs, such as adding features to add or delete items, or changing the layout and appearance of the views.&lt;/p&gt;
&lt;p&gt;ChatGPT&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That&amp;rsquo;s pretty cool. I don&amp;rsquo;t think that DateFormatter.mediumDate is a thing, so I changed that line to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Text(&amp;#34;Due: \(item.dueDate, style: .date)&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s okay ChatGPT, I get confused about the variety of ways to change dates too.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;great. show me the content view code that contains the TodoList and has a button to add items.&lt;/p&gt;
&lt;p&gt;me&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Here is an example of a &lt;code&gt;ContentView&lt;/code&gt; that contains a &lt;code&gt;TodoList&lt;/code&gt; and has a button to add items:&lt;/p&gt;
&lt;p&gt;ChatGPT&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ContentView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;TodoItem&lt;span style="color:#eceff4"&gt;]()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TodoList&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#bf616a"&gt;$&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;action&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// add new todo item here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;})&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Add Item&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;In this code, the &lt;code&gt;ContentView&lt;/code&gt; contains a &lt;code&gt;TodoList&lt;/code&gt; and a &lt;code&gt;Button&lt;/code&gt;. The &lt;code&gt;TodoList&lt;/code&gt; is initialized with the &lt;code&gt;items&lt;/code&gt; array, which is marked as &lt;code&gt;@State&lt;/code&gt; so that it can be modified in response to user actions. The &lt;code&gt;Button&lt;/code&gt; has an action closure that is executed when the button is tapped. You can add code inside this closure to create a new &lt;code&gt;TodoItem&lt;/code&gt; and add it to the &lt;code&gt;items&lt;/code&gt; array.&lt;/p&gt;
&lt;p&gt;You can modify this code to suit your specific needs, such as changing the layout and appearance of the views, or adding features to delete items from the list.&lt;/p&gt;
&lt;p&gt;ChatGPT&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To let this compile, I commented out the Preview and changed the items property in the top of the TodoList view to a binding:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;struct TodoList&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#bf616a"&gt;@&lt;/span&gt;Binding &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; items&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;TodoItem&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;thank you. what would the code for adding an item look like&lt;/p&gt;
&lt;p&gt;me&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Here is an example of code that you can use to add a new &lt;code&gt;TodoItem&lt;/code&gt; to the &lt;code&gt;items&lt;/code&gt; array when the button is tapped:&lt;/p&gt;
&lt;p&gt;ChatGPT&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ContentView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;TodoItem&lt;span style="color:#eceff4"&gt;]()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TodoList&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#bf616a"&gt;$&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;action&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; newItem &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; TodoItem&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;task&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;New Task&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; dueDate&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Date&lt;span style="color:#eceff4"&gt;(),&lt;/span&gt; repeatInterval&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;7&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; items&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;append&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;newItem&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;})&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Add Item&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;In this code, the &lt;code&gt;Button&lt;/code&gt; has an action closure that creates a new &lt;code&gt;TodoItem&lt;/code&gt; with the task &amp;ldquo;New Task&amp;rdquo;, a due date of the current date, and a repeat interval of 7 days. The new item is then appended to the &lt;code&gt;items&lt;/code&gt; array.&lt;/p&gt;
&lt;p&gt;You can modify this code to suit your specific needs, such as prompting the user for the task name and due date, or changing the default repeat interval. You can also add additional logic to handle errors or validation failures when creating the new item.&lt;/p&gt;
&lt;p&gt;ChateGPT&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This all runs and the add item works, but the NavigationLink to get to the detail view does not. Let&amp;rsquo;s wrap it in a NavigationView to fix that.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;struct ContentView&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#bf616a"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; items &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;TodoItem&lt;span style="color:#eceff4"&gt;]()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NavigationView &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TodoList&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#bf616a"&gt;Button&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;action&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; let newItem &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; TodoItem&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;task&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;New Task&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; dueDate&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Date&lt;span style="color:#eceff4"&gt;(),&lt;/span&gt; repeatInterval&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;7&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; items&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;append&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;newItem&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;})&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Add Item&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I went on to ask it to change this to core data for persistence, but as I wanted the app to be more and more complex, it needed me to fix more and more problems, and became less fun.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-12-10-at-2.47.12-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-12-10-at-2.47.16-pm.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>Copying a file via SSH</title><link>https://blog.iankulin.com/copying-a-file-via-ssh/</link><pubDate>Tue, 29 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/copying-a-file-via-ssh/</guid><description>&lt;p&gt;I have a Raspberry Pi on my home network that I purchased for some project that I can&amp;rsquo;t actually recall. It gets used for all sorts of completely unnecessary things such as playing with node.js or a private git server. To add to the list of things that I do on pi that could be more efficiently done on my MacBook I wanted to host my sample JSON from yesterday on it.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d already downloaded the JSON to my laptop (although I could have skipped that step and used curl to download it directly to the pi) and just wanted to transfer it into the apache html directory.&lt;/p&gt;
&lt;p&gt;I feel like in 2022 I should just be able to SSH to the directory on the pi, then drag and drop the file from my mac, but that&amp;rsquo;s not possible (although it does usefully paste the filepath to the file which could be helpful). Of course there is a command for this, you just need to know it - &lt;code&gt;scp&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;My pi is on 192.168.100.13 and I log in as ian. The file I want to copy is called sample_students.json.&lt;/p&gt;
&lt;p&gt;In a terminal window, on your development machine:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;scp sample_students.json ian@192.168.100.13:/home/ian&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The first argument is the file to copy, and the second is the ssh accessible machine you are copying to with the path after the colon.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-11-26-at-7.57.13-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-11-26-at-7.56.32-am.png" alt=""&gt;&lt;/p&gt;</description></item><item><title>Mock Data</title><link>https://blog.iankulin.com/mock-data/</link><pubDate>Mon, 28 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/mock-data/</guid><description>&lt;p&gt;One of the things we need during app development is some data to play with. It would be unethical for me to use real student data to test my app, even if I wasn&amp;rsquo;t sharing screenshots of the development here, so I&amp;rsquo;ll need to build some mock data. The prospect of making 400 rows of data manually does not sound like a good use of time, so I started to think about generating it in Excel. I&amp;rsquo;d used an online &amp;ldquo;random address generator&amp;rdquo; for an earlier project, so I was contemplating pasting that sort of data into Excel workbooks and randomly selecting from it.&lt;/p&gt;
&lt;p&gt;It occurred to me someone probably already solved this problem, and a quick search confirmed there are hundreds of web based test data generating sites. They provide data in all sorts of formats as downloads or via APIs. Somewhat at random, I selected &lt;a href="https://www.mockaroo.com/"&gt;Mockaroo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.mockaroo.com/"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-11-26-at-5.56.38-am.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In addition to letting you specify the fields, you control the type of data to go in them. They have a heap of options of specific formats such as ISBN&amp;rsquo;s, airport codes and so on. The whole thing took a couple of minutes to setup.&lt;/p&gt;
&lt;p&gt;As I was doing it, it occurred to me that a completely random date of birth didn&amp;rsquo;t make perfect sense - there really should be a connection between the year group of students and their date of birth. It&amp;rsquo;s completely immaterial for this project, so I didn&amp;rsquo;t bother with it, but in fact Mockaroo has ruby based scripting that allows you the flexibility to make one field somehow calculated from others so that would be possible&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-11-26-at-5.56.05-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;It was a simple matter to download my data as JSON, but there&amp;rsquo;s also options for fetching it from a REST API.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-11-26-at-5.59.38-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-11-26-at-5.59.38-am.png" width="273" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll download and host this on my home Raspberry Pi server that I bought for a different project, but it&amp;rsquo;s possible just to pull it direct from their server.&lt;/p&gt;
&lt;p&gt;A small note of caution is that if you sign in to save your schema, it is saved, but it&amp;rsquo;s also public. That may not matter for your context, but in some situations it could be important. This could be from the data you provide as options, or even just the property names might provide intelligence useful to someone. As IT professionals we need to be mindful of the risks we&amp;rsquo;re taking with client or user information and to either eliminate or explain them.&lt;/p&gt;</description></item><item><title>iOS 16 Developer Mode</title><link>https://blog.iankulin.com/ios-16-developer-mode/</link><pubDate>Sat, 19 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/ios-16-developer-mode/</guid><description>&lt;p&gt;I updated my iPhone to iOS16 this morning, and tonight when I went to run one of my apps, it complained that it needed Developer mode. This is a new, probably wise, way to avoid dodgy apps being loaded on a phone. I don&amp;rsquo;t know exactly how you&amp;rsquo;d do that, but then I&amp;rsquo;m not a black hatted cyber terrorist.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/img_3319.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I had to google the setting, it&amp;rsquo;s in &amp;ldquo;Privacy and Security&amp;rdquo; down the bottom, and requires a reboot. When you open the phone there&amp;rsquo;s another dialog and you need to reauth.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/img_3320.png" width="577" alt=""&gt;
&lt;img src="https://blog.iankulin.com/images/img_3321.png" width="577" alt=""&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/img_3322.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>SwiftLint</title><link>https://blog.iankulin.com/swiftlint/</link><pubDate>Mon, 10 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/swiftlint/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2022-10-04-at-08-30-59-code-complete-mcconnell-steve-amazon.com_.au-books.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I was watching a &lt;a href="https://www.techwithtim.net/"&gt;Tim Ruscica&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?v=wJNikDr-aNM"&gt;video&lt;/a&gt; about the things that highly effective developers do, and it called to mind a book I read years ago called &lt;a href="https://www.amazon.com.au/Code-Complete-Steve-McConnell/dp/0735619670"&gt;Code Complete&lt;/a&gt;. It is the only book I ever owned that I immediately purchased the new edition when it came out. It was about the meta stuff around programming that is the difference between coding and developing. In particular, it got me invested in source control and testing.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve been reading along, you&amp;rsquo;ll know I am keen to leverage the great tools available to support quality software development, and one I haven&amp;rsquo;t tackled till this week is a linter.&lt;/p&gt;
&lt;p&gt;Linters are tools to enforce (or at least suggest) rules to improve your code that are not strictly necessary (the compiler hasn&amp;rsquo;t enforced them) but they make your code better, or at least prettier, in other ways. I gather the most popular linter for Swift is &lt;a href="https://github.com/realm/SwiftLint"&gt;SwiftLint&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I installed it by downloading and running the &lt;a href="https://github.com/realm/SwiftLint/releases/download/0.49.1/SwiftLint.pkg"&gt;.pkg&lt;/a&gt;. Then in any Xcode projects you want to use it in, you go into &lt;em&gt;Build Phases&lt;/em&gt; for the current &lt;em&gt;Scheme&lt;/em&gt; in your project and add a new script that runs SwiftLint on the files in the project&amp;rsquo;s folder. There&amp;rsquo;s a good step by step &lt;a href="https://medium.com/developerinsider/how-to-use-swiftlint-with-xcode-to-enforce-swift-style-and-conventions-368e49e910"&gt;here&lt;/a&gt; by Vineet Choudhary. You should end up with something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-04-at-8.39.34-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Now when you Command-B to build your project, the linter will run and (especially the first time) add some extra warnings and errors.&lt;/p&gt;
&lt;p&gt;SwiftLint has many rules. Some are default rules (these are the most widely accepted ones) and some are optional. Turning rules off or on, or altering their parameters is done using a &lt;code&gt;swiftlint.yml&lt;/code&gt; config file in the top level folder of your project.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example from &lt;a href="https://github.com/IanKulin/dotfiles/blob/main/.swiftlint.yml"&gt;mine&lt;/a&gt;, where I want to alter a rule:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vertical_whitespace:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; max_empty_lines: 2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s a rule called vertical_whitespace. It throws a warning if there is more than one consecutive empty line in a file. I use two line gaps all over my code to signify a separate method or function, so that does not work for me. The config change above changes it to allow two empty lines. This same config file can also be used to disable any of the default rules, or enable any of the optional rules.&lt;/p&gt;
&lt;p&gt;Rules can also be disabled and enabled in your code with special comments. For example, in my tests, I have enormous multi-line strings which don&amp;rsquo;t follow proper indentation for a deliberate reason, so at the top of this file I have:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// swiftlint:disable line_length
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// swiftlint:disable indentation_width
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can turn rules back on as well. For example in this code snippet I have code that the linter wants me to write as a trailing closure, but I&amp;rsquo;m not sure how to do that in this situation yet - so I turn the rule off before it, then back on.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;stripSpaces&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;_&lt;/span&gt; codeLines&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt; &lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// break into lines&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; lines &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; codeLines&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;components&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;separatedBy&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ebcb8b"&gt;\n&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; minCount &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Int&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;max&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// step though the lines and count how many spaces, save the minimum amount&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; line &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; lines &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// swiftlint:disable trailing_closure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; leadingSpaces &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; line&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;prefix&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;while&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; $0 &lt;span style="color:#eceff4"&gt;==&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34; &amp;#34;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}).&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;count&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// swiftlint:enable trailing_closure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; minCount &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; leadingSpaces &lt;span style="color:#81a1c1"&gt;&amp;lt;&lt;/span&gt; minCount &lt;span style="color:#eceff4"&gt;?&lt;/span&gt; leadingSpaces &lt;span style="color:#eceff4"&gt;:&lt;/span&gt; minCount
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// step though the lines again, and trim the min amount from each line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; index &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; &lt;span style="color:#b48ead"&gt;0.&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&amp;lt;&lt;/span&gt;lines&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;count&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lines&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;index&lt;span style="color:#eceff4"&gt;]&lt;/span&gt; &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;lines&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;index&lt;span style="color:#eceff4"&gt;].&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;dropFirst&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;minCount&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// stitch it back up&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; lines&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;joined&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;separator&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ebcb8b"&gt;\n&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;My strategy with the linter was to turn all the optional rules on, then run it and go through to consider each of the optional rules I&amp;rsquo;ve transgressed to decide if it makes more sense to me to change the code or eliminate the rule.&lt;/p&gt;</description></item><item><title>Testing, testing</title><link>https://blog.iankulin.com/testing-testing/</link><pubDate>Sun, 09 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/testing-testing/</guid><description>&lt;p&gt;I have unit testing in my goals, and if I&amp;rsquo;m going to throw this &lt;a href="https://blog.iankulin.com/codetrimmer-first-macos-app/"&gt;space trimming&lt;/a&gt; macOS utility up on the web, now might be a good time to figure out how to add unit tests to a project, how to write them, and how to run them. XCode is well set up for this, so it&amp;rsquo;s really no drama to do.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-03-at-9.09.32-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Although I haven&amp;rsquo;t worried with any unit testing up to this point in my iOS/Swift learning, in my previous programming work I did a lot of work with the large calculations involved in translating GPS coordinates and robotic positioning models where errors would be bad - so I&amp;rsquo;ve written a lot of tests over the years. I&amp;rsquo;ve also definitely felt the confidence you can dramatically refactor code with when you know the code has a robust test suite. I&amp;rsquo;m a big fan.&lt;/p&gt;
&lt;h4 id="adding-tests-to-existing-project"&gt;Adding Tests to Existing Project&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-10-03-at-9.28.39-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-03-at-9.28.39-pm.png" width="239" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In Xcode, with your project open, you need to add the &lt;em&gt;testing target&lt;/em&gt;. &lt;code&gt;File | New | Target...&lt;/code&gt; then scroll down to find the &lt;em&gt;Unit Testing Bundle&lt;/em&gt;. When you add that, you&amp;rsquo;ll see a new folder in the Project Navigator, and a new source file - both with the name &lt;code&gt;&amp;lt;app name&amp;gt;Tests&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you click into the &lt;code&gt;&amp;lt;app name&amp;gt;Test.swift&lt;/code&gt; file, you&amp;rsquo;ll see a couple of methods for setting up the testing environment and cleaning up after it. They are called before and after each test - you&amp;rsquo;d use them if you needed to set something up like some files in a directory or similar and wanted to ensure it was not affected by the tests run before the current test. Don&amp;rsquo;t worry about them for the time being.&lt;/p&gt;
&lt;h4 id="writing-tests"&gt;Writing Tests&lt;/h4&gt;
&lt;p&gt;Before we can write any tests, we need to import any modules with the code we want to test. In my case, I want to test the function stripSpaces() which is in the ContentView of my app called CodeTrimmer. So in the top of the CodeTrimmerTests.swift file, under the other import, I add the import:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;import XCTest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@testable import CodeTrimmer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then we can write our tests. The test functions must start with the magic prefix &lt;em&gt;test&lt;/em&gt; so that Xcode treats them as such. The usual approach in the test is to call a function with some specific input, then test the output is what is expected with a special test version of assert():&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a couple. My function stripSpaces() deletes a number of spaces from each line of a block of code such that the block becomes left aligned. if the XCTAsserts() pass, the test passes and I get a green tick. To run the tests I just click in the breakpoint gutter next to the function declaration.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;testStripSpaces01&lt;/span&gt;&lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;throws&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; testString &lt;span style="color:#eceff4"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a3be8c"&gt; single line string no spaces
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a3be8c"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; resultString &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; stripSpaces&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;testString&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; XCTAssertTrue&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;resultString &lt;span style="color:#eceff4"&gt;==&lt;/span&gt; testString&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; testString&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;testStripSpaces02&lt;/span&gt;&lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;throws&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; testString &lt;span style="color:#eceff4"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a3be8c"&gt; single line 4 spaces
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a3be8c"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; resultString &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; stripSpaces&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;testString&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; XCTAssertFalse&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;resultString &lt;span style="color:#eceff4"&gt;==&lt;/span&gt; testString&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; testString&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; XCTAssertTrue&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;testString&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;dropFirst&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;4&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;==&lt;/span&gt; resultString&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; testString&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>How to upgrade XCode and Swift</title><link>https://blog.iankulin.com/how-to-upgrade-xcode-and-swift/</link><pubDate>Sun, 18 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/how-to-upgrade-xcode-and-swift/</guid><description>&lt;p&gt;With the September release of XCode 14 and Swift 5.7 it was time for my first update - I looked in &amp;ldquo;About&amp;rdquo; for an update link but there wasn&amp;rsquo;t one - so if you&amp;rsquo;re as dense as me, the tip is to head to the &lt;a href="https://www.google.com/url?sa=t&amp;amp;rct=j&amp;amp;q=&amp;amp;esrc=s&amp;amp;source=web&amp;amp;cd=&amp;amp;cad=rja&amp;amp;uact=8&amp;amp;ved=2ahUKEwjN8OGn85r6AhXlWHwKHf85DzAQFnoECBUQAQ&amp;amp;url=https%3A%2F%2Fapps.apple.com%2Fus%2Fapp%2Fxcode%2Fid497799835%3Fmt%3D12&amp;amp;usg=AOvVaw2fEvMbfRtGhB4SPHYB54NX"&gt;Mac App Store&lt;/a&gt; and have a look at the Updates page.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-17-at-8.53.34-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Your current XCode version can, of course be found in the &lt;em&gt;XCode | About&lt;/em&gt; dialogue. Mine was on 13.4.1. There&amp;rsquo;s a couple of ways of finding the Swift version - If you&amp;rsquo;ve got an XCode project open, click on the .xcodeproj in the explorer,and have a look in &lt;em&gt;Build Settings&lt;/em&gt; for &lt;em&gt;Swift Compiler - Language&lt;/em&gt; for the major version.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-17-at-8.56.44-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Or for a bit more accuracy, try &lt;code&gt;xcrun swift -version&lt;/code&gt; at the command line. I got a few errors, then the version 5.6.1&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-17-at-8.55.13-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I won&amp;rsquo;t bang on about the &lt;a href="https://www.youtube.com/watch?v=tYBZ8AVH0Q0"&gt;changes for XCode&lt;/a&gt; or &lt;a href="https://www.swift.org/blog/swift-5.7-released/"&gt;Swift&lt;/a&gt; since that&amp;rsquo;s already been done well elsewhere, and the Swift changes are mostly beyond my expertise level.&lt;/p&gt;</description></item><item><title>Visual Studio Code</title><link>https://blog.iankulin.com/visual-studio-code/</link><pubDate>Fri, 19 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/visual-studio-code/</guid><description>&lt;img src="https://blog.iankulin.com/images/visual_studio_code_1.35_icon.svg_.png" width="149" alt=""&gt;
&lt;p&gt;I&amp;rsquo;ve noticed &lt;a href="https://code.visualstudio.com/"&gt;Visual Studio Code&lt;/a&gt; in a few videos, and admired what a clean interface it had, and was impressed how opening a terminal window was automatically in the directory you were working in.&lt;/p&gt;
&lt;p&gt;I had a need to write some html/css, and some C++ in the last couple of days, so that seemed like a great excuse to give it a try. I&amp;rsquo;d have to say my opinion of it has only gone up. Clearly, it is right at home with HTML and CSS - code completion and syntax colouring all working nicely. I followed TechWithTim&amp;rsquo;s suggestion to install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer"&gt;Live Server extension&lt;/a&gt; - which was a completely painless experience.&lt;/p&gt;
&lt;p&gt;Same positive experience when I created a .cpp file, and VSC wanted me to take Microsoft&amp;rsquo;s advice about a bunch of extensions, and correctly suggested I build with Mac&amp;rsquo;s clang compiler.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-08-11-at-3.40.15-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So many things just work how you expect them to. Within seconds I was trying out breakpoints, compiling and managing the git commits. I even tried the Swift extension.&lt;/p&gt;
&lt;p&gt;I have no intention of leaving Xcode, but with a great tool like this, I will never be a &lt;a href="https://blog.iankulin.com/vim/"&gt;vim&lt;/a&gt; guy.&lt;/p&gt;</description></item><item><title>Oh My Zsh</title><link>https://blog.iankulin.com/oh-my-zsh/</link><pubDate>Sat, 06 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/oh-my-zsh/</guid><description>&lt;p&gt;I&amp;rsquo;ve been playing in the zsh shell since I started on the &lt;a href="https://blog.iankulin.com/missing-semester/"&gt;Missing Semester&lt;/a&gt;, and was wondering how to get my git branch name in the prompt. A few googles later, I&amp;rsquo;ve installed Oh My Zsh, and added the git and macos plugins. Pretty.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-08-02-at-7.26.08-pm.jpg" alt=""&gt;&lt;/p&gt;</description></item></channel></rss>