Quokka Labs · Updated 2026-09-28

Verify a JavaScript change with a failing example and eight checks

Fix a percentage calculation, define its input contract, and run an independent check before putting it into a website.

The task and the result

You are adding a percentage display to a small website. A visitor has completed 25 of 200 items, so the display must show 12.5%. This exercise starts with a plausible mistake, repairs it, and checks inputs that a happy-path demo would miss. You need a text editor and a current Node.js installation. No account, API key, paid course, or package installation is needed.

Create a file named percentage-check.mjs in an empty exercise folder. Keep this separate from your application while you work out the rule. The example accepts numbers, not text from an HTML input; converting form values belongs at the user-interface boundary.

Reproduce the error before editing

Suppose the initial function returns total / part * 100. With 25 and 200, it produces 800, not 12.5. Write down the expected answer yourself: 25 ÷ 200 is 0.125; multiplying by 100 gives 12.5. An independently calculated answer catches a wrong formula even if a generated test repeats the same wrong formula.

The rule for this exercise is: total must be positive, part must be nonnegative, and both values must be finite numbers. A part larger than total is allowed: 300 of a target of 200 is 150%. If your product instead measures progress that cannot exceed 100%, decide that separately rather than silently clipping every percentage calculation.

Replace the function

function percentage(part, total) {
  if (!Number.isFinite(part) || !Number.isFinite(total)) {
    throw new TypeError("Use finite numbers");
  }
  if (total <= 0 || part < 0) {
    throw new RangeError("Use a nonnegative part and a positive total");
  }
  return (part / total) * 100;
}

The finite-number check rejects text, NaN, and Infinity before arithmetic begins. The second check prevents a zero denominator and rejects negative amounts. Returning an unrounded number keeps presentation decisions out of the calculation. A UI can format 12.5 as 12.5%, but rounding inside this function would lose information needed by later calculations.

Run checks with independent expectations

Place the following code below the function in the same file. Run node percentage-check.mjs from that folder. Successful execution prints 8 checks passed; a failed assertion stops the command with an error. The TypeError and RangeError checks verify the rejected-input behavior, not just the displayed answer.

import assert from "node:assert/strict";

assert.equal(percentage(25, 200), 12.5);
assert.equal(percentage(0, 200), 0);
assert.equal(percentage(300, 200), 150);
assert.throws(() => percentage(1, 0), RangeError);
assert.throws(() => percentage(-1, 100), RangeError);
assert.throws(() => percentage("25", 200), TypeError);
assert.throws(() => percentage(NaN, 200), TypeError);
assert.throws(() => percentage(1, Infinity), TypeError);
console.log("8 checks passed");

To confirm that the checks detect the original bug, temporarily change the return expression back to total / part * 100 and run the command again. The first assertion must fail. Restore the corrected expression and rerun. A test that stays green after reintroducing the defect is not checking the intended behavior.

Connect the form without hiding invalid input

An HTML input gives you text. Read the raw value, reject an empty or whitespace-only string, and only then convert it with Number. Otherwise Number("") becomes zero and a blank field can look like a deliberate answer. Show the user an error beside the input; do not replace it with a made-up percentage. Treat comma-separated numbers and locale-specific decimal separators as an explicit product choice rather than guessing.

Check the browser separately: enter 25 and 200, enter zero for the total, clear the part field, then correct the values. Verify that the error clears and the result updates. Use the keyboard to reach the controls. These checks cover form wiring; the eight function checks do not prove that the browser UI works.

Ask an AI assistant for a bounded change

Use a request such as: “Fix this percentage function. The part is nonnegative and the total is positive; both are finite numbers. Values above 100% are allowed. Preserve the public function name. Show the changed lines and run the eight checks below.” Review the diff for unrelated edits, and record the command and its actual exit status. A completion message is not evidence that a command ran.

What this example does not cover

This is a learning example, not a currency or financial-precision library. JavaScript floating-point arithmetic can round decimal fractions. The checks also do not cover malicious form submissions, translations, or deployment. Keep calculation correctness, browser behavior, and production availability as separate results.

References and next exercise

Next, add one decimal input of your own and calculate the expected value on paper first. Decide how many decimals the UI should show without changing the function's result.

Corrections: dante@quokkalabs.net · Publishing approach