{"id":"l-tj-1-2","courseId":"testing-javascript","lang":"en","kind":"lesson","summary":"Add Vitest to a Vite project, write and run a first test file, and use watch mode and filters to keep the feedback loop under a second.","html":"<p>The fastest way to stop writing tests is a slow, fiddly setup. If running one test takes 15 seconds and three config files, you will skip it “just this once” until there are no tests. Vitest exists largely to fix that: it plugs into the Vite setup your app already has, so a first test takes about two minutes.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Install and run</h2>\n<p>In a Vite project (React, Vue, vanilla, it doesn’t matter):</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">npm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> install</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --save-dev</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> vitest</span></span></code></pre></div>\n<p>Add two scripts to <code dir=\"ltr\">package.json</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">package.json</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"scripts\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"test\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"vitest\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"test:run\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"vitest run\"</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p><code dir=\"ltr\">npm test</code> starts <strong>watch mode</strong>: Vitest runs the suite, then waits. When you save a file, it reruns only the tests that import it, using Vite’s module graph. On a laptop that is usually well under a second, which is what makes testing feel like part of coding instead of a chore at the end. <code dir=\"ltr\">npm run test:run</code> runs everything once and exits; that is the one for CI (Vitest also turns watch off automatically when the <code dir=\"ltr\">CI</code> environment variable is set).</p>\n<p>If you prefer a visual runner, install <code dir=\"ltr\">@vitest/ui</code> and start <code dir=\"ltr\">vitest --ui</code>: it opens a browser dashboard listing every file and test, with failures, console output and the module graph, and it reruns on save like the terminal does. The official Vitest extension for VS Code gives you the same run and debug buttons beside each test in the editor. Pick whichever keeps you in flow; the tests are identical.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">A first test file</h2>\n<p>Splitwise-lite keeps money as <strong>integer cents</strong> (more on why in the next lesson), so it needs a function that turns <code dir=\"ltr\">1234</code> into <code dir=\"ltr\">&quot;$12.34&quot;</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">src/money.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> formatMoney</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">cents</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> sign</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> cents </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x3C;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '-'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ''</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> abs</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Math.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">abs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(cents);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> dollars</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Math.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">floor</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(abs </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> rest</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(abs </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">padStart</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'0'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">sign</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}$${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">dollars</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}.${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">rest</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>Put the test next to it, named so Vitest finds it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">src/money.test.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { describe, test, expect } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'vitest'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { formatMoney } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> './money.js'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">describe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'formatMoney'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  test</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'formats dollars and cents'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">    expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">formatMoney</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1234</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toBe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'$12.34'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  });</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  test</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'pads single-digit cents'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">    expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">formatMoney</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">505</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toBe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'$5.05'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  });</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">});</span></span></code></pre></div>\n<p><code dir=\"ltr\">describe</code> groups related tests (it shows up as <code dir=\"ltr\">formatMoney &gt; pads single-digit cents</code> in the report). <code dir=\"ltr\">test</code> (alias <code dir=\"ltr\">it</code>) declares one test. <code dir=\"ltr\">expect(value).toBe(expected)</code> is the assertion: it throws when the values differ, exactly like the <code dir=\"ltr\">expectEqual</code> you wrote in the previous lesson.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Co-locate tests</span></p>\n<p>Keep <code dir=\"ltr\">money.test.js</code> beside <code dir=\"ltr\">money.js</code> rather than in a distant <code dir=\"ltr\">tests/</code> folder. You see at a glance which modules have tests, and moving a module moves its test with it.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Where the config lives</h2>\n<p>Vitest reads your existing <code dir=\"ltr\">vite.config.js</code>, so aliases and plugins work in tests for free. Test options go under a <code dir=\"ltr\">test</code> key:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">vite.config.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/// &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">reference</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> types</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"vitest/config\"</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\"> /></span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { defineConfig } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'vite'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> react </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '@vitejs/plugin-react'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> defineConfig</span><span style=\"color:#953800;--shiki-dark:#FFA657\">({</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  plugins: [</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">react</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()]</span><span style=\"color:#953800;--shiki-dark:#FFA657\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  test: {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    environment: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'node'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span><span style=\"color:#953800;--shiki-dark:#FFA657\">,</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">})</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre></div>\n<p>The triple-slash line gives your editor the types for the <code dir=\"ltr\">test</code> key. <code dir=\"ltr\">environment: 'node'</code> is the default, written out here so you know the switch exists. Pure logic like money formatting runs in Node, which is fast. Tests that need <code dir=\"ltr\">document</code> (you write those in section 3) use <code dir=\"ltr\">jsdom</code> or <code dir=\"ltr\">happy-dom</code>, installed separately and set either here or per file with a <code dir=\"ltr\">// @vitest-environment jsdom</code> comment at the top.</p>\n<p>You will also see <code dir=\"ltr\">globals: true</code> in some projects. It makes <code dir=\"ltr\">test</code> and <code dir=\"ltr\">expect</code> available without importing them, Jest style. Explicit imports are the default and play better with editors and linters, so this course imports them.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>A DOM environment for everything</span></p>\n<p>Setting <code dir=\"ltr\">environment: 'jsdom'</code> globally “to be safe” makes every test file pay the cost of building a fake browser, and it hides accidental DOM access in code that should be pure. Default to Node; opt in per file when a test needs a DOM.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Running a slice of the suite</h2>\n<p>As the suite grows you will want to run less of it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">npx</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> vitest</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> money</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">            # only files whose path contains \"money\"</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">npx</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> vitest</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -t</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"pads\"</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">        # only tests whose name matches \"pads\"</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">npx</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> vitest</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> run</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --reporter=verbose</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">   # one pass, every test name listed</span></span></code></pre></div>\n<p>Inside a file, <code dir=\"ltr\">test.only(...)</code> runs that test alone and <code dir=\"ltr\">test.skip(...)</code> parks one. Both are handy while debugging and dangerous if committed, because a stray <code dir=\"ltr\">.only</code> silently turns off the rest of the file. Vitest guards against that in CI: when the <code dir=\"ltr\">CI</code> environment variable is set, a test marked <code dir=\"ltr\">.only</code> fails with “Unexpected .only modifier” instead of quietly skipping its neighbours.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">In the playground</h2>\n<p>The exercises below run in a browser sandbox with a Vitest-compatible subset: <code dir=\"ltr\">describe</code>, <code dir=\"ltr\">test</code>, <code dir=\"ltr\">expect</code>, <code dir=\"ltr\">vi</code> and hooks are already in scope, and the code under test (the <strong>subject</strong>) is loaded for you, so there are no imports. When you check your work, your tests run against the real subject and must pass, then against a few deliberately broken versions and must fail on each.</p>\n<p>The next lesson looks closely at what goes inside a test and how to read the output when one fails.</p>\n","toc":[{"id":"sec-1","text":"Install and run","level":2},{"id":"sec-2","text":"A first test file","level":2},{"id":"sec-3","text":"Where the config lives","level":2},{"id":"sec-4","text":"Running a slice of the suite","level":2},{"id":"sec-5","text":"In the playground","level":2}],"takeaways":["Vitest reuses your Vite config, so the same aliases, plugins and transforms apply to your tests with almost no setup.","Files named <code dir=\"ltr\">*.test.js</code> or <code dir=\"ltr\">*.spec.js</code> (and their ts, jsx and tsx variants) are picked up automatically; put each one next to the code it tests.","Run <code dir=\"ltr\">vitest</code> while you work (watch mode reruns affected tests on save) and <code dir=\"ltr\">vitest run</code> in CI for a single pass.","The default environment is Node; switch to <code dir=\"ltr\">jsdom</code> or <code dir=\"ltr\">happy-dom</code> only for tests that need a DOM."],"further":[{"title":"Vitest — Getting Started","url":"https://vitest.dev/guide/"},{"title":"Vitest — Configuring Vitest","url":"https://vitest.dev/config/"},{"title":"Vitest — Test Environment","url":"https://vitest.dev/guide/environment"}],"quiz":[{"q":"<p>You run <code dir=\"ltr\">npx vitest</code> on your laptop and the command doesn’t exit after the tests finish. What is going on?</p>","options":[{"text":"A test is stuck waiting for a promise that never resolves.","why":"That would show as a timeout failure after a few seconds; here every test has finished and been reported."},{"text":"Vitest is in watch mode and will rerun the affected tests when you save a file.","why":"Correct. In an interactive terminal <code dir=\"ltr\">vitest</code> defaults to watch mode; <code dir=\"ltr\">vitest run</code> (or <code dir=\"ltr\">CI=true</code>) runs once and exits."},{"text":"Vitest needs a <code dir=\"ltr\">--exit</code> flag on macOS and Linux.","why":"There is no such requirement; the process stays alive on purpose because watch mode is the local default."}],"answer":1},{"q":"<p>Which file will Vitest pick up as a test file with the default configuration?</p>","options":[{"text":"<code dir=\"ltr\">src/money.tests.js</code>","why":"The default pattern matches <code dir=\"ltr\">.test.</code> or <code dir=\"ltr\">.spec.</code> before the extension; <code dir=\"ltr\">.tests.</code> with an s does not match."},{"text":"<code dir=\"ltr\">src/__tests__/money.js</code>","why":"Vitest does not treat <code dir=\"ltr\">__tests__</code> folders specially by default; the file name itself must contain <code dir=\"ltr\">.test.</code> or <code dir=\"ltr\">.spec.</code>."},{"text":"<code dir=\"ltr\">src/money.spec.ts</code>","why":"Correct. The default include pattern is <code dir=\"ltr\">**/*.{test,spec}.?(c|m)[jt]s?(x)</code>, which covers <code dir=\"ltr\">.spec.ts</code>."},{"text":"<code dir=\"ltr\">test/money.js</code>","why":"A <code dir=\"ltr\">test</code> folder name is not enough; without <code dir=\"ltr\">.test.</code> or <code dir=\"ltr\">.spec.</code> in the file name it is ignored by default."}],"answer":2},{"q":"<p>A test for <code dir=\"ltr\">formatMoney</code> fails with <code dir=\"ltr\">ReferenceError: document is not defined</code>, but <code dir=\"ltr\">formatMoney</code> never touches the DOM. What is the most likely cause?</p>","options":[{"text":"The test file imports a module that touches <code dir=\"ltr\">document</code> at import time, and the environment is Node.","why":"Correct. The default environment has no DOM; something in the import chain reads <code dir=\"ltr\">document</code>. Either split that module or set the environment for that file."},{"text":"Vitest needs <code dir=\"ltr\">globals: true</code> before tests can run.","why":"Globals only control whether <code dir=\"ltr\">test</code> and <code dir=\"ltr\">expect</code> are available without importing them; they have nothing to do with <code dir=\"ltr\">document</code>."},{"text":"Vitest can only test code that runs in the browser.","why":"The opposite: by default Vitest runs tests in Node, which is why <code dir=\"ltr\">document</code> is missing."}],"answer":0}],"exercise":{"kind":"web","mode":"test","subject":"// Splitwise-lite stores money as integer cents.\n// formatMoney(1234) -> \"$12.34\", formatMoney(-505) -> \"-$5.05\"\nfunction formatMoney(cents) {\n  const sign = cents < 0 ? '-' : '';\n  const abs = Math.abs(cents);\n  const dollars = Math.floor(abs / 100);\n  const rest = String(abs % 100).padStart(2, '0');\n  return `${sign}$${dollars}.${rest}`;\n}\n","mutants":[{"id":"no-padding","code":"// Splitwise-lite stores money as integer cents.\n// formatMoney(1234) -> \"$12.34\", formatMoney(-505) -> \"-$5.05\"\nfunction formatMoney(cents) {\n  const sign = cents < 0 ? '-' : '';\n  const abs = Math.abs(cents);\n  const dollars = Math.floor(abs / 100);\n  const rest = String(abs % 100);\n  return `${sign}$${dollars}.${rest}`;\n}\n"},{"id":"sign-lost","code":"// Splitwise-lite stores money as integer cents.\n// formatMoney(1234) -> \"$12.34\", formatMoney(-505) -> \"-$5.05\"\nfunction formatMoney(cents) {\n  const sign = '';\n  const abs = Math.abs(cents);\n  const dollars = Math.floor(abs / 100);\n  const rest = String(abs % 100).padStart(2, '0');\n  return `${sign}$${dollars}.${rest}`;\n}\n"},{"id":"rounds-dollars","code":"// Splitwise-lite stores money as integer cents.\n// formatMoney(1234) -> \"$12.34\", formatMoney(-505) -> \"-$5.05\"\nfunction formatMoney(cents) {\n  const sign = cents < 0 ? '-' : '';\n  const abs = Math.abs(cents);\n  const dollars = Math.round(abs / 100);\n  const rest = String(abs % 100).padStart(2, '0');\n  return `${sign}$${dollars}.${rest}`;\n}\n"}],"starter":{"js":"describe('formatMoney', () => {\n  test('formats dollars and cents', () => {\n    expect(formatMoney(1234)).toBe('$12.34');\n  });\n\n  // Add tests for: single-digit cents, negative amounts,\n  // and amounts where the cents are 50 or more.\n});\n"},"solution":{"js":"describe('formatMoney', () => {\n  test('formats dollars and cents', () => {\n    expect(formatMoney(1234)).toBe('$12.34');\n  });\n\n  test('pads single-digit cents with a zero', () => {\n    expect(formatMoney(505)).toBe('$5.05');\n  });\n\n  test('puts the minus sign before the dollar sign', () => {\n    expect(formatMoney(-505)).toBe('-$5.05');\n  });\n\n  test('keeps the dollars when the cents are 50 or more', () => {\n    expect(formatMoney(199)).toBe('$1.99');\n  });\n\n  test('formats zero', () => {\n    expect(formatMoney(0)).toBe('$0.00');\n  });\n});\n"},"checks":[{"id":"passes","suite":"subject"},{"id":"catches-no-padding","suite":"no-padding"},{"id":"catches-sign-lost","suite":"sign-lost"},{"id":"catches-rounds-dollars","suite":"rounds-dollars"}],"title":"First tests for formatMoney","prompt":"<p>The <strong>subject</strong> tab holds <code dir=\"ltr\">formatMoney(cents)</code>, which turns integer cents into a dollar string: <code dir=\"ltr\">1234</code> becomes <code dir=\"ltr\">&quot;$12.34&quot;</code> and <code dir=\"ltr\">-505</code> becomes <code dir=\"ltr\">&quot;-$5.05&quot;</code>.</p>\n<p>The starter has one test. Add tests until your suite passes on the real code <strong>and</strong> fails on each of three broken versions the checker has hidden: one forgets to pad single-digit cents, one drops the minus sign, and one rounds the dollars instead of flooring them. Use <code dir=\"ltr\">describe</code>, <code dir=\"ltr\">test</code> and <code dir=\"ltr\">expect(...).toBe(...)</code>; there is nothing to import.</p>\n","hints":["Each hidden bug only shows up for certain inputs. Which input makes padding matter? Which makes the sign matter?","Padding: try 505 (expect “$5.05”). Sign: try a negative number.","Rounding instead of flooring only changes the result when the cents are 50 or more, for example 199 should be “$1.99”, not “$2.99”."],"explanation":"<p>Each test targets one rule of the format, chosen with an input where breaking that rule changes the output. <code dir=\"ltr\">1234</code> alone can’t tell padding from no padding (34 has two digits) or floor from round (34 rounds down either way), which is why the starter suite let every bug through. Good examples are <strong>discriminating</strong>: pick inputs where a plausible mistake would produce a different answer. <code dir=\"ltr\">505</code> exercises padding, <code dir=\"ltr\">-505</code> the sign, <code dir=\"ltr\">199</code> the floor. Zero is a cheap extra that guards the most common display in the app.</p>\n","checkLabels":{"passes":"All your tests pass against the real formatMoney","catches-no-padding":"Catches the version that forgets to pad single-digit cents","catches-sign-lost":"Catches the version that drops the minus sign","catches-rounds-dollars":"Catches the version that rounds dollars instead of flooring"}},"words":700,"readMinutes":3}