100 lines
3.8 KiB
Zig
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);
|
|
}
|