Skip to content

Commit cf7996a

Browse files
committed
test: add unit tests for StateMachine behaviors
1 parent 0039c5d commit cf7996a

1 file changed

Lines changed: 152 additions & 0 deletions

File tree

tests/tech/state-machine.test.ts

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { StateMachine } from "../../src/player/StateMachine.ts";
3+
4+
const DT = 1 / 60;
5+
6+
describe("StateMachine", () => {
7+
test("locked blocks cross-state transitions, but forceState can restart the current state", () => {
8+
const log: string[] = [];
9+
const machine = new StateMachine<"boot" | "idle" | "dash">("boot");
10+
11+
machine.setCallbacks(
12+
"idle",
13+
undefined,
14+
undefined,
15+
() => log.push("idle begin"),
16+
() => log.push("idle end"),
17+
);
18+
machine.setCallbacks(
19+
"dash",
20+
undefined,
21+
undefined,
22+
() => log.push("dash begin"),
23+
() => log.push("dash end"),
24+
);
25+
26+
machine.state = "idle";
27+
expect(machine.state).toBe("idle");
28+
expect(log).toEqual(["idle begin"]);
29+
30+
log.length = 0;
31+
machine.locked = true;
32+
33+
machine.state = "dash";
34+
expect(machine.state).toBe("idle");
35+
expect(log).toEqual([]);
36+
37+
machine.forceState("dash");
38+
expect(machine.state).toBe("idle");
39+
expect(log).toEqual([]);
40+
41+
machine.forceState("idle");
42+
expect(machine.state).toBe("idle");
43+
expect(machine.previousState).toBe("idle");
44+
expect(log).toEqual(["idle end", "idle begin"]);
45+
});
46+
47+
test("forceState on the same state reruns end and begin and restarts the coroutine", () => {
48+
const log: string[] = [];
49+
let runs = 0;
50+
const machine = new StateMachine<"boot" | "dash">("boot");
51+
52+
machine.setCallbacks(
53+
"dash",
54+
undefined,
55+
function* () {
56+
const run = ++runs;
57+
log.push(`coroutine start ${run}`);
58+
yield null;
59+
log.push(`coroutine resume ${run}`);
60+
},
61+
() => log.push("dash begin"),
62+
() => log.push("dash end"),
63+
);
64+
65+
machine.state = "dash";
66+
expect(log).toEqual(["dash begin"]);
67+
68+
log.length = 0;
69+
machine.update(DT);
70+
expect(log).toEqual(["coroutine start 1"]);
71+
72+
machine.forceState("dash");
73+
expect(log).toEqual(["coroutine start 1", "dash end", "dash begin"]);
74+
75+
machine.update(DT);
76+
machine.update(DT);
77+
78+
expect(log).toEqual([
79+
"coroutine start 1",
80+
"dash end",
81+
"dash begin",
82+
"coroutine start 2",
83+
"coroutine resume 2",
84+
]);
85+
expect(log.includes("coroutine resume 1")).toBeFalse();
86+
});
87+
88+
test("same-state forceState can restart the current state even while locked", () => {
89+
const log: string[] = [];
90+
const machine = new StateMachine<"boot" | "idle">("boot");
91+
92+
machine.setCallbacks(
93+
"idle",
94+
undefined,
95+
undefined,
96+
() => log.push("idle begin"),
97+
() => log.push("idle end"),
98+
);
99+
100+
machine.state = "idle";
101+
log.length = 0;
102+
machine.locked = true;
103+
104+
machine.forceState("idle");
105+
106+
expect(machine.state).toBe("idle");
107+
expect(log).toEqual(["idle end", "idle begin"]);
108+
});
109+
110+
test("nested coroutine timing advances one layer per update and honors numeric waits", () => {
111+
const log: string[] = [];
112+
const machine = new StateMachine<"boot" | "combo">("boot");
113+
114+
function* inner(): Generator<number | null, void, unknown> {
115+
log.push("inner start");
116+
yield null;
117+
log.push("inner end");
118+
}
119+
120+
function* outer(): Generator<number | null | Generator<number | null, void, unknown>, void, unknown> {
121+
log.push("outer start");
122+
yield inner();
123+
log.push("outer after inner");
124+
yield 0.05;
125+
log.push("outer done");
126+
}
127+
128+
machine.setCallbacks("combo", undefined, outer);
129+
machine.state = "combo";
130+
131+
machine.update(DT);
132+
expect(log).toEqual(["outer start"]);
133+
134+
machine.update(DT);
135+
expect(log).toEqual(["outer start", "inner start"]);
136+
137+
machine.update(DT);
138+
expect(log).toEqual(["outer start", "inner start", "inner end"]);
139+
140+
machine.update(DT);
141+
expect(log).toEqual(["outer start", "inner start", "inner end", "outer after inner"]);
142+
143+
machine.update(0.03);
144+
expect(log).toEqual(["outer start", "inner start", "inner end", "outer after inner"]);
145+
146+
machine.update(0.03);
147+
expect(log).toEqual(["outer start", "inner start", "inner end", "outer after inner"]);
148+
149+
machine.update(DT);
150+
expect(log).toEqual(["outer start", "inner start", "inner end", "outer after inner", "outer done"]);
151+
});
152+
});

0 commit comments

Comments
 (0)