Why Tests

We fixed the evaluator and you replayed APPLE against SPEED to check it. The colors came out right, so the fix works for that one specific case. But Wordle’s coloring logic has to handle a lot of cases: different combinations of duplicate letters, different positions, full matches, no matches. So far we have only checked one of them. The fix might still be wrong for some of the others, and with what we have right now, the only way to find out is to keep playing rounds in the browser and checking every tile by eye.

Other Cases the Evaluator Has to Handle

A few more cases the evaluator has to get right:

  • ALLEY against HELLO — both words have two Ls, but at different positions
  • ROBOT against FLOOR — the target has two Os, but the guess only has one
  • GOOSE against MOOSE — four letters in common, with overlapping doubles
  • A guess that is exactly the target — every tile should be green
  • A guess that shares no letters with the target — every tile should be gray

Running each of these in the browser by hand every time anyone edits evaluate.ts is not something anyone is actually going to do.

What a Test Suite Is

What we want instead is a small program that calls evaluateGuess("APPLE", "SPEED") for us, checks that the result is ["absent", "correct", "absent", "absent", "present"], and reports whether it matched. Then another one for ALLEY against HELLO. Then another for ROBOT against FLOOR. A hundred of them if that is what it takes, one per case we care about. We run the whole thing, it finishes in under a second, and we get back a list of passes. When one of them starts failing, we see exactly which case broke and what the function returned instead of what we expected.

That is all a test suite is. It is a program that exercises your functions for you and tells you which cases came back wrong.

Tests Also Catch Regressions

There is a second reason to write tests, and in practice it matters at least as much as the coverage reason.

A month from now, someone is going to edit evaluateGuess again. Maybe that someone is you, adding hard mode. Maybe it is Copilot, on your instruction, extending the game to support six-letter words. Maybe it is a classmate who looks at the two-pass code, thinks they can make it cleaner, and rewrites it. They try their change by playing APPLE against SPEED, it looks fine, and they move on. But somewhere along the way they have broken ALLEY against HELLO, and nobody will notice until a real player types it.

The duplicate-letter rule is subtle, and whoever makes that future edit may not know about it at all. If there is a test in the project that runs evaluateGuess("ALLEY", "HELLO") every time someone changes the file, the test fails as soon as the rule gets broken, and the person making the change sees the failure before they ship anything. That is what a regression test is: a test you write after fixing a bug, so that the same bug gets caught if it comes back.

Why After the Fix, Not Before

We are writing tests for evaluateGuess now, after Copilot has already fixed it. The fixed code already exists. Copilot wrote it, you read through it, and the browser confirmed it is behaving correctly on the case you tried. The tests we are about to write do not define what “correct” means for the evaluator, because we already know that. Their job is to keep the behavior we already have from changing later.

Tests can also be written the other way around, before any code exists. You describe the behavior you want, and then someone (you, or Copilot, or both) writes code to make those descriptions pass. That is a different workflow, and we will use it later in the chapter for a feature the starter does not have. For now, we have a fix and we want it to stay fixed, so tests come second.