<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Everyday Frontend 🚀]]></title><description><![CDATA[Follow me on my journey as I write about real tech topics I face in my everyday life as a Frontend Developer.]]></description><link>https://cathalmacdonnacha.com</link><generator>RSS for Node</generator><lastBuildDate>Sat, 18 Apr 2026 10:58:47 GMT</lastBuildDate><atom:link href="https://cathalmacdonnacha.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Fixing Slow & Flaky Frontend Tests]]></title><description><![CDATA[After noticing a spike in failing Jest builds, I decided to investigate and found that slow and flaky tests were the main culprits. In this article, I go through some tips I discovered for making slow or flaky tests faster and more reliable.
There ar...]]></description><link>https://cathalmacdonnacha.com/fixing-slow-and-flaky-frontend-tests</link><guid isPermaLink="true">https://cathalmacdonnacha.com/fixing-slow-and-flaky-frontend-tests</guid><category><![CDATA[Testing]]></category><category><![CDATA[Testing Library]]></category><category><![CDATA[Jest]]></category><category><![CDATA[React]]></category><category><![CDATA[flaky-tests]]></category><dc:creator><![CDATA[Cathal Mac Donnacha]]></dc:creator><pubDate>Mon, 08 Sep 2025 13:42:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/ELfMdIihkTg/upload/ebf7c25111bd66b13ad0ee8dd1f8c79c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After noticing a spike in failing Jest builds, I decided to investigate and found that slow and flaky tests were the main culprits. In this article, I go through some tips I discovered for making slow or flaky tests faster and more reliable.</p>
<p>There are two main sections:</p>
<ul>
<li><p><a class="post-section-overview" href="#heading-improving-slow-tests">Improving slow tests</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-preventing-flaky-tests">Preventing flaky tests</a></p>
</li>
</ul>
<p>So let’s dive in!</p>
<h2 id="heading-improving-slow-tests"><strong>Improving slow tests</strong></h2>
<p>While researching and investigating our vast test suite, I noticed a few issues that were making tests run slowly. Here’s how we fixed them:</p>
<h3 id="heading-1-render-only-whats-necessary">1. Render only what's necessary</h3>
<p>Rendering large React components/pages was the <strong>most common</strong> reason for our tests to run slowly. Therefore, it's important to only render what is truly necessary when running each test.</p>
<p>If you're testing something quite isolated like max characters validation on an input field, you can just render a form (or even the input field itself) instead of the entire page. On the other hand, if you're asserting that an item is added to a table after filling out a form, rendering the parent component might make more sense.</p>
<p>Instead of rendering large tables or data grids in every test (e.g 50 rows), consider limiting the rows to just a handful.</p>
<h3 id="heading-2-consider-other-react-testing-library-queries">2. Consider other React Testing Library queries:</h3>
<p>It's important that we use the <code>byRole</code> query by default to <a target="_blank" href="https://testing-library.com/docs/queries/about/#priority">help improve our accessibility testing</a>. However, the <code>byRole</code> query is <a target="_blank" href="https://github.com/testing-library/dom-testing-library/issues/820">known to be slow</a> in some scenarios. As per the <a target="_blank" href="https://testing-library.com/docs/queries/byrole/#performance">guidance given by the Testing Library team</a>, if necessary, it may be worth considering alternatives like <code>byLabelText</code> or <code>byText</code> if you start to face performance issues.</p>
<h3 id="heading-3-consider-using-hidden-true-to-skip-visibility-checks">3. Consider using <code>hidden: true</code> to skip visibility checks</h3>
<p>As mentioned previously, it's best to mimic real user experiences as much as possible. However, if needed, <code>getByRole</code> performance can be improved by setting the <a target="_blank" href="https://testing-library.com/docs/queries/byrole/#hidden">hidden</a> option to <code>true</code> which avoids expensive visibility checks. Note that since it skips visibility checks, it will return <strong>all</strong> elements, even those that are not visible to the user.</p>
<pre><code class="lang-typescript">getByRole(<span class="hljs-string">'button'</span>, { name: <span class="hljs-string">'Submit'</span>, hidden: <span class="hljs-literal">true</span> });
</code></pre>
<h3 id="heading-4-use-msw-mock-service-worker-to-mock-endpoints">4. Use MSW (Mock Service Worker) to mock endpoints</h3>
<p>Prevent real API calls during testing to eliminate network delays and improve speed.</p>
<p>For more information on how to mock endpoints in the frontend, see <a target="_blank" href="https://mswjs.io/">Mock service worker (MSW)</a>.</p>
<h3 id="heading-5-avoid-usereventhover-where-possible">5. Avoid <code>userEvent.hover</code> where possible</h3>
<p>Hover actions can slow down tests. If necessary, consider using <code>userEvent.hover(button, { pointerEventsCheck: PointerEventsCheckLevel.Never })</code> as it skips multiple pointer event checks.</p>
<p>Using <code>{ pointerEventsCheck: PointerEventsCheckLevel.Never }</code> also comes in useful when you want to hover over a disabled element to ensure a tooltip is displayed. Without setting this option, the test will fail with an error specifying that the element is disabled and we cannot interact with it.</p>
<p>Again, only consider this when you actually face performance issues. It’s always best to simulate real user interactions by default.</p>
<h3 id="heading-6-optimize-component-source-code">6. Optimize component source code</h3>
<p>Optimize the source code of the components being rendered. If it’s slow or re-renders often within the browser, it’ll be even slower when running tests.</p>
<blockquote>
<p>Complex tests are often a symptom of complex code</p>
</blockquote>
<p>Given this 👆, it’s worth taking the time to refactor and simplify the code where possible.</p>
<h3 id="heading-7-combine-assertions">7. Combine assertions</h3>
<p>This may seem to be a bit of a contradiction, but sometimes it’s faster to write longer tests rather than multiple smaller ones.</p>
<p>Though we can run into timeouts when we have longer running tests, in most cases you can <a target="_blank" href="https://kentcdodds.com/blog/write-fewer-longer-tests">write fewer, longer tests</a> which actually result in faster overall test suite runs. i.e one single test which takes 2 seconds vs 2 separate tests which take 1.5 seconds each due to setup time.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// BEFORE</span>
it(<span class="hljs-string">'disables the purchase button while loading the page'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> purchaseButton = <span class="hljs-keyword">await</span> screen.findByRole(<span class="hljs-string">'button'</span>, { name: <span class="hljs-string">'Purchase'</span>});
  expect(purchaseButton).toBeDisabled();
});

it(<span class="hljs-string">'allows the user to purchase stuff'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-comment">// Fill in some required form fields...</span>
  ...

  expect(purchaseButton).toBeEnabled();

  <span class="hljs-keyword">await</span> userEvent.click(purchaseButton);
  expect(<span class="hljs-keyword">await</span> screen.findByText(<span class="hljs-string">'Purchased!'</span>).toBeInTheDocument();
});

<span class="hljs-comment">// AFTER</span>
<span class="hljs-comment">// We can move the assertion from the first test into the second, which shortens test suite time and actually improves readability.</span>
it(<span class="hljs-string">'allows the user to purchase stuff'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> purchaseButton = <span class="hljs-keyword">await</span> screen.findByRole(<span class="hljs-string">'button'</span>, { name: <span class="hljs-string">'Purchase'</span>});
  expect(purchaseButton).toBeDisabled();

  <span class="hljs-comment">// Fill in some required fields...</span>
  ...

  expect(purchaseButton).toBeEnabled();

  <span class="hljs-keyword">await</span> userEvent.click(purchaseButton);
  expect(<span class="hljs-keyword">await</span> screen.findByText(<span class="hljs-string">'Purchased!'</span>).toBeInTheDocument();
});
</code></pre>
<h3 id="heading-8-avoid-using-delay-in-userevent">8. Avoid using <code>delay</code> in <code>userEvent</code></h3>
<p>This has significant impacts on the performance of tests:</p>
<pre><code class="lang-typescript">userEvent.setup({ delay: <span class="hljs-number">1000</span> });
</code></pre>
<h3 id="heading-9-avoid-manually-readingwriting-to-the-dom">9. Avoid manually reading/writing to the DOM</h3>
<p>This can be quite slow in general, especially in JSDOM. React is optimized to do this in the most performant way possible. Therefore, it's best to avoid doing this ourselves.</p>
<h3 id="heading-10-avoid-using-regex-to-find-elements">10. Avoid using regex to find elements</h3>
<p>We found using regex instead of strings where unnecessary can add a minor performance overhead.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ❌ Unnecessary use of regex and has a minor performance impact.</span>
getByRole(‘button’, { name: <span class="hljs-regexp">/save/i</span> })

<span class="hljs-comment">// ✅ More performant and also prevents UX bugs.</span>
getByRole(‘button’, { name: ‘Save’ })
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Opinion time: The first example also has the potential to cause a minor UX bug since if a button's text was changed from "Save" to "save" by mistake, it won't be caught when using regex. You can decide if that’s a big deal or not.</div>
</div>

<h3 id="heading-11-consider-focus-and-paste-instead-of-usereventtype">11. Consider <code>.focus()</code> and <code>.paste()</code> instead of <code>userEvent.type</code></h3>
<p>When simulating user typing in tests, <code>userEvent.type</code> can be slow as it carefully simulates each keypress event. For improved performance in cases where you don't need to test specific keyboard interaction behaviors, you can use <code>.focus()</code> and <code>.paste()</code> methods instead.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ❌ Can be slow for longer input text</span>
<span class="hljs-keyword">const</span> descriptionInput = screen.getByLabelText(<span class="hljs-string">'Description'</span>);

<span class="hljs-keyword">await</span> userEvent.type(
  screen.getByLabelText(<span class="hljs-string">'Description'</span>),
  <span class="hljs-string">'This is a very long description text that would trigger multiple events'</span>
);

<span class="hljs-comment">// ✅ Faster alternative when you don't need to test keyboard behavior</span>
<span class="hljs-keyword">const</span> descriptionInput = screen.getByLabelText(<span class="hljs-string">'Description'</span>);

<span class="hljs-comment">// Focus on the input first</span>
descriptionInput.focus();

<span class="hljs-comment">// Paste the entire value directly</span>
<span class="hljs-keyword">await</span> userEvent.paste(
  descriptionInput,
  <span class="hljs-string">'This is a very long description text that would trigger multiple events'</span>
);
</code></pre>
<h3 id="heading-12-use-fake-timers-for-components-with-timeouts-or-debounce">12. Use fake timers for components with timeouts or debounce</h3>
<p>When testing components that rely on timeouts or debounce, you should use Jest's fake timers to skip the actual waiting time.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🚨</div>
<div data-node-type="callout-text">It's important to also call <code>runOnlyPendingTimers</code> before switching to real timers to ensure all pending timers are flushed. If you don't progress the timers and just switch to real timers, the scheduled tasks won't get executed and you may get unexpected behavior.</div>
</div>

<pre><code class="lang-typescript">it(<span class="hljs-string">'searches and finds a user'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });

  jest.useFakeTimers();

  render(&lt;UsersTable /&gt;);
  <span class="hljs-keyword">const</span> searchInput = <span class="hljs-keyword">await</span> screen.findByRole(<span class="hljs-string">'textbox'</span>, { name: <span class="hljs-string">'Search users'</span> });

  <span class="hljs-keyword">await</span> user.type(searchInput, <span class="hljs-string">'Jimmy'</span>);
  expect(<span class="hljs-keyword">await</span> screen.findByText(<span class="hljs-string">'Jimmy Mc Nulty'</span>)).toBeInTheDocument();

  <span class="hljs-comment">// Flush any pending timers and switch to real timers to avoid unexpected behavior in subsequent tests.</span>
  jest.runOnlyPendingTimers();
  jest.useRealTimers();
});
</code></pre>
<p>For more information on how to use fake timers, see:</p>
<ul>
<li><p><a target="_blank" href="https://jestjs.io/docs/timer-mocks#enable-fake-timers">Jest's documentation on fake timers</a></p>
</li>
<li><p><a target="_blank" href="https://testing-library.com/docs/using-fake-timers">Testing Library's documentation on fake timers</a></p>
</li>
<li><p><a target="_blank" href="https://testing-library.com/docs/user-event/options/#advancetimers">Testing Library's documentation on using <code>advanceTimers</code> with userEvent</a></p>
</li>
</ul>
<h2 id="heading-preventing-flaky-tests">Preventing flaky tests</h2>
<p>What even is a flaky test? It’s a test that sometimes fails, but if you retry it enough times, it passes, eventually.</p>
<p>Outside of speeding up slow tests, here are a couple of other things we can do to increase the reliability of our tests:</p>
<h3 id="heading-1-wait-for-an-element-to-be-visible-before-asserting-or-performing-an-action-on-it">1. Wait for an element to be visible before asserting or performing an action on it</h3>
<p>This happens more often when testing UIs which rely on async data. Therefore, it's important to use <code>findBy</code> when working with elements that may appear asynchronously.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ❌ This test can randomly fail as we don't wait for the element to be visible before asserting.</span>
it(<span class="hljs-string">'adds a user to the table'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> userEvent.click(screen.getByRole(<span class="hljs-string">'button'</span>, { name: <span class="hljs-string">'Add user'</span> }));
  expect(screen.getByText(<span class="hljs-string">'User added!'</span>)).toBeInTheDocument();
});

<span class="hljs-comment">// ✅ This test passes as we use findBy, which waits for the element first.</span>
it(<span class="hljs-string">'adds a user to the table'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> userEvent.click(<span class="hljs-keyword">await</span> screen.findByRole(<span class="hljs-string">'button'</span>, { name: <span class="hljs-string">'Add user'</span> }));
  expect(screen.getByText(<span class="hljs-string">'User added!'</span>)).toBeInTheDocument();

  <span class="hljs-comment">// We can use getBy from here on, until another async action occurs.</span>
  expect(screen.getByText(<span class="hljs-string">'Jimmy Mc Nulty'</span>)).toBeInTheDocument();
  expect(screen.getByText(<span class="hljs-string">'Detective'</span>)).toBeInTheDocument();

  <span class="hljs-comment">// Here's another async action</span>
  <span class="hljs-keyword">await</span> userEvent.click(screen.getByRole(<span class="hljs-string">'button'</span>, { name: <span class="hljs-string">'Update user'</span> }));

  <span class="hljs-comment">// Need to use findBy here again after performing an async action above.</span>
  expect(<span class="hljs-keyword">await</span> screen.findByText(<span class="hljs-string">'User updated!'</span>)).toBeInTheDocument();
});
</code></pre>
<h3 id="heading-2-avoid-asserting-loading-states-on-initial-load">2. Avoid asserting loading states on initial load</h3>
<p>We often show skeleton loading states while waiting for async data to load. This results in a nice experience as the user knows that something is happening in the background. However, since we use MSW to mock data in our tests, fetching often completes so quickly that the query can’t find the “loading” element by the time the assertion takes place.</p>
<p>Instead, it’s best to find an element you know will be present when the page has completed all required fetching and is fully loaded. This gives you the same result, but is more robust as you are no longer dealing with random timings or render cycles.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ❌ This assertion can randomly timeout and fail because the loading element never appears in the first place. The fetch completed so quickly!</span>
expect(<span class="hljs-keyword">await</span> screen.findByText(<span class="hljs-string">'Loading...'</span>)).toBeInTheDocument();

<span class="hljs-comment">// ✅ Instead, find an element you know will only appear when the UI is fully loaded.</span>
expect(
  <span class="hljs-keyword">await</span> screen.findByRole(<span class="hljs-string">'button'</span>, { name: <span class="hljs-string">'Purchase the thing'</span> })
)).toBeInTheDocument();
</code></pre>
<p><strong>But I want to test loading states…</strong></p>
<p>If you are solely testing a loading component and are using MSW, you can make use of <code>delay</code> (or <a target="_blank" href="https://cathalmacdonnacha.com/mocking-error-empty-and-loading-states-with-msw">page url query params</a>) to simulate a delay in the response, which will allow you to test the loading state in isolation.</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">'shows a loading state'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  server.use(
    http.get(<span class="hljs-string">'/users'</span>, <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">await</span> delay(<span class="hljs-string">'infinite'</span>)
    })
  )

  <span class="hljs-keyword">await</span> screen.findByText(<span class="hljs-string">'Loading...'</span>);
})
</code></pre>
<h3 id="heading-3-disable-react-query-retries">3. Disable React Query retries</h3>
<p>By default, in an error scenario, React Query will retry three times. This isn’t something you want to happen when running tests that intentionally check for error scenarios. Therefore, it’s best to turn off retries completely when running tests. The easiest way to do this is to create a custom wrapper which you can then reuse across all tests.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> queryClient = <span class="hljs-keyword">new</span> QueryClient({
  defaultOptions: {
    queries: {
      <span class="hljs-comment">// ✅ turns retries off</span>
      retry: <span class="hljs-literal">false</span>,
    },
  },
})

<span class="hljs-keyword">const</span> wrapper = <span class="hljs-function">(<span class="hljs-params">{ children }</span>) =&gt;</span> (
  &lt;QueryClientProvider client={queryClient}&gt;{children}&lt;/QueryClientProvider&gt;
);
</code></pre>
<p>See <a target="_blank" href="http://tanstack.com/query/v4/docs/framework/react/guides/testing#turn-off-retries">Turn off retries</a> for more information.</p>
<h3 id="heading-4-reset-msw-handlers">4. Reset MSW handlers</h3>
<p>When using MSW handlers directly in your tests, it's important to reset the handlers after each test to avoid side effects in subsequent tests. This can be done by calling <code>server.resetHandlers()</code> within the <code>afterEach</code> block.</p>
<pre><code class="lang-typescript">afterEach(<span class="hljs-function">() =&gt;</span> {
  server.resetHandlers();
});

it(<span class="hljs-string">'shows the list of users'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  server.use(
    http.get(<span class="hljs-string">'/users'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">return</span> HttpResponse.json({ data: users });
    })
  );

  render(&lt;SomeComponent /&gt;);

  <span class="hljs-comment">// Your test logic...</span>
});
</code></pre>
<p>Another option is to add it to <a target="_blank" href="https://github.com/alan2207/bulletproof-react/blob/49c4249fd68ef2196151ef34cc2c68cb4fe81dc1/apps/react-vite/src/testing/setup-tests.ts"><code>setupTests.ts</code></a> which will run it for each test across your project.</p>
<h3 id="heading-5-use-advancetimers-instead-of-delaynull-when-using-fake-timers">5. Use <code>advanceTimers</code> instead of <code>delay:null</code> when using fake timers</h3>
<p>As mentioned in the <a target="_blank" href="https://testing-library.com/docs/using-fake-timers">Testing Library documentation</a>, using <code>delay: null</code> can lead to unexpected behavior in tests. Instead, use <code>advanceTimers</code> to control the timing of your tests more predictably.</p>
<h3 id="heading-6-remove-dead-code-and-tests">6. Remove dead code and tests</h3>
<p>It's important to continuously remove code and accompanying tests that are no longer customer facing. Otherwise, they unnecessarily take up valuable CI/CD time and resources.</p>
<h3 id="heading-7-avoid-testing-implementation-details">7. Avoid testing implementation details</h3>
<p>If we already have UI based tests to cover features, we likely don’t need to test its <a target="_blank" href="https://kentcdodds.com/blog/testing-implementation-details">implementation details</a> as well (e.g <code>expect(function).toHaveBeenCalled()</code>), which are generally less valuable and often require changes any time you refactor your code.</p>
<h2 id="heading-measuring-and-identifying-slow-tests">Measuring and identifying slow tests</h2>
<p>We use <a target="_blank" href="https://github.com/Neizan93/jest-slow-test-highlighter">jest-slow-test-highlighter</a> to highlight slow tests. This runs automatically as part of each test run and will highlight any individual test that takes longer than a 5 seconds to run. The times will vary depending on the device it's being run on (your fast laptop vs a slower CI machine).</p>
<p>Another simple alternative is to use <code>console.time</code>, which is useful to determine exactly which line is causing bottlenecks in your test.</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should perform some action'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.time(<span class="hljs-string">'test-duration'</span>);

  <span class="hljs-comment">// Your test logic here</span>

  <span class="hljs-comment">// Logs the time spent on this test</span>
  <span class="hljs-built_in">console</span>.timeEnd(<span class="hljs-string">'test-duration'</span>);
});
</code></pre>
<h2 id="heading-other-considerations">Other considerations</h2>
<p>Here's a list of other things we considered, but they either didn't seem to make much difference, or we haven't gotten around to trying out yet:</p>
<ul>
<li><p>Switching from Jest to Vitest.</p>
</li>
<li><p>Switch from <code>babel-jest</code> to <a target="_blank" href="https://github.com/swc-project/jest">swc/jest</a> transformer.</p>
</li>
<li><p>Disable type checking in Jest by setting <code>isolatedModules</code> to <code>true</code> in <code>jest.config.ts</code>.</p>
</li>
<li><p>Switch from JSDOM to <a target="_blank" href="https://github.com/capricorn86/happy-dom">happy-dom</a>.</p>
</li>
<li><p>Try different variations of Jest's <a target="_blank" href="https://jestjs.io/docs/cli#--maxworkersnumstring">--maxWorkers flag</a>.</p>
</li>
</ul>
<h2 id="heading-final-thoughts"><strong>Final thoughts</strong></h2>
<p>After applying these fixes across our test suite, we saw a noticeable boost in speed and reliability, resulting in far fewer broken CI builds. Hopefully you can apply some of these same techniques to speed up your own tests and make them more reliable.</p>
<p>If you found this article helpful or have additional tips, please leave a comment below. 🙂</p>
<h2 id="heading-want-to-see-more">Want to see more?</h2>
<p>I mainly write about real tech topics I face in my everyday life as a frontend engineer. If this appeals to you then feel free to follow me on <a target="_blank" href="https://bsky.app/profile/cathalmacdonnacha.com"><strong>BlueSky</strong></a>. 🦋</p>
<p>Bye for now. 👋</p>
]]></content:encoded></item><item><title><![CDATA[Route-based code splitting with React]]></title><description><![CDATA[Code splitting is a technique used to optimize the loading performance of web apps by breaking down the bundled JavaScript files into smaller, more manageable chunks. By loading only the required code for a specific route or page, route-based code sp...]]></description><link>https://cathalmacdonnacha.com/route-based-code-splitting-with-react</link><guid isPermaLink="true">https://cathalmacdonnacha.com/route-based-code-splitting-with-react</guid><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Cathal Mac Donnacha]]></dc:creator><pubDate>Fri, 04 Aug 2023 13:27:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1691155541753/584c17e9-7e49-4082-ba4c-b12503517e7e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Code splitting is a technique used to optimize the loading performance of web apps by breaking down the bundled JavaScript files into smaller, more manageable chunks. By loading only the required code for a specific route or page, route-based code splitting significantly reduces the initial load time and improves the overall user experience.</p>
<p>In this article, we will explain some aspects of how we can achieve route-based code splitting along with some code examples.</p>
<h2 id="heading-why-route-based-code-splitting"><strong>Why Route-based Code Splitting?</strong></h2>
<p>When developing large-scale applications, loading all the JavaScript code upfront can lead to increased initial load times and negatively impact user experience. In contrast, route-based code splitting allows you to divide your application into smaller chunks based on different routes or features. Only the code relevant to the current route is loaded, resulting in faster loading times for the specific page and better overall application performance.</p>
<p>By using route-based code splitting, you can prioritize the most critical code for each route, optimizing the initial loading experience and reducing the time to interactive (TTI).</p>
<h2 id="heading-what-do-we-need"><strong>What do we need?</strong></h2>
<p>In order to actually implement route-based code splitting, we need to make use of two things:</p>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import">Dynamic import</a></p>
</li>
<li><p><a target="_blank" href="https://react.dev/reference/react/lazy">React.lazy()</a></p>
</li>
</ul>
<p>Let's take a look at these in a bit more detail.</p>
<h3 id="heading-dynamic-import">Dynamic import</h3>
<p>Dynamic import is an <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import">ECMAScript feature</a> that allows us to import modules on the fly. This is really powerful and unless you're unlucky enough to have to support IE, it can be used in all <a target="_blank" href="https://caniuse.com/es6-module-dynamic-import">major browsers</a>.</p>
<p>Here's what the syntax looks like:</p>
<p><code>import('./path-to-my-module.js');</code></p>
<p><code>import</code> will return a promise, so you would handle it just like any other promise within your app.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span>(<span class="hljs-string">'./carModule.js'</span>)
  .then(<span class="hljs-function"><span class="hljs-params">module</span> =&gt;</span> {
    <span class="hljs-built_in">module</span>.startEngine();
  })
  .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error loading carModule:'</span>, error);
});

<span class="hljs-comment">// or you can use async/await</span>
<span class="hljs-keyword">const</span> carModule = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./carModule.js'</span>);
carModule.startEngine();
</code></pre>
<p>A great use case for this would be when you only make use of a heavy module in a specific part of your app.</p>
<pre><code class="lang-typescript">&lt;button onClick={onSortCarsClick}&gt;Sort cars&lt;button/&gt;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onSortCarsClick</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Load in the heavy module</span>
  <span class="hljs-keyword">const</span> carModule = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./carModule.js'</span>);
  carModule.sortCars();
}
</code></pre>
<h2 id="heading-reactlazy"><strong>React.lazy()</strong></h2>
<p><code>React.lazy()</code> is a function in React that enables you to perform "lazy" or "on-demand" loading of components. It ensures that the component will only be loaded when it's actually rendered.</p>
<p>Before <code>React.lazy()</code> was introduced, you might have needed to set up a more complex build tooling configuration to achieve similar code splitting behavior. With <code>React.lazy()</code>, this process is simplified and integrated directly into React's core.</p>
<p><code>const MyLazyComponent = React.lazy(() =&gt; import('./MyComponent'));</code></p>
<h3 id="heading-suspense">Suspense</h3>
<p>Since the component is no longer statically imported, we need to display something while it's dynamically loading. For that, we use React's <code>&lt;Suspense&gt;</code> boundary.</p>
<p>In the example below, you'll see we are displaying some fallback UI while the component is being loaded.</p>
<pre><code class="lang-typescript">&lt;Suspense fallback={&lt;div&gt;Loading...&lt;/div&gt;}&gt;
  &lt;MyLazyComponent/&gt;
&lt;/Suspense&gt;
</code></pre>
<h2 id="heading-show-me-the-code"><strong>Show me the code</strong></h2>
<p>Here's a full example of how we can use a combination of dynamic imports, <code>React.lazy()</code> and React Router to achieve route-based code splitting.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { lazy, Suspense } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { BrowserRouter <span class="hljs-keyword">as</span> Router, Switch, Route, Link } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-comment">// These components will only be loaded when they're actually rendered.</span>
<span class="hljs-keyword">const</span> Home = lazy(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./components/Home'</span>));
<span class="hljs-keyword">const</span> About = lazy(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./components/About'</span>));
<span class="hljs-keyword">const</span> Contact = lazy(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./components/Contact'</span>));

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;Router&gt;
      &lt;nav&gt;
        &lt;ul&gt;
          &lt;li&gt;
            &lt;Link to=<span class="hljs-string">"/"</span>&gt;Home&lt;/Link&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;Link to=<span class="hljs-string">"/about"</span>&gt;About&lt;/Link&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;Link to=<span class="hljs-string">"/contact"</span>&gt;Contact&lt;/Link&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/nav&gt;

      &lt;Suspense fallback={&lt;div&gt;Loading...&lt;/div&gt;}&gt;
        &lt;Switch&gt;
          &lt;Route path=<span class="hljs-string">"/about"</span>&gt;
            &lt;About /&gt;
          &lt;/Route&gt;
          &lt;Route path=<span class="hljs-string">"/contact"</span>&gt;
            &lt;Contact /&gt;
          &lt;/Route&gt;
          &lt;Route path=<span class="hljs-string">"/"</span>&gt;
            &lt;Home /&gt;
          &lt;/Route&gt;
        &lt;/Switch&gt;
      &lt;/Suspense&gt;
    &lt;/Router&gt;
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h2 id="heading-code-splitting-using-bundlers"><strong>Code splitting using bundlers</strong></h2>
<p>Modern bundlers have built-in support for code splitting to enable efficient loading of modules. What often happens is when a bundler comes across a dynamic import within your app, it automatically creates a separate chunk (javascript file) which can be loaded later. This way it's not bundled within your main bundle file, and hence improving the initial load time of your app.</p>
<h2 id="heading-final-thoughts"><strong>Final thoughts</strong></h2>
<p>As you can see, code splitting has the potential to give us big performance improvements. However, it's important not to become too obsessed with it as like most performance-related features, it also adds complexity, so only use it where it makes sense. This is why it's often a great first step to only use it for routes, and take it from there.</p>
<h2 id="heading-want-to-see-more">Want to see more?</h2>
<p>I mainly write about real tech topics I face in my everyday life as a Frontend Developer. If this appeals to you then feel free to follow me on <a target="_blank" href="https://bsky.app/profile/cathalmacdonnacha.com">BlueSky</a>. 🦋</p>
<p>Bye for now. 👋</p>
]]></content:encoded></item><item><title><![CDATA[Mocking Error, Empty and Loading states with MSW]]></title><description><![CDATA[One of the less exciting things about being a Frontend Developer is having to handle error, empty and loading states. It may not be the most fun thing to do, but it's necessary in order to give your users the best experience possible. Thankfully, Moc...]]></description><link>https://cathalmacdonnacha.com/mocking-error-empty-and-loading-states-with-msw</link><guid isPermaLink="true">https://cathalmacdonnacha.com/mocking-error-empty-and-loading-states-with-msw</guid><category><![CDATA[msw]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Testing]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Cathal Mac Donnacha]]></dc:creator><pubDate>Tue, 06 Dec 2022 13:38:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670283578513/BiKmMAGVI.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the less exciting things about being a Frontend Developer is having to handle error, empty and loading states. It may not be the most fun thing to do, but it's necessary in order to give your users the best experience possible. Thankfully, <a target="_blank" href="https://mswjs.io/">Mock Service Worker (MSW)</a> makes this really simple, and in turn...kinda fun. 😀</p>
<p>In this article, we take a look at how we can use MSW to mock these states for both local development and in our tests.</p>
<h2 id="heading-getting-started">Getting started</h2>
<p>While mocking these states, you'll see that we make use of page URL query parameters. I find this to be a great pattern as it gives us the flexibility to change them on the fly using only the browser, which makes developing UIs to handle these scenarios a breeze!</p>
<p><img src="https://i.imgur.com/OBdtSs9.gif" alt="Codesandbox browser" /></p>
<h2 id="heading-mocking-errors">Mocking errors</h2>
<p>Here's an example of how you can mock a simple error:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> handlers = [
  rest.get(<span class="hljs-string">`/your/api/endpoint`</span>, <span class="hljs-function">(<span class="hljs-params">req, res, ctx</span>) =&gt;</span> {
    <span class="hljs-comment">// Check if the '?error=true' query param has been </span>
    <span class="hljs-comment">// included in the page url.</span>
    <span class="hljs-keyword">const</span> pageParams = <span class="hljs-keyword">new</span> URLSearchParams(<span class="hljs-built_in">window</span>.location.search);
    <span class="hljs-keyword">const</span> isError = pageParams.get(<span class="hljs-string">'error'</span>) === <span class="hljs-string">'true'</span>;

    <span class="hljs-comment">// Example: http://localhost:3000/your_page_url?error=true</span>
    <span class="hljs-keyword">if</span> (isError) {
      <span class="hljs-comment">// Query param was found, so return an error response.</span>
      <span class="hljs-keyword">return</span> res(
        ctx.status(<span class="hljs-number">404</span>),
        ctx.json({ message: <span class="hljs-string">'Oops! Something went terribly wrong.'</span> })
      );
    }

    <span class="hljs-comment">// Otherwise - return a 200 OK response.</span>
    <span class="hljs-keyword">return</span> res(ctx.status(<span class="hljs-number">200</span>), ctx.json({ data: <span class="hljs-string">'Some cool data'</span> }));
  })
];
</code></pre>
<p>As you can see, you have full control over what data MSW will return, depending on the query parameter included in the page URL.</p>
<p>You can also include this same error scenario in your tests:</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'MyPage'</span>, <span class="hljs-function">() =&gt;</span> {
  afterEach(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Make sure to reset page url state after each test, otherwise </span>
    <span class="hljs-comment">// tests written after this one will still include the </span>
    <span class="hljs-comment">// `?error=true` param.</span>
    <span class="hljs-built_in">window</span>.history.replaceState({}, <span class="hljs-string">''</span>, <span class="hljs-string">'/'</span>);
  });

  it(<span class="hljs-string">'should display an error for some reason'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-comment">// Add a query param to the page url so that we get </span>
    <span class="hljs-comment">// an error response back.</span>
    <span class="hljs-built_in">window</span>.history.replaceState({}, <span class="hljs-string">''</span>, <span class="hljs-string">'/?error=true'</span>);

    <span class="hljs-keyword">await</span> renderPage();

    <span class="hljs-comment">// Check that the error message was displayed.</span>
    expect(<span class="hljs-keyword">await</span> screen.findByText(<span class="hljs-string">'Oops! Something went terribly wrong.'</span>)).toBeInTheDocument();
  });
});
</code></pre>
<h3 id="heading-suppressing-intentional-errors">Suppressing intentional errors</h3>
<p>When testing network errors, you may notice some <code>console.error</code> messages polluting the test output. If this is the case for you, and since these errors are expected, you can simply suppress them inside your test:</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should display an error for some reason'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-comment">// Suppress console errors that we intentionally trigger in this test.</span>
  jest.spyOn(<span class="hljs-built_in">console</span>, <span class="hljs-string">'error'</span>).mockImplementation(<span class="hljs-function">() =&gt;</span> {});
}
</code></pre>
<h2 id="heading-mocking-empty-states">Mocking empty states</h2>
<p>This is similar to mocking errors, but you return an empty response instead:</p>
<pre><code class="lang-typescript">rest.get(<span class="hljs-string">`/your/api/endpoint`</span>, <span class="hljs-function">(<span class="hljs-params">req, res, ctx</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> pageParams = <span class="hljs-keyword">new</span> URLSearchParams(<span class="hljs-built_in">window</span>.location.search);
  <span class="hljs-keyword">const</span> isEmpty = pageParams.get(<span class="hljs-string">'empty'</span>) === <span class="hljs-string">'true'</span>;

  <span class="hljs-keyword">if</span> (isEmpty) {
    <span class="hljs-comment">// Example: http://localhost:3000/your_page_url?empty=true</span>
    <span class="hljs-keyword">return</span> res(ctx.status(<span class="hljs-number">200</span>), ctx.json([]));
  }
})
</code></pre>
<h2 id="heading-mocking-loading-states">Mocking loading states</h2>
<p>This is something I find extremely useful when developing loading components. Here, we apply an infinite delay to the response so that it never resolves:</p>
<pre><code class="lang-typescript">rest.get(<span class="hljs-string">`/your/api/endpoint`</span>, <span class="hljs-function">(<span class="hljs-params">req, res, ctx</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> pageParams = <span class="hljs-keyword">new</span> URLSearchParams(<span class="hljs-built_in">window</span>.location.search);
  <span class="hljs-keyword">const</span> isEmpty = pageParams.get(<span class="hljs-string">'loading'</span>) === <span class="hljs-string">'true'</span>;

  <span class="hljs-keyword">if</span> (isLoading) {
    <span class="hljs-comment">// Example http://localhost:3000/your_page_url?loading=true</span>
    <span class="hljs-keyword">return</span> res(ctx.status(<span class="hljs-number">200</span>), ctx.json({}), ctx.delay(<span class="hljs-string">"infinite"</span>));
  }
})
</code></pre>
<h2 id="heading-alternative-mocking-patterns">Alternative mocking patterns</h2>
<p>Though the examples given above which make use of page URL query parameters are what I recommended, here are some other ways to simulate states:</p>
<ul>
<li><p>In tests, you could use <a target="_blank" href="https://mswjs.io/docs/api/setup-server/use#permanent-override">runtime overrides</a> so that the response will only occur inside your single test.</p>
</li>
<li><p><a target="_blank" href="https://mswjs.io/docs/recipes/mocking-error-responses#examples">Return an error</a> depending on the payload sent. e.g A certain username.</p>
</li>
<li><p>Look up the request URL query parameters using <a target="_blank" href="https://mswjs.io/docs/basics/response-resolver#conditional-response">conditional responses</a>. This is similar to what we've used above, except you would get the query parameter from the request URL instead of the page URL.</p>
</li>
</ul>
<h2 id="heading-utility-functions">Utility functions</h2>
<p>After using this pattern for a while, I decided to create some utility functions which I've found useful while creating handlers.</p>
<h3 id="heading-hasqueryparam">hasQueryParam()</h3>
<p>As you can see from the examples above, we have some code that checks if a page URL query parameter is present:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> pageParams = <span class="hljs-keyword">new</span> URLSearchParams(<span class="hljs-built_in">window</span>.location.search);
<span class="hljs-keyword">const</span> isSomething = pageParams.get(<span class="hljs-string">'something'</span>) === <span class="hljs-string">'true'</span>;
</code></pre>
<p>This can get quite repetitive when you have multiple query parameters to check for. Let's go ahead and improve this by creating a re-usable utility function:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// utils.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hasQueryParam</span>(<span class="hljs-params">
  queryName: <span class="hljs-built_in">string</span>,
  queryValue: <span class="hljs-built_in">string</span> = '<span class="hljs-literal">true</span>'
</span>) </span>{
  <span class="hljs-keyword">const</span> searchParams = <span class="hljs-keyword">new</span> URLSearchParams(<span class="hljs-built_in">window</span>.location.search);
  <span class="hljs-keyword">return</span> searchParams.get(queryName) === queryValue;
}

<span class="hljs-comment">// handlers.ts</span>
<span class="hljs-keyword">import</span> { hasQueryParam } <span class="hljs-keyword">from</span> <span class="hljs-string">'./utils'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> handlers = [
  rest.get(<span class="hljs-string">`/your/api/endpoint`</span>, <span class="hljs-function">(<span class="hljs-params">req, res, ctx</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (hasQueryParam(<span class="hljs-string">'error'</span>) {
      <span class="hljs-keyword">return</span> res(
        ctx.status(<span class="hljs-number">404</span>),
        ctx.json({ message: <span class="hljs-string">'Oops! Something went terribly wrong.'</span> })
      );
    }

    <span class="hljs-comment">// You can also check for different values (true is the default).</span>
    <span class="hljs-comment">// Example http://localhost:3000/your_page_url?animal=dog</span>
    <span class="hljs-keyword">if</span> (hasQueryParam(<span class="hljs-string">'animal'</span>, <span class="hljs-string">'dog'</span>) {
      <span class="hljs-keyword">return</span> res(
        ctx.status(<span class="hljs-number">200</span>),
        ctx.json({ <span class="hljs-keyword">type</span>: <span class="hljs-string">'dog'</span>, name: <span class="hljs-string">'Banjo'</span> })
      );
    }

    <span class="hljs-keyword">return</span> res(ctx.status(<span class="hljs-number">200</span>), ctx.json({ data: <span class="hljs-string">'Some cool data'</span> }));
  })
];
</code></pre>
<h3 id="heading-customresponse">customResponse()</h3>
<p>As your project grows, so will your handlers. Having to deal with error, empty and loading scenarios for every single request handler will start to get quite verbose. Therefore, I take advantage of MSW's <a target="_blank" href="https://mswjs.io/docs/recipes/custom-response-composition">custom response composition</a> to automatically include these scenarios in each response. It will also add a realistic server delay to the response, mimicking a real-world app. This gives us some nice "built-in" features which are abstracted away from the handlers.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// utils.ts</span>
<span class="hljs-keyword">import</span> { compose, context, response, ResponseTransformer } <span class="hljs-keyword">from</span> <span class="hljs-string">'msw'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">customResponse</span>(<span class="hljs-params">...transformers: ResponseTransformer[]</span>) </span>{
  <span class="hljs-keyword">if</span> (hasQueryParam(<span class="hljs-string">'loading'</span>)) {
    <span class="hljs-keyword">return</span> response(
       context.delay(<span class="hljs-string">'infinite'</span>), 
       context.json({})
    );
  }

  <span class="hljs-keyword">if</span> (hasQueryParam(<span class="hljs-string">'error'</span>)) {
    <span class="hljs-keyword">return</span> response(
      context.status(<span class="hljs-number">400</span>), 
      context.json({ error: <span class="hljs-string">'Oops! Something went terribly wrong.'</span> })
    );
  }

  <span class="hljs-keyword">if</span> (hasQueryParam(<span class="hljs-string">'empty'</span>)) {
    <span class="hljs-keyword">return</span> response(
      context.status(<span class="hljs-number">200</span>), 
      context.json([])
    );
  }

  <span class="hljs-comment">// Return the exact same response transforms which were passed in, </span>
  <span class="hljs-comment">// but also add a realistic server delay.</span>
  <span class="hljs-keyword">return</span> response(...transformers, ctx.delay());
}

<span class="hljs-comment">// handlers.ts</span>
<span class="hljs-keyword">import</span> { customResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'./utils'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> handlers = [
  rest.get(<span class="hljs-string">`/your/api/endpoint`</span>, <span class="hljs-function">(<span class="hljs-params">req, res, ctx</span>) =&gt;</span> {
    <span class="hljs-comment">// Now error, loading and empty states are handled </span>
    <span class="hljs-comment">// under the hood by customResponse(). Nifty!</span>
    <span class="hljs-keyword">return</span> customResponse(
      ctx.status(<span class="hljs-number">200</span>), 
      ctx.json({ data: <span class="hljs-string">'Some cool data'</span> })
    );
  })
];
</code></pre>
<p>These utility functions have really improved the developer experience within our team as we no longer have to write the same error, loading and empty checks over and over. We also used to add delays to every single response individually, so not having to even think about that has been awesome!</p>
<h2 id="heading-page-vs-request-url-query-parameters">Page vs request URL query parameters</h2>
<p>Another popular pattern is to use request URL query parameters to return the appropriate response. For example:</p>
<pre><code class="lang-typescript">rest.post(<span class="hljs-string">'/your/api/endpoint'</span>, <span class="hljs-keyword">async</span> (req, res, ctx) =&gt; {
  <span class="hljs-comment">// Check if the 'userId' param exists in the request url.</span>
  <span class="hljs-keyword">const</span> userId = req.url.searchParams.get(<span class="hljs-string">'userId'</span>)

  <span class="hljs-keyword">if</span> (userId === <span class="hljs-string">'mr-bad-user-1234'</span>) {
    <span class="hljs-keyword">return</span> res(
      ctx.status(<span class="hljs-number">403</span>),
      ctx.json({ errorMessage: <span class="hljs-string">'User not found'</span> }),
    );
  }
})
</code></pre>
<p>The above is fine when only using MSW for tests, but not so great when using it for local development in the browser.</p>
<p>I prefer to use page URL query parameters for the following reasons:</p>
<ul>
<li><p>We can achieve the same behaviour across both tests and local development as you will be re-using the same mock definitions.</p>
</li>
<li><p>We can easily change the UI by swapping out query parameters in the browser's URL address bar.</p>
</li>
<li><p>We can bookmark URLs to quickly display certain scenarios, without having to remember the query parameters.</p>
</li>
</ul>
<h2 id="heading-live-code-example">Live code example</h2>
<p><a target="_blank" href="https://codesandbox.io/s/example-of-mocking-error-empty-and-loading-states-with-msw-nil5vs?file=/src/mocks/handlers.js">Here's a CodeSandbox app</a> where I've added live examples for the scenarios shown above.</p>
<p>You can also run <a target="_blank" href="https://codesandbox.io/s/example-of-mocking-error-empty-and-loading-states-with-msw-nil5vs?file=/src/App.test.js">the tests</a> by opening a new terminal within CodeSandbox and running the <code>yarn test</code> command.</p>
<p><img src="https://i.imgur.com/RSII9Qz.gif" alt="Codesandbox new terminal" /></p>
<h2 id="heading-upcoming-beta">Upcoming beta</h2>
<p>It's worth mentioning that at the time of writing this article, there's a new and improved version of MSW on the horizon which is <a target="_blank" href="https://github.com/mswjs/msw/discussions/1464">currently in beta</a>. Though I look forward to trying it out and applying these patterns to it, I decided to reference the latest production version (<code>0.47.1</code>) as it's what most folks will continue to use for the next while.</p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>That's about it! You can see just how useful MSW can be, not just for testing, but for local development too. At the end of the day, it's just Javascript, so there's nothing stopping you from returning whatever data you want depending on a query parameter. The sky is truly the limit!</p>
<p>If you found this article helpful or have additional tips, please leave a comment below. 🙂</p>
<h2 id="heading-want-to-see-more">Want to see more?</h2>
<p>I mainly write about real tech topics I face in my everyday life as a Frontend Developer. If this appeals to you then feel free to follow me on <a target="_blank" href="https://bsky.app/profile/cathalmacdonnacha.com">BlueSky</a>. 🦋</p>
<p>Bye for now. 👋</p>
]]></content:encoded></item><item><title><![CDATA[Migrating from Create React App (CRA) to Vite]]></title><description><![CDATA[I recently migrated a production app within my company from create-react-app (CRA) to Vite, and the results have been great so far!
In this article, I go through all the steps I took as part of the migration, in the hope that it might help others who...]]></description><link>https://cathalmacdonnacha.com/migrating-from-create-react-app-cra-to-vite</link><guid isPermaLink="true">https://cathalmacdonnacha.com/migrating-from-create-react-app-cra-to-vite</guid><category><![CDATA[vite]]></category><category><![CDATA[create-react-app]]></category><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Cathal Mac Donnacha]]></dc:creator><pubDate>Mon, 22 Aug 2022 09:51:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1660652816014/8bDjkXrbr.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently migrated a production app within my company from create-react-app (CRA) to Vite, and the results have been great so far!</p>
<p>In this article, I go through all the steps I took as part of the migration, in the hope that it might help others who are going through the same process.</p>
<h2 id="heading-why-switch">Why switch?</h2>
<p>I want to start by saying that I really like CRA, it's helped me to quickly set up and maintain lots of projects (personal and professional). However, here are some of the reasons why we ultimately decided to make the switch:</p>
<ul>
<li><p>No dedicated maintainer.</p>
</li>
<li><p>Slow to release. This will only cause more issues down the line as more features are added to React and Webpack.</p>
</li>
<li><p>Increasing number of vulnerabilities causing github dependabot alerts which our security team require we fix, regardless of it being a build tool or not.</p>
</li>
<li><p>Speed. This wasn't really an issue for me as I rarely restart my dev server and my CI pipeline handles the production build for me. In saying that, the app I'm migrating is quite small, so this may be a bigger deal for those with larger and more complex apps. I certainly wouldn't migrate for this reason alone, but I must admit, the speed improvements are quite impressive.</p>
</li>
<li><p>Vite has matured a lot and the community has grown significantly compared to when I first created this CRA based app 2 years ago. If I was to evaluate both again for a new project, I would choose Vite this time around.</p>
</li>
</ul>
<p>Given all of these, I thought it was time to make the switch.</p>
<p>The only real "disadvantage" to using Vite is that it doesn't type-check your code, it only transpiles it to Javascript. I personally think this is fine as many IDE's nowadays have great Typescript support. You can also use <a target="_blank" href="https://github.com/fi3ework/vite-plugin-checker">vite-plugin-checker</a> if you prefer having type errors directly reported in the browser.</p>
<h2 id="heading-migration-steps">Migration steps</h2>
<p>Here are all of the steps I took to migrate from CRA to Vite. It's worth noting that I am migrating a Typescript project, though most of the steps should be similar to Javascript projects.</p>
<p>Let's get started! 😀</p>
<h3 id="heading-1-install-dependencies">1. Install dependencies</h3>
<p>We need to install these 4 dependencies:</p>
<ul>
<li><p><a target="_blank" href="https://vitejs.dev/">Vite</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/vitejs/vite/tree/main/packages/plugin-react">@vitejs/plugin-react</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/aleclarson/vite-tsconfig-paths">vite-tsconfig-paths</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/pd4d10/vite-plugin-svgr">vite-plugin-svgr</a></p>
</li>
</ul>
<pre><code class="lang-bash">npm install --save-dev vite @vitejs/plugin-react vite-tsconfig-paths vite-plugin-svgr
</code></pre>
<blockquote>
<p><strong>Note:</strong></p>
<p>We need <a target="_blank" href="https://github.com/aleclarson/vite-tsconfig-paths">vite-tsconfig-paths</a> in order to tell Vite how to resolve absolute paths from the tsconfig file. This way you can import modules like this:</p>
<p><code>import MyButton from 'components'</code></p>
<p>instead of</p>
<p><code>import MyButton from '../../../components'</code></p>
<p>We need <a target="_blank" href="https://github.com/pd4d10/vite-plugin-svgr">vite-plugin-svgr</a> to import SVGs as React components. For example:</p>
<p><code>import Logo from './logo.svg?react'</code>.</p>
</blockquote>
<h3 id="heading-2-create-vite-config-file">2. Create Vite config file</h3>
<p>Create a <code>vite.config.ts</code> at the root of your project. This is where you specify all of the Vite configuration options.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'vite'</span>;
<span class="hljs-keyword">import</span> react <span class="hljs-keyword">from</span> <span class="hljs-string">'@vitejs/plugin-react'</span>;
<span class="hljs-keyword">import</span> viteTsconfigPaths <span class="hljs-keyword">from</span> <span class="hljs-string">'vite-tsconfig-paths'</span>;
<span class="hljs-keyword">import</span> svgr <span class="hljs-keyword">from</span> <span class="hljs-string">'vite-plugin-svgr'</span>;

<span class="hljs-comment">// https://vitejs.dev/config/</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  plugins: [
    react(), 
    viteTsconfigPaths(),
    svgr({
      include: <span class="hljs-string">'**/*.svg?react'</span>,
    }),
  ],
});
</code></pre>
<h3 id="heading-3-move-indexhtml">3. Move <code>index.html</code></h3>
<p>Move the <code>index.html</code> file from the <code>/public</code> folder out to the root of your project. Find out why this is done <a target="_blank" href="https://vitejs.dev/guide/#index-html-and-project-root">here</a>.</p>
<h3 id="heading-4-update-indexhtml">4. Update <code>index.html</code></h3>
<p>URLs are treated a bit differently in Vite, so we'll have to remove all references of <code>%PUBLIC_URL%</code>. For example:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Before</span>
&lt;link rel=<span class="hljs-string">"icon"</span> href=<span class="hljs-string">"%PUBLIC_URL%/favicon.ico"</span> /&gt;

<span class="hljs-comment">// After</span>
&lt;link rel=<span class="hljs-string">"icon"</span> href=<span class="hljs-string">"/favicon.ico"</span> /&gt;
</code></pre>
<p>We need to also add an entry point to the <code>&lt;body&gt;</code> element:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">noscript</span>&gt;</span>You need to enable JavaScript to run this app.<span class="hljs-tag">&lt;/<span class="hljs-name">noscript</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"root"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-comment">&lt;!-- Add entry point 👇 --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/src/index.tsx"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<h3 id="heading-5-update-tsconfigjson">5. Update <code>tsconfig.json</code></h3>
<p>The main things you need to update in the <code>tsconfig.json</code> file are <code>target</code>, <code>lib</code> and <code>types</code>. For example:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"target"</span>: <span class="hljs-string">"ESNext"</span>,
    <span class="hljs-attr">"lib"</span>: [<span class="hljs-string">"dom"</span>, <span class="hljs-string">"dom.iterable"</span>, <span class="hljs-string">"esnext"</span>],
    <span class="hljs-attr">"types"</span>: [<span class="hljs-string">"vite/client"</span>, <span class="hljs-string">"vite-plugin-svgr/client"</span>],
    <span class="hljs-attr">"allowJs"</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">"skipLibCheck"</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">"esModuleInterop"</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">"allowSyntheticDefaultImports"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"strict"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"forceConsistentCasingInFileNames"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"noFallthroughCasesInSwitch"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"module"</span>: <span class="hljs-string">"ESNext"</span>,
    <span class="hljs-attr">"moduleResolution"</span>: <span class="hljs-string">"node"</span>,
    <span class="hljs-attr">"resolveJsonModule"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"isolatedModules"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"noEmit"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"jsx"</span>: <span class="hljs-string">"react-jsx"</span>
  },
  <span class="hljs-attr">"include"</span>: [<span class="hljs-string">"src"</span>]
}
</code></pre>
<p>You can also take a look at Vite's <code>tsconfig.json</code> file <a target="_blank" href="https://github.com/vitejs/create-vite-app/blob/master/template-react-ts/tsconfig.json">here</a> for reference.</p>
<h3 id="heading-6-create-vite-envdts-file">6. Create <code>vite-env.d.ts</code> file</h3>
<p>Since we're using Typescript, we need to create a <code>vite-env.d.ts</code> file under the <code>src</code> folder with the following contents:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/// &lt;reference types="vite/client" /&gt;</span>
</code></pre>
<h3 id="heading-7-remove-react-scripts">7. Remove <code>react-scripts</code></h3>
<p>It's time to say goodbye to CRA once and for all. 👋 Run this command to uninstall it: <code>npm uninstall react-scripts</code>.</p>
<p>We can also delete the <code>react-app-env.d.ts</code> file.</p>
<h3 id="heading-8-update-scripts-in-packagejson">8. Update scripts in <code>package.json</code></h3>
<p>Since we've removed <code>react-scripts</code>, we now need to update the scripts within <code>package.json</code> to reference <code>vite</code> instead:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
  <span class="hljs-attr">"start"</span>: <span class="hljs-string">"vite"</span>,
  <span class="hljs-attr">"build"</span>: <span class="hljs-string">"tsc &amp;&amp; vite build"</span>,
  <span class="hljs-attr">"serve"</span>: <span class="hljs-string">"vite preview"</span>
},
</code></pre>
<h3 id="heading-9-start-it-up">9. Start it up!</h3>
<p>Once you run <code>npm start</code>, you should now hopefully see your app open in the browser, powered by the amazing Vite.</p>
<p>If your app is small enough, this is all you might need to do. Otherwise, read on for more optional steps.</p>
<h2 id="heading-optional-steps">Optional steps</h2>
<p>Here are some additional optional steps you can take, depending on your own project setup.</p>
<h3 id="heading-eslint-amp-prettier">ESLint &amp; Prettier</h3>
<p>I've written a separate guide on how you can set up ESLint &amp; Prettier <a target="_blank" href="https://cathalmacdonnacha.com/setting-up-eslint-prettier-in-vitejs">here</a>.</p>
<h3 id="heading-tests">Tests</h3>
<p>I've also written a guide on how you can replace Jest with Vitest <a target="_blank" href="https://cathalmacdonnacha.com/migrating-from-jest-to-vitest">here</a>.</p>
<h3 id="heading-environmental-variables">Environmental variables</h3>
<p>It's pretty straightforward to migrate environmental variables, you simply rename <code>REACT_APP_</code> to <code>VITE_</code> within your <code>.env</code> files. I just did a find and replace for these and it worked a treat.</p>
<p>Then, instead of referencing the variables using <code>process.env.REACT_APP_</code>, you'll use <code>import.meta.env.VITE_</code>.</p>
<p>You can even go one step further and specify types for your environment variables by creating an <code>env.d.ts</code> file in the <code>src</code> folder. For example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> ImportMetaEnv {
  <span class="hljs-keyword">readonly</span> VITE_TOKEN: <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">readonly</span> VITE_CLIENT_ID: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">interface</span> ImportMeta {
  <span class="hljs-keyword">readonly</span> env: ImportMetaEnv;
}
</code></pre>
<p>If you need to check for node environments (i.e development or production), you can do so by using the <code>import.meta.env</code> object:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (<span class="hljs-keyword">import</span>.meta.env.DEV) {
  <span class="hljs-comment">// do something in development mode only</span>
}

<span class="hljs-keyword">if</span> (<span class="hljs-keyword">import</span>.meta.env.PROD) {
  <span class="hljs-comment">// do something in production mode only</span>
}
</code></pre>
<p>For more on environmental variables, see <a target="_blank" href="https://vitejs.dev/guide/env-and-mode.html">these Vite docs</a>.</p>
<h3 id="heading-change-build-output-folder">Change build output folder</h3>
<p>In Vite, the default production build folder name is <code>dist</code>, you can change this to CRA's default <code>build</code> folder if needed. I found this useful as my CI/CD scripts all referenced <code>build</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// vite.config.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  ...
  build: {
    outDir: <span class="hljs-string">'build'</span>,
  },
});
</code></pre>
<h3 id="heading-automatically-open-the-app-on-server-start">Automatically open the app on server start</h3>
<p>Something I liked about CRA is that it would automatically open the app in the browser on server start. Vite has this option too:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// vite.config.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  ...
  server: {
    open: <span class="hljs-literal">true</span>,
  },
});
</code></pre>
<h3 id="heading-change-port-number">Change port number</h3>
<p>If you need to change the port number from the default <code>3000</code>, specify like this:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// vite.config.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  ...
  server: {
    port: <span class="hljs-number">4000</span>,
  },
});
</code></pre>
<h2 id="heading-benchmarks">Benchmarks</h2>
<p>Here about some benchmarks I recorded before and after the migration:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>CRA</td><td>Vite</td></tr>
</thead>
<tbody>
<tr>
<td>npm install</td><td>21 seconds</td><td>9 seconds</td></tr>
<tr>
<td>Server startup time (cold)</td><td>11 seconds</td><td>856 milliseconds</td></tr>
<tr>
<td>Tests run</td><td>17 seconds</td><td>14 seconds</td></tr>
<tr>
<td>Production build</td><td>45 seconds</td><td>17 seconds</td></tr>
<tr>
<td>Production build size</td><td>886 KB / gzip: 249 KB</td><td>656.91 KB / gzip: 195.21 KB</td></tr>
</tbody>
</table>
</div><p>You can really see the improvements in the server startup time, but other than that there wasn't a huge difference. Bear in mind though that this was a very small app, so this could be even more important for those larger apps.</p>
<h2 id="heading-troubleshooting">Troubleshooting</h2>
<p>Here are some errors you may come across:</p>
<p><strong>1. When running</strong> <code>npm start</code><strong>, I see the following error:</strong> <code>Error: Cannot find module 'node:path'</code><strong>.</strong></p>
<p><strong>Answer:</strong> As per <a target="_blank" href="https://github.com/vitejs/vite/issues/9113#issuecomment-1184319357">this issue</a>, you have to make sure you have updated your <code>node</code> version to <code>14.18.0</code> or <code>v16+</code>.</p>
<p><strong>2. When running</strong> <code>npm start</code><strong>, I see the following error:</strong> <code>No matching export in MODULE_NAME for import TYPE_NAME</code><strong>.</strong></p>
<p><strong>Answer:</strong> This often happens when you're using a library with a <code>umd</code> bundle, where Vite expects an <code>ESM</code> bundle. This happened to me with <a target="_blank" href="https://github.com/okta/okta-auth-js/issues/641">okta-auth-js</a> and the fix was to specifically tell Vite to load the <code>umd</code> bundle in the Vite config file:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// vite.config.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  ...
  resolve: {
    alias: {
      <span class="hljs-string">'@okta/okta-auth-js'</span>: <span class="hljs-string">'@okta/okta-auth-js/dist/okta-auth-js.umd.js'</span>,
    },
  },
});
</code></pre>
<p><strong>3. My screen is blank after running</strong> <code>npm start</code><strong>.</strong></p>
<p><strong>Answer:</strong> As per steps 3 and 4, make sure you've moved and updated the <code>index.html</code> file.</p>
<p>Check out the <a target="_blank" href="https://vitejs.dev/guide/troubleshooting.html">Vite troubleshooting docs</a> for more.</p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>Overall I've been very happy with Vite. The migration was straightforward and the developer experience has improved significantly. It can do everything CRA can, but with better implementations. If you found this article helpful or have additional tips, please leave a comment below. 🙂</p>
<h2 id="heading-want-to-see-more">Want to see more?</h2>
<p>I mainly write about real tech topics I face in my everyday life as a Frontend Developer. If this appeals to you then feel free to follow me on <a target="_blank" href="https://bsky.app/profile/cathalmacdonnacha.com">BlueSky</a>. 🦋</p>
<p>Bye for now 👋</p>
]]></content:encoded></item><item><title><![CDATA[Setting up ESLint & Prettier in ViteJS]]></title><description><![CDATA[I recently migrated from create-react-app (CRA) to ViteJS, and as part of that, I set up ESLint and Prettier. In this article, I go through all the steps I took.
Let's get started!
1. Install dependencies
We need to install the following dependencies...]]></description><link>https://cathalmacdonnacha.com/setting-up-eslint-prettier-in-vitejs</link><guid isPermaLink="true">https://cathalmacdonnacha.com/setting-up-eslint-prettier-in-vitejs</guid><category><![CDATA[vite]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Cathal Mac Donnacha]]></dc:creator><pubDate>Thu, 11 Aug 2022 09:50:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1660210510874/nM-2o7Lpt.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently migrated from create-react-app (CRA) to ViteJS, and as part of that, I set up ESLint and Prettier. In this article, I go through all the steps I took.</p>
<p>Let's get started!</p>
<h1 id="heading-1-install-dependencies">1. Install dependencies</h1>
<p>We need to install the following dependencies:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/eslint/eslint">ESLint</a>: Our main linter.</p>
</li>
<li><p><a target="_blank" href="https://github.com/prettier/prettier">Prettier</a>: Our main code formatter.</p>
</li>
<li><p><a target="_blank" href="https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin">@typescript-eslint/eslint-plugin</a>: An ESLint plugin which provides rules for TypeScript codebases.</p>
</li>
<li><p><a target="_blank" href="https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/parser">@typescript-eslint/parser</a>: A parser which allows ESLint to lint TypeScript source code.</p>
</li>
<li><p><a target="_blank" href="https://github.com/prettier/eslint-config-prettier">eslint-config-prettier</a>: An ESLint configuration which disables the formatting rules in ESLint that Prettier is going to be responsible for handling, hence avoiding any clashes.</p>
</li>
<li><p><a target="_blank" href="https://github.com/import-js/eslint-plugin-import">eslint-plugin-import</a>: Tells ESLint how to resolve imports.</p>
</li>
<li><p><a target="_blank" href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y">eslint-plugin-jsx-a11y</a>: Checks for accessiblity issues.</p>
</li>
<li><p><a target="_blank" href="https://github.com/jsx-eslint/eslint-plugin-react">eslint-plugin-react</a>: React specific linting rules for ESLint.</p>
</li>
</ul>
<pre><code class="lang-typescript">npm install eslint prettier <span class="hljs-meta">@typescript</span>-eslint/eslint-plugin <span class="hljs-meta">@typescript</span>-eslint/parser eslint-config-prettier eslint-plugin-<span class="hljs-keyword">import</span> eslint-plugin-jsx-a11y eslint-plugin-react
</code></pre>
<p>Something worth noting - when I looked into getting ESLint to work nicely with Prettier, I searched across many open source projects and found these 3 kept popping up:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/prettier/prettier-eslint">prettier-eslint</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/prettier/eslint-plugin-prettier">eslint-plugin-prettier</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/prettier/eslint-config-prettier">eslint-config-prettier</a></p>
</li>
</ul>
<p>I kept wondering which I should use as I saw that some projects used all, both, or only one. In the end, I came across <a target="_blank" href="https://stackoverflow.com/a/44690309/3472935">this answer on Stack Overflow</a> which helped me understand which plugin was most suitable (eslint-config-prettier).</p>
<h1 id="heading-2-configure-eslint">2. Configure ESLint</h1>
<p>Now it's time to configure ESLint.</p>
<h3 id="heading-eslint-config-file">ESLint config file</h3>
<p>First let's create the <code>.eslintrc.js</code> configuration file. I like to create mine as a javascript file so that I can add comments. Here's what it looks like:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">extends</span>: [
    <span class="hljs-comment">// By extending from a plugin config, we can get recommended rules without having to add them manually.</span>
    <span class="hljs-string">'eslint:recommended'</span>,
    <span class="hljs-string">'plugin:react/recommended'</span>,
    <span class="hljs-string">'plugin:import/recommended'</span>,
    <span class="hljs-string">'plugin:jsx-a11y/recommended'</span>,
    <span class="hljs-string">'plugin:@typescript-eslint/recommended'</span>,
    <span class="hljs-comment">// This disables the formatting rules in ESLint that Prettier is going to be responsible for handling.</span>
    <span class="hljs-comment">// Make sure it's always the last config, so it gets the chance to override other configs.</span>
    <span class="hljs-string">'eslint-config-prettier'</span>,
  ],
  <span class="hljs-attr">settings</span>: {
    <span class="hljs-attr">react</span>: {
      <span class="hljs-comment">// Tells eslint-plugin-react to automatically detect the version of React to use.</span>
      <span class="hljs-attr">version</span>: <span class="hljs-string">'detect'</span>,
    },
    <span class="hljs-comment">// Tells eslint how to resolve imports</span>
    <span class="hljs-string">'import/resolver'</span>: {
      <span class="hljs-attr">node</span>: {
        <span class="hljs-attr">paths</span>: [<span class="hljs-string">'src'</span>],
        <span class="hljs-attr">extensions</span>: [<span class="hljs-string">'.js'</span>, <span class="hljs-string">'.jsx'</span>, <span class="hljs-string">'.ts'</span>, <span class="hljs-string">'.tsx'</span>],
      },
    },
  },
  <span class="hljs-attr">rules</span>: {
    <span class="hljs-comment">// Add your own rules here to override ones from the extended configs.</span>
  },
};
</code></pre>
<h3 id="heading-eslint-ignore-file">ESLint ignore file</h3>
<p>Now we create an <code>.eslintignore</code> file. This is where we tell ESLint which directories and files to ignore. This is project dependent, but here's an example:</p>
<pre><code class="lang-typescript">node_modules/
dist/
.prettierrc.js
.eslintrc.js
env.d.ts
</code></pre>
<h3 id="heading-add-a-new-script-entry">Add a new script entry</h3>
<p>In your <code>package.json</code> file, you can add a <code>lint</code> script entry to run ESLint from the command line.</p>
<pre><code class="lang-json">  <span class="hljs-string">"scripts"</span>: {
    ...
    <span class="hljs-attr">"lint"</span>: <span class="hljs-string">"eslint . --ext .ts,.tsx"</span>
  },
</code></pre>
<p>Now run <code>npm run lint</code> to check for linting errors. If you don't see any, it might mean that there are none (lucky you), so make sure to purposely add one in order to test it out e.g declaring a variable without using it.</p>
<h1 id="heading-3-configure-prettier">3. Configure Prettier</h1>
<p>Now that we've taken care of ESLint, let's go ahead and create the <code>prettierrc.js</code> file. This is where we specify all of our Prettier formatting rules. Here's an example:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-string">"trailingComma"</span>: <span class="hljs-string">"all"</span>,
  <span class="hljs-string">"tabWidth"</span>: <span class="hljs-number">2</span>,
  <span class="hljs-string">"semi"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-string">"singleQuote"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-string">"printWidth"</span>: <span class="hljs-number">120</span>,
  <span class="hljs-string">"bracketSpacing"</span>: <span class="hljs-literal">true</span>
}
</code></pre>
<h3 id="heading-prettier-ignore-file">Prettier ignore file</h3>
<p>Similar to ESLint, we need to tell Prettier what files it should ignore by adding a <code>.prettierignore</code> file. Again this will depend on your own project, but here's mine:</p>
<pre><code class="lang-typescript">node_modules/
dist/
.prettierrc.js
</code></pre>
<h3 id="heading-ide-integration">IDE integration</h3>
<p>In order to take full advantage of Prettier, you should be using it with an IDE to format your code after you save a file. If you're using VS Code, you can install the <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode">Prettier extension</a>. You can then set the following settings:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659975153975/LlUi_TAGk.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659975173416/l_2Eohj_l.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659975223107/qZ1DjObGN.png" alt="image.png" /></p>
<p>Or if you have access to the <code>settings.json</code> file, you can simply add these:</p>
<pre><code class="lang-typescript">{
  <span class="hljs-string">"prettier.configPath"</span>: <span class="hljs-string">".prettierrc.js"</span>,
  <span class="hljs-string">"editor.defaultFormatter"</span>: <span class="hljs-string">"esbenp.prettier-vscode"</span>,
  <span class="hljs-string">"editor.formatOnSave"</span>: <span class="hljs-literal">true</span>
}
</code></pre>
<p>Now, whenever you make changes to a file and save it, you should notice that Prettier auto formats it for you if needed. Pretty nice right? 😀</p>
<h1 id="heading-automation">Automation</h1>
<p>If you'd like, you can go a bit further and automate the linting and formatting process a bit. I like to use <a target="_blank" href="https://github.com/typicode/husky">Husky</a> which allows you to run your linter, tests etc. on a git commit/push etc. You can also then use <a target="_blank" href="https://github.com/azz/pretty-quick">pretty-quick</a> along with <code>husky</code> to automatically format your code whenever you <code>git commit</code>, just in case someone on your team hasn't set it up in their IDE.</p>
<h2 id="heading-want-to-see-more">Want to see more?</h2>
<p>That's it for today! I mainly write about real tech topics I face in my everyday life as a Frontend Developer. If this appeals to you then feel free to follow me on <a target="_blank" href="https://bsky.app/profile/cathalmacdonnacha.com">BlueSky</a>. 🦋</p>
<p>Bye for now 👋</p>
]]></content:encoded></item><item><title><![CDATA[Migrating from Jest to Vitest]]></title><description><![CDATA[I recently migrated from create-react-app (CRA) to ViteJS, and as part of that, I switched my test runner from Jest to Vitest.
In this article, I go through all the steps I took as part of the migration, in the hope that it might help others who are ...]]></description><link>https://cathalmacdonnacha.com/migrating-from-jest-to-vitest</link><guid isPermaLink="true">https://cathalmacdonnacha.com/migrating-from-jest-to-vitest</guid><category><![CDATA[Testing]]></category><category><![CDATA[Jest]]></category><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[create-react-app]]></category><dc:creator><![CDATA[Cathal Mac Donnacha]]></dc:creator><pubDate>Wed, 25 May 2022 12:50:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1653404105759/Y1hVchtPo.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently migrated from create-react-app (CRA) to ViteJS, and as part of that, I switched my test runner from Jest to <a target="_blank" href="https://vitest.dev/">Vitest</a>.</p>
<p>In this article, I go through all the steps I took as part of the migration, in the hope that it might help others who are going through the same process.</p>
<h1 id="heading-why-switch">Why switch?</h1>
<p>I had originally planned to keep using Jest during the migration of CRA to ViteJS, but I kept running into issues, mainly because <a target="_blank" href="https://jestjs.io/docs/ecmascript-modules">Jest support for ES Modules is still experimental</a>. There is a Vite plugin called <a target="_blank" href="https://github.com/sodatea/vite-jest">vite-jest</a> but it's still very much a work in progress.</p>
<p>Vitest is also pretty early in its development stages, but I felt that it was stable enough to give it a try, and I'm sure glad I did. It has many advantages but one thing I really like about it compared to other test runners is that it shares the same config file and plugins as Vite itself, so there's only one single pipeline to worry about.</p>
<h1 id="heading-migration-steps">Migration steps</h1>
<p>Here are all of the steps I took to migrate from Jest to Vitest.</p>
<p><strong>Note: These steps are unique to the way Jest was installed within CRA, so they may vary if you installed and configured Jest manually.</strong></p>
<p>Let's get started!</p>
<h2 id="heading-1-install-dependencies">1. Install dependencies</h2>
<p>We need to install 3 dependencies:</p>
<ul>
<li><p><a target="_blank" href="https://vitest.dev/">Vitest</a>: Our test runner.</p>
</li>
<li><p><a target="_blank" href="https://github.com/jsdom/jsdom">jsdom</a>: To mimic the browser while running tests.</p>
</li>
<li><p><a target="_blank" href="https://github.com/bcoe/c8">c8</a>: To generate our code coverage reports.</p>
</li>
</ul>
<p>To install these dev dependencies, run the following command:</p>
<p><code>npm install --save-dev vitest jsdom c8</code></p>
<h2 id="heading-2-update-viteconfigts">2. Update vite.config.ts</h2>
<p>As I mentioned previously, the beauty of using Vitest is that it integrates seamlessly with Vite, so all we have to do is update the <code>vite.config.ts</code> file.</p>
<p>You'll also need to add a reference to the Vitest types using a <a target="_blank" href="https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-">triple slash command</a> at the top of your config file.</p>
<pre><code class="lang-plaintext">/// &lt;reference types="vitest" /&gt;
import { defineConfig } from 'vite'

export default defineConfig({
  test: {
    // ...
  },
})
</code></pre>
<p>You may have different requirements, but here's what mine ended up looking like:</p>
<pre><code class="lang-plaintext">/// &lt;reference types="vitest" /&gt;
import { defineConfig } from 'vite'

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/setupTests.ts',
    coverage: {
      reporter: ['text', 'html'],
      exclude: [
        'node_modules/',
        'src/setupTests.ts',
      ],
    },
  },
});
</code></pre>
<p><a target="_blank" href="https://vitest.dev/config/#deps">Here's the full list of config options</a>, but I'll briefly explain each one I used:</p>
<ul>
<li><p><code>globals</code>: Setting this to <code>true</code> allows you to reference the APIs globally (describe, expect, it, should etc.), just like Jest.</p>
</li>
<li><p><code>environment</code>: This is where you choose what browser-like environment you want to use.</p>
</li>
<li><p><code>setupFiles</code>: This is the path to the setup files which run before each test file. In CRA, this file (setupFiles.ts) is included by default, so I left it as is.</p>
</li>
<li><p><code>coverage</code>: This is the configuration I use for the <a target="_blank" href="https://github.com/bcoe/c8">c8</a> reporter. I also specify the folders that I exclude from the report.</p>
</li>
</ul>
<h2 id="heading-3-convert-tests">3. Convert tests</h2>
<p>Vitest has been designed with a Jest compatible API (describe, expect, it, should etc.), so migrating tests was an absolute breeze. Unless you mock modules or methods, you probably won't need to do anything here.</p>
<p>I was making use of Jest method mocking, but all I had to was change <code>jest.fn()</code> to <code>vi.fn()</code>. You'll have to import <code>vi</code> in your test if you want to do the same: <code>import { vi } from 'vitest';</code></p>
<h2 id="heading-4-update-packagejson">4. Update package.json</h2>
<p>Let's update the <code>package.json</code> scripts to reference <code>vitest</code> instead of <code>react-scripts</code>.</p>
<pre><code class="lang-plaintext"> "scripts": {
    ...
    "test": "vitest watch",
    "test:no-watch": "vitest run",
    "test:coverage": "vitest run --coverage"
  },
</code></pre>
<p>You'll notice I also added a new entry to run code coverage and provided a way to run tests without a watcher, this comes in handy when running tests in a CI/CD pipeline.</p>
<p>We can <code>npm uninstall @types/jest</code> since we won't need it anymore. I have kept <code>@testing-library/jest-dom</code> around though as it's required by React Testing Library.</p>
<p>Finally, I removed any "jest" configuration I had in this file:</p>
<pre><code class="lang-plaintext">  "jest": {
    "collectCoverageFrom": [
      "src/**/*.{js,ts,tsx}",
      "!/node_modules/",
    ]
  },
</code></pre>
<h2 id="heading-5-update-tsconfigjson">5. Update tsconfig.json</h2>
<p>To get TypeScript working with the global APIs, add <code>vitest/globals</code> to the types field in your <code>tsconfig.json</code> file.</p>
<pre><code class="lang-plaintext">"types": ["vitest/globals", .....]
</code></pre>
<p>You may also run into an error like this:</p>
<pre><code class="lang-plaintext">../node_modules/@types/jest/index.d.ts:34:1 - error TS6200: Definitions of the following identifiers conflict with those in another file: test, describe, it, expect, beforeAll, afterAll, beforeEach,
</code></pre>
<p>At the time of writing this article, it still seems to be an <a target="_blank" href="https://github.com/testing-library/jest-dom/issues/427">open issue</a>. However, a workaround I found is to add <code>"skipLibCheck": true,</code> to your <code>tsconfig.json</code> file.</p>
<h2 id="heading-6-run-tests">6. Run tests</h2>
<p>Hopefully the migration worked for you and all that's left to do now is to run <code>npm test</code>. 🎉</p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>Overall I'm extremely happy with Vitest, they've really made the migration so easy. I'm even more impressed with their docs given it's still pretty new, especially the number of <a target="_blank" href="https://vitest.dev/guide/#examples">examples</a> they have. If you found this article helpful or have suggestions, please leave a comment below. 🙂</p>
<h2 id="heading-want-to-see-more">Want to see more?</h2>
<p>I mainly write about real tech topics I face in my everyday life as a Frontend Developer. If this appeals to you then feel free to follow me on <a target="_blank" href="https://bsky.app/profile/cathalmacdonnacha.com">BlueSky</a>. 🦋</p>
<p>Bye for now 👋</p>
]]></content:encoded></item><item><title><![CDATA[Playwright E2E testing: Tips and best practices]]></title><description><![CDATA[I've been using Playwright for a couple of months now, and though I'm certainly no expert, I've learned some tips, tricks and best practices along the way. In this article, we go through some of them, with the aim of helping you write even better E2E...]]></description><link>https://cathalmacdonnacha.com/playwright-e2e-testing-tips-and-best-practices</link><guid isPermaLink="true">https://cathalmacdonnacha.com/playwright-e2e-testing-tips-and-best-practices</guid><category><![CDATA[Testing]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Cathal Mac Donnacha]]></dc:creator><pubDate>Wed, 23 Mar 2022 13:06:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1647963017955/RS73rQOtv.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been using Playwright for a couple of months now, and though I'm certainly no expert, I've learned some tips, tricks and best practices along the way. In this article, we go through some of them, with the aim of helping you write even better E2E tests.</p>
<h2 id="heading-1-prioritize-user-facing-attributes">1. Prioritize user-facing attributes</h2>
<p>You should use user-facing attributes like text content, accessibility roles and label as much as possible. A user won't know what "id" or "class" means so why should we use them in our tests to find elements? Not only will your tests mimic how your users find elements, but they will also be more robust as user-facing attributes generally change less than ids, class names or other implementation details.</p>
<p>For example, use <code>await page.locator('text=Login')</code> instead of <code>await page.locator('#login-button')</code>. A real user will find a button by its text content, not its id, so your tests should too.</p>
<p>Remember, the more your tests resemble the way your software is used, the more confidence they can give you.</p>
<h2 id="heading-2-use-locators-over-selectors">2. Use locators over selectors</h2>
<p>Using <a target="_blank" href="https://playwright.dev/docs/locators">locators</a> will help prevent flakiness or unnoticed breakages when your web page changes. These breakages have the potential to go unnoticed when using standard selectors.</p>
<p>For example, use <code>await page.locator('text=Login').click()</code> instead of <code>await page.click('text=Login')</code>.</p>
<p>The main reason locators help mitigate flakiness is down to its level of strictness. There are three possible outcomes when using locators:</p>
<ol>
<li><p>Test works as expected.</p>
</li>
<li><p>Selector does not match anything and test breaks loudly.</p>
</li>
<li><p>Multiple elements match the selector (e.g. there is a second "Login" button added to the page somewhere), and the locator complains about this and the test breaks with a nice error message.</p>
</li>
</ol>
<p>This means you don't have to be very thoughtful of selectors, and picking just <code>text=Login</code> is totally fine - Playwright will do all the heavy lifting to ensure correct and non-flaky testing.</p>
<h2 id="heading-3-use-page-object-model-pom">3. Use Page Object Model (POM)</h2>
<p><a target="_blank" href="https://martinfowler.com/bliki/PageObject.html">Page Object Model</a> is a common pattern that can help avoid duplication, improve maintainability and simplify interactions between pages in multiple tests.</p>
<p>Writing tests using POM feels more natural as it conveys more intent and encourages behaviour over raw mechanics. Playwright have included <a target="_blank" href="https://playwright.dev/docs/test-pom">this example</a> in their docs to give you an idea on how to implement it.</p>
<p>In saying that, you don't always have to use POM either. Use it when it makes sense to abstract. I often start without POM and only create page object models when I feel that the tests will benefit from it.</p>
<blockquote>
<p>Duplication is far cheaper than the wrong abstraction - Sandi Metz.</p>
</blockquote>
<h2 id="heading-4-use-double-quotes-to-find-specific-elements">4. Use double quotes to find specific elements</h2>
<p>If you are finding multiple elements with the same partial string, try using double quotes to enable case sensitivity. For example, <code>await page.locator('text=Checkout')</code> could return two elements as it finds a "Checkout" button and a "Check out this new shoe" heading. Use double quotes if you only want to return the button on its own e.g <code>await page.locator('text="Checkout"')</code>. See <a target="_blank" href="https://playwright.dev/docs/selectors#text-selector">Playwright text selectors</a> for more.</p>
<h2 id="heading-5-avoid-selectors-tied-to-implementation">5. Avoid selectors tied to implementation</h2>
<p><code>xpath</code> and <code>css</code> can be tied to the DOM structure or implementation. For example:</p>
<p><code>await page.locator('#tsf &gt; div:nth-child(2) &gt; div.A8SBwf &gt; div.RNNXgb &gt; div &gt; div.a4bIc &gt; input').click();</code></p>
<p>Pretty gnarly right? These selectors can break when the DOM structure changes, so it's best to avoid relying on them.</p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>That's all the tips I have for you today. If you have any tips or best practices of your own, please share them in the comments below. 🙂</p>
<h2 id="heading-want-to-see-more">Want to see more?</h2>
<p>I mainly write about real tech topics I face in my everyday life as a Frontend Developer. If this appeals to you then feel free to follow me on <a target="_blank" href="https://bsky.app/profile/cathalmacdonnacha.com">BlueSky</a>. 🦋</p>
<p>Bye for now 👋</p>
]]></content:encoded></item><item><title><![CDATA[Working around CORS in create-react-app]]></title><description><![CDATA[One problem we often face as frontend developers is dealing with CORS when making API requests.
What is CORS?
CORS (Cross-Origin Resource Sharing) is essentially a mechanism that allows a server to express what other domains can make requests to it. ...]]></description><link>https://cathalmacdonnacha.com/working-around-cors-in-create-react-app</link><guid isPermaLink="true">https://cathalmacdonnacha.com/working-around-cors-in-create-react-app</guid><category><![CDATA[Frontend Development]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[create-react-app]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[CORS]]></category><dc:creator><![CDATA[Cathal Mac Donnacha]]></dc:creator><pubDate>Mon, 31 Jan 2022 23:40:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1643672003383/SoEpuAvMB.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One problem we often face as frontend developers is dealing with CORS when making API requests.</p>
<h2 id="heading-what-is-cors">What is CORS?</h2>
<p>CORS (Cross-Origin Resource Sharing) is essentially a mechanism that allows a server to express what other domains can make requests to it. For example, my app could be hosted on <code>http://domain-a.com</code>, so only requests made from that same domain are allowed. If another app hosted on <code>http://domain-b.com</code> tried to make a request to <code>http://domain-a.com</code>, it would fail depending on the CORS policy.</p>
<h2 id="heading-where-does-cra-fit-in">Where does CRA fit in?</h2>
<p>While using CRA (create-react-app), I've often run into a situation where I want to test my local changes against another team's API endpoint. I mean, there's only so much mocking you can rely on! By default, CRA runs locally on <code>http://localhost:3000</code>, so if I try to make an API request out to <code>http://domain-a.com/users.json</code>, CORS would block it. However, when developing locally, CRA lets us get around this by proxying all unknown requests to a specified domain. This can now help us make sure our frontend code lines up with the backend's responses.</p>
<h2 id="heading-whats-the-workaround">What's the workaround?</h2>
<p>All we need to do is add one new field in <code>package.json</code>. For example:</p>
<p><code>"proxy": "http://domain-a.com",</code></p>
<p>After you restart your CRA dev server, you should now be free to make requests to <code>http://domain-a.com/users.json</code>. This will of course only work locally and you should only make requests to a dev API endpoint, not production. That's it!</p>
<h2 id="heading-want-to-see-more">Want to see more?</h2>
<p>I mainly write about real tech topics I face in my everyday life as a Frontend Developer. If this appeals to you then feel free to follow me on <a target="_blank" href="https://bsky.app/profile/cathalmacdonnacha.com">BlueSky</a>. 🦋</p>
<p>Bye for now 👋</p>
]]></content:encoded></item><item><title><![CDATA[Cypress vs Playwright: Which is best for E2E testing?]]></title><description><![CDATA[Cypress was our go-to end-to-end (E2E) testing tool, and we were pretty happy with it, up until recently that is. Lately, we've run into a couple of testing scenarios where Cypress support has been limited, notably around multiple domains/tabs and iF...]]></description><link>https://cathalmacdonnacha.com/cypress-vs-playwright-which-is-best-for-e2e-testing</link><guid isPermaLink="true">https://cathalmacdonnacha.com/cypress-vs-playwright-which-is-best-for-e2e-testing</guid><category><![CDATA[Frontend Development]]></category><category><![CDATA[Testing]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Software Testing]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Cathal Mac Donnacha]]></dc:creator><pubDate>Wed, 15 Dec 2021 16:11:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639612716723/41T_RsqdC.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Cypress was our go-to end-to-end (E2E) testing tool, and we were pretty happy with it, up until recently that is. Lately, we've run into a couple of testing scenarios where Cypress support has been limited, notably around multiple domains/tabs and iFrames. This caused us to re-evaluate the available E2E tools and it ultimately came down to two options; keep Cypress or switch to Playwright.</p>
<p>In this article, I compare both tools in the format of an <a target="_blank" href="https://adr.github.io/madr/#overview">ADR</a>, which should help us decide which tool to go with. Let the battle commence!</p>
<h2 id="heading-cypress"><strong>Cypress</strong></h2>
<p><a target="_blank" href="https://www.cypress.io/">https://www.cypress.io/</a></p>
<ul>
<li><p>Good, because it's very easy to learn and get set up with basic tests.</p>
</li>
<li><p>Good, because it has a nice dashboard to view test reports, analytics and recordings.</p>
</li>
<li><p>Good, because it supports Chromium and Firefox.</p>
</li>
<li><p>Good, because it has a very slick Test Runner UI.</p>
</li>
<li><p>Good, because it's built specifically for end-to-end testing.</p>
</li>
<li><p>Good, because you can edit your test code in the browser and instantly see it run as you change the code.</p>
</li>
<li><p>Good, because it's mature and has good community support.</p>
</li>
<li><p>Bad, because it doesn't support multiple domains.</p>
</li>
<li><p>Bad, because authentication requires a lot more setup due to lack of multi-domain support.</p>
</li>
<li><p>Bad, because it doesn't support Webkit (Safari)</p>
</li>
<li><p>Bad, because you cannot run tests against multiple browsers at the same time.</p>
</li>
<li><p>Bad, because iFrame support is limited.</p>
</li>
<li><p>Bad, because there is no "hover" support.</p>
</li>
<li><p>Bad, because the chaining command syntax can quickly get out of hand for more complex tests.</p>
</li>
<li><p>Bad, because you have to pay a premium to get access to some dashboard features (e.g flake detection)</p>
</li>
<li><p>Bad, because to do parallelization well, it requires vendor-locked software.</p>
</li>
</ul>
<h2 id="heading-playwright"><strong>Playwright</strong></h2>
<p><a target="_blank" href="https://playwright.dev/">https://playwright.dev/</a></p>
<ul>
<li><p>Good, because it supports Chromium, Firefox and Webkit (Safari).</p>
</li>
<li><p>Good, because it supports multiple domains and tabs.</p>
</li>
<li><p>Good, because it supports 5 language bindings (Javascript, Typescript, Java, Python, .NET)</p>
</li>
<li><p>Good, because it's <a target="_blank" href="https://blog.checklyhq.com/cypress-vs-selenium-vs-playwright-vs-puppeteer-speed-comparison/">fast</a>.</p>
</li>
<li><p>Good, because you can run tests against multiple browsers at the same time.</p>
</li>
<li><p>Good, because it fully supports parallelization, even locally.</p>
</li>
<li><p>Good, because it supports parallel tests within a single test file.</p>
</li>
<li><p>Good, because it's Javascript first, so feels more natural.</p>
</li>
<li><p>Good, because it has "hover" support.</p>
</li>
<li><p>Good, because iFrames are natively supported.</p>
</li>
<li><p>Good, because it supports reuse of authentication state to speed up tests.</p>
</li>
<li><p>Good, because it lets you choose your test runner (e.g. Jest but the default one is advised)</p>
</li>
<li><p>Good, because signing in is simple, you just fill in the form.</p>
</li>
<li><p>Good, because it's completely free.</p>
</li>
<li><p>Good, because it has few dependencies.</p>
</li>
<li><p>Bad, because it's still quite new, so possibility of a smaller community.</p>
</li>
<li><p>Bad, because 3rd party tutorials are out of date due to changing API.</p>
</li>
<li><p>Bad, because it's a mix between an automation and testing framework.</p>
</li>
<li><p>Bad, because it has a steeper learning curve.</p>
</li>
<li><p>Bad, because it does not have a dedicated dashboard so would be harder to debug tests remotely.</p>
</li>
</ul>
<h2 id="heading-common-features-between-both"><strong>Common features between both</strong></h2>
<ul>
<li><p>Good documentation</p>
</li>
<li><p>API testing</p>
</li>
<li><p>Point &amp; click test recording</p>
</li>
<li><p>Test debugging tools</p>
</li>
<li><p>Test retries</p>
</li>
<li><p>Automatic waiting</p>
</li>
<li><p>Video and screen capture</p>
</li>
<li><p>Mobile emulation</p>
</li>
<li><p>Regularly updated and well maintained</p>
</li>
<li><p>Run only a subset of tests</p>
</li>
<li><p>Network monitoring</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In the end, we decided to go with Playwright, mainly because of its native support for multiple domains, tabs and iFrames. I will say that I found Cypress' debugging to be more developer-friendly and in general "slicker", but that wasn't enough to make us stay put.</p>
<h2 id="heading-want-to-see-more">Want to see more?</h2>
<p>I mainly write about real tech topics I face in my everyday life as a Frontend Developer. If this appeals to you then feel free to follow me on <a target="_blank" href="https://bsky.app/profile/cathalmacdonnacha.com">BlueSky</a>. 🦋</p>
<p>Bye for now 👋</p>
]]></content:encoded></item><item><title><![CDATA[Why you should make your tests fail]]></title><description><![CDATA[Let's face it, most of us developers don't necessarily love writing tests. We sometimes end up rushing through them, and once we see that green tick next to a passing test, we're generally pretty happy to move on. However, an enemy is lurking amongst...]]></description><link>https://cathalmacdonnacha.com/why-you-should-make-your-tests-fail</link><guid isPermaLink="true">https://cathalmacdonnacha.com/why-you-should-make-your-tests-fail</guid><category><![CDATA[Testing]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[React]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Cathal Mac Donnacha]]></dc:creator><pubDate>Thu, 02 Dec 2021 13:58:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1638379727658/pkVXN_eQg.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let's face it, most of us developers don't necessarily love writing tests. We sometimes end up rushing through them, and once we see that green tick next to a passing test, we're generally pretty happy to move on. However, an enemy is lurking amongst us.</p>
<h2 id="heading-false-positive-test">False positive test</h2>
<p>The enemy I'm talking about here is otherwise known as a false positive test. Let's take a look at what this beast looks like.</p>
<p>Here we have a <code>select</code> element with some countries as options:</p>
<pre><code class="lang-tsx">&lt;select&gt;
  &lt;option value=""&gt;Select a country&lt;/option&gt;
  &lt;option value="US"&gt;United States&lt;/option&gt;
  &lt;option value="IE"&gt;Ireland&lt;/option&gt;
  &lt;option value="AT"&gt;Austria&lt;/option&gt;
&lt;/select&gt;
</code></pre>
<p>Here's my test:</p>
<pre><code class="lang-tsx">it('should allow user to change country', () =&gt; {
  render(&lt;App /&gt;)
  userEvent.selectOptions(
    screen.getByRole('combobox'),
    screen.getByRole('option', { name: 'Ireland' } ),
  )
  expect(screen.getByRole('option', { name: 'Ireland' })).toBeInTheDocument();
})
</code></pre>
<p>The test passes, isn't that great? ✅ I'm afraid not. 😭  Let's see why after we intentionally make it fail.</p>
<h2 id="heading-making-your-test-fail">Making your test fail</h2>
<p>Here's a real example of a false positive test situation I ran into recently:</p>
<pre><code class="lang-tsx">it('should allow user to change country', () =&gt; {
  render(&lt;App /&gt;)
  userEvent.selectOptions(
    screen.getByRole('combobox'),
    screen.getByRole('option', { name: 'Ireland' } ),
  )

  // Changed expected country from "Ireland" to "Austria" - this should fail.
  expect(screen.getByRole('option', { name: 'Austria' })).toBeInTheDocument();
})
</code></pre>
<p>I was expecting the check for "Austria" to fail because it wasn't the selected country, and I was pretty surprised to see that it was still passing. Looks like we have just identified a false positive test.</p>
<p>Let us take a step back. The purpose of my test is to ensure that when changing a country, it is indeed the now selected option. However, after debugging for while I eventually realised that the test above only checks that the country "Ireland" exists, instead of checking if it's selected.</p>
<p>Here's how I eventually fixed it:</p>
<pre><code class="lang-tsx">it('should allow user to change country', () =&gt; {
  render(&lt;App /&gt;)
  userEvent.selectOptions(
    screen.getByRole('combobox'),
    screen.getByRole('option', { name: 'Ireland' } ),
  )

  // Now checking if the option is selected
  expect(screen.getByRole('option', { name: 'Ireland' }).selected).toBe(true);
})
</code></pre>
<p>Now, I am correctly checking that the option is selected and all is good. I wouldn't have found this unless I intentionally made my test fail, so I'm glad my persistence has paid off and I avoided a potential bug.</p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>I've been burnt enough times in the past by false positive tests that I have vouched to always intentionally make my tests fail before moving on to the next one. Since doing this, I've gotten a lot more confident in my tests knowing that they'll only pass in the correct circumstances.</p>
<p>That's about all I have to share with you today. Let me know in the comments if you found this article useful. 🙌</p>
<h2 id="heading-want-to-follow-along">Want to follow along?</h2>
<p>I mainly write about real tech topics I face in my everyday life as a Frontend Developer. If this appeals to you then feel free to follow me on <a target="_blank" href="https://bsky.app/profile/cathalmacdonnacha.com">BlueSky</a>. 🦋</p>
<p>Bye for now 👋</p>
]]></content:encoded></item><item><title><![CDATA[6 tips every code review author should know]]></title><description><![CDATA[I've submitted my fair share of code reviews at this point of my web development career and I've picked up a couple of tips along the way, so I wanted to share some of them with you.
1. Keep it small 🤏
Try and keep your code reviews as small as poss...]]></description><link>https://cathalmacdonnacha.com/6-tips-every-code-review-author-should-know</link><guid isPermaLink="true">https://cathalmacdonnacha.com/6-tips-every-code-review-author-should-know</guid><category><![CDATA[#codenewbies]]></category><category><![CDATA[code review]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[General Programming]]></category><category><![CDATA[Productivity]]></category><dc:creator><![CDATA[Cathal Mac Donnacha]]></dc:creator><pubDate>Fri, 05 Nov 2021 15:28:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1637277224482/YGC5D90m1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've submitted my fair share of code reviews at this point of my web development career and I've picked up a couple of tips along the way, so I wanted to share some of them with you.</p>
<h2 id="heading-1-keep-it-small">1. Keep it small 🤏</h2>
<p>Try and keep your code reviews as small as possible. This is better for reviewers as it makes it easier for them to focus on one or two goals. It's also better for you as it means you can push your code to production more often, giving yourself a sense of achievement.</p>
<p>As a code reviewer, there's nothing worse than getting a huge code review that takes days to go through. You'll find a reviewer's attention to detail will drop off after 60 minutes of review time, meaning you won't get the best out of your reviews.</p>
<p>By keeping your reviews small, you're more likely to get faster feedback and approvals, meaning you can ship your code more often. Win. 🚀</p>
<p>In saying this, there are times where your code reviews simply can't be small. This may occur if you're making changes that affect multiple services or you're working with legacy code that can't be broken down as easily. This is fine too, these tips are just a guide to follow when you can.</p>
<p>As a frontend developer, I try to break down large stories into multiple smaller tasks, meaning I can submit smaller code reviews too. I often follow Martin Fowler's <a target="_blank" href="https://martinfowler.com/bliki/KeystoneInterface.html">Keystone Interface</a> method to achieve this.</p>
<h2 id="heading-2-write-a-good-description">2. Write a good description 👨‍💻</h2>
<p>Include a brief description in your code review where you give a summary of the changes made. This is very important as it gives the reviewer the context required to put themselves in the user's shoes while they review your code.</p>
<h2 id="heading-3-include-screenshots-or-recordings">3. Include screenshots or recordings 📹</h2>
<p>If the changes you have made are visual then it's important to include screenshots, or even better...screen recordings. Similar to the previous tip, this gives the reviewer some more context into the changes you've made as they can link your code with what they see in your screenshots or recordings. As they say, <em>"a picture paints a thousand words"</em>. I'm not sure if your screenshot will end up in any art galleries, but you can be sure they will benefit your reviewers. 😀</p>
<h2 id="heading-4-give-instructions-on-how-to-verify-your-changes">4. Give instructions on how to verify your changes 👨‍🏫</h2>
<p>Depending on the code review, it's important to give your reviewers a way to play around with your changes for themselves. This is the number one best way to receive feedback, especially if the changes are visual. I've often found small issues during code reviews relating to how responsive a webpage is, which is something I wouldn't have spotted by looking at the code or screenshots.</p>
<p>On our team, we often use <a target="_blank" href="https://devcenter.heroku.com/articles/github-integration-review-apps">Heroku review apps</a> which gives you a sandboxed version of your app. It gets deployed whenever we create a new code review. This has become a fundamental part of our code review process. However, most teams won't have this luxury so another good option is to ask the reviewer to checkout your branch and run it locally.</p>
<p>Here's an example of some instructions you could include in your code review:</p>
<ol>
<li><p>Checkout branch: <code>popup-toast-notification</code></p>
</li>
<li><p>Run app locally: <code>npm start</code></p>
</li>
<li><p>Go to the "Profile" page.</p>
</li>
<li><p>Change your name.</p>
</li>
<li><p>Click "Update".</p>
</li>
<li><p>You should see a toast notification appear at the top of your screen.</p>
</li>
</ol>
<h2 id="heading-5-give-reviewers-enough-time">5. Give reviewers enough time ⏱️</h2>
<p>There is no point in requesting a large code review on the very last day of the sprint. It's important to give reviewers enough time so that they don't feel rushed or pressured into giving approvals. By giving reviewers the time they need, they can give you adequate feedback and it also gives you that extra bandwidth to make any requested changes.</p>
<h2 id="heading-6-avoid-scope-creep">6. Avoid scope creep ✋</h2>
<p>As engineers, we always strive to develop the perfect solution. This can be good, but it can also lead to distractions in code reviews where reviewers will request extra features. How many times have you heard someone say <em>"This is good, could you add this X feature to make it even better?"</em>. As the code review author, it's important to know when a request is outside the scope of the task you're working on. Getting feature request ideas is fantastic, but it's something you should discuss with your product manager and team members, and possibly create a new task for it. This could then be delivered as part of a separate code review.</p>
<h2 id="heading-final-thoughts">Final thoughts 🤔</h2>
<p>That's it. 😀 Hopefully this article can help you improve your code reviews, but remember that these are just tips to guide you and so it's important to figure out what works for you and your team.</p>
<p>If you found this article useful then please give it a like, and if you have some tips of your own, feel free to leave a comment. 🙌</p>
<h2 id="heading-want-more">Want more? 📢</h2>
<p>I mainly write about real-world tech topics I face in my everyday life as a frontend developer. If this appeals to you, then feel free to follow me on <a target="_blank" href="https://bsky.app/profile/cathalmacdonnacha.com">BlueSky</a>. 🦋</p>
<p>Bye for now 👋</p>
]]></content:encoded></item><item><title><![CDATA[Tips for developers switching from Windows to Mac]]></title><description><![CDATA[I was a Windows fan all my life, both at home since I was 10 years old, and at work for 8 years of my frontend development career. However, when I moved jobs recently I was given a MacBook Pro, and so I had little choice but to dive headfirst into ev...]]></description><link>https://cathalmacdonnacha.com/tips-for-developers-switching-from-windows-to-mac</link><guid isPermaLink="true">https://cathalmacdonnacha.com/tips-for-developers-switching-from-windows-to-mac</guid><category><![CDATA[macOS]]></category><category><![CDATA[macbook]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[tips]]></category><dc:creator><![CDATA[Cathal Mac Donnacha]]></dc:creator><pubDate>Mon, 01 Nov 2021 12:58:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1637277767980/1XpgI3tFI.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was a Windows fan all my life, both at home since I was 10 years old, and at work for 8 years of my frontend development career. However, when I moved jobs recently I was given a MacBook Pro, and so I had little choice but to dive headfirst into everything MacOS. It was a bit strange at first and it honestly took me a few days to get settled in with the Mac, but once I did, I really started to like it.</p>
<p>From the beginning, there were a couple of things I missed about Windows, and some alternative apps that I had to find. In this article, I go through some of the things that helped me, in the hope that I can help smoothen the ride for other developers starting out on their Windows to Mac journey.</p>
<h2 id="heading-package-manager">Package manager 📦</h2>
<p>I used to use <a target="_blank" href="https://chocolatey.org/">Chocolatey</a> as my package manager on Windows. I found it really useful to install and organise all of my various packages, and so I wanted to find the best alternative for Mac. <a target="_blank" href="https://brew.sh/">Brew</a> was the answer! I've been using it for the past year and haven't had any issues or missing features compared to Chocolatey.</p>
<h2 id="heading-terminal">Terminal 📺</h2>
<p><a target="_blank" href="https://conemu.github.io/">ConEmu</a> was my go-to terminal on Windows and so I was pretty bummed out when I realised that it wasn't supported on Mac. After asking some folks on my team, they suggested <a target="_blank" href="https://iterm2.com/">iTerm2</a> and I must admit it's equally fantastic.</p>
<p>One feature that is not included in iTerm2 though is the ability to display the currently active git branch at a glance. Worry not! <a target="_blank" href="https://ohmyz.sh/">oh-my-zsh</a> to the rescue 🦸‍♂️ You can install it using <a target="_blank" href="https://ohmyz.sh/#install">this curl command</a> and hey presto, you now see those nice pretty git branches in your terminal.</p>
<p>Similar to ConEmu, I use a feature in iTerm2 called "window arrangements" to automatically launch tabs whenever I open the terminal. I find this really handy, as it automatically navigates to the correct directory and opens my regular 3 <code>git</code>, <code>serve</code> and <code>test</code> tabs for me. This feature has saved me countless hours over the years!</p>
<h2 id="heading-finder">Finder 🗄</h2>
<p>Finder is the default file manager on the Mac and it's equivalent to Windows Explorer, with some differences. The biggest difference for me was the lack of a classic cut and paste. On Windows you could simply press <code>ctrl</code> + <code>x</code> and <code>ctrl</code> + <code>v</code>. However, to do this on Mac you have to select the file and then press <code>cmd</code> + <code>c</code> and <code>cmd</code> + <code>option</code> + <code>v</code>.</p>
<h3 id="heading-hidden-files">Hidden Files</h3>
<p>To show hidden files in Finder just press <code>command</code> + <code>shift</code> + <code>.</code>.</p>
<p>If you want to show hidden files by default just open your terminal and run this command:</p>
<pre><code class="lang-bash">defaults write com.apple.finder AppleShowAllFiles YES; killall Finder;
</code></pre>
<h2 id="heading-split-screen">Split screen 💻</h2>
<p>I often use split screen to view both the browser and VS Code side by side. On Windows, this is quite simple to do using a feature called <a target="_blank" href="https://support.microsoft.com/en-us/windows/snap-your-windows-885a9b1e-a983-a3b1-16cd-c531795e6241#WindowsVersion=Windows_10">Snap Assist</a>. You simply just drag a window to the edge of the screen and then select the other window you want to show beside it.</p>
<p><img src="https://i.imgur.com/S4tZrd0.gif" alt="Windows Split View" /></p>
<p>Again I was pretty disappointed to find out that you couldn't easily achieve this on the Mac. Not to worry though, it does have a feature called <a target="_blank" href="https://support.apple.com/en-ie/HT204948">Split View</a>, which is close enough for me. It's not as quick and easy but gets the job done.</p>
<p><img src="https://i.imgur.com/P0H2Vd9.gif" alt="Mac Split View" /></p>
<h3 id="heading-window-management-app">Window management app</h3>
<p>A reader suggested <a target="_blank" href="https://rectangleapp.com/">Rectangle</a> for split screen window management and I must say, I'm impressed!</p>
<h2 id="heading-ms-paint">MS Paint 🎨</h2>
<p>Oh man, how I loved this app. As a frontend developer, I was constantly taking screenshots, pasting them into Paint and then drawing arrows, boxes and text on top of them to include in JIRA tickets, emails etc. I even remember using it to create some icons back in the day. I couldn't believe the Mac didn't have a native paint app. However, I found two solutions that helped me dry my tears.</p>
<h3 id="heading-1-shape-detection-in-preview">1. Shape detection in Preview</h3>
<p>I may be easily amused, but this feature blew my mind when I first tried it. When you take a screenshot (<code>command</code> + <code>shift</code> + <code>3</code>), a thumbnail will appear in the bottom right corner of the screen. Once you click on the thumbnail, the native Preview app on the Mac will open. From here you can draw a rough circle, square, or arrow and if it's recognised as a standard shape, it’s replaced by that shape. How cool is that?!</p>
<p><img src="https://i.imgur.com/fZPwazm.gif" alt="Shape detection in Preview app" /></p>
<h3 id="heading-2-sketchpad">2. Sketchpad</h3>
<p>One drawback to the native Preview app is that you can't simply copy and paste two images onto one canvas. Again, I do this a lot when creating "before and after" screenshots of tasks I'm working on. It's so much easier to convey this in one single side-by-side image instead of two separate ones.</p>
<p>Again MS Paint was great for this, so I had to find an alternative. I tried lots of Mac apps, browser extensions and web apps but struggled to find anything suitable, user friendly, and free, which had similar features to MS Paint. Finally, I found it! <a target="_blank" href="https://sketch.io/sketchpad/">Sketchpad</a> is a fantastic web app where you can quickly copy and paste images side by side, draw, add text, fill vectors and lots more.</p>
<h2 id="heading-screenshots-and-video-recording">Screenshots and video recording 📹</h2>
<p>I touched on this earlier, but as a frontend developer, I take <strong>a lot</strong> of screenshots and recordings so that I can attach them to JIRA tickets, Slack messages, emails and so on. I think that this is an important part of any frontend developer's workflow, so I wanted to go through it in a bit more detail.</p>
<h3 id="heading-basic-screenshot">Basic screenshot</h3>
<p>Using the <code>command</code> + <code>shift</code> + <code>3</code> shortcut, you can take a screenshot of the currently active screen. You should then see a thumbnail appear in the right bottom corner of your screen. Clicking on this will bring up the native Preview app, which (as mentioned earlier) you can use to draw on the image. It will save the image to your default screenshots folder on your Mac, which is useful if you want to refer back to it later.</p>
<h3 id="heading-capture-a-portion-of-the-screen">Capture a portion of the screen</h3>
<p>Using the <code>command</code> + <code>shift</code> + <code>4</code> shortcut, you can take a screenshot of a portion of the screen. This will save the image to your Mac. Out of all the screenshot options, I probably use this the most.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1635622962173/sbZw41Co2.png" alt="Capture a portion of the screen" /></p>
<h3 id="heading-capture-a-portion-of-the-screen-and-copy-to-clipboard">Capture a portion of the screen and copy to clipboard</h3>
<p>Using the <code>command</code> + <code>shift</code> + <code>control</code> + <code>4</code> shortcut, you can take a screenshot of a portion of the screen. The difference between this and the previous shortcut is that it will only copy the image to your clipboard. It will not save the image to your Mac. This is useful if you want to quickly copy and paste screenshots into chat or an email body, but not take up space on your hard drive.</p>
<h3 id="heading-screenshot-a-window">Screenshot a window</h3>
<p>Using the <code>command</code> + <code>shift</code> + <code>control</code> + <code>space</code> shortcut, you can take a screenshot of a particular window.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1635622880504/cVoCEuyxf.png" alt="Screenshot a window" /></p>
<h3 id="heading-record-the-screen">Record the screen</h3>
<p>Using the <code>command</code> + <code>shift</code> + <code>5</code> shortcut, you will see a toolbar appear which allows you to record the entire screen, or just a portion of it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1635623129663/4TmBzZsyf.png" alt="Mac record video toolbar" /></p>
<p>To stop recording, just press the same <code>command</code> + <code>shift</code> + <code>5</code> shortcut again and press the "stop" button. I use this very often in PRs, or when showing my team some early progress of a feature I'm working on.</p>
<p>You can even trim the beginning and end of the video using this button:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1635623459731/NDwUc-vkE.png" alt="Trim video button on Mac" /></p>
<h3 id="heading-noteworthy-screenshot-app">Noteworthy screenshot app</h3>
<p>I thought it was worth calling out a great free tool which I've found very useful called <a target="_blank" href="https://shottr.cc/">Shottr</a>. It hasn't fully replaced the native screenshot functionality for me, but it's mighty close.</p>
<h2 id="heading-shortcuts">Shortcuts ⌨️</h2>
<p>First of all, the keyboard is subtly different compared to Windows. The <code>command</code> key is what you'll use for most shortcuts and commands, it's mostly similar to the <code>ctrl</code> key on Windows. In most cases, shortcuts that require the use of the <code>alt</code> key on windows will use the <code>option</code> key on Mac.</p>
<p>Once I got used to these differences I started to look up various shortcuts for applications I use every day. Below are only some of the shortcuts I use, but <a target="_blank" href="https://support.apple.com/en-us/HT201236">here</a> is the full list if you need it.</p>
<h3 id="heading-basics">Basics</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Shortcut</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>command</code> + <code>x</code></td><td>Cut</td></tr>
<tr>
<td><code>command</code> + <code>c</code></td><td>Copy</td></tr>
<tr>
<td><code>command</code> + <code>v</code></td><td>Paste</td></tr>
<tr>
<td><code>command</code> + <code>w</code></td><td>Close window</td></tr>
<tr>
<td><code>command</code> + <code>z</code></td><td>Undo</td></tr>
<tr>
<td><code>command</code> + <code>spacebar</code></td><td>Open spotlight to quickly find and open apps, documents, and other files.</td></tr>
<tr>
<td><code>command</code> + <code>tab</code></td><td>Switch apps</td></tr>
<tr>
<td><code>command</code> + <code>n</code></td><td>New finder window</td></tr>
<tr>
<td><code>command</code> + <code>delete</code></td><td>Delete file / folder</td></tr>
<tr>
<td><code>command</code> + <code>shift</code> + <code>.</code></td><td>Show hidden files in Finder</td></tr>
<tr>
<td><code>control</code> + <code>down arrow</code></td><td>Show all windows of the active app (i.e all Chrome windows)</td></tr>
<tr>
<td><code>command</code> + <code>shift</code> + <code>3</code></td><td>Take a screenshot of the currently active screen</td></tr>
<tr>
<td><code>command</code> + <code>shift</code> + <code>4</code></td><td>Take a screenshot a portion of the screen</td></tr>
<tr>
<td><code>command</code> + <code>shift</code> + <code>4</code> + <code>space</code></td><td>Take a sccreenshot of a window of your choosing</td></tr>
<tr>
<td><code>command</code> + <code>shift</code> + <code>control</code> + <code>4</code></td><td>Take a screenshot of a portion of the screen and copy it to the clipboard</td></tr>
<tr>
<td><code>command</code> + <code>c</code> then <code>command</code> + <code>option</code> + <code>v</code></td><td>Cut and paste files in finder</td></tr>
<tr>
<td><code>enter</code></td><td>Rename selected file or folder</td></tr>
</tbody>
</table>
</div><h3 id="heading-text-editing">Text editing</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Shortcut</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>command</code> + <code>left arrow</code></td><td>Go to beginning of line</td></tr>
<tr>
<td><code>command</code> + <code>right arrow</code></td><td>Go to end of line</td></tr>
<tr>
<td><code>command</code> + <code>control</code> + <code>spacebar</code></td><td>Show Emoji viewer 😃</td></tr>
<tr>
<td><code>command</code> + <code>shift</code> + <code>v</code></td><td>Paste without formatting</td></tr>
<tr>
<td><code>option</code> + <code>backspace</code></td><td>Delete word</td></tr>
<tr>
<td><code>command</code> + <code>backspace</code></td><td>Delete all text left of cursor</td></tr>
</tbody>
</table>
</div><h3 id="heading-chrome">Chrome</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Shortcut</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>command</code> + <code>option</code> + <code>i</code></td><td>Open dev tools</td></tr>
<tr>
<td><code>command</code> + <code>r</code></td><td>Reload page</td></tr>
<tr>
<td><code>command</code> + <code>f</code></td><td>Search within the page</td></tr>
<tr>
<td><code>command</code> + <code>shift</code> + <code>t</code></td><td>Open last closed tab</td></tr>
</tbody>
</table>
</div><h3 id="heading-slack">Slack</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Shortcut</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>command</code> + <code>shift</code> + <code>c</code></td><td>Format higlighted text to code</td></tr>
<tr>
<td><code>command</code> + <code>k</code></td><td>Quick switcher</td></tr>
<tr>
<td><code>command</code> + <code>shift</code> + <code>u</code></td><td>Add link text (You can also paste a link directly onto highlighted text to achieve the same)</td></tr>
</tbody>
</table>
</div><h3 id="heading-iterm2">iTerm2</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Shortcut</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>control</code> + <code>u</code></td><td>Delete line</td></tr>
<tr>
<td><code>control</code> + <code>y</code></td><td>Restore previous deleted line. This is handy if you need to run another command then come back to the previous one.</td></tr>
<tr>
<td><code>command</code> + <code>t</code></td><td>New tab</td></tr>
</tbody>
</table>
</div><h3 id="heading-vs-code">VS Code</h3>
<p>For VS Code, I have customised a lot of the shortcuts myself as I personally think they make more sense and are easier to remember, so I've included them below. However, <a target="_blank" href="https://code.visualstudio.com/shortcuts/keyboard-shortcuts-macos.pdf">here's a list</a> of the default out-of-the-box shortcuts.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Shortcut</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>command</code> + <code>click</code></td><td>Go to definition</td></tr>
<tr>
<td><code>command</code> + <code>p</code></td><td>Quick search</td></tr>
<tr>
<td><code>option</code> + <code>left arrow</code></td><td>Go back</td></tr>
<tr>
<td><code>option</code> + <code>right arrow</code></td><td>Go forward</td></tr>
<tr>
<td><code>command</code> + <code>d</code></td><td>Copy lines down i.e duplicate line of code</td></tr>
<tr>
<td><code>control</code> + <code>e</code></td><td>Add cursor to next matching text</td></tr>
<tr>
<td><code>command</code> + <code>/</code></td><td>Comment out line</td></tr>
<tr>
<td><code>command</code> + <code>backspace</code></td><td>Delete line</td></tr>
<tr>
<td><code>command</code> + <code>\</code></td><td>Open file in side pane</td></tr>
</tbody>
</table>
</div><h2 id="heading-final-thoughts">Final thoughts 🤔</h2>
<p>You might wonder if I miss Windows for frontend development, well my answer is "kinda but not really". Yes, Windows does some things better, but Mac has some of its own advantages. I think Mac is a happy medium for software developers who work on a lot of backend code as it's based on Unix, runs most Linux applications but is still user friendly. However, as a frontend developer, all I really need is VS Code and a browser to do my work, both of which work exactly the same regardless of which OS I'm using.</p>
<p>Hopefully, this article can make the Windows to Mac switch that small bit easier for you. If you found this article useful, or have some tips of your own, feel free to leave a comment 🙌</p>
<h2 id="heading-useful-resources">Useful resources 📖</h2>
<p>I found these resources useful when learning the basics during my Windows to Mac switcharoo:</p>
<ul>
<li><p><a target="_blank" href="https://support.apple.com/en-us/HT204216">Mac tips for Windows switchers</a></p>
</li>
<li><p><a target="_blank" href="https://support.apple.com/en-gb/guide/mac-help/cpmh0038/mac">A list of Windows and Mac terms to help you find what you’re looking for.</a></p>
</li>
</ul>
<h2 id="heading-want-more">Want more? 📢</h2>
<p>I mainly write about real-world tech topics I face in my everyday life as a frontend developer. If this appeals to you, then feel free to follow me on <a target="_blank" href="https://bsky.app/profile/cathalmacdonnacha.com">BlueSky</a>. 🦋</p>
<p>Bye for now 👋</p>
]]></content:encoded></item><item><title><![CDATA[How to test a select element with React Testing Library]]></title><description><![CDATA[I recently needed to add tests for a <select> element I was developing, and I couldn't find a lot of resources on how to do this with React Testing Library, so I'll share the approach I went with.
The <select> element
First of all, let's create a <se...]]></description><link>https://cathalmacdonnacha.com/how-to-test-a-select-element-with-react-testing-library</link><guid isPermaLink="true">https://cathalmacdonnacha.com/how-to-test-a-select-element-with-react-testing-library</guid><category><![CDATA[React]]></category><category><![CDATA[Testing Library]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Cathal Mac Donnacha]]></dc:creator><pubDate>Wed, 06 Oct 2021 18:40:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1633535047249/McHvmmjlj.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently needed to add tests for a <code>&lt;select&gt;</code> element I was developing, and I couldn't find a lot of resources on how to do this with <a target="_blank" href="https://testing-library.com/docs/react-testing-library/intro/">React Testing Library</a>, so I'll share the approach I went with.</p>
<h2 id="heading-the-element"><strong>The</strong> <code>&lt;select&gt;</code> element</h2>
<p>First of all, let's create a <code>&lt;select&gt;</code> element with some options. Here I have an array with 3 countries:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> countries = [ 
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"Austria"</span>, <span class="hljs-attr">isoCode</span>: <span class="hljs-string">"AT"</span> },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"United States"</span>, <span class="hljs-attr">isoCode</span>: <span class="hljs-string">"US"</span> }, 
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"Ireland"</span>, <span class="hljs-attr">isoCode</span>: <span class="hljs-string">"IE"</span> }, 
]
</code></pre>
<p>Here's the <code>&lt;select&gt;</code> element itself, it has:</p>
<ol>
<li><p>A default placeholder <code>&lt;option&gt;</code> asking the user to "Select a country".</p>
</li>
<li><p>A <code>.map</code> method so we can iterate over the <code>countries</code> array and add an <code>&lt;option&gt;</code> element for each one.</p>
</li>
</ol>
<pre><code class="lang-jsx">&lt;select&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span>Select a country<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span></span>
  {countries.map(<span class="hljs-function"><span class="hljs-params">country</span> =&gt;</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{country.isoCode}</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{country.isoCode}</span>&gt;</span>
      {country.name}
    <span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span></span>
  ))}
&lt;/select&gt;
</code></pre>
<h2 id="heading-tests">Tests</h2>
<p>Now that we have a basic <code>&lt;select&gt;</code> element which displays some countries, let's go ahead and write some tests! Yay...my favourite part 😀</p>
<p>The beauty of React Testing Library is that it makes you focus more on writing tests the way an actual user would interact with your application, so that's the approach I've taken with the tests below. Of course you may have your own unique scenarios, if you do, just think <em>"How would a real user interact with my select element?"</em>.</p>
<h4 id="heading-default-selection"><strong>Default selection</strong></h4>
<pre><code class="lang-jsx">it(<span class="hljs-string">'should correctly set default option'</span>, <span class="hljs-function">() =&gt;</span> {
  render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>)
  expect(screen.getByRole(<span class="hljs-string">'option'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Select a country'</span> }).selected).toBe(<span class="hljs-literal">true</span>)
})
</code></pre>
<h4 id="heading-correct-number-of-options"><strong>Correct number of options</strong></h4>
<pre><code class="lang-jsx">it(<span class="hljs-string">'should display the correct number of options'</span>, <span class="hljs-function">() =&gt;</span> {
  render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>)
  expect(screen.getAllByRole(<span class="hljs-string">'option'</span>).length).toBe(<span class="hljs-number">4</span>)
})
</code></pre>
<h4 id="heading-change-selected-option"><strong>Change selected option</strong></h4>
<pre><code class="lang-jsx">it(<span class="hljs-string">'should allow user to change country'</span>, <span class="hljs-function">() =&gt;</span> {
  render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>)
  userEvent.selectOptions(
    <span class="hljs-comment">// Find the select element, like a real user would.</span>
    screen.getByRole(<span class="hljs-string">'combobox'</span>),
    <span class="hljs-comment">// Find and select the Ireland option, like a real user would.</span>
    screen.getByRole(<span class="hljs-string">'option'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Ireland'</span> }),
  )
  expect(screen.getByRole(<span class="hljs-string">'option'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Ireland'</span> }).selected).toBe(<span class="hljs-literal">true</span>)
})
</code></pre>
<h2 id="heading-gotchas">Gotchas</h2>
<p>Initially when I started to look into writing tests for these scenarios I went with the following approach:</p>
<pre><code class="lang-jsx">it(<span class="hljs-string">'should allow user to change country'</span>, <span class="hljs-function">() =&gt;</span> {
  render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>)
  userEvent.selectOptions(
    screen.getByRole(<span class="hljs-string">'combobox'</span>),
    screen.getByRole(<span class="hljs-string">'option'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Ireland'</span> } ),
  )
  expect(screen.getByRole(<span class="hljs-string">'option'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Ireland'</span> })).toBeInTheDocument();
})
</code></pre>
<p>Notice the difference? I was only checking that the "Ireland" <code>&lt;option&gt;</code> existed instead of checking if it was actually selected. Yet my test was still passing 🤔</p>
<pre><code class="lang-jsx">expect(screen.getByRole(<span class="hljs-string">'option'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Ireland'</span> })).toBeInTheDocument();
</code></pre>
<p>Let's take a look at why this happened. When the component is loaded, the following is rendered:</p>
<pre><code class="lang-jsx">&lt;select&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">""</span>&gt;</span>Select a country<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"US"</span>&gt;</span>United States<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"IE"</span>&gt;</span>Ireland<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"AT"</span>&gt;</span>Austria<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span></span>
&lt;/select&gt;
</code></pre>
<p>So from JSDOM's point of view, the "Ireland" <code>&lt;option&gt;</code> <strong>always</strong> exists within the document, causing my test to pass!</p>
<p>Whereas the correct approach is to use <code>.selected</code>:</p>
<pre><code class="lang-jsx">expect(screen.getByRole(<span class="hljs-string">'option'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Ireland'</span> }).selected).toBe(<span class="hljs-literal">true</span>);
</code></pre>
<p>Gotchas like this can be just as dangerous as not writing the test in the first place as it gives you false confidence about your tests. This is why I always recommend intentionally causing your tests to fail, like this:</p>
<pre><code class="lang-jsx">expect(screen.getByRole(<span class="hljs-string">'option'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Austria'</span> }).selected).toBe(<span class="hljs-literal">true</span>);
</code></pre>
<pre><code class="lang-bash">❌ Test failed: should allow user to change country
Expected: <span class="hljs-literal">true</span>
Received: <span class="hljs-literal">false</span>
</code></pre>
<p>This way you can be confident that it only passes for the intended scenario 🥳</p>
<h2 id="heading-full-code-example">Full code example</h2>
<p><a target="_blank" href="https://codesandbox.io/s/testing-a-select-element-with-react-testing-library-wtxze?file=/src/App.test.js">Here's a codesandox</a> which includes the basic examples shown above.</p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>So there it is, you should now be able to write some basic tests for your <code>&lt;select&gt;</code> elements using React Testing Library. Of course, I'm not an expert on this topic, I'm simply sharing what I learned in the hope that I can pass on some knowledge.</p>
<p>If you found this article useful, please give it a like and feel free to leave any feedback in the comments 🙏</p>
<h2 id="heading-want-to-see-more">Want to see more?</h2>
<p>I mainly write about real tech topics I face in my everyday life as a Frontend Developer. If this appeals to you then feel free to follow me on <a target="_blank" href="https://bsky.app/profile/cathalmacdonnacha.com">BlueSky</a>. 🦋</p>
<p>Bye for now 👋</p>
]]></content:encoded></item></channel></rss>