3 min read·Run it here

The Four Asserts

Second lap of the test recipe, against a given undo/redo editor. Same four helpers, same two-test main, different prop.

Pattern
Four helpers · same throw shape
Time
the recipe
Space
O(1)

Same recipe as Write the Test Runner. Different prop. TextDocument and EditorHistory are already written — you are here to produce the four helpers cold.

The recipe

Retype it. Do not paste it.

function assertEqual<T>(actual: T, expected: T, label: string): void {
  if (actual !== expected) {
    throw new Error(`${label}: expected ${expected}, got ${actual}`);
  }
}

function assertTrue(actual: boolean, label: string): void {
  if (actual !== true) {
    throw new Error(`${label}: expected true, got ${actual}`);
  }
}

function assertFalse(actual: boolean, label: string): void {
  if (actual !== false) {
    throw new Error(`${label}: expected false, got ${actual}`);
  }
}

function assertDeepEqual<T>(actual: T, expected: T, label: string): void {
  const actualJson = JSON.stringify(actual);
  const expectedJson = JSON.stringify(expected);

  if (actualJson !== expectedJson) {
    throw new Error(`${label}: expected ${expectedJson}, got ${actualJson}`);
  }
}

function test(name: string, callback: () => void): void {
  try {
    callback();
    console.log(`PASS: ${name}`);
  } catch (error) {
    console.error(`FAIL: ${name}`);
    console.error(error);
  }
}

What this class is for

EditorHistory.undo() and redo() return booleans. That is why this one exists — phase two has real assertTrue / assertFalse material instead of comparing strings to strings.

function main(): void {
  test("undo is a no-op on a fresh history", () => {
    const doc = new TextDocument("hello");
    const history = new EditorHistory(doc);
    assertEqual(history.undo(), false, "undo result");
    assertEqual(doc.getText(), "hello", "document text");
    assertFalse(history.undo(), "still nothing to undo");
    assertDeepEqual({ text: doc.getText() }, { text: "hello" }, "snapshot");
  });

  test("insert then undo", () => {
    const doc = new TextDocument("hello");
    const history = new EditorHistory(doc);
    history.apply({ type: "insert", index: 5, text: " there" });
    assertEqual(doc.getText(), "hello there", "after insert");
    assertTrue(history.undo(), "undo after apply");
    assertDeepEqual({ text: doc.getText() }, { text: "hello" }, "after undo");
  });
}

main();

Two tests. A mix of all four helpers. That is a passing lap.

Note that each test builds its own doc and history. That is not style: the bench rebinds the helper names to count your assertions and replays main() to do it, so the helpers have to be function declarations rather than const arrows, and a fixture shared across tests will not survive the second run.

The three sets

Phase 1assertEqual, test, main. Two passing assertEqual calls.

Phase 2 — add assertTrue and assertFalse. undo() on a fresh history is false; undo() after apply is true.

Phase 3 — add assertDeepEqual. Snapshot the text. Do not invent a deep object the class does not have.

Notes

If you can type the four helpers without looking, the design problems stop spending their first five minutes on a console.log. That is the only score this extra keeps.