JS Sketchbooksee JavaScript think ✏️
← back to phase 10

10.7 — Checkpoint: test the tip calculator

Checkpoint — and a homecoming. In 3.11 you built the tip calculator: three small machines, chained. They’ve run unguarded for seven phases. Today, you put them under guard, then sabotage your own code and watch the suite catch you in seconds — the red-green loop of 10.1’s promise, experienced firsthand instead of believed.

Nothing new is taught today. Everything from 10.1–10.6 gets used.

watch it happen
// the Phase 3 brain, back under guard
function tipAmount(bill, percent) {
  return bill * percent / 100;
}
function totalWithTip(bill, percent) {
  return bill + tipAmount(bill, percent);
}
function perPerson(bill, percent, people) {
  return totalWithTip(bill, percent) / people;
}

// the suite (10.3's shape, 10.5's spirit)
check("tip: 10% of 1200 is 120",
      tipAmount(1200, 10),      120);
check("total: 1200 + tip is 1320",
      totalWithTip(1200, 10),   1320);
check("split: 1320 across 4 is 330",
      perPerson(1200, 10, 4),   330);
check("solo diner pays it all",
      perPerson(1200, 10, 1),   1320);

The brain, reread with Phase 10 eyes: tipAmount is a pure function (unit-layer perfect), totalWithTip calls it, perPerson calls THAT — a three-link call chain, which is a three-link dependency graph. Remember 10.1: graphs are where regressions travel. Today we guard every link.

the suite board· tip: 10% of 1200 is 120· total: 1200 + tip is 1320· split: 1320 across 4 is 330· solo diner pays it allthe mission: 3.11’s three functions have run unguarded for seven phases —today they get a suite
under the hood

The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”

The cascade teaches a real triage rule used daily: when many tests fail at once, sort by depth of dependency and read the deepest failure first — it’s usually the one true cause, and the rest are echoes. Some runners even help: Vitest prints failures in file order, but a well-structured suite (one describe per machine, bottom-up) makes the deepest machine’s block appear first.

Should you ALSO test totalWithTip with a stubbed tipAmount, to isolate it perfectly? You could — but 10.6’s rule says no: tipAmount is yours, fast, and deterministic; stubbing it would only prove the wiring you can already see. Save doubles for boundaries. This judgment call — where isolation stops being worth it — is one you’ll make weekly on the job.

Your four tests have a blind spot worth naming: floats. perPerson(100, 15, 3) is 38.333…, and toBe/=== on that invites 1.9’s dust. The professional habit: keep money in integer paise/cents, or assert with toBeCloseTo (10.4). Real invoice code does the former; real test suites do both.

Job note: this checkpoint is a complete interview story — “tell me about a time tests caught a bug” is better answered with the sabotage ritual than with luck: I write the suite, I break the code deliberately to verify the net, and the cascade pattern tells me where to look. That sentence sounds like five years of experience. You earned it in one lesson.

your turn

⌨️ guard the brain — find the saboteur

The starter ships the Phase 3 tip calculator — but ONE function is already sabotaged. Write the suite first, let the red point at the culprit, fix it, and leave everything green with an honest summary.

requirements:

  • Write check(name, actual, expected): strict compare; print PASS: name or FAIL: name — got actual, and count into outer passed/failed counters (5.3’s outer variables).
  • Write the suite — expected values from PAPER, never from running: tip of 10% on 1200 is 120; the total is 1320; split 4 ways is 330. Names are sentences.
  • Run it. Read the red top-down: the DEEPEST failing machine is the culprit. Fix the ONE broken line (a division has gone missing…), never the tests.
  • Finish green with the summary line 3 passed, 0 failed — built from the counters.

when you press RUN, the console must show exactly:

PASS: tip: 10% of 1200 is 120
PASS: total: 1200 + tip is 1320
PASS: split: 1320 across 4 is 330
3 passed, 0 failed

✏️ Quick check 1

You deleted ÷100 in tipAmount only — yet the totalWithTip and perPerson tests ALSO turned red. Why? (one short phrase)

✏️ Quick check 2

Four tests are red in a cascade. Which failure do you read FIRST — the shallowest machine or the deepest?

✏️ Quick check 3

The suite turns red after your change. You could fix the code — or edit the tests until they pass. Which is ever acceptable?

teach it back

🗣️ Now teach it back

Tell the whole checkpoint as a story: enumerating behaviors, where expected values came from, the sabotage ritual and what the four-red cascade revealed, how you diagnosed the culprit, and what the seconds-long catch proves about 10.1’s cost curve.

Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.

a few sentences, minimum — you’ve got this
to remember
The full loop, lived: enumerate behaviors → suite with paper-computed expected values and sentence names → green → SABOTAGE (÷100 deleted) → four reds → fix the code → green in seconds. 10.1’s cost curve, experienced.
One bug, four reds: failures cascade up the call chain — the red pattern IS the dependency graph. Triage rule: read the DEEPEST failure first; the rest are echoes.
Never edit tests to match buggy output (the enshrined-bug trap). Do it for real in the 9.8 workspace with Vitest — then Phase 11 hands this exact discipline a browser.