Test harness for TEA programs
The test harness SugarCraft pioneers that bubble-tea issue #1654 never shipped: ProgramSimulator for driving TEA programs with scripted input, Assertions for golden-file and cell-grid snapshots, and TapeRecorder for VHS demo capture.
composer require sugarcraft/candy-testing:@dev
use SugarCraft\Testing\ProgramSimulator;
use SugarCraft\Testing\Input\ScriptedInput;
use SugarCraft\Testing\Snapshot\Assertions;
// Script terminal input: keys, plus a bracketed paste decoded by candy-core's
// real InputReader (sanitised by default, as at runtime).
$script = ScriptedInput::new()
->key('+')
->key('+')
->paste("hello\n")
->enter();
$sim = ProgramSimulator::for(new Counter()); // a Model or a Program
foreach ($script->build() as $msg) {
$sim->send($msg);
}
$result = $sim->run(); // TestResult: model, view, cmds, output
Assertions::assertGoldenAnsi(__DIR__ . '/fixtures/counter.golden', $result->view);
echo "Final count: " . $result->model->count;
build() returns the list.InputReader, one argument per read (split into 4096-byte reads like the runtime), with the runtime's idle recovery afterwards: a lone ESC becomes an Escape key and an unterminated paste is closed.bytes(), so the model sees the same PasteStartMsg, PasteEndMsg, PasteMsg sequence as at runtime (longer pastes arrive in several PasteMsg chunks).false to deliver them verbatim. Call it before bytes().| Class | Method | Description |
|---|---|---|
| ProgramSimulator | for(Model|Program): self | Factory: wrap a Program or a bare Model |
| ProgramSimulator | send(Msg): self | Fluent message enqueue |
| ProgramSimulator | withFakeCmdRunner(callable): self | Intercept commands |
| ProgramSimulator | run(): TestResult | Drain queue, return result |
| ScriptedInput | new() / key() / namedKey() / enter() / paste(string) / bytes(string ...$reads) / withSanitizePaste(bool) / build() | Scripted terminal input |
| TestResult | model: object | Final model after all messages |
| TestResult | view: string | Last view() output |
| TestResult | cmds: list<Closure> | Captured commands |
| TestResult | output: string | Raw accumulated ANSI bytes |
| Assertions | assertGoldenAnsi(string, string): void | Golden file snapshot assertion |
| Assertions | assertCellGrid(array, Buffer): void | Cell-by-cell buffer comparison |
| Assertions | assertAnsiEquals(string, string): void | Byte-exact ANSI diff |
| GoldenFile | load(string): ?string | Load golden file contents |
| GoldenFile | save(string, string): void | Save golden file |
| TapeRecorder | to(string): self | Factory: create recorder |
| TapeRecorder | header(...): self | Write VHS header |
| TapeRecorder | type(string): self | Record keypress |
| TapeRecorder | enter(): self | Record Enter key |
| TapeRecorder | sleep(float): self | Record delay |
| TapeRecorder | save(): void | Write .tape file |
| ScriptedInput | new(): self | Factory: empty input builder |
| ScriptedInput | key(string): self | Append character key |
| ScriptedInput | enter(): self | Append Enter key |
| ScriptedInput | arrow(string): self | Append arrow key |
| ScriptedInput | build(): list<Msg> | Return message sequence |