← All 41 libraries

CandyTesting

🍬 CandyTesting

Test harness for TEA programs

pioneering (no upstream) testing tea foundation-package

CandyTesting code coverage

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.

Install

composer require sugarcraft/candy-testing:@dev

Quickstart

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;

ProgramSimulator

for(Model|Program)Factory: wrap a Program, or a bare Model in a default Program, for deterministic testing.
send(Msg)Fluent message enqueue — chain multiple sends.
withFakeCmdRunner()Intercept commands instead of executing them.
run()Drain the queue, return TestResult with model/view/cmds/output. Subscription output is delivered right after the message that triggered it, none is dropped, and run() can be called again.

ScriptedInput

key() / namedKey() / enter() / arrow() / mouse() / resize() / ticks()Fluent builders for the messages a session produces; build() returns the list.
bytes(...$reads)Raw terminal input parsed by candy-core's real 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.
paste(content)A bracketed paste sent through bytes(), so the model sees the same PasteStartMsg, PasteEndMsg, PasteMsg sequence as at runtime (longer pastes arrive in several PasteMsg chunks).
withSanitizePaste(bool)Pasted payloads are sanitised by default, as at runtime; pass false to deliver them verbatim. Call it before bytes().

Assertions

assertGoldenAnsiSnapshot test — auto-creates golden on UPDATE_GOLDENS=1.
assertCellGridCell-by-cell diff for Buffer-based renderers.
assertAnsiEqualsByte-exact ANSI comparison with readable ESC diff.

TapeRecorder

to(path)Factory: create a tape recorder writing to the given path.
header()Write VHS header (theme, font size, width, height).
type() / enter() / sleep()Record keypresses and delays for demo playback.
save()Write the completed .tape file.

Use it for

Source & demos

Try the quickstart →

API

ClassMethodDescription
ProgramSimulatorfor(Model|Program): selfFactory: wrap a Program or a bare Model
ProgramSimulatorsend(Msg): selfFluent message enqueue
ProgramSimulatorwithFakeCmdRunner(callable): selfIntercept commands
ProgramSimulatorrun(): TestResultDrain queue, return result
ScriptedInputnew() / key() / namedKey() / enter() / paste(string) / bytes(string ...$reads) / withSanitizePaste(bool) / build()Scripted terminal input
TestResultmodel: objectFinal model after all messages
TestResultview: stringLast view() output
TestResultcmds: list<Closure>Captured commands
TestResultoutput: stringRaw accumulated ANSI bytes
AssertionsassertGoldenAnsi(string, string): voidGolden file snapshot assertion
AssertionsassertCellGrid(array, Buffer): voidCell-by-cell buffer comparison
AssertionsassertAnsiEquals(string, string): voidByte-exact ANSI diff
GoldenFileload(string): ?stringLoad golden file contents
GoldenFilesave(string, string): voidSave golden file
TapeRecorderto(string): selfFactory: create recorder
TapeRecorderheader(...): selfWrite VHS header
TapeRecordertype(string): selfRecord keypress
TapeRecorderenter(): selfRecord Enter key
TapeRecordersleep(float): selfRecord delay
TapeRecordersave(): voidWrite .tape file
ScriptedInputnew(): selfFactory: empty input builder
ScriptedInputkey(string): selfAppend character key
ScriptedInputenter(): selfAppend Enter key
ScriptedInputarrow(string): selfAppend arrow key
ScriptedInputbuild(): list<Msg>Return message sequence