// The SpellGrid command line, played from a script (the reference's own check; not a course test). import { readFileSync } from "node:fs"; import { expect, test } from "vitest"; import { run } from "../src/cli"; function play(level: string, input: string[]): { outcome: string; out: string } { let out = ""; const outcome = run(readFileSync(`levels/${level}.json`, "utf8"), () => input.shift(), (s) => (out += s)); return { outcome, out }; } test("cli_wins_level_1", () => { const { outcome, out } = play("l1-double-tap", ["w", "w", "c Whirlwind", "s", "s", "a", "s", "s", "d", "d"]); expect(outcome).toBe("won"); expect(out).toContain("DEA IS DOU"); expect(out).toContain("You win!"); }); test("cli_wins_level_2_with_choices_and_play", () => { const input = ["c Graft", "Ember Moth", "Rune Scribe", "bottom", "c Ember Moth (Assembled) 4 3", "a", "a"]; const { outcome, out } = play("l2-spliced-corpse", input); expect(outcome).toBe("won"); expect(out).toContain("Choose the base"); expect(out).toContain("Hand: Ember Moth (Assembled)"); }); test("cli_prints_the_log_tree_of_each_command", () => { const { out } = play("l1-double-tap", ["w", "w", "c Whirlwind", "q"]); // The moves run no Sequence, so only the cast prints a log: its tree, Deathrattles nested. expect(out.split("Log:\n")).toHaveLength(2); expect(out).toContain( [ "Log:", 'SpellCast{card: "Whirlwind"}', "Damaged{target: 32, amount: 1}", "Damaged{target: 33, amount: 1}", "Destroyed{id: 32}", "DeathPhase{round: 1}", 'Triggered{source: 32, trigger: "deathrattle"}', " Damaged{target: 33, amount: 1}", 'Triggered{source: 32, trigger: "deathrattle"}', " Damaged{target: 33, amount: 1}", "Destroyed{id: 33}", "DeathPhase{round: 2}", "", ].join("\n"), ); }); test("cli_reports_errors_and_quits", () => { const { outcome, out } = play("l3-simultaneous-paradox", ["c Spark", "x", "q"]); expect(outcome).toBe("quit"); expect(out).toContain('"Spark" is not in the hand'); expect(out).toContain("Commands:"); }); // The two reference CLIs print the same text for the same input (tools/diff-tracks.mjs checks it). test("cli_prints_card_names_as_typed", () => { const { out } = play("l3-simultaneous-paradox", ['c a"b', "q"]); expect(out).toContain('"a"b" is not in the hand'); }); test("cli_numbers_are_plain_digits", () => { // "-1" is not a coordinate, so it is part of the card's name. const cast = play("l1-double-tap", ["c Whirlwind -1 0", "q"]); expect(cast.out).toContain('"Whirlwind -1 0" is not in the hand'); // "+2" is not a number, so the chooser takes the first option. const graft = play("l2-spliced-corpse", ["c Graft", "+2", "1", "1", "q"]); expect(graft.out).toContain("Hand: Clay Golem (Assembled)"); });