Files
2026-07-13 12:29:49 +08:00

100 lines
3.8 KiB
Zig

//! Behavioral harness for ts-countdown-bugfix. The grader copies this next to
//! the transpiled core.zig and the rt kernel, then runs `zig test harness.zig`.
//! Asserts the intended countdown behavior from the prompt — each test
//! isolates one of the planted drifts.
const std = @import("std");
const core = @import("core.zig");
const rt = core.rt;
fn freshModel() *const core.Model {
rt.resetAll();
const committed = core.commitModelRoot(core.initialModel());
rt.frameReset();
return committed;
}
fn dispatch(model: *const core.Model, msg: core.Msg) *const core.Model {
const next = core.update(model, msg);
const committed = core.commitModelRoot(next);
rt.frameReset();
return committed;
}
test "ticks only count down while running" {
var model = freshModel();
const initial = model.remainingSeconds;
model = dispatch(model, .tick);
try std.testing.expect(model.remainingSeconds == initial);
model = dispatch(model, .start);
try std.testing.expect(model.running);
model = dispatch(model, .tick);
try std.testing.expect(model.remainingSeconds == initial - 1);
model = dispatch(model, .pause);
try std.testing.expect(!model.running);
model = dispatch(model, .tick);
try std.testing.expect(model.remainingSeconds == initial - 1);
}
test "completion fires exactly once, stops the timer, floors at zero" {
var model = freshModel();
model = dispatch(model, .{ .set_duration = 2 });
model = dispatch(model, .start);
model = dispatch(model, .tick);
try std.testing.expect(model.remainingSeconds == 1);
try std.testing.expect(model.completedCount == 0);
model = dispatch(model, .tick);
try std.testing.expect(model.remainingSeconds == 0);
try std.testing.expect(model.completedCount == 1);
try std.testing.expect(!model.running);
// Finished sessions do not keep counting or re-completing.
model = dispatch(model, .tick);
model = dispatch(model, .tick);
try std.testing.expect(model.remainingSeconds == 0);
try std.testing.expect(model.completedCount == 1);
// start alone does not restart a finished session.
model = dispatch(model, .start);
try std.testing.expect(!model.running);
}
test "reset restores the configured duration and keeps the completed count" {
var model = freshModel();
model = dispatch(model, .{ .set_duration = 90 });
model = dispatch(model, .start);
model = dispatch(model, .tick);
model = dispatch(model, .tick);
try std.testing.expect(model.remainingSeconds == 88);
model = dispatch(model, .reset);
try std.testing.expect(model.remainingSeconds == 90);
try std.testing.expect(!model.running);
// Run a 1-second session to completion, then reset: count survives.
model = dispatch(model, .{ .set_duration = 1 });
model = dispatch(model, .start);
model = dispatch(model, .tick);
try std.testing.expect(model.completedCount == 1);
model = dispatch(model, .reset);
try std.testing.expect(model.completedCount == 1);
try std.testing.expect(model.remainingSeconds == 1);
}
test "set_duration is guarded: ignored while running and for non-positive values" {
var model = freshModel();
model = dispatch(model, .{ .set_duration = 60 });
try std.testing.expect(model.durationSeconds == 60);
try std.testing.expect(model.remainingSeconds == 60);
model = dispatch(model, .start);
model = dispatch(model, .{ .set_duration = 10 });
try std.testing.expect(model.durationSeconds == 60);
try std.testing.expect(model.remainingSeconds == 60);
model = dispatch(model, .pause);
model = dispatch(model, .{ .set_duration = 0 });
try std.testing.expect(model.durationSeconds == 60);
model = dispatch(model, .{ .set_duration = -5 });
try std.testing.expect(model.durationSeconds == 60);
}