<?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" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ automation testing  - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ automation testing  - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 30 May 2026 08:55:43 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/automation-testing/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How I Tested Malaysia's Open Data Portals with Plain English ]]>
                </title>
                <description>
                    <![CDATA[ Most end-to-end test suites drive a real browser and click through an app like a user. They check whether a page renders and whether elements appear. But they don't check whether the numbers on those  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-i-tested-malaysia-s-open-data-portals-with-plain-english/</link>
                <guid isPermaLink="false">69eaad32904b915438ce46f9</guid>
                
                    <category>
                        <![CDATA[ postmark ]]>
                    </category>
                
                    <category>
                        <![CDATA[ playwright ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation testing  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ breakingappshackathon ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tech With RJ ]]>
                </dc:creator>
                <pubDate>Thu, 23 Apr 2026 23:37:22 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/605584805f8d5121697263ca/d4859bd4-15d5-4bb7-ba9e-d4693c90163d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most end-to-end test suites drive a real browser and click through an app like a user. They check whether a page renders and whether elements appear.</p>
<p>But they don't check whether the numbers on those elements are correct. A data-pipeline bug that shows Malaysia's population as 3.4 million instead of the real 34 million slips past every selector test in the suite.</p>
<p>The element still exists. A number still renders. The page still looks right. But the bug ships and sits there until a human notices.</p>
<p>I work as a full-stack engineer. Writing end-to-end (E2E) tests with <a href="https://playwright.dev">Playwright</a> and unit tests with <a href="https://jestjs.io">Jest</a> is part of my day job. I also use <a href="https://github.com/microsoft/playwright-mcp">Playwright MCP</a>, the bridge between AI assistants like Claude and a running browser, when I need to generate first-draft test code or debug a flow.</p>
<p>None of that tooling closes the maintenance tax on selector-based suites. Every E2E suite I keep alive at work accumulates <code>data-testid</code> selectors, <code>waitForSelector</code> calls, and tests that break because someone renamed a button.</p>
<p>Bug0's <a href="https://hashnode.com/hackathons/breaking-things">Breaking Apps Hackathon</a> gave me a pretext to try something different. Over a weekend, <a href="https://github.com/LeeRenJie/passmark-hackathon">I built an automated regression suite</a> for Malaysia's three public open data portals, <a href="https://data.gov.my">data.gov.my</a>, <a href="https://open.dosm.gov.my">OpenDOSM</a>, and <a href="https://data.moh.gov.my">KKMNow</a>, using <a href="https://github.com/bug0inc/passmark">Passmark</a>, Bug0's open-source AI-driven Playwright library.</p>
<p>The tests are written in plain English. Two AI models verify each assertion. A third arbitrates disagreements.</p>
<h3 id="heading-what-youll-find-below">What You'll Find Below:</h3>
<ul>
<li><p>How to write an E2E test that checks whether a dashboard's numbers are correct, not only whether the page renders</p>
</li>
<li><p>A specific assertion pattern (range-bounded KPIs) that catches an entire class of data-pipeline bug that selector tests miss, with working examples ready to copy</p>
</li>
<li><p>A cross-field math assertion that takes one sentence in Passmark and around a hundred lines of code without it</p>
</li>
<li><p>How Passmark's own failure explanations became my debugging loop (the single biggest shift in how I'll write E2E tests going forward)</p>
</li>
<li><p>The real limits: a 14% cache-hit rate, a dependency on OpenRouter, and what two-model voting fails to catch</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-why-malaysias-open-data-portals">Why Malaysia's Open Data Portals</a>?</p>
</li>
<li><p><a href="#heading-what-is-passmark">What Is Passmark</a>?</p>
</li>
<li><p><a href="#heading-the-hero-spec-range-bounded-assertions">The Hero Spec: Range-Bounded Assertions</a></p>
<ul>
<li><a href="#heading-what-two-model-voting-doesnt-catch">What Two-Model Voting Doesn't Catch</a></li>
</ul>
</li>
<li><p><a href="#heading-going-further-cross-field-math">Going Further: Cross-Field Math</a></p>
</li>
<li><p><a href="#heading-what-i-found-across-three-runs">What I Found Across Three Runs</a></p>
<ul>
<li><p><a href="#heading-the-debugging-loop">The Debugging Loop</a></p>
</li>
<li><p><a href="#heading-the-two-specs-that-still-fail-are-the-most-interesting">The Two Specs That Still Fail Are the Most Interesting</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-what-it-cost-and-why-cache-rate-is-cost-rate">What It Cost, and Why Cache Rate Is Cost Rate</a></p>
</li>
<li><p><a href="#heading-the-pattern-worth-stealing">The Pattern Worth Stealing</a></p>
</li>
<li><p><a href="#heading-honest-verdict">Honest Verdict</a></p>
</li>
<li><p><a href="#heading-resources">Resources</a></p>
</li>
</ul>
<h2 id="heading-why-malaysias-open-data-portals">Why Malaysia's Open Data Portals?</h2>
<p>The hackathon suggested targets like Vercel Commerce, Cal.com, and Hashnode. These all would've been solid picks.</p>
<p>But I wanted to test something local and closer to my day-to-day work instead. I also wanted a data-heavy site where the numbers on screen have to be accurate, as I work with numbers too on a daily basis.</p>
<p>Malaysia has three public open-data portals:</p>
<ul>
<li><p><a href="https://data.gov.my">data.gov.my</a>, run by MAMPU, the government's digital transformation agency</p>
</li>
<li><p><a href="https://open.dosm.gov.my">OpenDOSM</a>, run by the Department of Statistics</p>
</li>
<li><p><a href="https://data.moh.gov.my">KKMNow</a>, run by the Ministry of Health</p>
</li>
</ul>
<p>They're public, no authentication required, with documented APIs. Seemed like a good fit for an automated test suite. The data on them is what Malaysians use every day, so accuracy isn't optional.</p>
<h2 id="heading-what-is-passmark">What Is Passmark?</h2>
<p>Passmark is a Playwright library where the tests read like specs. Here's an example:</p>
<pre><code class="language-typescript">await runSteps({
  page,
  userFlow: "population dashboard smoke",
  steps: [
    { description: "Navigate to https://data.gov.my/dashboard/kawasanku" },
    {
      description: "Wait for the country-level Malaysia view to render",
      waitUntil: "A headline population number is visible",
    },
  ],
  assertions: [
    {
      assertion:
        "The page shows Malaysia's total population as a number greater than 20 million and less than 40 million",
    },
  ],
  test,
  expect,
});
</code></pre>
<p>There are no selectors, no <code>data-testid</code>, and no <code>page.locator()</code>. The assertion expresses what I care about, in the words I would use with a colleague.</p>
<p>On the first run, an AI agent drives the page and caches the resolved Playwright action to Redis. Every run after that replays at native Playwright speed with zero model calls.</p>
<p>When the UI changes and a cached action fails, the AI re-engages only for that step. Two assertion models (Claude and Gemini) vote. A third model arbitrates disagreements.</p>
<h2 id="heading-the-hero-spec-range-bounded-assertions">The Hero Spec: Range-Bounded Assertions</h2>
<p>Range-bounded assertions were the first shape of test I wrote, and the one I came back to most across the suite.</p>
<p>The idea is straightforward: check that a number on the page falls inside a sensible range, not that a specific element exists.</p>
<p>The image below is the Playwright report from the population spec, with all four range-bounded assertions passing.</p>
<img src="https://cdn.hashnode.com/uploads/covers/605584805f8d5121697263ca/4a5f70e6-8a75-489b-8d0d-7d4226653b1a.png" alt="Playwright HTML report detail for the population spec. Passmark's annotation reads: &quot;Total Population (2025) with a value of 34.2 million, which is between 20 million and 40 million.&quot; All four range-bounded assertions pass." style="display:block;margin:0 auto" width="1019" height="1569" loading="lazy">

<p>The range-bounded population test is the one that shows Passmark's real value.</p>
<p>Traditional Playwright asserts DOM structure. It confirms that an element with class <code>kpi-total</code> contains the text <code>34.2 million</code>. That tells you the page rendered, not whether the number makes sense.</p>
<p>A bug that shows Malaysia's population as <code>3.42 million</code> sails past any selector test. The DOM is correct. The number renders. Nothing breaks in the conventional sense.</p>
<p>Passmark reads the page, evaluates the claim, and fails because <code>3.42 million</code> falls outside the sane range. Two models vote. A hallucination by one model alone produces no false pass.</p>
<h3 id="heading-what-two-model-voting-doesnt-catch">What Two-Model Voting Doesn't Catch</h3>
<p>Voting defends against one model misreading the page. It doesn't defend against both models misreading the page the same way. If Claude and Gemini both parse "32.4 million" as "3.24 million" because of the same unusual spacing in the DOM, they agree, they vote pass, and the bug ships.</p>
<p>The mitigation is assertion design. Write assertions that are hard to misread. A range check ("between 20 million and 40 million") is harder for a model to get wrong than a prose check ("roughly 34 million"). Numerical bounds leave less room for interpretation than adjectives. The more your assertion looks like a unit test written in English, the less room the models have to disagree.</p>
<h2 id="heading-going-further-cross-field-math">Going Further: Cross-Field Math</h2>
<p>Range-bounded assertions are a good first step. They catch "is this number in the right ballpark?" But they don't catch "do these numbers agree with each other?"</p>
<p>For that, you need cross-field math. If a dashboard shows a total population and a breakdown by gender, those two things are supposed to agree. Male plus female should equal total. Ethnicity breakdown percentages should sum to 100.</p>
<pre><code class="language-typescript">test("Cross-field math: sex breakdown sums to total population", async ({ page }) =&gt; {
  test.setTimeout(180_000);
  await runSteps({
    page,
    userFlow: "population sex breakdown consistency",
    steps: [
      { description: "Navigate to https://data.gov.my/dashboard/kawasanku" },
      {
        description: "Wait for the Malaysia country-level view with breakdown data",
        waitUntil:
          "A headline total population figure is visible and a breakdown by sex is shown on the page",
      },
    ],
    assertions: [
      {
        assertion:
          "The male and female population values shown on the page add up to approximately the headline total population, within a 5% margin",
      },
      {
        assertion:
          "Any percentage-based breakdowns visible on the page (by sex, age, or ethnicity) sum to approximately 100% within a 2 percentage-point margin",
      },
      {
        assertion: "No breakdown value is negative or greater than the headline total",
      },
    ],
    test,
    expect,
  });
});
</code></pre>
<p>Try writing that in vanilla Playwright. You need selectors for the headline number, selectors for the breakdown components, number parsing with a comma-aware regex, and a margin calculation. Seventy to a hundred lines of code to verify three invariants a primary school student would call obvious.</p>
<p>The Passmark version is one spec. I ran it against <a href="https://data.gov.my/dashboard/kawasanku">Kawasanku's</a> live country view. All three assertions passed in 1.4 minutes. Passmark's annotation, verbatim:</p>
<blockquote>
<p><em>"The headline total population figure 'Malaysia has a population of 32,447,385 people.' is visible, and 'Gender And Age Distribution' is shown, which implies a breakdown by sex (male, female) will be available."</em></p>
</blockquote>
<p>Two models read the page, extract the numbers, do the arithmetic, and agree. When the dashboard changes layout in three months, the same assertion still works, because it never named a selector.</p>
<p>This is the class of test I want running against every dashboard product that I touch. Financial totals matching their line items. Percentages that sum to 100. Inventory counts equal to the sum of warehouse locations. This rarely gets checked today, because writing the check by hand outweighs the perceived value of running it.</p>
<h2 id="heading-what-i-found-across-three-runs">What I Found Across Three Runs</h2>
<table>
<thead>
<tr>
<th>Run</th>
<th>Passed</th>
<th>Key change</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>4 of 13 (31%)</td>
<td>Baseline. Wrote specs without looking at the target pages</td>
</tr>
<tr>
<td>2</td>
<td>8 of 13 (62%)</td>
<td>Rewrote five over-specified assertions using Passmark's own feedback</td>
</tr>
<tr>
<td>3</td>
<td>12 of 13 (92%)</td>
<td>Dropped one more wrong assertion, bumped timeouts, added retry, installed WebKit</td>
</tr>
</tbody></table>
<img src="https://cdn.hashnode.com/uploads/covers/605584805f8d5121697263ca/0181e92e-3341-4f1a-9830-cd4acd305e1c.png" alt="Playwright HTML report overview page showing the final run. 11 tests passed, 2 failed, total time 21.1 minutes across 13 specs." style="display:block;margin:0 auto" width="1012" height="1460" loading="lazy">

<p>Every passing spec after run 1 came from Passmark telling me, in plain English, why my assertion didn't match the page.</p>
<p>Here are three examples from run 1:</p>
<p>For <code>dataset-detail.spec.ts</code>, I asserted "an API usage snippet (curl or JS) is shown." knowingly that the page is using Python and I wanted to see what the result was. Passmark replied:</p>
<blockquote>
<p><em>"The page contains API usage snippets, but they are specifically for Python using the requests library. There are no snippets provided in curl or JavaScript formats."</em></p>
</blockquote>
<p>The page had snippets. I asked for the wrong languages. Fix: accept any language.</p>
<p>For <code>dashboard-population.spec.ts</code>, I asserted "a chart visualizing population by age or ethnicity is rendered." Passmark replied:</p>
<blockquote>
<p><em>"The current page displays charts for vital statistics such as Live Births, Deaths, and Natural Increase over time, but there is no chart visualizing population specifically by age groups or ethnicity."</em></p>
</blockquote>
<p>The charts are there. Not the slice I guessed. Fix: accept any chart about population.</p>
<p>For <code>kkmnow/hospital-utilisation.spec.ts</code>, I asserted a "headline bed-utilisation percentage." Passmark replied:</p>
<blockquote>
<p><em>"While there are multiple bed-utilisation percentages listed in tables and rankings further down the page, there is no prominent, top-level headline KPI figure displaying the overall bed-utilisation percentage."</em></p>
</blockquote>
<p>The numbers are there. I had asked for a layout the designers didn't build.</p>
<p><strong>This is the killer feature:</strong> Passmark's failure messages aren't stack traces. They're explanations. The AI read the page, compared it against my words, and pointed me at the fix. Nothing like a selector-based test throwing <code>TimeoutError: waiting for locator</code>.</p>
<h3 id="heading-the-debugging-loop">The Debugging Loop</h3>
<p>Once I saw the pattern, the loop became my main technique. Here's the procedure:</p>
<ol>
<li><p>Read the failure message word for word. Don't skim.</p>
</li>
<li><p>Trust it as a description of what is on the page. The AI has read the page. Your assertion has not.</p>
</li>
<li><p>Rewrite the assertion so it matches what's on the page. Broaden, narrow, or restate.</p>
</li>
<li><p>Run it again.</p>
</li>
</ol>
<p>The discipline is to not argue with the tool. The page is what the page is. Your assertion is what is wrong. Every time I tried to "fix" the page (convinced my assertion was right and the site was broken), I lost some time. Every time I took the failure message at face value and rewrote, the test passed on the next run.</p>
<p>This is the one of the changes in how I'll write E2E tests going forward. The feedback loop is the tool. Every failed assertion is a draft of the correct one.</p>
<h3 id="heading-the-two-specs-that-still-fail-are-the-most-interesting">The Two Specs That Still Fail Are the Most Interesting</h3>
<h4 id="heading-1-the-two-models-disagreed-and-the-arbiter-call-failed">1. The two models disagreed and the arbiter call failed.</h4>
<p>On <code>catalogue-search.spec.ts</code>, Claude voted fail (72% confidence) and Gemini voted pass (100% confidence) on the same assertion. I had written the assertion in a way that read two ways.</p>
<p>Passmark escalated to an arbiter model through OpenRouter. The call came back with a 504 from Cloudflare. The arbiter never ran. The suite failed the spec.</p>
<p>This is an honest limit, not a fluke. Any CI that runs Passmark depends on OpenRouter's availability. External gateway errors happen. My fix for the final run was a global retry wrapper around the OpenRouter call, and the 504 stopped being a problem in practice.</p>
<p>If you bring this to production CI, plan for retries and treat OpenRouter outages as a first-class failure mode in your runbook.</p>
<img src="https://cdn.hashnode.com/uploads/covers/605584805f8d5121697263ca/9e08d49a-00e6-4803-a86e-decfa0534308.png" alt="Playwright HTML report detail for the catalogue-search failure. Shows Claude and Gemini returning different verdicts on the same assertion, Passmark escalating to an arbiter model, and the arbiter call aborting with a 504 from Cloudflare." style="display:block;margin:0 auto" width="995" height="1259" loading="lazy">

<p>This failure taught me something about assertion design: my wording was ambiguous. Claude's reading was reasonable. Gemini's reading was reasonable. When you write tests in English, being precise about what you mean is part of writing a good test.</p>
<h4 id="heading-2-the-wait-condition-fired-too-early">2. The wait condition fired too early.</h4>
<p>On the KKMNow spec, I had <code>waitUntil: "A utilisation metric is visible"</code>. The page showed the section label "Hospital Bed Utilisation (%)" before the numbers finished loading. The wait step saw the label, decided the condition was met, and moved on. By the time the numbers rendered, the test had run out of time. Once the page was fully loaded, the range assertions would have passed on content.</p>
<blockquote>
<p><em>"The page displays multiple bed-utilisation percentages within the specified range (0% to 120%). For example, the ranked list shows Perlis at 93.1% and Melaka at 88.2%."</em></p>
</blockquote>
<img src="https://cdn.hashnode.com/uploads/covers/605584805f8d5121697263ca/2a9c8ade-f2ab-4c58-a56b-f7714bcabef5.png" alt="Playwright HTML report detail for the KKMNow spec. The test times out on the initial waitUntil, but Passmark's annotations show the range and state-selector assertions passed on content once the dashboard hydrated. Example quoted: &quot;Perlis at 93.1% and Melaka at 88.2%.&quot;" style="display:block;margin:0 auto" width="1000" height="1152" loading="lazy">

<p>The lesson: your <code>waitUntil</code> wording needs the same care as your assertion wording. Both are read by AI. A vague wait is as bad as a vague assertion.</p>
<h2 id="heading-what-it-cost-and-why-cache-rate-is-cost-rate">What It Cost, and Why Cache Rate Is Cost Rate</h2>
<p>Each of the three runs took about 20 minutes on 13 specs with a single worker. The hackathon's pooled OpenRouter key covered the AI costs, so I have no personal dollar figure to report.</p>
<p>The more useful cost finding is what gets cached.</p>
<pre><code class="language-bash">$ docker exec passmark-redis redis-cli DBSIZE
5
</code></pre>
<p>Five steps out of roughly 35 were cached across three runs. A 14% cache-hit rate. The Passmark README explains why:</p>
<blockquote>
<p><em>Only steps that produced a single tool call get cached. Multi-step sequences are considered non-deterministic.</em></p>
</blockquote>
<p>Most of my steps described multi-tool sequences. "Open the area selector and choose Selangor, then wait for navigation" becomes click, wait, verify. Those don't cache by design.</p>
<p>This matters for your budget. An 86% miss rate means 86% of your steps call a model on every run. The cost model is per-tool-call via OpenRouter.</p>
<p>To estimate your own bill: count non-atomic steps in your suite, multiply by your chosen model's per-call price at current OpenRouter rates, and the product is your recurring cost per run. Cache rate is cost rate.</p>
<p>The fix is authoring discipline. Split compound descriptions into atomic steps. Treat cache fill rate as a metric you track, not an implementation detail to ignore. A suite with 80% atomic steps costs a fifth of a suite with 14%.</p>
<h2 id="heading-the-pattern-worth-stealing">The Pattern Worth Stealing</h2>
<p>The idea here is bigger than Passmark.</p>
<p><strong>Check that the numbers on your dashboards make sense.</strong> Most teams don't. They should.</p>
<p>A one-line assertion like "the headline number is between 20 million and 40 million" catches several classes of bug regular tests miss.</p>
<p>Here are four common ones:</p>
<ul>
<li><p>The data pipeline divided by the wrong thing, so the number on screen is ten times too small.</p>
</li>
<li><p>A timezone bug made yesterday's total show up under tomorrow's date.</p>
</li>
<li><p>The data never refreshed, so users are looking at last week's numbers.</p>
</li>
<li><p>A locale flip swapped commas and decimals, so 1,234,567 is now reading as 1.234567.</p>
</li>
</ul>
<p>Civic portals were my target. The pattern applies anywhere a dashboard shows numbers. Fintech reports, SaaS analytics, healthcare metrics, e-commerce admin panels. Any screen where a number is supposed to mean something.</p>
<p>Most of these numbers never get tested. Writing the check by hand is tedious. You need a selector to find the number, code to parse it, code to handle units, and a margin calculation. Fifty lines for one check. Nobody bothers.</p>
<p>You don't need Passmark to steal the idea. The same check works in plain Playwright with <code>page.evaluate</code> and number parsing. The Passmark version is just more efficient to write and readable by anyone on the team, not only engineers.</p>
<h2 id="heading-honest-verdict">Honest Verdict</h2>
<p>Passmark works. Across three runs I went from 4 of 13 passing to 12 of 13 without touching a selector, guided by the tool's own feedback.</p>
<p>Still, the caveats are real:</p>
<ul>
<li><p>On a cold cache, every step waits for a model. Budget more wall-clock time than a selector suite.</p>
</li>
<li><p>In my suite only 14% of steps cached. The other 86% pays model cost on every run. Authoring discipline (atomic steps) is the difference between cents and dollars per run.</p>
</li>
<li><p>Two-model voting doesn't protect against both models misreading the same way. Write assertions that are hard to misread.</p>
</li>
<li><p>Every assertion depends on OpenRouter's availability. External gateway errors need a retry strategy before this runs in CI.</p>
</li>
</ul>
<p>What stuck with me: Passmark didn't make me better at Playwright. It made me write tests I would have skipped otherwise.</p>
<p>What I imagine myself doing at work:</p>
<ul>
<li><p>Run a small nightly Passmark suite against the critical dashboards, focused on range and freshness checks.</p>
</li>
<li><p>Keep traditional Playwright and Jest for everything that has to be fast and deterministic.</p>
</li>
<li><p>Treat every Passmark failure message as a specification of the page, not an error to argue with.</p>
</li>
</ul>
<p>Try this, even if you never touch Passmark. Pick a number on a dashboard you work with. Write a test that fails if the number is outside a sane range. See what breaks. That is the whole pattern and purpose of this article.</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p>Repo: <a href="https://github.com/LeeRenJie/passmark-hackathon">github.com/LeeRenJie/passmark-hackathon</a></p>
</li>
<li><p>Passmark: <a href="https://github.com/bug0inc/passmark">github.com/bug0inc/passmark</a></p>
</li>
<li><p>Breaking Apps Hackathon: <a href="https://hashnode.com/hackathons/breaking-things">hashnode.com/hackathons/breaking-things</a></p>
</li>
<li><p>Test targets: <a href="https://data.gov.my">data.gov.my</a>, <a href="https://open.dosm.gov.my">OpenDOSM</a>, <a href="https://data.moh.gov.my">KKMNow</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The AI Coding Loop: How to Guide AI With Rules and Tests ]]>
                </title>
                <description>
                    <![CDATA[ Building great software isn't about perfect prompts, it's about a disciplined process. In this guide, I'll share my workflow for shipping secure code: defining clear goals, mapping edge cases, and bui ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-guide-ai-with-rules-and-tests/</link>
                <guid isPermaLink="false">699e41d20daf99859e60d319</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation testing  ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sumit Saha ]]>
                </dc:creator>
                <pubDate>Wed, 25 Feb 2026 00:26:58 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/b757ebbf-c9e9-44ec-b92c-7a38a8616e68.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building great software isn't about perfect prompts, it's about a disciplined process. In this guide, I'll share my workflow for shipping secure code: defining clear goals, mapping edge cases, and building incrementally with runnable tests.</p>
<p>Using a Node.js shopping cart example, I'll show why server-side validation and test-driven development beat "one-shot" AI outputs every time. Let's dive into how to make AI your most reliable collaborator.</p>
<h2 id="heading-some-background">Some Background</h2>
<p>Last week I did something that felt amazing for about… five seconds. I opened an AI tool, typed one sentence, and it generated a whole shopping cart module for an e-commerce app. Lots of files, lots of code, even folders and patterns. It looked professional.</p>
<p>And then I realized something: the problem was not "how fast AI wrote code." The problem was "how do I know this code is correct?"</p>
<p>Here's the truth: a big pile of code that you didn't write is not a shortcut. For most developers, it's actually extra work. You have to read it, understand it, and still catch the hidden mistakes.</p>
<p>So today I'm not going to give you another "AI is coming" talk. Instead, I'll show you a simple loop that any developer can follow – beginner, mid-level, or senior – to get better results from AI, step by step, without getting trapped. And I'll show it with a real example you can run in one file.</p>
<h2 id="heading-heres-what-well-cover">Here’s What We’ll Cover:</h2>
<ul>
<li><p><a href="#heading-the-5-second-high-and-the-real-problem">The 5-second high (and the real problem)</a></p>
</li>
<li><p><a href="#heading-the-golden-rule-never-trust-user-prices">The golden rule: never trust user prices</a></p>
</li>
<li><p><a href="#heading-the-mindset-shift-stop-asking-for-the-whole-app">The mindset shift: stop asking for the whole app</a></p>
</li>
<li><p><a href="#heading-the-ai-coding-loop-the-7-step-workflow">The AI coding loop (the 7-step workflow)</a></p>
</li>
<li><p><a href="#heading-apply-the-loop-a-server-side-cart-total-calculator">Apply the loop: a server-side cart total calculator</a></p>
<ul>
<li><a href="#heading-the-prompt-small-piece-strong-constraints">The prompt (small piece, strong constraints)</a></li>
</ul>
</li>
<li><p><a href="#heading-one-file-runnable-example-with-a-wrong-version-on-purpose">One-file runnable example (with a wrong version on purpose)</a></p>
<ul>
<li><a href="#heading-what-you-should-notice-here">What you should notice here</a></li>
</ul>
</li>
<li><p><a href="#heading-how-to-use-failing-tests-as-a-flashlight">How to use failing tests as a flashlight</a></p>
</li>
<li><p><a href="#heading-copy-paste-prompt-template">Copy-paste prompt template</a></p>
</li>
<li><p><a href="#heading-a-calm-hype-check-why-fundamentals-matter-more-now">A calm hype check: why fundamentals matter more now</a></p>
<ul>
<li><a href="#heading-a-simple-exercise-do-this-once-and-youll-feel-the-skill">A simple exercise (do this once and you'll feel the skill)</a></li>
</ul>
</li>
<li><p><a href="#heading-recap">Recap</a></p>
</li>
</ul>
<h2 id="heading-the-5-second-high-and-the-real-problem">The 5-Second High (and the Real Problem)</h2>
<p>A lot of people misunderstand AI coding. They think the main job is typing code. But the main job is thinking clearly. Typing is cheap now. Thinking is expensive.</p>
<p>When AI produces a "perfect-looking" module in one shot, the real work doesn't disappear. It moves downstream:</p>
<ul>
<li><p>You still need to understand what it generated</p>
</li>
<li><p>You still need to verify it matches your rules</p>
</li>
<li><p>You still need to catch the mistakes that hide inside "nice looking code"</p>
</li>
</ul>
<p>If you can't verify it, you don't own it. And if you don't own it, you can't safely ship it.</p>
<p><strong>Tip:</strong> Treat AI output like code from a stranger on the internet: useful, but untrusted until proven.</p>
<h2 id="heading-the-golden-rule-never-trust-user-prices">The Golden Rule: Never Trust User Prices</h2>
<p>I started exactly like a beginner would start. I opened AI and wrote a vague prompt:</p>
<blockquote>
<p>Design and develop an e-commerce shopping cart module for me.</p>
</blockquote>
<p>AI replied with a big output. It looked clean. If you're new, you might think:</p>
<blockquote>
<p>Wow, it solved it.</p>
</blockquote>
<p>But then I asked myself:</p>
<blockquote>
<p>What is the easiest way this can go wrong in real life?</p>
</blockquote>
<p>And the answer is also simple: “money can be stolen”. Because a shopping cart has one golden rule: never trust prices coming from the user.</p>
<p>If the browser sends you: “T-shirt price is \(1" and you accept it, someone can pay \)1 for a $20 product. And when AI generates a big module quickly, that kind of mistake can easily hide inside "nice looking code."</p>
<p><strong>Warning:</strong> Any system that accepts client-sent prices is basically inviting price tampering.</p>
<h2 id="heading-the-mindset-shift-stop-asking-for-the-whole-app">The Mindset Shift: Stop Asking for the Whole App</h2>
<p>So instead of accepting the big AI output, I changed my approach. I said:</p>
<blockquote>
<p>I'm not going to ask AI to build the whole app. I will break the big thing into small parts, and I will guide AI like a real engineer.</p>
</blockquote>
<p>That is the first mindset shift. In the AI era, your value is not how fast you type. Your value is how well you can do three things:</p>
<ul>
<li><p>define the problem clearly</p>
</li>
<li><p>break it into small pieces</p>
</li>
<li><p>prove the result is correct</p>
</li>
</ul>
<p>Big systems are built from small correct pieces. That's not "prompt engineering." That's engineering.</p>
<h2 id="heading-the-ai-coding-loop-the-7-step-workflow">The AI Coding Loop (the 7-Step Workflow)</h2>
<p>Here's the loop I use. It's simple English. You can copy it and use it for any project:</p>
<ul>
<li><p>Write the goal in one sentence</p>
</li>
<li><p>Write the rules (what must be true)</p>
</li>
<li><p>Write two examples (input → output)</p>
</li>
<li><p>Write two bad situations (weird cases)</p>
</li>
<li><p>Ask AI for a small piece, not the whole thing</p>
</li>
<li><p>Ask for tests, then run them</p>
</li>
<li><p>If something fails, improve the prompt and repeat</p>
</li>
</ul>
<p>That's it. That's the loop. Here it is in visual form:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771426083349/2f126c6b-9e17-469e-881f-68e3c6c384a9.png" alt="AI coding loop workflow" style="display:block;margin:0 auto" width="2006" height="1202" loading="lazy">

<p><strong>Tip:</strong> The loop is the skill. Tools will change. The loop will still work.</p>
<h2 id="heading-apply-the-loop-a-server-side-cart-total-calculator">Apply the Loop: a Server-side Cart Total Calculator</h2>
<p>Now let's apply it to the shopping cart example. Instead of "build me a cart module," I wrote a tiny requirement note:</p>
<blockquote>
<p>We need a cart total calculator on the server. User sends <code>productId</code> and <code>quantity</code>. We must ignore any <code>price</code> from the user. We must use our own product list. We must handle unknown products and invalid <code>quantity</code>. We must calculate <code>subtotal</code>, <code>discount</code>, <code>tax</code>, and final <code>total</code>. We must round money correctly. We must have tests.</p>
</blockquote>
<p>This is not a large or complex requirements specification - just a clear and concise note.</p>
<p>And then I asked AI for only one small piece:</p>
<ul>
<li><p>Not the UI</p>
</li>
<li><p>Not the database</p>
</li>
<li><p>Not the entire architecture</p>
</li>
<li><p>Just one function, with tests</p>
</li>
</ul>
<p>Because the fastest way to build something real is to prove one brick at a time. We have written down everything we discussed in the requirement note. It would be great to also create a visual representation of those ideas. Along with the requirement note, we can prepare a simple sketch or diagram for our own reference. This way, it can serve as a clean and well-documented requirement specification, which we can keep recorded in our project's GitHub <code>README.md</code> file.</p>
<p>In the diagram below, we can have a browser on the left and the server on the right. The browser/user is an untrusted input source. The user may send <code>productId</code>, <code>qty</code>, and even a fake <code>price</code>, but the server must treat only <code>productId</code> and <code>qty</code> as input and must ignore any client-sent price. The server then looks up the real price from its own trusted product catalog, validates the quantity, and calculates totals from server-side data. This is the trust boundary: prices come from the server, not from the client.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771426306275/e21c0f2c-3eda-42f9-bd44-65d74b2aa10e.png" alt="Trust boundary and price tampering" style="display:block;margin:0 auto" width="2006" height="1202" loading="lazy">

<h3 id="heading-the-prompt-small-piece-strong-constraints">The prompt (small piece, strong constraints)</h3>
<p>This is the shape of the prompt I used:</p>
<p>Create a single JavaScript file I can run with Node.</p>
<p><strong>Goal:</strong></p>
<p>Calculate shopping cart totals.</p>
<p><strong>Rules:</strong></p>
<ul>
<li><p>Input items have productId and qty.</p>
</li>
<li><p>Do NOT trust price from user input.</p>
</li>
<li><p>Use my product catalog.</p>
</li>
<li><p>qty must be at least 1.</p>
</li>
<li><p>discountPercent and taxPercent must not be negative.</p>
</li>
<li><p>discount first, then tax.</p>
</li>
<li><p>round money to 2 decimals.</p>
</li>
</ul>
<p><strong>Examples:</strong></p>
<ul>
<li><p>2 T-shirts (20 each) + 1 mug (12.50) =&gt; subtotal 52.50</p>
</li>
<li><p>discount 10%, tax 8% =&gt; discount first, then tax</p>
</li>
</ul>
<p><strong>Deliver:</strong></p>
<ul>
<li><p>one function</p>
</li>
<li><p>simple tests using Node's built-in assert</p>
</li>
<li><p>print one example output</p>
</li>
</ul>
<p>One small change makes a massive difference: “rules + examples + tests”. AI still tries to help fast, but now it has guardrails. And if it still makes a mistake, you can catch it, because you asked for proof.</p>
<p>Here is a visual representation of the "Cart Totals Pipeline" that covers all the use cases involved in the cart totals calculation process.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771426340852/b9a1d43c-3e89-468b-b809-7c9d7ad53932.png" alt="Cart totals pipeline (discount then tax)" style="display:block;margin:0 auto" width="2006" height="1202" loading="lazy">

<p>In the diagram, the cart total calculation follows a fixed pipeline. First, validate inputs (known <code>productId</code>, valid <code>qty</code>, non-negative discount/tax). Next, compute <code>subtotal</code> from the trusted product catalog. Then apply the discount to get the discounted amount. After that, calculate tax on the discounted amount (not on the original subtotal). Finally, round values correctly and return the result (<code>subtotal</code>, <code>discount</code>, <code>tax</code>, and <code>total</code>). The key rule is the order: discount first, then tax.</p>
<h2 id="heading-one-file-runnable-example-with-a-wrong-version-on-purpose">One-File Runnable Example (with a Wrong Version on Purpose)</h2>
<p>Now here's the one-file example you can run right now. No setup. Just Node. Create a file named <code>cart.js</code>, paste in the below code, and run <code>node cart.js</code>.</p>
<p>It includes two versions:</p>
<ul>
<li><p>a wrong version that trusts user price (this is the mistake we want to learn from)</p>
</li>
<li><p>a correct version that uses a trusted catalog</p>
</li>
</ul>
<pre><code class="language-js">// cart.js

// Run: node cart.js

const assert = require("node:assert/strict");

// Trusted product catalog (server-side truth)

const PRODUCTS = {
    tshirt: { name: "T-shirt", priceCents: 2000 }, // $20.00

    mug: { name: "Mug", priceCents: 1250 }, // $12.50

    book: { name: "Book", priceCents: 1599 }, // $15.99
};

function money(cents) {
    return (cents / 100).toFixed(2);
}

// WRONG: trusts user price

function cartTotal_WRONG(cartItems, discountPercent = 0, taxPercent = 0) {
    let subtotalCents = 0;

    for (const item of cartItems) {
        const priceCents = Math.round((item.price ?? 0) * 100); // user can cheat

        subtotalCents += priceCents * item.qty;
    }

    const discountCents = Math.round(subtotalCents * (discountPercent / 100));

    const afterDiscount = subtotalCents - discountCents;

    const taxCents = Math.round(afterDiscount * (taxPercent / 100));

    const totalCents = afterDiscount + taxCents;

    return totalCents;
}

// Correct: uses trusted catalog + checks

function cartTotal(cartItems, discountPercent = 0, taxPercent = 0) {
    if (!Array.isArray(cartItems))
        throw new Error("cartItems must be an array");

    if (typeof discountPercent !== "number" || discountPercent &lt; 0)
        throw new Error("discountPercent must be non-negative");

    if (typeof taxPercent !== "number" || taxPercent &lt; 0)
        throw new Error("taxPercent must be non-negative");

    let subtotalCents = 0;

    for (const item of cartItems) {
        const { productId, qty } = item || {};

        if (typeof productId !== "string" || !PRODUCTS[productId]) {
            throw new Error("Unknown productId: " + productId);
        }

        if (typeof qty !== "number" || qty &lt; 1) {
            throw new Error("qty must be at least 1");
        }

        subtotalCents += PRODUCTS[productId].priceCents * qty;
    }

    const discountCents = Math.round(subtotalCents * (discountPercent / 100));

    let afterDiscountCents = subtotalCents - discountCents;

    if (afterDiscountCents &lt; 0) afterDiscountCents = 0;

    const taxCents = Math.round(afterDiscountCents * (taxPercent / 100));

    const totalCents = afterDiscountCents + taxCents;

    return { subtotalCents, discountCents, taxCents, totalCents };
}

function runTests() {
    // Normal example

    const cart = [
        { productId: "tshirt", qty: 2 },

        { productId: "mug", qty: 1 },
    ];

    const r = cartTotal(cart, 10, 8);

    assert.equal(r.subtotalCents, 5250); // 52.50

    assert.equal(r.discountCents, 525); // 10% of 52.50

    assert.equal(r.taxCents, 378); // 8% of 47.25

    assert.equal(r.totalCents, 5103); // 51.03

    // Attack example: user tries to cheat with price = 1

    const attackerCart = [
        { productId: "tshirt", qty: 2, price: 1 },

        { productId: "mug", qty: 1, price: 1 },
    ];

    const wrong = cartTotal_WRONG(attackerCart, 0, 0);

    assert.equal(money(wrong), "3.00"); // totally wrong in real life

    const safe = cartTotal(attackerCart, 0, 0);

    assert.equal(money(safe.totalCents), "52.50"); // correct, ignores user price

    // Edge cases

    assert.throws(() =&gt; cartTotal([{ productId: "unknown", qty: 1 }], 0, 0));

    assert.throws(() =&gt; cartTotal([{ productId: "tshirt", qty: 0 }], 0, 0));

    assert.throws(() =&gt; cartTotal(cart, -1, 0));

    assert.throws(() =&gt; cartTotal(cart, 0, -1));
}

runTests();

console.log("All tests passed.");

const example = cartTotal(
    [
        { productId: "tshirt", qty: 1 },

        { productId: "book", qty: 2 },
    ],

    15,

    5,
);

console.log("Example subtotal:", money(example.subtotalCents));

console.log("Example discount:", money(example.discountCents));

console.log("Example tax:", money(example.taxCents));

console.log("Example total:", money(example.totalCents));
</code></pre>
<p>In this code, we didn't do a magic trick. We did some engineering:</p>
<ul>
<li><p>We took a big problem and broke it into a small piece</p>
</li>
<li><p>We wrote rules so the AI doesn't guess</p>
</li>
<li><p>We wrote examples so the AI understands</p>
</li>
<li><p>We asked for tests so we can prove it</p>
</li>
<li><p>We ran the tests so we can trust it</p>
</li>
</ul>
<p>That is the loop you can reuse for any project.</p>
<h2 id="heading-how-to-use-failing-tests-as-a-flashlight">How to Use Failing Tests as a Flashlight</h2>
<p>This is the part many developers skip. They ask for code, but they don't ask for proof. When you run the tests, one of two things happens:</p>
<ul>
<li><p>Tests pass: great, you earned confidence</p>
</li>
<li><p>Tests fail: even better, you earned clarity</p>
</li>
</ul>
<p>A failing test is a flashlight. It shows you the exact place where your thinking (or your prompt) needs improvement. Instead of "AI is wrong," you get a real question:</p>
<blockquote>
<p>Which rule was unclear, missing, or contradictory?</p>
</blockquote>
<p>Then you adjust:</p>
<ul>
<li><p>add a stricter rule</p>
</li>
<li><p>add an example that removes ambiguity</p>
</li>
<li><p>add an edge case that forces the correct behavior</p>
</li>
<li><p>regenerate only the small piece, not the whole codebase</p>
</li>
</ul>
<h2 id="heading-copy-paste-prompt-template">Copy-Paste Prompt Template</h2>
<p>Here is a copy-paste prompt template you can reuse from today (see below the image):</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771426376813/8b9c29e0-7681-433d-989b-f4c693ad4fb4.png" alt="Copy-paste prompt template" style="display:block;margin:0 auto" width="2006" height="1202" loading="lazy">

<pre><code class="language-txt">
Build ONE small piece, not the full app.

Goal:

(One sentence)

Rules:

(3 to 7 bullets)

Examples:

(2 examples: input -&gt; output)

Edge cases:

(2 cases that can break it)

Deliver:

- one runnable file

- include tests using Node assert

- print one example output

Then ask:

Before giving code, list the possible mistakes and confirm the rules.
</code></pre>
<p>That last line is powerful. It forces the AI to think about failure before writing code.</p>
<h2 id="heading-a-calm-hype-check-why-fundamentals-matter-more-now">A Calm Hype Check: Why Fundamentals Matter More Now</h2>
<p>A lot of content online makes it sound like: "AI codes now, so you don't need to learn coding." That idea is a trap. Because yes, AI can type code. But AI cannot replace your responsibilities as a developer and engineer.</p>
<p>If you ship a broken cart, you can lose money. If you ship insecure code, you can get hacked. If you ship unreliable software, users leave. And in real life, nobody will accept the excuse: "The AI wrote it."</p>
<p>In the AI era, learning coding isn't less important. It's more important, just in a different way. The goal isn't to become a fast typist. The goal is to become a strong thinker.</p>
<p>Fundamentals matter more than before:</p>
<ul>
<li><p>how data flows through a system</p>
</li>
<li><p>how to break big problems into small parts</p>
</li>
<li><p>how to write clear rules and requirements</p>
</li>
<li><p>how to test and verify</p>
</li>
<li><p>how to notice edge cases</p>
</li>
<li><p>how to think about security</p>
</li>
<li><p>how to understand the tools you use, not just copy answers</p>
</li>
</ul>
<p>Average software will be everywhere. It will be cheap. It will be copied. It will be easy to make. So the only software that matters will be software that is truly valuable: safe, reliable, high quality, and built with real understanding.</p>
<p>That's good news for serious learners. Because the best engineers will become even more valuable, not less.</p>
<h3 id="heading-a-simple-exercise-do-this-once-and-youll-feel-the-skill">A Simple Exercise (do this once and you'll feel the skill)</h3>
<p>Add one more rule to the cart, like:</p>
<ul>
<li><p>qty cannot be more than 10</p>
</li>
<li><p>Write the test first. Then ask AI to update the function. Run the tests.</p>
</li>
<li><p>That's how you train the real AI skill: not prompting, but guiding and verifying.</p>
</li>
<li><p>Let AI type the code.</p>
</li>
<li><p>You do the thinking.</p>
</li>
<li><p>You do the breaking down.</p>
</li>
<li><p>You do the proof.</p>
</li>
</ul>
<h2 id="heading-recap">Recap</h2>
<ul>
<li><p>Don't ask AI to build the whole app</p>
</li>
<li><p>Break the problem into one small piece</p>
</li>
<li><p>Write rules, examples, and edge cases so AI doesn't guess</p>
</li>
<li><p>Always ask for tests and run them</p>
</li>
<li><p>Treat failing tests as a flashlight</p>
</li>
<li><p>Repeat the loop until you can trust what you ship</p>
</li>
</ul>
<p>That's the game now. And if you play it well, you're not behind, you're ahead.</p>
<h2 id="heading-final-words">Final Words</h2>
<p>If you found the information here valuable, feel free to share it with others who might benefit from it.</p>
<p>I’d really appreciate your thoughts – mention me on X <a href="https://x.com/sumit_analyzen">@sumit_analyzen</a> or on Facebook <a href="https://facebook.com/sumit.analyzen">@sumit.analyzen</a>, <a href="https://youtube.com/@logicBaseLabs">watch my coding tutorials</a>, or simply <a href="https://www.linkedin.com/in/sumitanalyzen/">connect with me on LinkedIn</a>.</p>
<p>You can also checkout my official website <a href="https://www.sumitsaha.me">sumitsaha.me</a> for details about me.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Best Automation Testing Tools For Developers ]]>
                </title>
                <description>
                    <![CDATA[ Test-driven development is something that every software developer should implement in their projects.  The success of using TDD, however, depends highly on how productive the developer can be while implementing code and application testing. This is ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/best-automation-testing-tools-for-developers/</link>
                <guid isPermaLink="false">66bb5246074d8d7b12eae37a</guid>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation testing  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ry Vee ]]>
                </dc:creator>
                <pubDate>Mon, 05 Oct 2020 16:47:54 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/09/arif-riyanto-G1N9kDHqBrQ-unsplash-1-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Test-driven development is something that every software developer should implement in their projects. </p>
<p>The success of using <a target="_blank" href="https://www.guru99.com/test-driven-development.html">TDD</a>, however, depends highly on how productive the developer can be while implementing code and application testing.</p>
<p>This is where automation tools come in.</p>
<p>Below is a list of 10 recommended testing automation frameworks/platforms and a short summary of their features.</p>
<h2 id="heading-seleniumhttpswwwseleniumdev"><a target="_blank" href="https://www.selenium.dev/">Selenium</a></h2>
<p><img src="https://lh3.googleusercontent.com/fOEtWbGlSP_hzAkDaSowqKEUiz9l6e8l9nGNuVuZhXhCJRVGSEuA59oWIWyP387ndCrEsuB1K2Z3_c2quLaM1OB5iaLreOLydfaTiPlmhlAvj_OnS-jIJUhUBAg0bSZh1A" alt="Image" width="421" height="298" loading="lazy"></p>
<p>Selenium is, arguably, the most popular automated testing framework. It is a browser-based framework that works on different operating systems and browsers.</p>
<p>It features the Selenium IDE, which is Chrome and Firefox extension that allows the recording and playback of user interactions. For scaling tests (i.e, running on several machines) then Selenium Grid is the tool of choice.</p>
<p>The most popular tool in the framework is Selenium Webdriver, which is a collection of language specific bindings and allows for the creation of browser-based regression tests.</p>
<h2 id="heading-perfectoiohttpswwwperfectoio"><a target="_blank" href="https://www.perfecto.io/">Perfecto.io</a></h2>
<p><img src="https://lh4.googleusercontent.com/DplVGhU1kNQJVWhayvQ4h738H_M97iYitDoxAaDFCRDTwlZffOg9KlyhuJM2QW1HvpJrAOvX8yhlyuufjiNc_GnoqUw7nZE2IEZIvjTFSKhVwZz57KENe4XXlRoGNAY9ww" alt="Image" width="1200" height="750" loading="lazy"></p>
<p>Perfecto is both a web and mobile app testing framework. It’s delivered as a SaaS tool and allows testing engineers to run their tests from anywhere.</p>
<p>The web testing tool provides for parallel testing of apps on different operating systems and browsers. It allows for a huge volume of daily tests to be run, and the tests are 50% faster than any other framework.</p>
<p>The mobile test tool allows engineers to test on both emulators and actual devices. Just like the web test tool, it allows for a high volume of test runs daily (10,000 executions).</p>
<h2 id="heading-serenityhttpwwwthucydidesinfo"><a target="_blank" href="http://www.thucydides.info/#/">Serenity</a></h2>
<p><img src="https://lh4.googleusercontent.com/TQ1Vqg7502EEaW8P8S9cYHz_1ebaXSqA95ilO6KzMe13vR_CK29WH_jZESyU2EutJD6XOV63R0PbnR7_NtqKgS59Rh7nk6iuA8YlcIlCFq3heOeN3b4K06Zs8_eLPSFeww" alt="Image" width="1008" height="609" loading="lazy"></p>
<p>Serenity BDD’s slogan is “Automated Acceptance Testing with Style”. This is because Serenity’s unique angle is helping testers write world-class test reports and documentation.</p>
<p>Testing starts with the creation of user stories and acceptance criteria. Serenity then automates the acceptance criteria. Tests are automatically broken down into steps that make them more readable. Developers can easily run these tests against actual application executions.</p>
<p>Once tests are finished, Serenity creates detailed reports that include screenshots of the tests and all relevant information such as error messages and execution times.</p>
<h2 id="heading-cypresshttpswwwcypressio"><a target="_blank" href="https://www.cypress.io/">Cypress</a></h2>
<p><img src="https://lh4.googleusercontent.com/q6KHdpPaoPhHi5JKT5HA4oH_00gVtIuCW8JdPITzA7VxGZhL1GbFp3g5dxH9roP6WRU4wys8jQLWyH0_Depy1o4FblwzNweaEiCeS6AmlifxVFWE07IZlJX5VNM1M_Zvpg" alt="Image" width="1102" height="599" loading="lazy"></p>
<p>Cypress is a great tool to use for frontend or end-to-end <a target="_blank" href="https://www.perfecto.io/blog/what-is-test-automation">automated testing</a>.</p>
<p>Installing Cypress in a project is as easy as running a simple <code>npm install cypress</code> or <code>yarn add cypress</code> command. JavaScript developers who are used to unit testing tools such as <a target="_blank" href="http://jestjs.io/">Jest</a> or <a target="_blank" href="https://mochajs.org/">Mocha</a> will find writing Cypress test scripts a breeze.</p>
<p>Its dashboard makes testing more powerful and faster by allowing tests to be grouped by browser type, environment, package type, and so on. The parallelization feature allows developers to run more tests and test more features easily.</p>
<h2 id="heading-lambda-testhttpswwwlambdatestcom"><a target="_blank" href="https://www.lambdatest.com/">Lambda Test</a></h2>
<p><img src="https://lh5.googleusercontent.com/7w_SznSrnJ3RbUpAjsWmHuuHZmJYCRIJpncsvXuedQrBix8lp80YKqBDADgSjNKYdoY6a2q6gmeyPeQ3vDwG8y9BxP8iABYLJvpWq8d9_ATFXmdLhvCpW7Lz6XFmDrEWCg" alt="Image" width="758" height="560" loading="lazy"></p>
<p>LambdaTest is a leading test automation software for both desktop and web apps.</p>
<p>It allows for live and interactive testing of both publicly hosted and local machine hosted web applications and websites.</p>
<p>LambdaTest also allows developers to run Selenium test scripts with its Browser Testing Grid. It is a very powerful and versatile suite that also integrates with CI/CD tools such as Jenkins, Circle CI, and Travis CI.</p>
<p>Now the coolest part is that it allows for geo-testing, meaning web applications can be tested for how they perform depending on which location in the world they are being accessed from.</p>
<h2 id="heading-testprojectiohttpstestprojectio"><a target="_blank" href="https://testproject.io/">TestProject.io</a></h2>
<p><img src="https://lh3.googleusercontent.com/AFtWUpNPNMMP382wf89DoDKiWgtJn8FFNY6NEYKT2DQ-w4PAJlw72WaByvEWHatg0hA6JZxYVSnnN0VovjB7swkEu_XstmWNnSiWaNzn-eCN2THVed57P0j4ZcLxcm6kEw" alt="Image" width="1335" height="587" loading="lazy"></p>
<p>TestProject’s community is probably one of the biggest among those in the testing community. It is an end-to-end and API cloud hosted test framework.</p>
<p>As an open-source project, users get easy access to updates and can even participate in its improvements. It is ready for use with Selenium with all of its pre-packaged dependencies.</p>
<p>With its myriad of integration tools, it allows for testing on different browsers, and even in Docker.</p>
<p>One of the best things about TestProject is it allows non-coders to perform testing through its Scriptless Test recorder.</p>
<h2 id="heading-katalon-studiohttpswwwkataloncomhomepagepkabeabtestinghomepage082020amppkabvlayout2"><a target="_blank" href="https://www.katalon.com/homepage/?pk_abe=AB_testing_Homepage_08_2020&amp;pk_abv=layout2">Katalon Studio</a></h2>
<p><img src="https://lh4.googleusercontent.com/Etrs1mFEa-U-0wofZ-Vw-Cmn0G9dlZbA1DcMjs1M2hDXVK3qBe2VJ4X-o1KKH-6dUoxLHdm-LknJPbinYRpWBlBIQTtYM9Qf8nf0hNTYDQrbNYzud_0ZQg-4wLNoTiBWEg" alt="Image" width="873" height="466" loading="lazy"></p>
<p>Another open-source API, web, and mobile testing suite is Katalon Studio. It is one of the most complete testing suites in the entire list, with features like recording, auto-generating test scripts, and powerful integrations.</p>
<p>It is a testing framework that is easy to start (yes, even for non-coders) but is powerful enough for scaling.</p>
<p>Katalon Studio works well with existing CI/CD setups. For instance, it's easy to integrate it with Github or Gitlab’s continuous testing tools. This makes it great for Agile teams.</p>
<h2 id="heading-opentesthttpsgetopentestorg"><a target="_blank" href="https://getopentest.org/">OpenTest</a></h2>
<p><img src="https://lh6.googleusercontent.com/ht1yDKuYIf2V3aQakjQqZSKeIPIfUrOr5b_sowtlr7f2l-h7Hr3_bHcX41fZoLDBNGJXWhaY8Gd7RUAiCAZMYGoGA7Ra63WwSWrO9iwxeu7mjisKyZ5dANIoq97eq_jIiw" alt="Image" width="1600" height="1000" loading="lazy"></p>
<p>OpenTest is another open source tool that automates testing for API’s, web, mobile and desktop applications. It is mostly used for functional testing.</p>
<p>It still has lots of limitations as there are some test scenarios that are not yet supported. However, it is very good for beginners and non-coders alike since it uses plain English keywords for writing test actions.</p>
<p>Some of its most notable features include web testing with Selenium, mobile testing with Appium, keyword-driven testing, parallel testing, and data-driven testing.</p>
<h2 id="heading-accelqhttpswwwaccelqcom"><a target="_blank" href="https://www.accelq.com/">AccelQ</a></h2>
<p><img src="https://lh3.googleusercontent.com/HLpAj_Qg-Prqnp6Zq5i9WtahnDrBQjclRAknTaCiS_20fc40jEUDWgnu1gt5M6mpt4sfYtQkMGwnazK95RRTU8LRyk9jajQ4OkeSKptTgaP8sq14cDttfsb7MqtHY30CMw" alt="Image" width="1600" height="862" loading="lazy"></p>
<p>AccelQ is a codeless test automation platform that is artificial-intelligence based. As such, it is best used for Agile development as it allows for rapid building and easy introduction of changes in the project.</p>
<p>The platform allows for API and end-to-end testing. The AI backbone provides predictive analytics that help in accelerating the test script generation. It has an intuitive and easy to use UI that allows even beginners to get started quickly.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The choice boils down to, not surprisingly, your particular use-case. </p>
<p>However, given the extensive capabilities of each of these tools, it is understandably difficult to select one. The beautiful thing about a lot of them being open source is that development teams can use one for a particular project, and another for a different one.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create a Great User Experience with React, TypeScript, and the React Testing Library ]]>
                </title>
                <description>
                    <![CDATA[ By TK I'm always willing to learn, no matter how much I know. As a software engineer, my thirst for knowledge has increased a lot. I know that I have a lot of things to learn daily. But before I could learn more, I wanted to master the fundamentals. ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/ux-studies-with-react-typescript-and-testing-library/</link>
                <guid isPermaLink="false">66d852c7b56109a3fff9ddc3</guid>
                
                    <category>
                        <![CDATA[ automation testing  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ react hooks ]]>
                    </category>
                
                    <category>
                        <![CDATA[ react testing library ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 22 Jun 2020 09:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/06/cover-2.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By TK</p>
<p>I'm always willing to learn, no matter how much I know. As a software engineer, my thirst for knowledge has increased a lot. I know that I have a lot of things to learn daily.</p>
<p>But before I could learn more, I wanted to master the fundamentals. To make myself a better developer, I wanted to understand more about how to create great product experiences.</p>
<p>This post is my attempt to illustrate a Proof of Concept (PoC) I built to try out some ideas.</p>
<p>I had some topics in mind for this project. It needed to:</p>
<ul>
<li>Use high-quality software</li>
<li>Provide a great user experience</li>
</ul>
<p>When I say high-quality software, this can mean so many different things. But I wanted to focus on three parts:</p>
<ul>
<li>Clean Code: Strive to write human-readable code that is easy to read and simple to maintain. Separate responsibility for functions and components.</li>
<li>Good test coverage: It's actually not about coverage. It's about tests that cover important parts of components' behavior without knowing too much about implementation details.</li>
<li>Consistent state management: I wanted to build with software that enables the app to have consistent data. Predictability is important.</li>
</ul>
<p>User experience was the main focus of this PoC. The software and techniques would be the foundation that enabled a good experience for users.</p>
<p>To make the state consistent, I wanted a type system. So I chose TypeScript. This was my first time using Typescript with React. This project also allowed me to build custom hooks and test it properly.</p>
<h2 id="heading-setting-up-the-project">Setting up the project</h2>
<p>I came across this library called <a target="_blank" href="https://github.com/jaredpalmer/tsdx">tsdx</a> that sets up all the Typescript configuration for you. It's mainly used to build packages. Since this was a simple side project, I didn't mind giving it a try.</p>
<p>After installing it, I chose the React template and I was ready to code. But before the fun part, I wanted to set up the test configuration too. I used the <a target="_blank" href="https://github.com/testing-library/react-testing-library">React Testing Library</a> as the main library together with <a target="_blank" href="https://github.com/testing-library/jest-dom">jest-dom</a> to provide some awesome custom methods (I really like the <code>toBeInTheDocument</code> matcher).</p>
<p>With all that installed, I overwrote the jest config by adding a new <code>jest.config.js</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = {
  verbose: <span class="hljs-literal">true</span>,
  setupFilesAfterEnv: [<span class="hljs-string">"./setupTests.ts"</span>],
};
</code></pre>
<p>And a <code>setupTests.ts</code> to import everything I needed.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-string">"@testing-library/jest-dom"</span>;
</code></pre>
<p>In this case, I just had the <code>jest-dom</code> library to import. That way, I didn't need to import this package in my test files. Now it worked out of the box.</p>
<p>To test this installation and configuration, I built a simple component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Thing = <span class="hljs-function">() =&gt;</span> &lt;h1&gt;I<span class="hljs-string">'m TK&lt;/h1&gt;;</span>
</code></pre>
<p>In my test, I wanted to render it and see if it was in the DOM.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { render } <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/react'</span>;
<span class="hljs-keyword">import</span> { Thing } <span class="hljs-keyword">from</span> <span class="hljs-string">'../index'</span>;

describe(<span class="hljs-string">'Thing'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'renders the correct text in the document'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> { getByText } = render(&lt;Thing /&gt;);

    expect(getByText(<span class="hljs-string">"I'm TK"</span>)).toBeInTheDocument();
  });
});
</code></pre>
<p>Now we are ready for the next step.</p>
<h2 id="heading-configuring-routes">Configuring routes</h2>
<p>Here I wanted to have only two routes for now. The home page and the search page - even though I'll do nothing about the home page.</p>
<p>For this project, I'm using the <code>react-router-dom</code> library to handle all things router-related. It's simple, easy, and fun to work with.</p>
<p>After installing it, I added the router components in the <code>app.typescript</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { BrowserRouter <span class="hljs-keyword">as</span> Router, Switch, Route } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> (
  &lt;Router&gt;
    &lt;Switch&gt;
      &lt;Route path=<span class="hljs-string">"/search"</span>&gt;
        &lt;h1&gt;It<span class="hljs-string">'s the search!&lt;/h1&gt;
      &lt;/Route&gt;
      &lt;Route path="/"&gt;
        &lt;h1&gt;It'</span>s Home&lt;/h1&gt;
      &lt;/Route&gt;
    &lt;/Switch&gt;
  &lt;/Router&gt;
);
</code></pre>
<p>Now if we enter the <code>localhost:1234</code>, we see the title <code>It's Home</code>. Go to the <code>localhost:1234/search</code>, and we'll see the text <code>It's the search!</code>.</p>
<p>Before we continue to start implementing our search page, I wanted to build a simple menu to switch between home and search pages without manipulating the URL. For this project, I'm using <a target="_blank" href="https://material-ui.com/">Material UI</a> to build the UI foundation.</p>
<p>For now, we are just installing the <code>@material-ui/core</code>.</p>
<p>To build the menu, we have the button to open the menu options. In this case they're the "home" and "search" options. </p>
<p>But to build a better component abstraction, I prefer to hide the content (link and label) for the menu items and make the <code>Menu</code> component receive this data as a prop. This way, the menu doesn't know about the items, it will just iterate through the items list and render them.</p>
<p>It looks like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React, { Fragment, useState, MouseEvent } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { Link } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">'@material-ui/core/Button'</span>;
<span class="hljs-keyword">import</span> MuiMenu <span class="hljs-keyword">from</span> <span class="hljs-string">'@material-ui/core/Menu'</span>;
<span class="hljs-keyword">import</span> MuiMenuItem <span class="hljs-keyword">from</span> <span class="hljs-string">'@material-ui/core/MenuItem'</span>;

<span class="hljs-keyword">import</span> { MenuItem } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../types/MenuItem'</span>;

<span class="hljs-keyword">type</span> MenuPropsType = { menuItems: MenuItem[] };

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Menu = <span class="hljs-function">(<span class="hljs-params">{ menuItems }: MenuPropsType</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [anchorEl, setAnchorEl] = useState&lt;<span class="hljs-literal">null</span> | HTMLElement&gt;(<span class="hljs-literal">null</span>);

  <span class="hljs-keyword">const</span> handleClick = (event: MouseEvent&lt;HTMLButtonElement&gt;): <span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
    setAnchorEl(event.currentTarget);
  };

  <span class="hljs-keyword">const</span> handleClose = (): <span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
    setAnchorEl(<span class="hljs-literal">null</span>);
  };

  <span class="hljs-keyword">return</span> (
    &lt;Fragment&gt;
      &lt;Button aria-controls=<span class="hljs-string">"menu"</span> aria-haspopup=<span class="hljs-string">"true"</span> onClick={handleClick}&gt;
        Open Menu
      &lt;/Button&gt;
      &lt;MuiMenu
        id=<span class="hljs-string">"simple-menu"</span>
        anchorEl={anchorEl}
        keepMounted
        open={<span class="hljs-built_in">Boolean</span>(anchorEl)}
        onClose={handleClose}
      &gt;
        {menuItems.map(<span class="hljs-function">(<span class="hljs-params">item: MenuItem</span>) =&gt;</span> (
          &lt;Link to={item.linkTo} onClick={handleClose} key={item.key}&gt;
            &lt;MuiMenuItem&gt;{item.label}&lt;/MuiMenuItem&gt;
          &lt;/Link&gt;
        ))}
      &lt;/MuiMenu&gt;
    &lt;/Fragment&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Menu;
</code></pre>
<p>Don't panic! I know it is a huge block of code, but it is pretty simple. the <code>Fragment</code> wrap the <code>Button</code> and <code>MuiMenu</code> (<code>Mui</code> stands for Material UI. I needed to rename the component because the component I'm building is also called menu).</p>
<p>It receives the <code>menuItems</code> as a prop and maps through it to build the menu item wrapped by the <code>Link</code> component. Link is a component from react-router to link to a given URL.</p>
<p>The menu behavior is also simple: we bind the <code>handleClick</code> function to the button's <code>onClick</code>. That way, we can change <code>anchorEl</code> when the button is triggered (or clicked if you prefer). The <code>anchorEl</code> is just a component state that represents the Mui menu element to open the menu switch. So it will open the menu items to let the user chooses one of those.</p>
<p>Now, how do we use this component?</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Menu } <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/Menu'</span>;
<span class="hljs-keyword">import</span> { MenuItem } <span class="hljs-keyword">from</span> <span class="hljs-string">'./types/MenuItem'</span>;

<span class="hljs-keyword">const</span> menuItems: MenuItem[] = [
  {
    linkTo: <span class="hljs-string">'/'</span>,
    label: <span class="hljs-string">'Home'</span>,
    key: <span class="hljs-string">'link-to-home'</span>,
  },
  {
    linkTo: <span class="hljs-string">'/search'</span>,
    label: <span class="hljs-string">'Search'</span>,
    key: <span class="hljs-string">'link-to-search'</span>,
  },
];

&lt;Menu menuItems={menuItems} /&gt;
</code></pre>
<p>The <code>menuItems</code> is a list of objects. The object has the correct contract expected by the <code>Menu</code> component. The type <code>MenuItem</code> ensures that the contract is correct. It is just a Typescript <code>type</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> MenuItem = {
  linkTo: <span class="hljs-built_in">string</span>;
  label: <span class="hljs-built_in">string</span>;
  key: <span class="hljs-built_in">string</span>;
};
</code></pre>
<h2 id="heading-search">Search</h2>
<p>Now we are ready to build the search page with all the products and a great experience. But before building the list of products, I wanted to create a fetch function to handle the request for products. As I don't have an API of products yet, I can just mock the fetch request.</p>
<p>At first, I just built the fetching with <code>useEffect</code> in the <code>Search</code> component. The idea would look like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React, { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { getProducts } <span class="hljs-keyword">from</span> <span class="hljs-string">'api'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Search = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [products, setProducts] = useState([]);
  <span class="hljs-keyword">const</span> [isLoading, setIsLoading] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [hasError, setHasError] = useState(<span class="hljs-literal">false</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> fetchProducts = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        setIsLoading(<span class="hljs-literal">true</span>);

        <span class="hljs-keyword">const</span> fetchedProducts = <span class="hljs-keyword">await</span> getProducts();

        setIsLoading(<span class="hljs-literal">false</span>);
        setProducts(fetchedProducts);
      } <span class="hljs-keyword">catch</span> (error) {
        setIsLoading(<span class="hljs-literal">false</span>);
        setHasError(<span class="hljs-literal">true</span>);
      }
    };

    fetchProducts();
  }, []);
};
</code></pre>
<p>I have:</p>
<ul>
<li><code>products</code> initialized as an empty array</li>
<li><code>isLoading</code> initialized as false</li>
<li><code>hasError</code> initialized as false</li>
<li>The <code>fetchProducts</code> is an async function that calls <code>getProducts</code> from the <code>api</code> module. As we don't have a proper API for products yet, this <code>getProducts</code> would return a mock data.</li>
<li>When the <code>fetchProducts</code> is executed, we set the <code>isLoading</code> to true, fetch the products, and then set the <code>isLoading</code> to false, because the fetching finished, and the set the fetched products into <code>products</code> to be used in the component.</li>
<li>If it gets any error in the fetching, we catch them, set the <code>isLoading</code> to false, and the <code>hasError</code> to true. In this context, the component will know that we had an error while fetching and can handle this case.</li>
<li>Everything is encapsulated into a <code>useEffect</code> because we are doing a side effect here.</li>
</ul>
<p>To handle all the state logic (when to update each part for the specific context), we can extract it to a simple reducer.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { State, FetchActionType, FetchAction } <span class="hljs-keyword">from</span> <span class="hljs-string">'./types'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> fetchReducer = (state: State, action: FetchAction): <span class="hljs-function"><span class="hljs-params">State</span> =&gt;</span> {
  <span class="hljs-keyword">switch</span> (action.type) {
    <span class="hljs-keyword">case</span> FetchActionType.FETCH_INIT:
      <span class="hljs-keyword">return</span> {
        ...state,
        isLoading: <span class="hljs-literal">true</span>,
        hasError: <span class="hljs-literal">false</span>,
      };
    <span class="hljs-keyword">case</span> FetchActionType.FETCH_SUCCESS:
      <span class="hljs-keyword">return</span> {
        ...state,
        hasError: <span class="hljs-literal">false</span>,
        isLoading: <span class="hljs-literal">false</span>,
        data: action.payload,
      };
    <span class="hljs-keyword">case</span> FetchActionType.FETCH_ERROR:
      <span class="hljs-keyword">return</span> {
        ...state,
        hasError: <span class="hljs-literal">true</span>,
        isLoading: <span class="hljs-literal">false</span>,
      };
    <span class="hljs-keyword">default</span>:
      <span class="hljs-keyword">return</span> state;
  }
};
</code></pre>
<p>The idea here is to separate each action type and handle each state update. So the <code>fetchReducer</code> will receive the state and the action and it will return a new state. This part is interesting because it gets the current state and then returns a new state, but we keep the state contract by using the <code>State</code> type.</p>
<p>And for each action type, we will update the state the right way.</p>
<ul>
<li><code>FETCH_INIT</code>: <code>isLoading</code> is true and <code>hasError</code> is false.</li>
<li><code>FETCH_SUCCESS</code>: <code>hasError</code> is false, <code>isLoading</code> is false, and the data (products) is updated.</li>
<li><code>FETCH_ERROR</code>: <code>hasError</code> is true and <code>isLoading</code> is false.</li>
</ul>
<p>In case it doesn't match any action type, just return the current state.</p>
<p>The <code>FetchActionType</code> is a simple Typescript enum:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-built_in">enum</span> FetchActionType {
  FETCH_INIT = <span class="hljs-string">'FETCH_INIT'</span>,
  FETCH_SUCCESS = <span class="hljs-string">'FETCH_SUCCESS'</span>,
  FETCH_ERROR = <span class="hljs-string">'FETCH_ERROR'</span>,
}
</code></pre>
<p>And the <code>State</code> is just a simple type:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> ProductType = {
  name: <span class="hljs-built_in">string</span>;
  price: <span class="hljs-built_in">number</span>;
  imageUrl: <span class="hljs-built_in">string</span>;
  description: <span class="hljs-built_in">string</span>;
  isShippingFree: <span class="hljs-built_in">boolean</span>;
  discount: <span class="hljs-built_in">number</span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> Data = ProductType[];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> State = {
  isLoading: <span class="hljs-built_in">boolean</span>;
  hasError: <span class="hljs-built_in">boolean</span>;
  data: Data;
};
</code></pre>
<p>With this new reducer, now we can <code>useReducer</code> in our fetch. We pass the new reducer and the initial state to it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> initialState: State = {
  isLoading: <span class="hljs-literal">false</span>,
  hasError: <span class="hljs-literal">false</span>,
  data: fakeData,
};

<span class="hljs-keyword">const</span> [state, dispatch] = useReducer(fetchReducer, initialState);

useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> fetchAPI = <span class="hljs-keyword">async</span> () =&gt; {
    dispatch({ <span class="hljs-keyword">type</span>: FetchActionType.FETCH_INIT });

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> payload = <span class="hljs-keyword">await</span> fetchProducts();

      dispatch({
        <span class="hljs-keyword">type</span>: FetchActionType.FETCH_SUCCESS,
        payload,
      });
    } <span class="hljs-keyword">catch</span> (error) {
      dispatch({ <span class="hljs-keyword">type</span>: FetchActionType.FETCH_ERROR });
    }
  };

  fetchAPI();
}, []);
</code></pre>
<p>The <code>initialState</code> has the same contract type. And we pass it to the <code>useReducer</code> together with the <code>fetchReducer</code> we just built. The <code>useReducer</code> provides the state and a function called <code>dispatch</code> to call actions to update our state.</p>
<ul>
<li>State fetching: dispatch <code>FETCH_INIT</code></li>
<li>Finished fetch: dispatch <code>FETCH_SUCCESS</code> with the products payload</li>
<li>Get an error while fetching: dispatch <code>FETCH_ERROR</code></li>
</ul>
<p>This abstraction got very big and can be very verbose in our component. We could extract it as a separate hook called <code>useProductFetchAPI</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useProductFetchAPI = (): <span class="hljs-function"><span class="hljs-params">State</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> initialState: State = {
    isLoading: <span class="hljs-literal">false</span>,
    hasError: <span class="hljs-literal">false</span>,
    data: fakeData,
  };

  <span class="hljs-keyword">const</span> [state, dispatch] = useReducer(fetchReducer, initialState);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> fetchAPI = <span class="hljs-keyword">async</span> () =&gt; {
      dispatch({ <span class="hljs-keyword">type</span>: FetchActionType.FETCH_INIT });

      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> payload = <span class="hljs-keyword">await</span> fetchProducts();

        dispatch({
          <span class="hljs-keyword">type</span>: FetchActionType.FETCH_SUCCESS,
          payload,
        });
      } <span class="hljs-keyword">catch</span> (error) {
        dispatch({ <span class="hljs-keyword">type</span>: FetchActionType.FETCH_ERROR });
      }
    };

    fetchAPI();
  }, []);

  <span class="hljs-keyword">return</span> state;
};
</code></pre>
<p>It is just a function that wraps our fetch operation. Now, in the <code>Search</code> component, we can import and call it.</p>
<pre><code><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Search = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { isLoading, hasError, data }: State = useProductFetchAPI();
};
</code></pre><p>We have all the API: <code>isLoading</code>, <code>hasError</code>, and <code>data</code> to use in our component. With this API, we can render a loading spinner or a skeleton based on the <code>isLoading</code> data. We can render an error message based on the <code>hasError</code> value. Or just render the list of products using the <code>data</code>.</p>
<p>Before starting implementing our products list, I want to stop and add tests for our custom hook. We have two parts to test here: the reducer and the custom hook.</p>
<p>The reducer is easier as it is just a pure function. It receives value, process, and returns a new value. No side-effect. Everything deterministic.</p>
<p>To cover all the possibilities of this reducer, I created three contexts: <code>FETCH_INIT</code>, <code>FETCH_SUCCESS</code>, and <code>FETCH_ERROR</code> actions.</p>
<p>Before implementing anything, I set up the initial data to work with.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> initialData: Data = [];
<span class="hljs-keyword">const</span> initialState: State = {
  isLoading: <span class="hljs-literal">false</span>,
  hasError: <span class="hljs-literal">false</span>,
  data: initialData,
};
</code></pre>
<p>Now I can pass this initial state for the reducer together with the specific action I want to cover. For this first test, I wanted to cover the <code>FETCH_INIT</code> action:</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'when dispatch FETCH_INIT action'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'returns the isLoading as true without any error'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> action: FetchAction = {
      <span class="hljs-keyword">type</span>: FetchActionType.FETCH_INIT,
    };

    expect(fetchReducer(initialState, action)).toEqual({
      isLoading: <span class="hljs-literal">true</span>,
      hasError: <span class="hljs-literal">false</span>,
      data: initialData,
    });
  });
});
</code></pre>
<p>It's pretty simple. It receives the initial state and the action, and we expect the proper return value: the new state with the <code>isLoading</code> as <code>true</code>.</p>
<p>The <code>FETCH_ERROR</code> is pretty similar:</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'when dispatch FETCH_ERROR action'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'returns the isLoading as true without any error'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> action: FetchAction = {
      <span class="hljs-keyword">type</span>: FetchActionType.FETCH_ERROR,
    };

    expect(fetchReducer(initialState, action)).toEqual({
      isLoading: <span class="hljs-literal">false</span>,
      hasError: <span class="hljs-literal">true</span>,
      data: [],
    });
  });
});
</code></pre>
<p>But we pass a different action and expect the <code>hasError</code> to be <code>true</code>.</p>
<p>The <code>FETCH_SUCCESS</code> is a bit complex as we just need to build a new state and add it to the payload attribute in the action.</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'when dispatch FETCH_SUCCESS action'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'returns the the API data'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> product: ProductType = {
      name: <span class="hljs-string">'iPhone'</span>,
      price: <span class="hljs-number">3500</span>,
      imageUrl: <span class="hljs-string">'image-url.png'</span>,
      description: <span class="hljs-string">'Apple mobile phone'</span>,
      isShippingFree: <span class="hljs-literal">true</span>,
      discount: <span class="hljs-number">0</span>,
    };

    <span class="hljs-keyword">const</span> action: FetchAction = {
      <span class="hljs-keyword">type</span>: FetchActionType.FETCH_SUCCESS,
      payload: [product],
    };

    expect(fetchReducer(initialState, action)).toEqual({
      isLoading: <span class="hljs-literal">false</span>,
      hasError: <span class="hljs-literal">false</span>,
      data: [product],
    });
  });
});
</code></pre>
<p>But nothing too complex here. The new data is there. A list of products. In this case, just one, the iPhone product.</p>
<p>The second test will cover the custom hook we built. In these tests, I wrote three contexts: a time-out request, a failed network request, and a success request.</p>
<p>Here, as I'm using <code>axios</code> to fetch data (when I have an API to fetch the data, I will use it properly), I'm using <code>axios-mock-adapter</code> to mock each context for our tests.</p>
<p>The set up first: Initializing our data and set up an axios mock.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> mock: MockAdapter = <span class="hljs-keyword">new</span> MockAdapter(axios);
<span class="hljs-keyword">const</span> url: <span class="hljs-built_in">string</span> = <span class="hljs-string">'/search'</span>;
<span class="hljs-keyword">const</span> initialData: Data = [];
</code></pre>
<p>We start implementing a test for the timeout request:</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">'handles error on timed-out api request'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  mock.onGet(url).timeout();

  <span class="hljs-keyword">const</span> { result, waitForNextUpdate } = renderHook(<span class="hljs-function">() =&gt;</span>
    useProductFetchAPI(url, initialData)
  );

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

  <span class="hljs-keyword">const</span> { isLoading, hasError, data }: State = result.current;

  expect(isLoading).toEqual(<span class="hljs-literal">false</span>);
  expect(hasError).toEqual(<span class="hljs-literal">true</span>);
  expect(data).toEqual(initialData);
});
</code></pre>
<p>We set up the mock to return a timeout. The test calls the <code>useProductFetchAPI</code>, wait for an update, and then we can get the state. The <code>isLoading</code> is false, the <code>data</code> is still the same (an empty list), and the <code>hasError</code> is now true as expected.</p>
<p>The network request is pretty much the same behavior. The only difference is that the mock will have a network error instead of a timeout.</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">'handles error on failed network api request'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  mock.onGet(url).networkError();

  <span class="hljs-keyword">const</span> { result, waitForNextUpdate } = renderHook(<span class="hljs-function">() =&gt;</span>
    useFetchAPI(url, initialData)
  );

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

  <span class="hljs-keyword">const</span> { isLoading, hasError, data }: State = result.current;

  expect(isLoading).toEqual(<span class="hljs-literal">false</span>);
  expect(hasError).toEqual(<span class="hljs-literal">true</span>);
  expect(data).toEqual(initialData);
});
</code></pre>
<p>And for the success case, we need to create a product object to use it as a request-response data. We also expect the <code>data</code> to be a list of this product object. The <code>hasError</code> and the <code>isLoading</code> are false in this case.</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">'gets and updates data from the api request'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> product: ProductType = {
    name: <span class="hljs-string">'iPhone'</span>,
    price: <span class="hljs-number">3500</span>,
    imageUrl: <span class="hljs-string">'image-url.png'</span>,
    description: <span class="hljs-string">'Apple mobile phone'</span>,
    isShippingFree: <span class="hljs-literal">true</span>,
    discount: <span class="hljs-number">0</span>,
  };

  <span class="hljs-keyword">const</span> mockedResponseData: Data = [product];

  mock.onGet(url).reply(<span class="hljs-number">200</span>, mockedResponseData);

  <span class="hljs-keyword">const</span> { result, waitForNextUpdate } = renderHook(<span class="hljs-function">() =&gt;</span>
    useFetchAPI(url, initialData)
  );

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

  <span class="hljs-keyword">const</span> { isLoading, hasError, data }: State = result.current;

  expect(isLoading).toEqual(<span class="hljs-literal">false</span>);
  expect(hasError).toEqual(<span class="hljs-literal">false</span>);
  expect(data).toEqual([product]);
});
</code></pre>
<p>Great. We covered everything we needed for this custom hook and the reducer we created. Now we can focus on building the products list.</p>
<h2 id="heading-products-list">Products list</h2>
<p>The idea of the products list is to list products that have some information: title, description, price, discount, and if it has free shipping. The final product card would look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screen-Shot-2020-06-06-at-15.52.17.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>To build this card, I created the foundation for the product component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> Product = <span class="hljs-function">() =&gt;</span> (
  &lt;Box&gt;
    &lt;Image /&gt;
    &lt;TitleDescription/&gt;
    &lt;Price /&gt;
    &lt;Tag /&gt;
  &lt;/Box&gt;
);
</code></pre>
<p>To build the product, we will need to build each component that is inside it.</p>
<p>But before start building the product component, I want to show the <code>JSON</code> data that the fake API will return for us.</p>
<pre><code class="lang-typescript">{
  imageUrl: <span class="hljs-string">'a-url-for-tokyo-tower.png'</span>,
  name: <span class="hljs-string">'Tokyo Tower'</span>,
  description: <span class="hljs-string">'Some description here'</span>,
  price: <span class="hljs-number">45</span>,
  discount: <span class="hljs-number">20</span>,
  isShippingFree: <span class="hljs-literal">true</span>,
}
</code></pre>
<p>This data is passed from the <code>Search</code> component to the <code>ProductList</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Search = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { isLoading, hasError, data }: State = useProductFetchAPI();

  <span class="hljs-keyword">if</span> (hasError) {
    <span class="hljs-keyword">return</span> &lt;h2&gt;<span class="hljs-built_in">Error</span>&lt;/h2&gt;;
  }

  <span class="hljs-keyword">return</span> &lt;ProductList products={data} isLoading={isLoading} /&gt;;
};
</code></pre>
<p>As I'm using Typescript, I can enforce the static types for the component props. In this case, I have the prop <code>products</code> and the <code>isLoading</code>.</p>
<p>I built a <code>ProductListPropsType</code> type to handle the product list props.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> ProductListPropsType = {
  products: ProductType[];
  isLoading: <span class="hljs-built_in">boolean</span>;
};
</code></pre>
<p>And the <code>ProductType</code> is a simple type representing the product:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> ProductType = {
  name: <span class="hljs-built_in">string</span>;
  price: <span class="hljs-built_in">number</span>;
  imageUrl: <span class="hljs-built_in">string</span>;
  description: <span class="hljs-built_in">string</span>;
  isShippingFree: <span class="hljs-built_in">boolean</span>;
  discount: <span class="hljs-built_in">number</span>;
};
</code></pre>
<p>To build the ProductList, I'll use the <code>Grid</code> component from Material UI. First, we have a grid container and then, for each product, we will render a grid item.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ProductList = <span class="hljs-function">(<span class="hljs-params">{ products, isLoading }: ProductListPropsType</span>) =&gt;</span> (
  &lt;Grid container spacing={<span class="hljs-number">3</span>}&gt;
    {products.map(<span class="hljs-function"><span class="hljs-params">product</span> =&gt;</span> (
      &lt;Grid
        item
        xs={<span class="hljs-number">6</span>}
        md={<span class="hljs-number">3</span>}
        key={<span class="hljs-string">`grid-<span class="hljs-subst">${product.name}</span>-<span class="hljs-subst">${product.description}</span>-<span class="hljs-subst">${product.price}</span>`</span>}
      &gt;
        &lt;Product
          key={<span class="hljs-string">`product-<span class="hljs-subst">${product.name}</span>-<span class="hljs-subst">${product.description}</span>-<span class="hljs-subst">${product.price}</span>`</span>}
          imageUrl={product.imageUrl}
          name={product.name}
          description={product.description}
          price={product.price}
          discount={product.discount}
          isShippingFree={product.isShippingFree}
          isLoading={isLoading}
        /&gt;
      &lt;/Grid&gt;
    ))}
  &lt;/Grid&gt;
);
</code></pre>
<p>The <code>Grid</code> item will display 2 items per row for mobile as we use the value <code>6</code> for each column. And for the desktop version, it will render 4 items per row.</p>
<p>We iterate through the <code>products</code> list and render the <code>Product</code> component passing all the data it will need.</p>
<p>Now we can focus on building the <code>Product</code> component.</p>
<p>Let's start with the easiest one: the <code>Tag</code>. We will pass three data to this component. <code>label</code>, <code>isVisible</code>, and <code>isLoading</code>. When it is not visible, we just return <code>null</code> to don't render it. If it is loading, we will render a <code>Skeleton</code> component from Material UI. But after loading it, we render the tag info with the <code>Free Shipping</code> label.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Tag = <span class="hljs-function">(<span class="hljs-params">{ label, isVisible, isLoading }: TagProps</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (!isVisible) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  <span class="hljs-keyword">if</span> (isLoading) {
    <span class="hljs-keyword">return</span> (
      &lt;Skeleton width=<span class="hljs-string">"110px"</span> height=<span class="hljs-string">"40px"</span> data-testid=<span class="hljs-string">"tag-skeleton-loader"</span> /&gt;
    );
  }

  <span class="hljs-keyword">return</span> (
    &lt;Box mt={<span class="hljs-number">1</span>} data-testid=<span class="hljs-string">"tag-label-wrapper"</span>&gt;
      &lt;span style={tabStyle}&gt;{label}&lt;/span&gt;
    &lt;/Box&gt;
  );
};
</code></pre>
<p>The <code>TagProps</code> is a simple type:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> TagProps = {
  label: <span class="hljs-built_in">string</span>;
  isVisible: <span class="hljs-built_in">boolean</span>;
  isLoading: <span class="hljs-built_in">boolean</span>;
};
</code></pre>
<p>I'm also using an object to style the <code>span</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> tabStyle = {
  padding: <span class="hljs-string">'4px 8px'</span>,
  backgroundColor: <span class="hljs-string">'#f2f3fe'</span>,
  color: <span class="hljs-string">'#87a7ff'</span>,
  borderRadius: <span class="hljs-string">'4px'</span>,
};
</code></pre>
<p>I also wanted to build tests for this component trying to think of its behavior:</p>
<ul>
<li>when it's not visible: the tag will not be in the document.</li>
</ul>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'when is not visible'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'does not render anything'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> { queryByTestId } = render(
      &lt;Tag label=<span class="hljs-string">"a label"</span> isVisible={<span class="hljs-literal">false</span>} isLoading={<span class="hljs-literal">false</span>} /&gt;
    );

    expect(queryByTestId(<span class="hljs-string">'tag-label-wrapper'</span>)).not.toBeInTheDocument();
  });
});
</code></pre>
<ul>
<li>when it's loading: the skeleton will be in the document.</li>
</ul>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'when is loading'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'renders the tag label'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> { queryByTestId } = render(
      &lt;Tag label=<span class="hljs-string">"a label"</span> isVisible isLoading /&gt;
    );

    expect(queryByTestId(<span class="hljs-string">'tag-skeleton-loader'</span>)).toBeInTheDocument();
  });
});
</code></pre>
<ul>
<li>when it's ready to render: the tag will be in the document.</li>
</ul>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'when is visible and not loading'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'renders the tag label'</span>, <span class="hljs-function">() =&gt;</span> {
    render(&lt;Tag label=<span class="hljs-string">"a label"</span> isVisible isLoading={<span class="hljs-literal">false</span>} /&gt;);

    expect(screen.getByText(<span class="hljs-string">'a label'</span>)).toBeInTheDocument();
  });
});
</code></pre>
<ul>
<li>bonus point: accessibility. I also built an automated test to cover accessibility violations using <code>jest-axe</code>.</li>
</ul>
<pre><code class="lang-typescript">it(<span class="hljs-string">'has no accessibility violations'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> { container } = render(
    &lt;Tag label=<span class="hljs-string">"a label"</span> isVisible isLoading={<span class="hljs-literal">false</span>} /&gt;
  );

  <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> axe(container);

  expect(results).toHaveNoViolations();
});
</code></pre>
<p>We are ready to implement another component: the <code>TitleDescription</code>. It will work almost similar to the <code>Tag</code> component. It receives some props: <code>name</code>, <code>description</code>, and <code>isLoading</code>.</p>
<p>As we have the <code>Product</code> type with the type definition for the <code>name</code> and the <code>description</code>, I wanted to reuse it. I tried different things - and you can <a target="_blank" href="https://leandrotk.github.io/tk/2020/05/typescript-learnings-interesting-types/index.html">take a look here for more details</a> - and I found the <code>Pick</code> type. With that, I could get the <code>name</code> and the <code>description</code> from the <code>ProductType</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> TitleDescriptionType = Pick&lt;ProductType, <span class="hljs-string">'name'</span> | <span class="hljs-string">'description'</span>&gt;;
</code></pre>
<p>With this new type, I could create the <code>TitleDescriptionPropsType</code> for the component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> TitleDescriptionPropsType = TitleDescriptionType &amp; {
  isLoading: <span class="hljs-built_in">boolean</span>;
};
</code></pre>
<p>Now working inside the component, If the <code>isLoading</code> is true, the component renders the proper skeleton component before it renders the actual title and description texts.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (isLoading) {
  <span class="hljs-keyword">return</span> (
    &lt;Fragment&gt;
      &lt;Skeleton
        width=<span class="hljs-string">"60%"</span>
        height=<span class="hljs-string">"24px"</span>
        data-testid=<span class="hljs-string">"name-skeleton-loader"</span>
      /&gt;
      &lt;Skeleton
        style={descriptionSkeletonStyle}
        height=<span class="hljs-string">"20px"</span>
        data-testid=<span class="hljs-string">"description-skeleton-loader"</span>
      /&gt;
    &lt;/Fragment&gt;
  );
}
</code></pre>
<p>If the component is not loading anymore, we render the title and description texts. Here we use the <code>Typography</code> component.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">return</span> (
  &lt;Fragment&gt;
    &lt;Typography data-testid=<span class="hljs-string">"product-name"</span>&gt;{name}&lt;/Typography&gt;
    &lt;Typography
      data-testid=<span class="hljs-string">"product-description"</span>
      color=<span class="hljs-string">"textSecondary"</span>
      variant=<span class="hljs-string">"body2"</span>
      style={descriptionStyle}
    &gt;
      {description}
    &lt;/Typography&gt;
  &lt;/Fragment&gt;
);
</code></pre>
<p>For the tests, we want three things:</p>
<ul>
<li>when it is loading, the component renders the skeletons</li>
<li>when it is not loading anymore, the component renders the texts</li>
<li>make sure the component doesn't violate the accessibility</li>
</ul>
<p>We will use the same idea we use for the <code>Tag</code> tests: see if it in the document or not based on the state.</p>
<p>When it is loading, we want to see if the skeleton is in the document, but the title and description texts are not.</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'when is loading'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'does not render anything'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> { queryByTestId } = render(
      &lt;TitleDescription
        name={product.name}
        description={product.description}
        isLoading
      /&gt;
    );

    expect(queryByTestId(<span class="hljs-string">'name-skeleton-loader'</span>)).toBeInTheDocument();
    expect(queryByTestId(<span class="hljs-string">'description-skeleton-loader'</span>)).toBeInTheDocument();
    expect(queryByTestId(<span class="hljs-string">'product-name'</span>)).not.toBeInTheDocument();
    expect(queryByTestId(<span class="hljs-string">'product-description'</span>)).not.toBeInTheDocument();
  });
});
</code></pre>
<p>When it is not loading anymore, it renders the texts in the DOM:</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'when finished loading'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'renders the product name and description'</span>, <span class="hljs-function">() =&gt;</span> {
    render(
      &lt;TitleDescription
        name={product.name}
        description={product.description}
        isLoading={<span class="hljs-literal">false</span>}
      /&gt;
    );

    expect(screen.getByText(product.name)).toBeInTheDocument();
    expect(screen.getByText(product.description)).toBeInTheDocument();
  });
});
</code></pre>
<p>And a simple test to cover accessibility issues:</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">'has no accessibility violations'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> { container } = render(
    &lt;TitleDescription
      name={product.name}
      description={product.description}
      isLoading={<span class="hljs-literal">false</span>}
    /&gt;
  );

  <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> axe(container);

  expect(results).toHaveNoViolations();
});
</code></pre>
<p>The next component is the <code>Price</code>. In this component we will provide a skeleton when it is still loading as we did in the other component, and add three different components here:</p>
<ul>
<li><code>PriceWithDiscount</code>: we apply the discount into the original price and render it</li>
<li><code>OriginalPrice</code>: it just renders the product price</li>
<li><code>Discount</code>: it renders the discount percentage when the product has a discount</li>
</ul>
<p>But before I start implementing these components, I wanted to structure the data to be used. The <code>price</code> and the <code>discount</code> values are numbers. So let's build a function called <code>getPriceInfo</code> that receives the <code>price</code> and the <code>discount</code> and it will return this data:</p>
<pre><code class="lang-typescript">{
  priceWithDiscount,
  originalPrice,
  discountOff,
  hasDiscount,
};
</code></pre>
<p>With this type contract:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> PriceInfoType = {
  priceWithDiscount: <span class="hljs-built_in">string</span>;
  originalPrice: <span class="hljs-built_in">string</span>;
  discountOff: <span class="hljs-built_in">string</span>;
  hasDiscount: <span class="hljs-built_in">boolean</span>;
};
</code></pre>
<p>In this function, it will get the <code>discount</code> and transform it into a <code>boolean</code>, then apply the <code>discount</code> to build the <code>priceWithDiscount</code>, use the <code>hasDiscount</code> to build the discount percentage, and build the <code>originalPrice</code> with the dollar sign:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> applyDiscount = (price: <span class="hljs-built_in">number</span>, discount: <span class="hljs-built_in">number</span>): <span class="hljs-function"><span class="hljs-params">number</span> =&gt;</span>
  price - (price * discount) / <span class="hljs-number">100</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getPriceInfo = (
  price: <span class="hljs-built_in">number</span>,
  discount: <span class="hljs-built_in">number</span>
): <span class="hljs-function"><span class="hljs-params">PriceInfoType</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> hasDiscount: <span class="hljs-built_in">boolean</span> = <span class="hljs-built_in">Boolean</span>(discount);
  <span class="hljs-keyword">const</span> priceWithDiscount: <span class="hljs-built_in">string</span> = hasDiscount
    ? <span class="hljs-string">`$<span class="hljs-subst">${applyDiscount(price, discount)}</span>`</span>
    : <span class="hljs-string">`$<span class="hljs-subst">${price}</span>`</span>;

  <span class="hljs-keyword">const</span> originalPrice: <span class="hljs-built_in">string</span> = <span class="hljs-string">`$<span class="hljs-subst">${price}</span>`</span>;
  <span class="hljs-keyword">const</span> discountOff: <span class="hljs-built_in">string</span> = hasDiscount ? <span class="hljs-string">`<span class="hljs-subst">${discount}</span>% OFF`</span> : <span class="hljs-string">''</span>;

  <span class="hljs-keyword">return</span> {
    priceWithDiscount,
    originalPrice,
    discountOff,
    hasDiscount,
  };
};
</code></pre>
<p>Here I also built an <code>applytDiscount</code> function to extract the discount calculation.</p>
<p>I added some tests to cover these functions. As they are pure functions, we just need to pass some values and expect new data.</p>
<p>Test for the <code>applyDiscount</code>:</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'applyDiscount'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'applies 20% discount in the price'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(applyDiscount(<span class="hljs-number">100</span>, <span class="hljs-number">20</span>)).toEqual(<span class="hljs-number">80</span>);
  });

  it(<span class="hljs-string">'applies 95% discount in the price'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(applyDiscount(<span class="hljs-number">100</span>, <span class="hljs-number">95</span>)).toEqual(<span class="hljs-number">5</span>);
  });
});
</code></pre>
<p>Test for the <code>getPriceInfo</code>:</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'getPriceInfo'</span>, <span class="hljs-function">() =&gt;</span> {
  describe(<span class="hljs-string">'with discount'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'returns the correct price info'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(getPriceInfo(<span class="hljs-number">100</span>, <span class="hljs-number">20</span>)).toMatchObject({
        priceWithDiscount: <span class="hljs-string">'$80'</span>,
        originalPrice: <span class="hljs-string">'$100'</span>,
        discountOff: <span class="hljs-string">'20% OFF'</span>,
        hasDiscount: <span class="hljs-literal">true</span>,
      });
    });
  });

  describe(<span class="hljs-string">'without discount'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'returns the correct price info'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(getPriceInfo(<span class="hljs-number">100</span>, <span class="hljs-number">0</span>)).toMatchObject({
        priceWithDiscount: <span class="hljs-string">'$100'</span>,
        originalPrice: <span class="hljs-string">'$100'</span>,
        discountOff: <span class="hljs-string">''</span>,
        hasDiscount: <span class="hljs-literal">false</span>,
      });
    });
  });
});
</code></pre>
<p>Now we can use the <code>getPriceInfo</code> in the <code>Price</code> components to get this structure data and pass down for the other components like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Price = <span class="hljs-function">(<span class="hljs-params">{ price, discount, isLoading }: PricePropsType</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (isLoading) {
    <span class="hljs-keyword">return</span> (
      &lt;Skeleton width=<span class="hljs-string">"80%"</span> height=<span class="hljs-string">"18px"</span> data-testid=<span class="hljs-string">"price-skeleton-loader"</span> /&gt;
    );
  }

  <span class="hljs-keyword">const</span> {
    priceWithDiscount,
    originalPrice,
    discountOff,
    hasDiscount,
  }: PriceInfoType = getPriceInfo(price, discount);

  <span class="hljs-keyword">return</span> (
    &lt;Fragment&gt;
      &lt;PriceWithDiscount price={priceWithDiscount} /&gt;
      &lt;OriginalPrice hasDiscount={hasDiscount} price={originalPrice} /&gt;
      &lt;Discount hasDiscount={hasDiscount} discountOff={discountOff} /&gt;
    &lt;/Fragment&gt;
  );
};
</code></pre>
<p>As we talked earlier, when it is loading, we just render the <code>Skeleton</code> component. When it finishes the loading, it will build the structured data and render the price info. Let's build each component now!</p>
<p>Let's start with the <code>OriginalPrice</code>. We just need to pass the <code>price</code> as a prop and it renders with the <code>Typography</code> component.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> OriginalPricePropsType = {
  price: <span class="hljs-built_in">string</span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> OriginalPrice = <span class="hljs-function">(<span class="hljs-params">{ price }: OriginalPricePropsType</span>) =&gt;</span> (
  &lt;Typography display=<span class="hljs-string">"inline"</span> style={originalPriceStyle} color=<span class="hljs-string">"textSecondary"</span>&gt;
    {price}
  &lt;/Typography&gt;
);
</code></pre>
<p>Very simple! Let's add a test now.</p>
<p>Just pass a price and see it if was rendered in the DOM:</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">'shows the price'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> price = <span class="hljs-string">'$200'</span>;
  render(&lt;OriginalPrice price={price} /&gt;);
  expect(screen.getByText(price)).toBeInTheDocument();
});
</code></pre>
<p>I also added a test to cover accessibility issues:</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">'has no accessibility violations'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> { container } = render(&lt;OriginalPrice price=<span class="hljs-string">"$200"</span> /&gt;);
  <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> axe(container);

  expect(results).toHaveNoViolations();
});
</code></pre>
<p>The <code>PriceWithDiscount</code> component has a very similar implementation, but we pass the <code>hasDiscount</code> boolean to render this price or not. If it has a discount, render the price with the discount. Otherwise, it won't render anything.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> PricePropsType = {
  hasDiscount: <span class="hljs-built_in">boolean</span>;
  price: <span class="hljs-built_in">string</span>;
};
</code></pre>
<p>The props type has the <code>hasDiscount</code> and the <code>price</code>. And the component just renders things based on the <code>hasDiscount</code> value.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> PriceWithDiscount = <span class="hljs-function">(<span class="hljs-params">{ price, hasDiscount }: PricePropsType</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (!hasDiscount) {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  }

  <span class="hljs-keyword">return</span> (
    &lt;Typography display=<span class="hljs-string">"inline"</span> style={priceWithDiscountStyle}&gt;
      {price}
    &lt;/Typography&gt;
  );
};
</code></pre>
<p>The tests will cover this logic when it has or doesn't have the discount. If it hasn't the discount, the prices will not be rendered.</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'when the product has no discount'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'shows nothing'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> { queryByTestId } = render(
      &lt;PriceWithDiscount hasDiscount={<span class="hljs-literal">false</span>} price=<span class="hljs-string">""</span> /&gt;
    );

    expect(queryByTestId(<span class="hljs-string">'discount-off-label'</span>)).not.toBeInTheDocument();
  });
});
</code></pre>
<p>If it has the discount, it will be the rendered in the DOM:</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'when the product has a discount'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'shows the price'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> price = <span class="hljs-string">'$200'</span>;
    render(&lt;PriceWithDiscount hasDiscount price={price} /&gt;);
    expect(screen.getByText(price)).toBeInTheDocument();
  });
});
</code></pre>
<p>And as always, a test to cover accessibility violations:</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">'has no accessibility violations'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> { container } = render(
    &lt;PriceWithDiscount hasDiscount price=<span class="hljs-string">"$200"</span> /&gt;
  );

  <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> axe(container);

  expect(results).toHaveNoViolations();
});
</code></pre>
<p>The <code>Discount</code> component is pretty much the same as the <code>PriceWithDiscount</code>. Render the discount tag if the product has a discount:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> DiscountPropsType = {
  hasDiscount: <span class="hljs-built_in">boolean</span>;
  discountOff: <span class="hljs-built_in">string</span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Discount = <span class="hljs-function">(<span class="hljs-params">{ hasDiscount, discountOff }: DiscountPropsType</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (!hasDiscount) {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  }

  <span class="hljs-keyword">return</span> (
    &lt;Typography
      display=<span class="hljs-string">"inline"</span>
      color=<span class="hljs-string">"secondary"</span>
      data-testid=<span class="hljs-string">"discount-off-label"</span>
    &gt;
      {discountOff}
    &lt;/Typography&gt;
  );
};
</code></pre>
<p>And all the tests we did for the other component, we do the same thing for the <code>Discount</code> component:</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'Discount'</span>, <span class="hljs-function">() =&gt;</span> {
  describe(<span class="hljs-string">'when the product has a discount'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'shows the discount label'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> discountOff = <span class="hljs-string">'20% OFF'</span>;
      render(&lt;Discount hasDiscount discountOff={discountOff} /&gt;);
      expect(screen.getByText(discountOff)).toBeInTheDocument();
    });
  });

  describe(<span class="hljs-string">'when the product has no discount'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'shows nothing'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> { queryByTestId } = render(
        &lt;Discount hasDiscount={<span class="hljs-literal">false</span>} discountOff=<span class="hljs-string">""</span> /&gt;
      );

      expect(queryByTestId(<span class="hljs-string">'discount-off-label'</span>)).not.toBeInTheDocument();
    });
  });

  it(<span class="hljs-string">'has no accessibility violations'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> { container } = render(
      &lt;Discount hasDiscount discountOff=<span class="hljs-string">"20% OFF"</span> /&gt;
    );

    <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> axe(container);

    expect(results).toHaveNoViolations();
  });
});
</code></pre>
<p>Now we will build an <code>Image</code> component. This component has the basic skeleton as any other component we've built. If it is loading, wait to render the image source and render the skeleton instead. When it finishes the loading, we will render the image, but only if the component is in the intersection of the browser window.</p>
<p>What does it mean? When you are on a website on your mobile device, you'll probably see the first 4 products. They will render the skeleton and then the image. But below these 4 products, as you're not seeing any of them, it doesn't matter if we are rendering them or not. And we can choose to not render them. Not for now. But on-demand. When you are scrolling, if the product's image is at the intersection of the browser window, we start rendering the image source.</p>
<p>That way we gain performance by speeding up the page load time and reduce the cost by requesting images on demand.</p>
<p>We will use the Intersection Observer API to download images on demand. But before writing any code about this technology, let's start building our component with the image and the skeleton view.</p>
<p>Image props will have this object:</p>
<pre><code class="lang-typescript">{
  imageUrl,
  imageAlt,
  width,
  isLoading,
  imageWrapperStyle,
  imageStyle,
}
</code></pre>
<p>The <code>imageUrl</code>, <code>imageAlt</code>, and the <code>isLoading</code> props are passed by the product component. The <code>width</code> is an attribute for the skeleton and the image tag. The <code>imageWrapperStyle</code> and the <code>imageStyle</code> are props that have a default value in the image component. We'll talk about this later.</p>
<p>Let's add a type for this props:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> ImageUrlType = Pick&lt;ProductType, <span class="hljs-string">'imageUrl'</span>&gt;;
<span class="hljs-keyword">type</span> ImageAttrType = { imageAlt: <span class="hljs-built_in">string</span>; width: <span class="hljs-built_in">string</span> };
<span class="hljs-keyword">type</span> ImageStateType = { isLoading: <span class="hljs-built_in">boolean</span> };
<span class="hljs-keyword">type</span> ImageStyleType = {
  imageWrapperStyle: CSSProperties;
  imageStyle: CSSProperties;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> ImagePropsType = ImageUrlType &amp;
  ImageAttrType &amp;
  ImageStateType &amp;
  ImageStyleType;
</code></pre>
<p>The idea here is to give meaning for the types and then compose everything. We can get the <code>imageUrl</code> from the <code>ProductType</code>. The attribute type will have the <code>imageAlt</code> and the <code>width</code>. The image state has the <code>isLoading</code> state. And the image style has some <code>CSSProperties</code>.</p>
<p>At first, the component would like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Image = <span class="hljs-function">(<span class="hljs-params">{
  imageUrl,
  imageAlt,
  width,
  isLoading,
  imageWrapperStyle,
  imageStyle,
}: ImagePropsType</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (isLoading) {
    &lt;Skeleton
      variant=<span class="hljs-string">"rect"</span>
      width={width}
      data-testid=<span class="hljs-string">"image-skeleton-loader"</span>
    /&gt;
  }

  <span class="hljs-keyword">return</span> (
    &lt;img
      src={imageUrl}
      alt={imageAlt}
      width={width}
      style={imageStyle}
    /&gt;
  );
};
</code></pre>
<p>Let's build the code to make the intersection observer works.</p>
<p>The idea of the intersection observer is to receive a target to be observed and a callback function that is executed whenever the observed target enters or exits the viewport. So the implementation would be very simple:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> observer: IntersectionObserver = <span class="hljs-keyword">new</span> IntersectionObserver(
  onIntersect,
  options
);

observer.observe(target);
</code></pre>
<p>Instantiate the <code>IntersectionObserver</code> class by passing an options object and the callback function. The <code>observer</code> will observe the <code>target</code> element.</p>
<p>As it is an effect in the DOM, we can wrap this into a <code>useEffect</code>.</p>
<pre><code class="lang-typescript">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> observer: IntersectionObserver = <span class="hljs-keyword">new</span> IntersectionObserver(
    onIntersect,
    options
  );

  observer.observe(target);

  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    observer.unobserve(target);
  };
}, [target]);
</code></pre>
<p>Using <code>useEffect</code>, we have two different things here: the dependency array and the returning function. We pass the <code>target</code> as the dependency function to make sure that we will re-run the effect if the <code>target</code> changes. And the returning function is a cleanup function. React performs the cleanup when the component unmounts, so it will clean up the effect before running another effect for every render.</p>
<p>In this cleanup function, we just stop observing the <code>target</code> element.</p>
<p>When the component starts rendering, the <code>target</code> reference is not set yet, so we need to have a guard to not observe an <code>undefined</code> target.</p>
<pre><code class="lang-typescript">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (!target) {
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-keyword">const</span> observer: IntersectionObserver = <span class="hljs-keyword">new</span> IntersectionObserver(
    onIntersect,
    options
  );

  observer.observe(target);

  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    observer.unobserve(target);
  };
}, [target]);
</code></pre>
<p>Instead of using this effect in our component, we could build a custom hook to receive the target, some options to customize the configuration, and it would provide a boolean telling if the target is at the intersection of the viewport or not.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> TargetType = Element | HTMLDivElement | <span class="hljs-literal">undefined</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> IntersectionStatus = {
  isIntersecting: <span class="hljs-built_in">boolean</span>;
};

<span class="hljs-keyword">const</span> defaultOptions: IntersectionObserverInit = {
  rootMargin: <span class="hljs-string">'0px'</span>,
  threshold: <span class="hljs-number">0.1</span>,
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useIntersectionObserver = (
  target: TargetType,
  options: IntersectionObserverInit = defaultOptions
): <span class="hljs-function"><span class="hljs-params">IntersectionStatus</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> [isIntersecting, setIsIntersecting] = useState(<span class="hljs-literal">false</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!target) {
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">const</span> onIntersect = <span class="hljs-function">(<span class="hljs-params">[entry]: IntersectionObserverEntry[]</span>) =&gt;</span> {
      setIsIntersecting(entry.isIntersecting);

            <span class="hljs-keyword">if</span> (entry.isIntersecting) {
        observer.unobserve(target);
      }
    };

    <span class="hljs-keyword">const</span> observer: IntersectionObserver = <span class="hljs-keyword">new</span> IntersectionObserver(
      onIntersect,
      options
    );

    observer.observe(target);

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      observer.unobserve(target);
    };
  }, [target]);

  <span class="hljs-keyword">return</span> { isIntersecting };
};
</code></pre>
<p>In our callback function, we just set if the entry target is intersecting the viewport or not. The <code>setIsIntersecting</code> is a setter from the <code>useState</code> hook we define at the top of our custom hook.</p>
<p>It is initialized as <code>false</code> but will update to <code>true</code> if it is intersecting the viewport.</p>
<p>With this new information in the component, we can render the image or not. If it is intersecting, we can render the image. If not, just render a skeleton until the user gets to the viewport intersection of the product image.</p>
<p>How does it look in practice?</p>
<p>First we define the wrapper reference using <code>useState</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> [wrapperRef, setWrapperRef] = useState&lt;HTMLDivElement&gt;();
</code></pre>
<p>It start as <code>undefined</code>. Then build a wrapper callback to set the element node:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> wrapperCallback = useCallback(<span class="hljs-function"><span class="hljs-params">node</span> =&gt;</span> {
  setWrapperRef(node);
}, []);
</code></pre>
<p>With that, we can use it to get the wrapper reference by using a <code>ref</code> prop in our <code>div</code>.</p>
<pre><code class="lang-typescript">&lt;div ref={wrapperCallback}&gt;
</code></pre>
<p>After setting the <code>wrapperRef</code>, we can pass it as the <code>target</code> for our <code>useIntersectionObserver</code> and expect a <code>isIntersecting</code> status as a result:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> { isIntersecting }: IntersectionStatus = useIntersectionObserver(wrapperRef);
</code></pre>
<p>With this new value, we can build a boolean value to know if we render the skeleton or the product image.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> showImageSkeleton: <span class="hljs-built_in">boolean</span> = isLoading || !isIntersecting;
</code></pre>
<p>So now we can render the appropriate node to the DOM.</p>
<pre><code class="lang-typescript">&lt;div ref={wrapperCallback} style={imageWrapperStyle}&gt;
  {showImageSkeleton ? (
    &lt;Skeleton
      variant=<span class="hljs-string">"rect"</span>
      width={width}
      height={imageWrapperStyle.height}
      style={skeletonStyle}
      data-testid=<span class="hljs-string">"image-skeleton-loader"</span>
    /&gt;
  ) : (
    &lt;img
      src={imageUrl}
      alt={imageAlt}
      width={width}
    /&gt;
  )}
&lt;/div&gt;
</code></pre>
<p>The full component looks like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Image = <span class="hljs-function">(<span class="hljs-params">{
  imageUrl,
  imageAlt,
  width,
  isLoading,
  imageWrapperStyle,
}: ImagePropsType</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [wrapperRef, setWrapperRef] = useState&lt;HTMLDivElement&gt;();
  <span class="hljs-keyword">const</span> wrapperCallback = useCallback(<span class="hljs-function"><span class="hljs-params">node</span> =&gt;</span> {
    setWrapperRef(node);
  }, []);

  <span class="hljs-keyword">const</span> { isIntersecting }: IntersectionStatus = useIntersectionObserver(wrapperRef);
  <span class="hljs-keyword">const</span> showImageSkeleton: <span class="hljs-built_in">boolean</span> = isLoading || !isIntersecting;

  <span class="hljs-keyword">return</span> (
    &lt;div ref={wrapperCallback} style={imageWrapperStyle}&gt;
      {showImageSkeleton ? (
        &lt;Skeleton
          variant=<span class="hljs-string">"rect"</span>
          width={width}
          height={imageWrapperStyle.height}
          style={skeletonStyle}
          data-testid=<span class="hljs-string">"image-skeleton-loader"</span>
        /&gt;
      ) : (
        &lt;img
          src={imageUrl}
          alt={imageAlt}
          width={width}
        /&gt;
      )}
    &lt;/div&gt;
  );
};
</code></pre>
<p>Great, now the loading on-demand works well. But I want to build a slightly better experience. The idea here is to have two different sizes of the same image. The low-quality image is requested and we make it visible, but blur while the high-quality image is requested in the background. When the high-quality image finally finishes loading, we transition from the low-quality to the high-quality image with an ease-in/ease-out transition to make it a smooth experience.</p>
<p>Let's build this logic. We could build this into the component, but we could also extract this logic into a custom hook.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useImageOnLoad = (): <span class="hljs-function"><span class="hljs-params">ImageOnLoadType</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> [isLoaded, setIsLoaded] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> handleImageOnLoad = <span class="hljs-function">() =&gt;</span> setIsLoaded(<span class="hljs-literal">true</span>);

  <span class="hljs-keyword">const</span> imageVisibility: CSSProperties = {
    visibility: isLoaded ? <span class="hljs-string">'hidden'</span> : <span class="hljs-string">'visible'</span>,
    filter: <span class="hljs-string">'blur(10px)'</span>,
    transition: <span class="hljs-string">'visibility 0ms ease-out 500ms'</span>,
  };

  <span class="hljs-keyword">const</span> imageOpactity: CSSProperties = {
    opacity: isLoaded ? <span class="hljs-number">1</span> : <span class="hljs-number">0</span>,
    transition: <span class="hljs-string">'opacity 500ms ease-in 0ms'</span>,
  };

  <span class="hljs-keyword">return</span> { handleImageOnLoad, imageVisibility, imageOpactity };
};
</code></pre>
<p>This hook just provides some data and behavior for the component. The <code>handleImageOnLoad</code> we talked earlier, the <code>imageVisibility</code> to make the low-quality image visible or not, and the <code>imageOpactity</code> to make the transition from transparent to opaque, that way we make it visible after loading it.</p>
<p>The <code>isLoaded</code> is a simple boolean to handle the visibility of the images. Another small detail is the <code>filter: 'blur(10px)'</code> to make the low-quality-image blur and then slowly focusing while transitioning from the low-quality image to the high-quality image.</p>
<p>With this new hook, we just import it, and call inside the component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> {
  handleImageOnLoad,
  imageVisibility,
  imageOpactity,
}: ImageOnLoadType = useImageOnLoad();
</code></pre>
<p>And start using the data and behavior we built.</p>
<pre><code class="lang-typescript">&lt;Fragment&gt;
  &lt;img
    src={thumbUrl}
    alt={imageAlt}
    width={width}
    style={{ ...imageStyle, ...imageVisibility }}
  /&gt;
  &lt;img
    onLoad={handleImageOnLoad}
    src={imageUrl}
    alt={imageAlt}
    width={width}
    style={{ ...imageStyle, ...imageOpactity }}
  /&gt;
&lt;/Fragment&gt;
</code></pre>
<p>The first one has a low-quality image, the <code>thumbUrl</code>. The second has the original high-quality image, the <code>imageUrl</code>. When the high-quality image is loaded, it calls the <code>handleImageOnLoad</code> function. This function will make the transition between one image to the other.</p>

  


<h2 id="heading-wrapping-up">Wrapping up</h2>
<p>This is the first part of this project to learn more about user experience, native APIs, typed frontend, and tests.</p>
<p>For the next part of this series, we are going to think more in an architectural way to build the search with filters, but keeping the mindset to bring technical solutions to make the user experience as smooth as possible.</p>
<p>You can find other articles like this on <a target="_blank" href="https://leandrotk.github.io/tk/2020/06/ux-studies-with-react-typescript-and-testing-library/index.html">TK's blog</a>.</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><a target="_blank" href="https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video">Lazy Loading Images and Video</a></li>
<li><a target="_blank" href="https://css-tricks.com/a-few-functional-uses-for-intersection-observer-to-know-when-an-element-is-in-view/">Functional Uses for Intersection Observer</a></li>
<li><a target="_blank" href="https://css-tricks.com/tips-for-rolling-your-own-lazy-loading/">Tips for rolling your own lazy loading</a></li>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">Intersection Observer API - MDN</a></li>
<li><a target="_blank" href="https://github.com/typescript-cheatsheets/react-typescript-cheatsheet">React Typescript Cheatsheet</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Don’t Write All Your Software Tests First – Just Write One ]]>
                </title>
                <description>
                    <![CDATA[ By Alex Bunardzic Test Driven Development (TDD) is sometimes described as “writing tests first”. The TDD mantra states that we should not write code before we have written automated tests that exercise that code. Writing code first is considered subo... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/dont-write-all-your-software-tests-first-just-write-one/</link>
                <guid isPermaLink="false">66d45d59b3016bf139028cf7</guid>
                
                    <category>
                        <![CDATA[ automation testing  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Quality Assurance ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 11 Jun 2020 05:31:23 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9c9a5c740569d1a4ca252b.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Alex Bunardzic</p>
<p>Test Driven Development (TDD) is sometimes described as “writing tests first”. The TDD mantra states that we should not write code before we have written automated tests that exercise that code. Writing code first is considered suboptimal.</p>
<p>And of course, writing code first is how we develop software following the so-called waterfall model. In that model, we divide software development activities into stages. For example, we have a ‘requirements gathering’ stage, we have an ‘application building’ stage, we have an ‘application testing’ stage, we have an ‘application deployment’ stage and so on.</p>
<p>But how is that different from the agile methodology? Don’t we have the exact same stages in agile? </p>
<h3 id="heading-agile-methodology-vs-waterfall-methodology">Agile Methodology vs Waterfall Methodology</h3>
<p>Of course we do. The crucial difference is that in agile, those stages are not gated. </p>
<p>In waterfall, we gate the stages and execute them in strict sequence. This means we won’t begin building the shipping application until such time as the requirements have been gathered, completed, signed off on and frozen. Once requirements are frozen (and controlled by our change management policies), we move into the next stage (or phase) – application building. </p>
<p>And similarly, we won’t move into the testing stage until the entire application has been built and we have reached the code complete milestone, at which point code changes have been frozen. </p>
<p>Once code gets frozen (and code freeze is then controlled by our change management policies), we hand it off to the testers. The testing phase begins, and only once all testing has completed (and provided that no significant defects have been detected), do we move into the deployment phase.</p>
<p>In agile, we do all the above activities in parallel. At the same time. We keep working on user stories (specs) while simultaneously building the shipping application. As we’re building the application we are also testing it. And as we are building and testing the application, we are also deploying it. </p>
<p>We learn from the shipping application deployed to production and use that validated learning as the feedback that will inform new user stories. That way, the loop gets closed, and we’re iterating, improving the value incrementally.</p>
<p>The only way to enable such iterative value stream delivery is by relying on automated tests. And as we’ve described, those tests are being written very early in the game. Actually, tests must be written before we write shipping code.</p>
<p>Why then is the title of this article “Don’t Write All Your Tests First, Just Write One”? It sounds a bit confusing. Let’s unpack the meaning of this title. But first, here's an overview of what tech we'll be using:</p>
<h3 id="heading-the-technology-stack-used-for-this-exercise">The technology stack used for this exercise</h3>
<p>In the attempt to keep the exercise simple and easy to follow, I have chosen <strong><a target="_blank" href="https://dotnet.microsoft.com/">.NET Core</a></strong> platform, together with <strong><a target="_blank" href="https://xunit.net/">xUnit.net</a></strong> testing platform. To follow the coding examples, please install <code>.NET Core</code> and <code>xUnit.net</code>.</p>
<p>In order to be able to run the sample code, please open <code>./tests/tests.csproj</code> file and add this line to the <code>ItemGroup</code>:</p>
<pre><code class="lang-c#">&lt;ProjectReference Include=<span class="hljs-string">"../app/app.csproj"</span> /&gt;
</code></pre>
<p>You’re now all set for following the coding exercises.</p>
<h2 id="heading-a-simple-example">A simple example</h2>
<p>To understand the difference between writing all tests first and writing one test first, it may be better to show rather than just tell. </p>
<p>So let’s try to build a simple example – for this exercise I’ve chosen a trivial case of calculating a tip at a restaurant. Often times we find ourselves in a position where we want to tip the restaurant for the service, but it’s tough to calculate percentages in our head. So a nifty little <code>Tip Calculator</code> could come in handy.</p>
<p>Here are the expectations:</p>
<p><strong>As a patron<br>I want to calculate the total bill (total plus the tip)<br>Because I want to compliment the restaurant for the service</strong></p>
<h3 id="heading-scenario-1-patron-calculates-the-total-for-terrible-service">Scenario #1: Patron calculates the total for terrible service</h3>
<p>Given that the restaurant total is $100.00<br>And the service was terrible<br>When the tip calculator calculates the total charge<br>Then tip calculator shows $100.00 total charge</p>
<h3 id="heading-scenario-2-patron-calculates-the-total-for-poor-service">Scenario #2: Patron calculates the total for poor service</h3>
<p>Given that the restaurant total is $100.00<br>And the service was poor<br>When the tip calculator calculates the total charge<br>Then tip calculator shows $105.00 total charge</p>
<h3 id="heading-scenario-3-patron-calculates-the-total-for-good-service">Scenario #3: Patron calculates the total for good service</h3>
<p>Given that the restaurant total is $100.00<br>And the service was good<br>When the tip calculator calculates the total charge<br>Then tip calculator shows $110.00 total charge</p>
<h3 id="heading-scenario-4-patron-calculates-the-total-for-great-service">Scenario #4: Patron calculates the total for great service</h3>
<p>Given that the restaurant total is $100.00<br>And the service was great<br>When the tip calculator calculates the total charge<br>Then tip calculator shows $115.00 total charge</p>
<h3 id="heading-scenario-5-patron-calculates-the-total-for-excellent-service">Scenario #5: Patron calculates the total for excellent service</h3>
<p>Given that the restaurant total is $100.00<br>And the service was excellent<br>When the tip calculator calculates the total charge<br>Then tip calculator shows $120.00 total charge</p>
<p>Let’s now implement the above user story.</p>
<p>We see that the story has 5 acceptance criteria (a.k.a. scenarios). Now we move into the analysis phase – think about what should be the first functionality that our <code>Tip Calculator</code> application should implement. But first, let’s open the command line terminal and create the new directory:</p>
<pre><code>md TipCalculator
cd TipCalculator
</code></pre><p>and create <code>app</code> and <code>tests</code> directories inside the <code>TipCalculator</code> directory.</p>
<p>Now <code>cd tests</code> and run: </p>
<pre><code class="lang-net">dotnet new xunit
</code></pre>
<p>Then <code>cd ..</code> and <code>cd app</code>, then run: </p>
<pre><code>dotnet <span class="hljs-keyword">new</span> classlib
</code></pre><p>We’re now ready to boogie!</p>
<p>Open your favourite text editor (mine is <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code</a>) and set your mind on the expectations. What behaviour are we expecting from the <code>Tip Calculator</code>?</p>
<p>To narrow the scope of our expectations, it usually helps to take one acceptance criteria (i.e. one scenario) and focus on it first. Let’s take scenario #1:</p>
<h3 id="heading-scenario-1-patron-calculates-the-total-for-terrible-service-1">Scenario #1: Patron calculates the total for terrible service</h3>
<p>Given that the restaurant total is $100.00<br>And the service was terrible<br>When the tip calculator calculates the total charge<br>Then tip calculator shows $100.00 total charge</p>
<p>In case the service was terrible, we are not adding any tips, and <code>Tip Calculator</code> is calculating a $0.00 tip. So how do we automate that scenario?</p>
<p>My first expectation would be that we need to somehow inform the <code>Tip Calculator</code> that the service was terrible. We either type the word ‘Terrible’ into the input field, or we select ‘Terrible’ from the list of available service ratings. </p>
<p>So the first thing to do here is to articulate some expectations regarding <code>Tip Calculator</code>’s ability to get notified that the service was terrible.</p>
<p>I like to always start with the expectation that what the user inputs is valid. So I’d first write a test that checks if the rating ‘Terrible’ is recognized by the <code>Tip Calculator</code> as a valid rating. </p>
<p>Go to the <code>tests</code> directory, rename the autogenerated <code>UnitTest1.cs</code> file to <code>TipCalculatorTests.cs</code> and add the following test:</p>
<pre><code class="lang-c#">[<span class="hljs-meta">Fact</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CheckIfRatingTerribleIsValid</span>(<span class="hljs-params"></span>)</span>{    
  <span class="hljs-keyword">var</span> expectedResponseForValidRating = <span class="hljs-literal">true</span>;    
  <span class="hljs-keyword">var</span> actualResponseForValidRating = <span class="hljs-literal">false</span>;    
  Assert.Equal(expectedResponseForValidRating, 
  actualResponseForValidRating);
}
</code></pre>
<p>Now go to the command line, <code>cd tests</code>, and run: </p>
<pre><code>dotnet test
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-21.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Of course, the above trivial test will fail, because we have hardcoded the values. But it’s always a good practice to make sure we see our tests fail before we proceed. Not observing a test fail may give us false sense of safety later on, if no tests fail and we end up thinking that everything works as expected.</p>
<p>A few more observations about the above test:</p>
<ul>
<li>It helps if the test name is descriptive. I chose <code>CheckIfRatingTerribleIsValid</code> to communicate the fact that we must make sure our application is capable of recognizing our commands.</li>
<li>It also helps if the expected and actual variable names are descriptive. I chose <code>expectedResponseForValidRating</code> and <code>actualResponseForValidRating</code> as fairly indicative of what our expectation in this test is, and also what actual value will the <code>Tip Calculator</code> produce.</li>
<li>Test is a first-class source code and must be approached with equal care lavished upon the production code.</li>
</ul>
<h2 id="heading-first-design-decision">First design decision</h2>
<p>At this point, we are forced to make a decision – how will our nascent <code>Tip Calculator</code> know if the service rating provided by the user is valid or not? </p>
<p>The design decision that comes to mind is that <code>Tip Calculator</code> must be able to store and retrieve some data. In this case, the data we’re interested in is the service rating.</p>
<p>If we go back to the user story and review the five acceptance criteria, we will see that the expectations are that <code>Tip Calculator</code> must be able to recognize five different service ratings:</p>
<ol>
<li>Terrible</li>
<li>Poor</li>
<li>Good</li>
<li>Great</li>
<li>Excellent</li>
</ol>
<p>So the simplest way to get <code>Tip Calculator</code> to store that information would be to endow it with an array, or a list. </p>
<p>But rather than rushing in to implement that list, we should examine the expectations again, to see if there’s anything else we may have missed. And there is – not only must <code>Tip Calculator</code> be able to recognize valid service ratings, it also must be able to associate each rating with a percentage value. </p>
<p>Our analysis shows the following associations:</p>
<ol>
<li>Terrible =&gt; 0%</li>
<li>Poor =&gt; 5%</li>
<li>Good =&gt; 10%</li>
<li>Great =&gt; 15%</li>
<li>Excellent =&gt; 20%</li>
</ol>
<p>In this case, a simple array or a simple list won’t be sufficient for holding the above associations. What’s the next simplest data structure that will allow us to implement these associations? After doing a little bit of research, we figure out that <code>Hashtable</code> is probably the most fitting data structure that can cover our needs with the least amount of ceremony.</p>
<p>We now navigate to the <code>app</code> directory and rename autogenerated <code>Class1.cs</code> file to <code>TipCalculator.cs</code>. We now want to add a <code>Hashtable</code> that will hold service ratings and the associated percentage values:</p>
<pre><code class="lang-c#">System.Collections.Hashtable ratingPercentages = <span class="hljs-keyword">new</span> System.Collections.Hashtable();
</code></pre>
<p>Now is a good time to recall that TDD is focused on coupling the expectations to the application’s behaviour, not to the application’s structure. Knowing that, we need to modify our test to make <code>Tip Calculator</code> exhibit some behaviour. The test codifies some expectations with regards to how the application must behave, and the running application provides the evidence of the expected behaviour.</p>
<p>But what is the evidence of the application’s behaviour? There is no other way for us to assess and evaluate application’s behaviour other than through examining the values that the running application produces. </p>
<p>In this case, we are expecting the running application to produce values <code>true</code> or <code>false</code> (Boolean values) after we ask the application if certain value (i.e. service rating) is valid.</p>
<p>To teach the application how to behave in the expected fashion, we need to endow it with an API. In this case, we design the API as follows:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">CheckIfRatingIsValid</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> rating</span>)</span>
</code></pre>
<p>In our test, we will modify the actual expected value to exercise the running application and collect the output value:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image001--1-.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>As you can see from the screenshot above, we have instantiated <code>TipCalculator</code> but when attempting to ask the instance to check if the supplied rating (“Terrible”) is valid, the editor is complaining that it cannot find that method.</p>
<p>Well of course, the method hasn’t been implemented yet. Now’s the time to go ahead and do it:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">CheckIfRatingIsValid</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> rating</span>)</span> {    
  <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
</code></pre>
<p>Now that the method is implemented, the test works; here is the complete listing:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> Xunit;
<span class="hljs-keyword">using</span> app;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">tests</span> {    
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TipCalculatorTests</span> {        
    TipCalculator tipCalculator = <span class="hljs-keyword">new</span> TipCalculator();        

    [<span class="hljs-meta">Fact</span>]        
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CheckIfRatingTerribleIsValid</span>(<span class="hljs-params"></span>)</span>{            
      <span class="hljs-keyword">var</span> expectedResponseForValidRating = <span class="hljs-literal">true</span>;            
      <span class="hljs-keyword">var</span> actualResponseForValidRating = 
      tipCalculator.CheckIfRatingIsValid(<span class="hljs-string">"Terrible"</span>);                  
      Assert.Equal(expectedResponseForValidRating, 
      actualResponseForValidRating);        
    }    
  }
}
</code></pre>
<p>We see from the above example that we’re cheating again (we have hardcoded return false; in our newly minted method). What’s the point of beating around the bush and merely creating skeletons and scaffoldings instead of rolling up our sleeves and doing actual coding? Let’s discuss this important topic.</p>
<h3 id="heading-discussion-about-our-first-design-decision">Discussion about our first design decision</h3>
<p>We’re illustrating here how to do TDD step-by-step. The funny part is that this step-by-step illustration is actually the exact way how we do TDD: step-by-step. There is no other way to do TDD than by doing it step-by-step. One step at a time.</p>
<p>How’s that different from any other way of doing software development? Don’t we also do everything step-by-step even when not following TDD methodology? Well, not really. Let me explain:</p>
<p>TDD to me feels like riding a galloping horse. We’re moving swiftly toward our goal, but we’re frequently touching the ground (the galloping horse is every now and then hitting the ground in order to bounce off and run fast). </p>
<p>In comparison, when I’m doing software development without TDD, it feels to me like I’m flying a kite. I’m making swift moves with the kite, but I never touch the ground, not even once. By the time I land the kite, the landing place may not be where I intended the kite to go (it’s very hard to control the direction of a kite if it’s flying in a strong wind).</p>
<p>With TDD, any time we make a change to the code (both the test code and the shipping application code), we run the tests and so we touch the ground. We are galloping, but at the same time we need this frequent grounding. We need to see whether we’re going in the right direction and also whether we’ve broken anything during our galloping. Our tests are the Oracle who keeps telling us if everything works as expected or if something started misbehaving.</p>
<p>Making changes to the code is a risky business. TDD provides a nice harness that is both guiding our design decisions and ensuring we don’t mess up something that we’ve already confirmed works to our expectations.</p>
<h2 id="heading-replace-hardcoded-value-with-actual-processing-logic">Replace hardcoded value with actual processing logic</h2>
<p>Let’s now replace the hardcoded value with actual running code. We first teach our <code>Tip Calculator</code> that there is a service rating called “Terrible” and that tip percentage associated with this rating is 0:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">CheckIfRatingIsValid</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> rating</span>)</span> {      
  ratingPercentages.Add(<span class="hljs-string">"Terrible"</span>, <span class="hljs-number">0</span>);    
  <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
</code></pre>
<p>Our <code>Tip Calculator</code> is now knowledgeable about the fact that there is a service rating labeled “Terrible” and the tip percentage associated with terrible service is 0%. Great, but we’re still returning hardcoded value <code>false</code>. Time to replace it with actual calculation:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">CheckIfRatingIsValid</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> rating</span>)</span> {    
  ratingPercentages.Add(<span class="hljs-string">"Terrible"</span>, <span class="hljs-number">0</span>);    
  <span class="hljs-keyword">return</span> ratingPercentages.ContainsKey(rating);
}
</code></pre>
<p>Run the test again:  </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-22.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Great, but the code still looks contrived. We are loading the “Terrible” value into the instance of <code>Hashtable ratingPercentages</code> and then immediately checking to see if that value exists in the <code>Hashtable</code>. Now that we have moved from the failing test (Red) to the passing test (Green), it’s time to perform the third step in the TDD loop – Refactor.</p>
<p>Refactoring is basically the activity of modifying the code structure without affecting the code behaviour. Our task here is simple: extract the code responsible for populating of the <code>Hashtable ratingPercentages</code> into a separate block of code. </p>
<p>The most natural place for this loading is in the block of code that is doing the initialization of the <code>Tip Calculator</code> – the <code>constructor</code> method. After refactoring, our shipping application source code looks like this:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> System.Collections;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">app</span> {    
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TipCalculator</span> {        
    <span class="hljs-keyword">private</span> Hashtable ratingPercentages = <span class="hljs-keyword">new</span> Hashtable();        
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TipCalculator</span>(<span class="hljs-params"></span>)</span> {            
      ratingPercentages.Add(<span class="hljs-string">"Terrible"</span>, <span class="hljs-number">0</span>);        
    }        

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">CheckIfRatingIsValid</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> rating</span>)</span> {            
      <span class="hljs-keyword">return</span> ratingPercentages.ContainsKey(rating);        
    }    
  }
}
</code></pre>
<p>Run the test again, and it passes (we’re in green). We have modified the structure of the code without modifying its behaviour! Good job.</p>
<h2 id="heading-flip-the-coin">Flip the coin</h2>
<p>Any time we satisfy a positive expectation, it is a prudent practice to turn things on their head and describe the negative expectation. </p>
<p>At this point, since we’ve satisfied that a legitimate service rating value is found in the <code>Tip Calculator</code>, we want to ensure that non-legitimate values are not found in the <code>Tip Calculator</code>. </p>
<p>What do we mean by non-legitimate values? Any value other than “Terrible”, “Poor”, “Good”, “Great” and “Excellent”. Time to write the new expectation (i.e. test):</p>
<pre><code class="lang-c#">[<span class="hljs-meta">Fact</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CheckIfRatingWhateverIsValid</span>(<span class="hljs-params"></span>)</span> {    
  <span class="hljs-keyword">var</span> expectedResponseForValidRating = <span class="hljs-literal">true</span>;    
  <span class="hljs-keyword">var</span> actualResponseForValidRating =                        
  tipCalculator.CheckIfRatingIsValid(<span class="hljs-string">"Whatever"</span>);
  Assert.Equal(expectedResponseForValidRating, 
  actualResponseForValidRating);
}
</code></pre>
<p>Run the tests:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-23.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Fails. As expected. (We specified that our expectation when supplying the service rating as “Whatever” should be <code>true</code>. In reality, it is <code>false</code>, because our <code>Tip Calculator</code> does not contain value “Whatever”.)</p>
<p>Fix the test (change the <code>expectedResponseForValidRating</code> from <code>true</code> to <code>false</code>) and run it again:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-24.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>A moment of reflection: why did we fake the first test run and made it fail? Because we always want to make sure we observe our new test failing. That way, we’ll know that in the future any successful passing of the test is not merely a false positive.</p>
<h2 id="heading-in-praise-of-steady-state">In praise of steady state</h2>
<p>Software engineering is a balancing act between steady state and periods of unstable state. What do we mean by steady state? </p>
<p>If we have a system (a running application) that behaves the way we expect it to behave (i.e. it produces values we have specified as expected values), we declare that the system is in a steady state. It is running, and delivering some value. </p>
<p>That value delivery is still partial. In our case, the only value to the users this <code>Tip Calculator</code> delivers is its ability to recognize service rating “Terrible” as a legitimate rating. In addition, it is capable of informing us that service rating “Whatever” is not a legitimate rating.</p>
<p>That’s not much, but still is better than nothing. And good news – our running application is currently in a steady state. Now we want to look into how to add more valuable behaviour to our <code>Tip Calculator</code>. And the only way to add more value is by making some changes.</p>
<p>Any time we make a change to our application, we disturb its steady state. This disturbance is risky. It may mean our changes could break something that is already working. Because of that concern, we strive to make the duration of this unstable state as short as possible. </p>
<p>Remember how we compared TDD to riding a galloping horse? When the horse is in flight (i.e. not touching the ground) it is advancing toward our goal, but it’s not in the steady state. Only when the horse touches the ground does its state stabilize.</p>
<p>TDD encourages making small changes (in flight) and immediately grounding the system by verifying that it is back in the steady state. We value steady state despite the fact that we eagerly embrace changes. Without changes, we won’t be able to deliver value, but we must do it in a very deliberate, careful fashion. </p>
<p>When doing TDD, we treat changes to steady state like walking on eggshells. No matter how sure we may be in knowing what and how we’re doing software engineering, it is prudent to still let failing tests guide our decisions.</p>
<h2 id="heading-check-if-correct-tip-percentage-is-associated-with-service-rating">Check if correct tip percentage is associated with service rating</h2>
<p>Let’s now introduce another change into our application – a test to verify if correct tip percentage is associated with service rating “Terrible”. Remember that we populated the instance of <code>Hashtable ratingPercentages</code> with the following values:</p>
<pre><code class="lang-c#">ratingPercentages.Add(<span class="hljs-string">"Terrible"</span>, <span class="hljs-number">0</span>);
</code></pre>
<p>We have written a test that verifies that our <code>Hashtable ratingPercentages</code> does contain legitimate service rating “Terrible”. Now we need a test that verifies that service rating “Terrible” means that the tip percentage for that rating is 0.</p>
<pre><code class="lang-c#">[<span class="hljs-meta">Fact</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CheckIfRatingTerribleHasZeroPercentTip</span>(<span class="hljs-params"></span>)</span> {    
  <span class="hljs-keyword">var</span> expectedZeroPercentForTerribleRating = <span class="hljs-number">0</span>;    
  <span class="hljs-keyword">var</span> actualZeroPercentForTerribleRating = <span class="hljs-number">10</span>;    
  Assert.Equal(expectedZeroPercentForTerribleRating, 
  actualZeroPercentForTerribleRating);
}
</code></pre>
<p>The new test <code>CheckIfRatingTerribleHasZeroPercentTip</code> should fail:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-25.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Again, we’re purposefully hard coding wrong actual values just so that we could observe our brand new test fail. Now we must replace hard coded value with the actual call to the <code>Tip Calculator</code>’s method that returns tip percentage for the service rating:</p>
<pre><code class="lang-c#">[<span class="hljs-meta">Fact</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CheckIfRatingTerribleHasZeroPercentTip</span>(<span class="hljs-params"></span>)</span> {    
  <span class="hljs-keyword">var</span> expectedZeroPercentForTerribleRating = <span class="hljs-number">0</span>;    
  <span class="hljs-keyword">var</span> actualZeroPercentForTerribleRating =                 
  tipCalculator.GetPercentageTipForRating(<span class="hljs-string">"Terrible"</span>);
  Assert.Equal(expectedZeroPercentForTerribleRating, 
  actualZeroPercentForTerribleRating);
}
</code></pre>
<p>As in the previous case, we have invented a new API for <code>Tip Calculator</code>. We call this new capability <code>GetPercentageTipForRating("Terrible")</code>. It takes the value of the service rating and returns the tip percentage for that rating.</p>
<p>Flip over to the <code>app/TipCalculator.cs</code> and add the hard coded skeleton of the new method:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">GetPercentageTipForRating</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> rating</span>)</span> {    
  <span class="hljs-keyword">return</span> <span class="hljs-number">10</span>;
}
</code></pre>
<p>Running the test fails again, because we have hard coded the return value. Let’s replace it with actual processing:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">GetPercentageTipForRating</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> rating</span>)</span> {    
  <span class="hljs-keyword">int</span> tipPercentage = Int32.Parse(ratingPercentages[rating].ToString());
  <span class="hljs-keyword">return</span> tipPercentage;
}
</code></pre>
<p>Run the test again:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-26.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>All three tests pass – we’re in green, we’re back to steady state!</p>
<h2 id="heading-what-tip-percentage-do-we-expect-for-non-legitimate-service-ratings">What tip percentage do we expect for non-legitimate service ratings?</h2>
<p>Many years of experience in the field taught me to be a bit pessimistic. Now that we have our application back in the steady state and delivering value (answering questions about legitimate service ratings and also giving us correct tip percentage for the “Terrible” rating), we need to see what happens when we run our application by giving it non-legitimate service rating value (for example, by giving it service rating “Whatever”).</p>
<p>Time for leaving the steady state yet again. We will write another test:</p>
<pre><code class="lang-c#">[<span class="hljs-meta">Fact</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CheckIfRatingWhateverHasNegativeOnePercentTip</span>(<span class="hljs-params"></span>)</span> {    
  <span class="hljs-keyword">var</span> expectedZeroPercentForWhateverRating = <span class="hljs-number">-1</span>;    
  <span class="hljs-keyword">var</span> actualZeroPercentForWhateverRating =       
  tipCalculator.GetPercentageTipForRating(<span class="hljs-string">"Whatever"</span>);
  Assert.Equal(expectedZeroPercentForWhateverRating, 
  actualZeroPercentForWhateverRating);
}
</code></pre>
<p>We are describing our expectation when <code>Tip Calculator</code> is asked to return tip percentage for service rating “Whatever”. Because service rating “Whatever” is a non-legitimate rating, we are expecting <code>Tip Calculator</code> to return tip percentage of value -1.</p>
<p>This test now precipitates one improvement to our shipping code. We need to add some logic to first check whether the supplied service rating is legitimate or not. Only if it is legitimate do we ask <code>Hashtable ratingPercentages</code> to tell us what the associated value of the tip percentage is. If the supplied service rating is non-legitimate (for example, if it is “Whatever”) we bypass talking to <code>Hashtable ratingPercentages</code> and simply return -1.</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">GetPercentageTipForRating</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> rating</span>)</span> {    
  <span class="hljs-keyword">int</span> tipPercentage = <span class="hljs-number">-1</span>;    
  <span class="hljs-keyword">if</span>(CheckIfRatingIsValid(rating)) {        
    tipPercentage = Int32.Parse(ratingPercentages[rating].ToString());    
  }    
  <span class="hljs-keyword">return</span> tipPercentage;
}
</code></pre>
<p>Run the tests, and all 4 tests pass:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-27.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We are back to the steady state. Another short excursion into the volatile area, and another swift victory and a safe return to steady, imperturbable state.</p>
<h2 id="heading-populate-other-service-rating-tip-percentages">Populate other service rating tip percentages</h2>
<p>Now is a good time to take a breather and make less risky changes, following the already established pattern. Leave the safety of the steady state and make short trips into the volatile territory by adding a new test to verify if service rating “Poor” is a valid, legitimate rating:</p>
<pre><code class="lang-c#">[<span class="hljs-meta">Fact</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CheckIfRatingPoorIsValid</span>(<span class="hljs-params"></span>)</span> {    
  <span class="hljs-keyword">var</span> expectedResponseForValidRating = <span class="hljs-literal">true</span>;    
  <span class="hljs-keyword">var</span> actualResponseForValidRating =       
  tipCalculator.CheckIfRatingIsValid(<span class="hljs-string">"Poor"</span>);
  Assert.Equal(expectedResponseForValidRating, 
  actualResponseForValidRating);
}
</code></pre>
<p>Running this test will fail:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-28.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Service rating “Poor” hasn’t been implemented yet. To make the test pass, implement service rating “Poor” by adding this line to the <code>TipCalculator</code> constructor:</p>
<pre><code class="lang-c#">ratingPercentages.Add(<span class="hljs-string">"Poor"</span>, <span class="hljs-number">5</span>);
</code></pre>
<p>Run the tests, and we’re back to safety:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-29.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We’re enjoying steady state with 6 tests successfully passing.</p>
<p>Now that we have added service rating “Poor” associated with the 5% tip, let’s write a test that will describe that expectation:</p>
<pre><code class="lang-c#">[<span class="hljs-meta">Fact</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CheckIfRatingPoorHasFivePercentTip</span>(<span class="hljs-params"></span>)</span> {    
  <span class="hljs-keyword">var</span> expectedZeroPercentForPoorRating = <span class="hljs-number">5</span>;    
  <span class="hljs-keyword">var</span> actualZeroPercentForPoorRating =       
  tipCalculator.GetPercentageTipForRating(<span class="hljs-string">"Poor"</span>);
  Assert.Equal(expectedZeroPercentForPoorRating, 
  actualZeroPercentForPoorRating);
}
</code></pre>
<p>The tests run successfully, and we’re back to being safe in the steady state.<br>I will leave it to the reader to make the changes that will drive the implementation of the service ratings “Good”, “Great” and “Excellent”. At the end of the exercise you should have your system back in the steady state with 12 tests successfully passing:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-35.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-calculate-grand-total-given-the-total-and-the-service-rating">Calculate grand total given the total and the service rating</h2>
<p>We are now ready for the final step – given the total bill and the service rating, we expect <code>Tip Calculator</code> to calculate tip percentage and add it to the total, producing the grand total to be paid to the restaurant.</p>
<p>As we always do, first we describe the expectation:</p>
<pre><code class="lang-c#">[<span class="hljs-meta">Fact</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CalculateTotalWithTip</span>(<span class="hljs-params"></span>)</span> {    
  <span class="hljs-keyword">var</span> expectedTotalWithTip = <span class="hljs-number">135.7</span>;    
  <span class="hljs-keyword">var</span> actualTotalWithTip = <span class="hljs-number">200.0</span>;    
  Assert.Equal(expectedTotalWithTip, actualTotalWithTip);
}
</code></pre>
<p>As usual, we first hard code some expectations that we know are going to fail. This is so that we observe our new test failing:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-36.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Time to implement processing logic that will calculate correct total with tip. Given the total of $118.0, and the service rating “Great” (15% tip), we’re expecting the total to be $135.7:</p>
<pre><code class="lang-c#">[<span class="hljs-meta">Fact</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CalculateTotalWithTip</span>(<span class="hljs-params"></span>)</span> {    
  <span class="hljs-keyword">var</span> rating = <span class="hljs-string">"Great"</span>;    
  <span class="hljs-keyword">var</span> total = <span class="hljs-number">118</span>;    
  <span class="hljs-keyword">var</span> expectedTotalWithTip = <span class="hljs-number">135.7</span>;    
  <span class="hljs-keyword">var</span> actualTotalWithTip = tipCalculator.CalculateTotalWithTip(total, 
  rating);    
  Assert.Equal(expectedTotalWithTip, actualTotalWithTip);
}
</code></pre>
<p>We have designed a new API the <code>Tip Calculator</code> – a method called <code>CalculateTotalWithTip(total, rating)</code>. It takes the total value and the service rating and returns the total with tip. The implementation of the method looks like this:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">CalculateTotalWithTip</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> total, <span class="hljs-keyword">string</span> rating</span>)</span> {    
  <span class="hljs-keyword">double</span> totalWithTip = <span class="hljs-number">-1</span>;    
  <span class="hljs-keyword">if</span>(CheckIfRatingIsValid(rating)) {        
    <span class="hljs-keyword">int</span> percentage = GetPercentageTipForRating(rating);        
    totalWithTip = total + ((total/<span class="hljs-number">100</span>) * percentage);    
  }    
  <span class="hljs-keyword">return</span> totalWithTip;
}
</code></pre>
<p>Run the tests, and we’re back to steady state:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-37.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-are-we-done-here">Are we done here?</h2>
<p>No, not yet. Even when all tests are in green and we’re back to the steady state, there are still a couple of things we need to do. </p>
<p>To begin with, we need to add a pessimistic expectation for our <code>Tip Calculator</code> calculation of total with the tip based on the service rating:</p>
<pre><code class="lang-c#">[<span class="hljs-meta">Fact</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CalculateTotalWithTipForNonlegitimateRating</span>(<span class="hljs-params"></span>)</span> {    
  <span class="hljs-keyword">var</span> rating = <span class="hljs-string">"Meh"</span>;    
  <span class="hljs-keyword">var</span> total = <span class="hljs-number">118</span>;    
  <span class="hljs-keyword">var</span> expectedTotalWithTip = <span class="hljs-number">135.7</span>;    
  <span class="hljs-keyword">var</span> actualTotalWithTip = tipCalculator.CalculateTotalWithTip(total, 
  rating);    
  Assert.Equal(expectedTotalWithTip, actualTotalWithTip);
}
</code></pre>
<p>Running the tests produces one failing test:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-38.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Our expectation for non-legitimate service rating (“Meh”) was incorrect. The actual total is -1, so we need to adjust our expectation by replacing 135.7 with -1. Run the tests again, and we’re back to the steady state!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-39.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We now have 14 tests, they all successfully pass, and our <em>Tip Calculator</em> works according to our expectations and satisfies the acceptance criteria.</p>
<p>We’re almost done. One more sanity check before we can confidently ship our shiny new <code>Tip Calculator</code> – we must run <em><a target="_blank" href="https://opensource.com/article/19/9/mutation-testing-example-definition">mutation testing</a></em>. </p>
<p>Our mutation testing framework will mutate the shipping code, one line at a time, and will run all tests for each individual mutation. </p>
<p>If the tests complain about the mutated code, all is good, we have killed the mutant. If the tests don’t complain, we’re in trouble. We have a surviving mutant in our codebase, which means there are lines of code in our repo that are doing something for which we haven’t provided any expectations.</p>
<p>Let’s run mutation testing to see how solid our solution is. Good news – our solution has killed 100% of mutants!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/image-40.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Mutation testing has given our shipping application a clean bill of health. Our <code>Tip Calculator</code> seems to be in good shape.</p>
<h2 id="heading-red-green-refactor-reflect">Red-Green-Refactor-Reflect</h2>
<p>Let’s review our <code>Tip Calculator</code> building exercise. We started the process by describing our expectations using the classical user story format. User story (as the name implies) is focused on describing scenarios that fulfill end user’s goals. </p>
<p>In this case, the simple goal is to calculate the tip amount from the supplied service rating and the restaurant bill total. The calculated tip amount is then automatically added to the total.</p>
<p>From there we proceeded to build our shipping application by following the TDD methodology. As we’ve demonstrated, the methodology consists of writing a failing test, observing it fail (the Red phase of TDD), then immediately making code changes that ensure the test passes (the Green phase of TDD). Once the test passes, we move into the Refactor phase (we restructure the code without affecting its behaviour). That way, we make sure our code is not expensive to change.</p>
<p>A proper TDD practice also mandates frequent retrospective – we call it reflection. We stop and think about the things we’ve accomplished thus far, to see if we could learn from our recent experiences. This reflection fortifies the process, as it relies on frequent and tight feedback provided by the failing, then succeeding tests.</p>
<p>I have already compared Test Driven Development to the experience of riding a galloping horse. While riding a horse, we’re alternating between flying through the air (i.e. speed achieved when the horse leaps from the ground) and steering the horse. It is impossible to steer the horse while we’re off the ground, up in the air. At that point, we gain speed, but we cannot make any changes of the direction. It is only once the horse touches the ground that we can make a change in direction.</p>
<p>In TDD, we strive to touch the ground as frequently as possible. The longer the leaps we make without touching the ground, the less chance we have for correcting the course.</p>
<p>I also compared software development practices that don’t follow TDD principles to the experience of flying a kite. When flying a kite, we never touch the ground. It is an exhilarating feeling of letting the wind pick the kite up and bounce it up in the air. We can achieve considerable speed that way. But we struggle in such situations to maintain desired course. And after we eventually land the kite, it usually does not land in the spot we originally wanted it to land.</p>
<p>Why is the emphasis of this article on “don’t write tests first”? Many software engineers who are not familiar with agile practices as implemented in TDD usually either claim that writing automated tests isn’t necessary, or claim that automated tests should be written after the code is complete. </p>
<p>Once they start learning about agile and TDD, they may reconsider their practices and decide that writing tests before writing implementation code may make more sense. Still, because of the ingrained waterfall mentality, some of those engineers make the mistake of writing all tests first, and only then move into writing the code.</p>
<p>That approach is completely wrong. It is equivalent to the traditional waterfall approach where we go through the development process by respecting gated phases. </p>
<p>First we write the requirements (in this case, requirements would be expectations written in the form of automated tests). Only once all the requirements (i.e. automated tests) have been written, signed off and frozen, do we move into the next gated phase – write the code for the shipping application.</p>
<p>TDD is the exact opposite of the “write tests first” approach. In TDD, we always write only one test. That test describes a desired behaviour. The desired behaviour does not exist yet (that’s why it is desired), and the test fails. </p>
<p>We then immediately move into making changes to the code in the attempt to create the desired behaviour. Once desired behaviour is created, it gets validated by the test, and if the expectations of the test are satisfied, we move into refactoring the code (to satisfy nonfunctional requirements, such as cost of change).</p>
<p>We practice a rigorous discipline to never succumb to the temptation to write more than one test at a time. That way, we ensure that we keep touching the ground as frequently as possible. </p>
<p>We prefer to remain ‘in flight’ for the shortest possible time. We are ‘in flight’ during that period when the desired behaviour described in the test has not materialized yet. The smaller the expected and desired behaviour is, the shorter will be our ‘in flight’ trajectory. That way, we keep touching the ground often, which gives us a chance to adjust the steering.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Building a simple <code>Tip Calculator</code> is a toy sized problem, and using that exercise to illustrate TDD methodology is not necessarily providing a convincing argument in favour of TDD. Still, within the constraints of a technical article, going over this hands-on exercise may provide valuable insights into the benefits of adopting TDD.</p>
<p>We would still argue that the real benefits of TDD only become apparent when dealing with much larger, more complex software engineering efforts. The ability to remain grounded while making potentially risky changes to a large, complex system is often a life saver. </p>
<p>In addition to that, building software using TDD methodology results in much less rework. TDD drives high degree of modularization, which results in high cohesiveness of the modules and low coupling between the modules. </p>
<p>All these characteristics produce a shipping application whose codebase is easy and inexpensive to change. And lowering the cost of change has proven to be the best way on the path to embracing changes and abandoning the concept known as ‘scope creep’. </p>
<p>Bottom line, TDD enables software engineering teams to deliver high degree of flexibility to the business.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What are Github Actions and How Can You Automate Tests and Slack Notifications? ]]>
                </title>
                <description>
                    <![CDATA[ Automation is a powerful tool. It both saves us time and can help reduce human error.  But automation can be tough and can sometimes prove to be costly. How can Github Actions help harden our code and give us more time to work on features instead of ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-are-github-actions-and-how-can-you-automate-tests-and-slack-notifications/</link>
                <guid isPermaLink="false">66b8e39047c23b7ae1ad0bdf</guid>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation testing  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CI/CD ]]>
                    </category>
                
                    <category>
                        <![CDATA[ continuous delivery ]]>
                    </category>
                
                    <category>
                        <![CDATA[ continuous deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Continuous Integration ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub Actions ]]>
                    </category>
                
                    <category>
                        <![CDATA[ slack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Colby Fayock ]]>
                </dc:creator>
                <pubDate>Wed, 03 Jun 2020 14:45:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/05/github-actions.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Automation is a powerful tool. It both saves us time and can help reduce human error. </p>
<p>But automation can be tough and can sometimes prove to be costly. How can Github Actions help harden our code and give us more time to work on features instead of bugs?</p>
<ul>
<li><a class="post-section-overview" href="#heading-what-are-github-actions">What are Github Actions?</a></li>
<li><a class="post-section-overview" href="#heading-what-is-cicd">What is CI/CD?</a></li>
<li><a class="post-section-overview" href="#heading-what-are-we-going-to-build">What are we going to build?</a></li>
<li><a class="post-section-overview" href="#heading-part-0-setting-up-a-project">Part 0: Setting up a project</a></li>
<li><a class="post-section-overview" href="#heading-part-1-automating-tests">Part 1: Automating tests</a></li>
<li><a class="post-section-overview" href="#heading-part-2-post-new-pull-requests-to-slack">Part 2: Post new pull requests to Slack</a></li>
</ul>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/1n-jHHNSoTw" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-what-are-github-actions">What are Github Actions?</h2>
<p><a target="_blank" href="https://github.com/features/actions">Actions</a> are a relatively new feature to <a target="_blank" href="https://github.com/">Github</a> that allow you to set up CI/CD workflows using a configuration file right in your Github repo.</p>
<p>Previously, if you wanted to set up any kind of automation with tests, builds, or deployments, you would have to look to services like <a target="_blank" href="https://circleci.com/">Circle CI</a> and <a target="_blank" href="https://travis-ci.org/">Travis</a> or write your own scripts. But with Actions, you have first class support to powerful tooling to automate your workflow.</p>
<h2 id="heading-what-is-cicd">What is CI/CD?</h2>
<p>CD/CD stands for Continuous Integration and Continuous Deployment (or can be Continuous Delivery). They're both practices in software development that allow teams to build projects together quickly, efficiently, and ideally with less errors.</p>
<p>Continuous Integration is the idea that as different members of the team work on code on different git branches, the code is merged to a single working branch which is then built and tested with automated workflows. This helps to constantly make sure everyone's code is working properly together and is well-tested.</p>
<p>Continuous Deployment takes this a step further and takes this automation to the deployment level. Where with the CI process, you automate the testing and the building, Continuous Deployment will automate deploying the project to an environment. </p>
<p>The idea is that the code, once through any building and testing processes, is in a deployable state, so it should be able to be deployed.</p>
<h2 id="heading-what-are-we-going-to-build">What are we going to build?</h2>
<p>We're going to tackle two different workflows.</p>
<p>The first will be to simply run some automated tests that will prevent a pull request from being merged if it is failing. We won't walk through building the tests, but we'll walk through running tests that already exist.</p>
<p>In the second part, we'll set up a workflow that sends a message to slack with a link to a pull request whenever a new one is created. This can be super helpful when working on open source projects with a team and you need a way to keep track of requests.</p>
<h2 id="heading-part-0-setting-up-a-project">Part 0: Setting up a project</h2>
<p>For this guide, you can really work through any node-based project as long as it has tests you can run for Part 1.</p>
<p>If you'd like to follow along with a simpler example that I'll be using, I've set up a new project that you can clone with a single function that has two tests that are able to run and pass.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/function-with-test.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>A function with two tests</em></p>
<p>If you'd like to check out this code to get started, you can run:</p>
<pre><code class="lang-shell">git clone --single-branch --branch start git@github.com:colbyfayock/my-github-actions.git
</code></pre>
<p>Once you have that cloned locally and have installed the dependencies, you should be able to run the tests and see them pass!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/passing-tests.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Passing tests</em></p>
<p>It should also be noted that you'll be required to have this project added as a new repository on Github in order to follow along.</p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-github-actions/commit/6919b1b9beea4823fd28375f1864d233e23f2d26">Follow along with the commit!</a></p>
<h2 id="heading-part-1-automating-tests">Part 1: Automating tests</h2>
<p>Tests are an important part of any project that allow us to make sure we're not breaking existing code while we work. While they're important, they're also easy to forget about.</p>
<p>We can remove the human nature out of the equation and automate running our tests to make sure we can't proceed without fixing what we broke.</p>
<h3 id="heading-step-1-creating-a-new-action">Step 1: Creating a new action</h3>
<p>The good news, is Github actually makes it really easy to get this workflow started as it comes as one of their pre-baked options.</p>
<p>We'll start by navigating to the <strong>Actions</strong> tab on our repository page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-actions-dashboard.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Github Actions starting page</em></p>
<p>Once there, we'll immediately see some starter workflows that Github provides for us to dive in with. Since we're using a node project, we can go ahead and click <strong>Set up this workflow</strong> under the <strong>Node.js</strong> workflow.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-action-new-nodejs-workflow.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Setting up a Node.js Github Action workflow</em></p>
<p>After the page loads, Github will land you on a new file editor that already has a bunch of configuration options added.</p>
<p>We're actually going to leave this "as is" for our first step. Optionally, you can change the name of the file to <code>tests.yml</code> or something you'll remember.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-action-create-new-workflow.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Adding a new Github Action workflow file</em></p>
<p>You can go ahead and click <strong>Start commit</strong> then either commit it directory to the <code>master</code> branch or add the change to a new branch. For this walkthrough, I'll be committing straight to <code>master</code>.</p>
<p>To see our new action run, we can again click on the <strong>Actions</strong> tab which will navigate us back to our new Actions dashboard.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-action-workflow-status.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Viewing Github Action workflow events</em></p>
<p>From there, you can click on <strong>Node.js CI</strong> and select the commit that you just made above and you'll land on our new action dashboard. You can then click one of the node versions in the sidebar via <strong>build (#.x)</strong>, click the <strong>Run npm test</strong> dropdown, and we'll be able to see the output of our tests being run (which if you're following along with me, should pass!).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-action-workflow-logs.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Viewing logs of a Github Action workflow</em></p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-github-actions/commit/10e397966572ed9975cac40f6ab5f41c1255a947">Follow along with the commit!</a></p>
<h3 id="heading-step-2-configuring-our-new-action">Step 2: Configuring our new action</h3>
<p>So what did we just do above? We'll walk through the configuration file and what we can customize.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-action-workflow-file.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Github Action Node.js workflow file</em></p>
<p>Starting from the top, we specify our name:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Node.js</span> <span class="hljs-string">CI</span>
</code></pre>
<p>This can really be whatever you want. Whatever you pick should help you remember what it is. I'm going to customize this to "Tests" so I know exactly what's going on.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">master</span> ]
  <span class="hljs-attr">pull_request:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">master</span> ]
</code></pre>
<p>The <code>on</code> key is how we specify what events trigger our action. This can be a variety of things like based on time with <a target="_blank" href="https://en.wikipedia.org/wiki/Cron">cron</a>. But here, we're saying that we want this action to run any time someone pushes commits to  <code>master</code> or someone creates a pull request targeting the <code>master</code> branch. We're not going to make a change here.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
</code></pre>
<p>This next bit creates a new job called <code>build</code>. Here we're saying that we want to use the latest version of Ubuntu to run our tests on. <a target="_blank" href="https://ubuntu.com/">Ubuntu</a> is common, so you'll only want to customize this if you want to run it on a specific environment.</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">strategy:</span>
      <span class="hljs-attr">matrix:</span>
        <span class="hljs-attr">node-version:</span> [<span class="hljs-number">10.</span><span class="hljs-string">x</span>, <span class="hljs-number">12.</span><span class="hljs-string">x</span>, <span class="hljs-number">14.</span><span class="hljs-string">x</span>]
</code></pre>
<p>Inside of our job we specify a <a target="_blank" href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategy">strategy</a> matrix. This allows us to run the same tests on a few different variations. </p>
<p>In this instance, we're running the tests on 3 different versions of <a target="_blank" href="https://nodejs.org/en/">node</a> to make sure it works on all of them. This is definitely helpful to make sure your code is flexible and future proof, but if you're building and running your code on a specific node version, you're safe to change this to only that version.</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">Node.js</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node-version</span> <span class="hljs-string">}}</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v1</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">node-version:</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node-version</span> <span class="hljs-string">}}</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">build</span> <span class="hljs-string">--if-present</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">test</span>
</code></pre>
<p>Finally, we specify the steps we want our job to run. Breaking this down:</p>
<ul>
<li><code>uses: actions/checkout@v2</code>: In order for us to run our code, we need to have it available. This checks out our code on our job environment so we can use it to run tests.</li>
<li><code>uses: actions/setup-node@v1</code>: Since we're using node with our project, we'll need it set up on our environment. We're using this action to do that setup  for us for each version we've specified in the matrix we configured above.</li>
<li><code>run: npm ci</code>: If you're not familiar with <code>npm ci</code>, it's similar to running <code>npm install</code> but uses the <code>package-lock.json</code> file without performing any patch upgrades. So essentially, this installs our dependencies.</li>
<li><code>run: npm run build --if-present</code>: <code>npm run build</code> runs the build script in our project. The <code>--if-present</code> flag performs what it sounds like and only runs this command if the build script is present. It doesn't hurt anything to leave this in as it won't run without the script, but feel free to remove this as we're not building the project here.</li>
<li><code>run: npm test</code>: Finally, we run <code>npm test</code> to run our tests. This uses the <code>test</code> npm script set up in our <code>package.json</code> file.</li>
</ul>
<p>And with that, we've made a few tweaks, but our tests should run after we've committed those changes and pass like before!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-action-workflow-logs-npm-test.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Logs of passing tests in Github Action workflow</em></p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-github-actions/commit/087cd8e8592d1f2b520b6e44b70b0a242a9d2d72">Follow along with the commit!</a></p>
<h3 id="heading-step-3-testing-that-our-tests-fail-and-prevent-merges">Step 3: Testing that our tests fail and prevent merges</h3>
<p>Now that our tests are set up to automatically run, let's try to break it to see it work.</p>
<p>At this point, you can really do whatever you want to intentionally break the tests, but <a target="_blank" href="https://github.com/colbyfayock/my-github-actions/pull/1">here's what I did</a>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/bad-changes-code-diff.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Code diff - https://github.com/colbyfayock/my-github-actions/pull/1</em></p>
<p>I'm intentionally returning different expected output so that my tests will fail. And they do!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-failing-checks.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Failing status checks on pull request</em></p>
<p>In my new pull request, my new branch breaks the tests, so it tells me my checks have failed. If you noticed though, it's still green to merge, so how can we prevent merges?</p>
<p>We can prevent pull requests from being merged by setting up a Protected Branch in our project settings.</p>
<p>First, navigate to <strong>Settings</strong>, then <strong>Branches</strong>, and click <strong>Add rule</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-add-protected-branch.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Github branch protection rules</em></p>
<p>We'll then want to set the branch name pattern to <code>*</code>, which means all branches, check the <strong>Require status checks to pass before merging option</strong>, then select all of our different status checks that we'd like to require to pass before merging.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-configure-protected-branch.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Setting up a branch protection rule in Github</em></p>
<p>Finally, hit <strong>Create</strong> at the bottom of the page.</p>
<p>And once you navigate back to the pull request, you'll notice that the messaging is a bit different and states that we need our statuses to pass before we can merge.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-failing-checks-cant-merge.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Failing tests preventing merge in pull request</em></p>
<p><em>Note: as an administrator of a repository, you'll still be able to merge, so this technically only prevents non-administrators from merging. But will give you increased messaging if the tests fail.</em></p>
<p>And with that, we have a new Github Action that runs our tests and prevents pull requests from merging if they fail.</p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-github-actions/pull/1">Follow along with the pull request!</a></p>
<p><em>Note: we won't be merging that pull request before continuing to Part 2.</em></p>
<h2 id="heading-part-2-post-new-pull-requests-to-slack">Part 2: Post new pull requests to Slack</h2>
<p>Now that we're preventing merge requests if they're failing, we want to post a message to our <a target="_blank" href="http://slack.com/">Slack</a> workspace whenever a new pull request is opened up. This will help us keep tabs on our repos right in Slack.</p>
<p>For this part of the guide, you'll need a Slack workspace that you have permissions to create a new developer app with and the ability to create a new channel for the bot user that will be associated with that app.</p>
<h3 id="heading-step-1-setting-up-slack">Step 1: Setting up Slack</h3>
<p>There are a few things we're going to walk through as we set up Slack for our workflow:</p>
<ul>
<li>Create a new app for our workspace</li>
<li>Assign our bot permissions</li>
<li>Install our bot to our workspace</li>
<li>Invite our new bot to our channel</li>
</ul>
<p>To get started, we'll create a new app. Head over to the <a target="_blank" href="https://api.slack.com/apps">Slack API Apps dashboard</a>. If you already haven't, log in to your Slack account with the Workspace you'd like to set this up with.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/slack-create-new-app.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a new Slack app</em></p>
<p>Now, click <strong>Create New App</strong> where you'll be prompted to put in a name and select a workspace you want this app to be created for. I'm going to call my app "Gitbot" as the name, but you can choose whatever makes sense for you. Then click <strong>Create App</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/slack-add-name-new-app.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Configuring a new Slack app</em></p>
<p>Once created, navigate to the <strong>App Home</strong> link in the left sidebar. In order to use our bot, we need to assign it <a target="_blank" href="https://oauth.net/">OAuth</a> scopes so it has permissions to work in our channel, so select <strong>Review Scopes to Add</strong> on that page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/slack-app-review-scopes.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Reviewing Slack app scopes</em></p>
<p>Scroll own and you'll see a <strong>Scopes</strong> section and under that a <strong>Bot Token</strong> section. Here, click <strong>Add an OAuth Scope</strong>. For our bot, we don't need a ton of permissions, so add the <code>channels:join</code> and <code>chat:write</code> scopes and we should be good to go.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/slack-app-add-scopes.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Adding scopes for a Slack app Bot Token</em></p>
<p>Now that we have our scopes, let's add our bot to our workspace. Scroll up on that same page to the top and you'll see a button that says <strong>Install App to Workspace</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/slack-install-app-to-workspace.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Installing Slack app to a workspace</em></p>
<p>Once you click this, you'll be redirected to an authorization page. Here, you can see the scopes we selected for our bot. Next, click <strong>Allow</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/slack-app-allow-workspace-permissions.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Allowing permission for Slack app to be installed to workspace</em></p>
<p>At this point, our Slack bot is ready to go. At the top of the <strong>OAuth &amp; Permissions</strong> page, you'll see a <strong>Bot User OAuth Access Token</strong>. This is what we'll use when setting up our workflow, so either copy and save this token or remember this location so you know how to find it later.</p>
<p><em>Note: this token is private - don't give this out, show it in a screencast, or let anyone see it!</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/slack-app-oauth-token.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Copying OAuth Access Token for Slack bot user</em></p>
<p>Finally, we need to invite our Slack bot to our channel. If you open up your workspace, you can either use an existing channel or create a new channel for these notifications, but you'll want to enter the command <code>/invite @[botname]</code> which will invite our bot to our channel.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/slack-invite-bot-to-channel.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Inviting Slack bot user to channel</em></p>
<p>And once added, we're done with setting up Slack!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/slack-app-bot-joined-channel.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Slack bot was added to channel</em></p>
<h3 id="heading-create-a-github-action-to-notify-slack">Create a Github Action to notify Slack</h3>
<p>Our next step will be somewhat similar to when we created our first Github Action. We'll create a workflow file which we'll configure to send our notifications.</p>
<p>While we can use our code editors to do this by creating a file in the <code>.github</code> directory, I'm going to use the Github UI.</p>
<p>First, let's navigate back to our <em>Actions</em> tab in our repository. Once there, select <strong>New workflow</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-new-workflow.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Setting up a new Github Action workflow</em></p>
<p>This time, we're going to start the workflow manually instead of using a pre-made Action. Select <strong>set up a workflow yourself</strong> at the top.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-set-up-new-workflow.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Setting up a Github Action workflow manually</em></p>
<p>Once the new page loads, you'll be dropped in to a new template where we can start working. Here's what our new workflow will look like:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Slack</span> <span class="hljs-string">Notifications</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">pull_request:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">master</span> ]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">notifySlack:</span>

    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>

    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Notify</span> <span class="hljs-string">slack</span>
      <span class="hljs-attr">env:</span>
        <span class="hljs-attr">SLACK_BOT_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.SLACK_BOT_TOKEN</span> <span class="hljs-string">}}</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">abinoda/slack-action@master</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">args:</span> <span class="hljs-string">'{\"channel\":\"[Channel ID]\",\"blocks\":[{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"*Pull Request:* $<span class="hljs-template-variable">{{ github.event.pull_request.title }}</span>\"}},{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"*Who?:* $<span class="hljs-template-variable">{{ github.event.pull_request.user.login }}</span>\n*Request State:* $<span class="hljs-template-variable">{{ github.event.pull_request.state }}</span>\"}},{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"&lt;$<span class="hljs-template-variable">{{ github.event.pull_request.html_url }}</span>|View Pull Request&gt;\"}}]}'</span>
</code></pre>
<p>So what's happening in the above?</p>
<ul>
<li><code>name</code>: we're setting a friendly name for our workflow</li>
<li><code>on</code>: we want our workflow to trigger when there's a pull request is created that targets our <code>master</code> branch</li>
<li><code>jobs</code>: we're creating a new job called <code>notifySlack</code></li>
<li><code>jobs.notifySlack.runs-on</code>: we want our job to run on a basic setup of the latest Unbuntu</li>
<li><code>jobs.notifySlack.steps</code>: we really only have one step here - we're using a pre-existing Github Action called <a target="_blank" href="https://github.com/marketplace/actions/post-slack-message">Slack Action</a> and we're configuring it to publish a notification to our Slack</li>
</ul>
<p>There are two points here we'll need to pay attention to, the <code>env.SLACK_BOT_TOKEN</code> and the <code>with.args</code>.</p>
<p>In order for Github to communicate with Slack, we'll need a token. This is what we're setting in <code>env.SLACK_BOT_TOKEN</code>. We generated this token in the first step. Now that we'll be using this in our workflow configuration, we'll need to <a target="_blank" href="https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository">add it as a Git Secret in our project</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-slack-token-secret.jpg" alt="Image" width="600" height="400" loading="lazy">
_Github secrets including SLACK_BOT<em>TOKEN</em></p>
<p>The  <code>with.args</code> property is what we use to configure the payload to the Slack API that includes the channel ID (<code>channel</code>) and our actual message (<code>blocks</code>).</p>
<p>The payload in the arguments is stringified and escaped. For example, when expanded it looks like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"channel"</span>: <span class="hljs-string">"[Channel ID]"</span>,
  <span class="hljs-attr">"blocks"</span>: [{
    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"section"</span>,
    <span class="hljs-attr">"text"</span>: {
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"mrkdwn"</span>,
      <span class="hljs-attr">"text"</span>: <span class="hljs-string">"*Pull Request:* ${{ github.event.pull_request.title }}"</span>
    }
  }, {
    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"section"</span>,
    <span class="hljs-attr">"text"</span>: {
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"mrkdwn"</span>,
      <span class="hljs-attr">"text"</span>: <span class="hljs-string">"*Who?:*n${{ github.event.pull_request.user.login }}n*State:*n${{ github.event.pull_request.state }}"</span>
    }
  }, {
    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"section"</span>,
    <span class="hljs-attr">"text"</span>: {
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"mrkdwn"</span>,
      <span class="hljs-attr">"text"</span>: <span class="hljs-string">"&lt;${{ github.event.pull_request._links.html.href }}|View Pull Request&gt;"</span>
    }
  }]
}
</code></pre>
<p><em>Note: this is just to show what the content looks like, we need to use the original file with the stringified and escaped argument.</em></p>
<p>Back to our configuration file, the first thing we set is our channel ID. To find our channel ID, you'll need to use the Slack web interface. Once you open Slack in your browser, you want to find your channel ID in the URL:</p>
<pre><code>https:<span class="hljs-comment">//app.slack.com/client/[workspace ID]/[channel ID]</span>
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2020/05/slack-web-channel-id.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Channel ID in Slack web app URL</em></p>
<p>With that channel ID, you can modify our workflow configuration and replace <code>[Channel ID]</code> with that ID:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">with:</span>
  <span class="hljs-attr">args:</span> <span class="hljs-string">'{\"channel\":\"C014RMKG6H2\",...</span>
</code></pre>
<p>The rest of the arguments property is how we set up our message. It includes variables from the Github event that we use to customize our message. </p>
<p>We won't go into tweaking that here, as what we already have will send a basic pull request message, but you can test out and build your own payload with Slack's <a target="_blank" href="https://app.slack.com/block-kit-builder/">Block Kit Builder</a>.</p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-github-actions/commit/e228b9899ef3da218d1a100d06a72259d45ea19e">Follow along with the commit!</a></p>
<h3 id="heading-test-out-our-slack-workflow">Test out our Slack workflow</h3>
<p>So now we have our workflow configured with our Slack app, finally we're ready to use our bot!</p>
<p>For this part, all we need to do is create a new pull request with any change we want. To test this out, I simply <a target="_blank" href="https://github.com/colbyfayock/my-github-actions/pull/2">created a new branch</a> where I added a sentence to the <code>README.md</code> file.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/github-test-pull-request.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Code diff - <a target="_blank" href="https://github.com/colbyfayock/my-github-actions/pull/2">https://github.com/colbyfayock/my-github-actions/pull/2</a></em></p>
<p>Once you <a target="_blank" href="https://github.com/colbyfayock/my-github-actions/pull/2">create that pull request</a>, similar to our tests workflow, Github will run our Slack workflow! You can see this running in the Actions tab just like before.</p>
<p>As long as you set everything up correctly, once the workflow runs, you should now have a new message in Slack from your new bot.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/slack-github-notification.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Slack bot automated message about new pull request</em></p>
<p><em>Note: we won't be merging that pull request in.</em></p>
<h2 id="heading-what-else-can-we-do">What else can we do?</h2>
<h3 id="heading-customize-your-slack-notifications">Customize your Slack notifications</h3>
<p>The message I put together is simple. It tells us who created the pull request and gives us a link to it.</p>
<p>To customize the formatting and messaging, you can use the Github <a target="_blank" href="https://app.slack.com/block-kit-builder/">Block Kit Builder</a> to create your own.</p>
<p>If you'd like to include additional details like the variables I used for the pull request, you can make use of Github's available <a target="_blank" href="https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#contexts">contexts</a>. This lets you pull information about the environment and the job to customize your message.</p>
<p>I couldn't seem to find any sample payloads, so here's an example of a sample <code>github</code> context payload you would expect in the event.</p>
<p><a target="_blank" href="https://gist.github.com/colbyfayock/1710edb9f47ceda0569844f791403e7e">Sample github context</a></p>
<h3 id="heading-more-github-actions">More Github actions</h3>
<p>With our ability to create new custom workflows, that's not a lot we can't automate. Github even has a <a target="_blank" href="https://github.com/marketplace?type=actions">marketplace</a> where you can browse around for one.</p>
<p>If you're feeling like taking it a step further, you can even create your own! This lets you set up scripts to configure a workflow to perform whatever tasks you need for your project.</p>
<h2 id="heading-join-in-the-conversation">Join in the conversation!</h2>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/colbyfayock/status/1268197100539514881"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<h2 id="heading-what-do-you-use-github-actions-for">What do you use Github actions for?</h2>
<p>Share with me on <a target="_blank" href="https://twitter.com/colbyfayock">Twitter</a>!</p>
<div id="colbyfayock-author-card">
  <p>
    <a href="https://twitter.com/colbyfayock">
      <img src="https://res.cloudinary.com/fay/image/upload/w_2000,h_400,c_fill,q_auto,f_auto/w_1020,c_fit,co_rgb:007079,g_north_west,x_635,y_70,l_text:Source%20Sans%20Pro_64_line_spacing_-10_bold:Colby%20Fayock/w_1020,c_fit,co_rgb:383f43,g_west,x_635,y_6,l_text:Source%20Sans%20Pro_44_line_spacing_0_normal:Follow%20me%20for%20more%20JavaScript%252c%20UX%252c%20and%20other%20interesting%20things!/w_1020,c_fit,co_rgb:007079,g_south_west,x_635,y_70,l_text:Source%20Sans%20Pro_40_line_spacing_-10_semibold:colbyfayock.com/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_68,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_145,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_222,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_295,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/v1/social-footer-card" alt="Follow me for more Javascript, UX, and other interesting things!" width="2000" height="400" loading="lazy">
    </a>
  </p>
  <ul>
    <li>
      <a href="https://twitter.com/colbyfayock">? Follow Me On Twitter</a>
    </li>
    <li>
      <a href="https://youtube.com/colbyfayock">?️ Subscribe To My Youtube</a>
    </li>
    <li>
      <a href="https://www.colbyfayock.com/newsletter/">✉️ Sign Up For My Newsletter</a>
    </li>
  </ul>
</div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to analyze website performance with Lighthouse ]]>
                </title>
                <description>
                    <![CDATA[ By Adam Henson Audit website performance manually, programmatically, or automatically Cityscape Birds Eye View Lighthouse is an open-source project by Google that gives you a way to measure web page performance. It has configurable settings for repr... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/three-ways-to-analyze-website-performance-with-lighthouse-8d100966c04b/</link>
                <guid isPermaLink="false">66c36340020f8e9c31066e6d</guid>
                
                    <category>
                        <![CDATA[ automation testing  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Productivity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SEO ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Website performance ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 11 Apr 2019 14:11:19 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*ydkP__0WxXbUl9Jy8D67DA.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Adam Henson</p>
<h4 id="heading-audit-website-performance-manually-programmatically-or-automatically">Audit website performance manually, programmatically, or automatically</h4>
<p><img src="https://cdn-media-1.freecodecamp.org/images/KZlzVvOb9fZcuBwmwxqRHgrrZyct4MCKNB4X" alt="Image" width="800" height="398" loading="lazy">
<em>Cityscape Birds Eye View</em></p>
<p>Lighthouse is an open-source project by Google that gives you a way to measure web page performance. It has configurable settings for reproducing various conditions. You can set network and device type to simulate, for example.</p>
<blockquote>
<p>You give Lighthouse a URL to audit, it runs a series of audits against the page, and then it generates a report on how well the page did. From there, use the failing audits as indicators on how to improve the page. Each audit has a reference doc explaining why the audit is important, as well as how to fix it. <a target="_blank" href="https://developers.google.com/web/tools/lighthouse/">Lighthouse</a></p>
</blockquote>
<p>There are many reasons why you’d want to measure performance, but one of the most important is about the impact on SEO. I go into more detail about this and how to address certain metrics in <a target="_blank" href="https://medium.freecodecamp.org/taming-performance-in-todays-web-app-with-lighthouse-webpack-and-react-loadable-components-b2d3fa04e0ab">this article</a>.</p>
<h3 id="heading-running-lighthouse-with-chrome-devtools">Running Lighthouse with Chrome DevTools</h3>
<p>You can run performance audits manually with the <a target="_blank" href="https://developers.google.com/web/tools/chrome-devtools/">Chrome DevTools browser extension</a>. Simply fire up the extension from the web page you’d like to test and select the “Audits” panel.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/CoSD1b8oLEO5ahFg4E4dRazNeo7c5L5y5sHo" alt="Image" width="800" height="117" loading="lazy">
<em>Chrome DevTools “Audits” Panel</em></p>
<p>Among a variety of audits, you can choose “performance”. You can also choose to simulate device type and network throttling. Some information specifically about throttling can be found in the <a target="_blank" href="https://github.com/GoogleChrome/lighthouse/blob/master/docs/throttling.md">Lighthouse project Github repo</a>.</p>
<p>Click on “Run audits” next. Upon completion, Lighthouse provides a report within the extension UI.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/rQqYaIzYRyX4kCF5-V4m7sr0MncTgrI9sJf9" alt="Image" width="800" height="361" loading="lazy">
<em>Lighthouse Performance Report</em></p>
<p>This report is a general overview of important metrics, opportunities, and overall performance score. Thumbnails illustrate the lifecycle of page load. What does this all mean? Google provides a plethora of <a target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits">documentation describing each metric</a>, how to address them and the <a target="_blank" href="https://developers.google.com/web/tools/lighthouse/v3/scoring">overall performance score</a>.</p>
<p>In the top left side of Chrome DevTools panel is a download icon that you can use to download the full report in JSON format. You can then use it to create a PDF report via <a target="_blank" href="https://github.com/GoogleChrome/lighthouse#using-the-node-cli">Lighthouse Report Viewer</a>.</p>
<p>Due to the high volume of factors playing into the lifecycle of page load, it’s important to compare results in batches. Taking an average of 5 runs, for example, will provide better insight.</p>
<h3 id="heading-running-lighthouse-programmatically">Running Lighthouse Programmatically</h3>
<p>For our standard “run of the mill” situations, the above should suffice. Another way to run Lighthouse involves installing the open-source package via NPM and following the instructions in the <a target="_blank" href="https://github.com/GoogleChrome/lighthouse#using-the-node-cli">CLI documentation</a>. This can be beneficial if you want to run audits programmatically in a build pipeline, for example.</p>
<p>Similar to the above, you can also run Lighthouse in code by following the <a target="_blank" href="https://github.com/GoogleChrome/lighthouse/blob/master/docs/readme.md#using-programmatically">documentation for using the Node module programmatically</a>. You could create a full-fledged Node.js application with Lighthouse ?!</p>
<h3 id="heading-running-lighthouse-automatically-over-time">Running Lighthouse Automatically Over Time</h3>
<p>So now that we’re pros — let’s take this to the next level. There are many <a target="_blank" href="https://github.com/GoogleChrome/lighthouse#lighthouse-integrations">integrations listed in the Lighthouse documentation</a>, so let’s take a look at one of them.</p>
<h4 id="heading-using-foo-to-run-lighthouse-and-compare-results-over-time">Using “Foo” to Run Lighthouse and Compare Results Over Time</h4>
<p>In an engineering setting where many developers are deploying application changes on a regular basis, it can be important to monitor website performance over time to associate change sets with performance degradation or improvement. Another example would be teams that have initiatives to improve performance for SEO ranking or other reasons. In these situations, it’s critical to monitor website performance over days, weeks, months, etc.</p>
<p>You can add URLs to track at <a target="_blank" href="https://www.foo.software">www.foo.software</a> and monitor performance change. Foo also provides email, Slack or PagerDuty notifications when performance has dropped below a threshold defined by the user, when it’s back to normal, and when improvements are identified automatically!</p>
<p>The best part about it is that you can <a target="_blank" href="https://www.foo.software/register">create an account for free</a>! Once registered and logged in, click the “Pages” link from the top navigation. This is where you can add URLs to monitor. Foo saves results and displays a timeline chart providing a visualization of important metrics. You can toggle through days, weeks, months and drill into detailed reports.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/aMrCtx3sHKfiNW4UbN32f5y7WwUejkJqEKYg" alt="Image" width="800" height="391" loading="lazy">
<em>Amazon Example Foo Lighthouse Timeline Chart</em></p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Lighthouse is becoming an industry standard in website performance measurement. There are books worth <a target="_blank" href="https://developers.google.com/web/tools/lighthouse/">documentation about Lighthouse that provides details of important metrics</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to uniquely name your elements and  automate tests for your desktop app ]]>
                </title>
                <description>
                    <![CDATA[ By Vinicius de Melo Rocha Motivation Writing automated tests for Desktop applications is not an easy task. Especially when it uses Windows Presentation Foundation (WPF). This allows so many possibilities of nested control and complex grids and menus.... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-uniquely-name-your-elements-and-automate-tests-for-your-desktop-app-8fec67eaca4b/</link>
                <guid isPermaLink="false">66c35552a6c3eebadae8d2d7</guid>
                
                    <category>
                        <![CDATA[ automation testing  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 05 Nov 2018 20:43:40 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*N9MfB3_mghGuOlRHX5YAzw.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Vinicius de Melo Rocha</p>
<h3 id="heading-motivation">Motivation</h3>
<p>Writing automated tests for Desktop applications is not an easy task. Especially when it uses Windows Presentation Foundation (WPF). This allows so many possibilities of nested control and complex grids and menus.</p>
<p>Here at <a target="_blank" href="https://www.clemex.com/">Clemex</a>, we use a tool to automate desktop tests that rely on the WPF <code>[FrameworkElement.Name](https://msdn.microsoft.com/en-us/library/system.windows.frameworkelement(v=vs.110).aspx)</code> property to interact with the application. Because we need to create dynamic controls based on collection data, they can end up with the same name for multiple UI elements.</p>
<p>For example, the following code generates a menu based on a collection of panels.</p>
<p>Inspecting the element tree, we would see that we now have multiple elements with the same name: “MenuBtn”.</p>
<p>To avoid this situation and have unique names for each button, we came up with four different approaches.</p>
<ul>
<li>Using the Code-Behind</li>
<li>Using data binding</li>
<li>Using attached properties</li>
<li>Using collection indexes</li>
</ul>
<h3 id="heading-using-the-code-behind">Using the Code-Behind</h3>
<p>Assuming that we have access to some unique ID on the elements data context. The easiest approach is to use the <code>Loaded</code> event of <code>FrameworkElement</code> to set a unique name using the code-behind model.</p>
<p>Now, when we check the element tree, we will see that we have unique names for each button.</p>
<h3 id="heading-using-data-binding">Using data binding</h3>
<p>Using data binding makes our code much cleaner, as well as easier to read and understand. If we try a similar approach using data binding we might end up with source code like the following:</p>
<p>Unfortunately, if we try to build this code, we will get a compilation error with the message:</p>
<blockquote>
<p>MarkupExtensions are not allowed for Uid or Name property values, so ‘{Binding Panel.PanelType, StringFormat=’MenuBtn{0}’}’ is not valid.</p>
</blockquote>
<p>This restriction prevents us from binding directly to the <code>Name</code> property.</p>
<h3 id="heading-using-attached-properties">Using attached properties</h3>
<p>To overcome the limitation of the previous attempt we can define a new property that would set the name for us. To add new properties to existing controls we can use <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/attached-properties-overview">Attached Properties</a>.</p>
<p>The <code>OnValueChanged</code> event triggers every time the value of our property changes. When that happens, we get the new value and set it to be the <code>FrameworkElement</code> name. We are giving our attached property the name <code>Name</code>. It could be anything we want, like <code>CustomName</code> or <code>TestName</code>.</p>
<p>To use the new property, we need to add a namespace to the XAML and attach the property to our button.</p>
<p>Our code will now compile without any problems, and we will have unique names for each element.</p>
<h3 id="heading-using-collection-indexes">Using collection indexes</h3>
<p>In the previous example, we created unique names by appending the property <code>Id</code>. There are other scenarios where we don’t have an ID on the item to create a unique element name. For that, we can instead use the collection index.</p>
<p>Let’s try to bind our button collection to a list of strings.</p>
<p>To achieve that, we can use the same <code>AttachedProperty</code> with a converter. It will look for the index of the element inside the collection.</p>
<p>In the XAML, we will now use <a target="_blank" href="https://msdn.microsoft.com/en-us/library/system.windows.data.multibinding(v=vs.110).aspx">MultiBinding</a> because we need both the element and the collection.</p>
<p>Looking at the element tree we can see that our buttons are named <code>MenuBtn00</code>, <code>MenuBtn01</code> and so on.</p>
<h3 id="heading-summary">Summary</h3>
<p>Generating unique names for dynamically created WPF controls can be done in an elegant way by using Attached Properties and using the multi-binding with a custom converter.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
