@drmxrcy/tcg-core 0.0.0-202602060542
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +882 -0
- package/package.json +58 -0
- package/src/__tests__/alpha-clash-engine-definition.test.ts +319 -0
- package/src/__tests__/createMockAlphaClashGame.ts +462 -0
- package/src/__tests__/createMockGrandArchiveGame.ts +373 -0
- package/src/__tests__/createMockGundamGame.ts +379 -0
- package/src/__tests__/createMockLorcanaGame.ts +328 -0
- package/src/__tests__/createMockOnePieceGame.ts +429 -0
- package/src/__tests__/createMockRiftboundGame.ts +462 -0
- package/src/__tests__/grand-archive-engine-definition.test.ts +118 -0
- package/src/__tests__/gundam-engine-definition.test.ts +110 -0
- package/src/__tests__/integration-complete-game.test.ts +508 -0
- package/src/__tests__/integration-network-sync.test.ts +469 -0
- package/src/__tests__/lorcana-engine-definition.test.ts +100 -0
- package/src/__tests__/move-enumeration.test.ts +725 -0
- package/src/__tests__/multiplayer-engine.test.ts +555 -0
- package/src/__tests__/one-piece-engine-definition.test.ts +114 -0
- package/src/__tests__/riftbound-engine-definition.test.ts +124 -0
- package/src/actions/action-definition.test.ts +201 -0
- package/src/actions/action-definition.ts +122 -0
- package/src/actions/action-timing.test.ts +490 -0
- package/src/actions/action-timing.ts +257 -0
- package/src/cards/card-definition.test.ts +268 -0
- package/src/cards/card-definition.ts +27 -0
- package/src/cards/card-instance.test.ts +422 -0
- package/src/cards/card-instance.ts +49 -0
- package/src/cards/computed-properties.test.ts +530 -0
- package/src/cards/computed-properties.ts +84 -0
- package/src/cards/conditional-modifiers.test.ts +390 -0
- package/src/cards/modifiers.test.ts +286 -0
- package/src/cards/modifiers.ts +51 -0
- package/src/engine/MULTIPLAYER.md +425 -0
- package/src/engine/__tests__/rule-engine-flow.test.ts +348 -0
- package/src/engine/__tests__/rule-engine-history.test.ts +535 -0
- package/src/engine/__tests__/rule-engine-moves.test.ts +488 -0
- package/src/engine/__tests__/rule-engine.test.ts +366 -0
- package/src/engine/index.ts +14 -0
- package/src/engine/multiplayer-engine.example.ts +571 -0
- package/src/engine/multiplayer-engine.ts +409 -0
- package/src/engine/rule-engine.test.ts +286 -0
- package/src/engine/rule-engine.ts +1539 -0
- package/src/engine/tracker-system.ts +172 -0
- package/src/examples/__tests__/coin-flip-game.test.ts +641 -0
- package/src/filtering/card-filter.test.ts +230 -0
- package/src/filtering/card-filter.ts +91 -0
- package/src/filtering/card-query.test.ts +901 -0
- package/src/filtering/card-query.ts +273 -0
- package/src/filtering/filter-matching.test.ts +944 -0
- package/src/filtering/filter-matching.ts +315 -0
- package/src/flow/SERIALIZATION.md +428 -0
- package/src/flow/__tests__/flow-definition.test.ts +427 -0
- package/src/flow/__tests__/flow-manager.test.ts +756 -0
- package/src/flow/__tests__/flow-serialization.test.ts +565 -0
- package/src/flow/flow-definition.ts +453 -0
- package/src/flow/flow-manager.ts +1044 -0
- package/src/flow/index.ts +35 -0
- package/src/game-definition/__tests__/game-definition-validation.test.ts +359 -0
- package/src/game-definition/__tests__/game-definition.test.ts +291 -0
- package/src/game-definition/__tests__/move-definitions.test.ts +328 -0
- package/src/game-definition/game-definition.ts +261 -0
- package/src/game-definition/index.ts +28 -0
- package/src/game-definition/move-definitions.ts +188 -0
- package/src/game-definition/validation.ts +183 -0
- package/src/history/history-manager.test.ts +497 -0
- package/src/history/history-manager.ts +312 -0
- package/src/history/history-operations.ts +122 -0
- package/src/history/index.ts +9 -0
- package/src/history/types.ts +255 -0
- package/src/index.ts +32 -0
- package/src/logging/index.ts +27 -0
- package/src/logging/log-formatter.ts +187 -0
- package/src/logging/logger.ts +276 -0
- package/src/logging/types.ts +148 -0
- package/src/moves/create-move.test.ts +331 -0
- package/src/moves/create-move.ts +64 -0
- package/src/moves/move-enumeration.ts +228 -0
- package/src/moves/move-executor.test.ts +431 -0
- package/src/moves/move-executor.ts +195 -0
- package/src/moves/move-system.test.ts +380 -0
- package/src/moves/move-system.ts +463 -0
- package/src/moves/standard-moves.ts +231 -0
- package/src/operations/card-operations.test.ts +236 -0
- package/src/operations/card-operations.ts +116 -0
- package/src/operations/card-registry-impl.test.ts +251 -0
- package/src/operations/card-registry-impl.ts +70 -0
- package/src/operations/card-registry.test.ts +234 -0
- package/src/operations/card-registry.ts +106 -0
- package/src/operations/counter-operations.ts +152 -0
- package/src/operations/game-operations.test.ts +280 -0
- package/src/operations/game-operations.ts +140 -0
- package/src/operations/index.ts +24 -0
- package/src/operations/operations-impl.test.ts +354 -0
- package/src/operations/operations-impl.ts +468 -0
- package/src/operations/zone-operations.test.ts +295 -0
- package/src/operations/zone-operations.ts +223 -0
- package/src/rng/seeded-rng.test.ts +339 -0
- package/src/rng/seeded-rng.ts +123 -0
- package/src/targeting/index.ts +48 -0
- package/src/targeting/target-definition.test.ts +273 -0
- package/src/targeting/target-definition.ts +37 -0
- package/src/targeting/target-dsl.ts +279 -0
- package/src/targeting/target-resolver.ts +486 -0
- package/src/targeting/target-validation.test.ts +994 -0
- package/src/targeting/target-validation.ts +286 -0
- package/src/telemetry/events.ts +202 -0
- package/src/telemetry/index.ts +21 -0
- package/src/telemetry/telemetry-manager.ts +127 -0
- package/src/telemetry/types.ts +68 -0
- package/src/testing/__tests__/testing-utilities-integration.test.ts +161 -0
- package/src/testing/index.ts +88 -0
- package/src/testing/test-assertions.test.ts +341 -0
- package/src/testing/test-assertions.ts +256 -0
- package/src/testing/test-card-factory.test.ts +228 -0
- package/src/testing/test-card-factory.ts +111 -0
- package/src/testing/test-context-factory.ts +187 -0
- package/src/testing/test-end-assertions.test.ts +262 -0
- package/src/testing/test-end-assertions.ts +95 -0
- package/src/testing/test-engine-builder.test.ts +389 -0
- package/src/testing/test-engine-builder.ts +46 -0
- package/src/testing/test-flow-assertions.test.ts +284 -0
- package/src/testing/test-flow-assertions.ts +115 -0
- package/src/testing/test-player-builder.test.ts +132 -0
- package/src/testing/test-player-builder.ts +46 -0
- package/src/testing/test-replay-assertions.test.ts +356 -0
- package/src/testing/test-replay-assertions.ts +164 -0
- package/src/testing/test-rng-helpers.test.ts +260 -0
- package/src/testing/test-rng-helpers.ts +190 -0
- package/src/testing/test-state-builder.test.ts +373 -0
- package/src/testing/test-state-builder.ts +99 -0
- package/src/testing/test-zone-factory.test.ts +295 -0
- package/src/testing/test-zone-factory.ts +224 -0
- package/src/types/branded-utils.ts +54 -0
- package/src/types/branded.test.ts +175 -0
- package/src/types/branded.ts +33 -0
- package/src/types/index.ts +8 -0
- package/src/types/state.test.ts +198 -0
- package/src/types/state.ts +154 -0
- package/src/validation/card-type-guards.test.ts +242 -0
- package/src/validation/card-type-guards.ts +179 -0
- package/src/validation/index.ts +40 -0
- package/src/validation/schema-builders.test.ts +403 -0
- package/src/validation/schema-builders.ts +345 -0
- package/src/validation/type-guard-builder.test.ts +216 -0
- package/src/validation/type-guard-builder.ts +109 -0
- package/src/validation/validator-builder.test.ts +375 -0
- package/src/validation/validator-builder.ts +273 -0
- package/src/zones/index.ts +28 -0
- package/src/zones/zone-factory.test.ts +183 -0
- package/src/zones/zone-factory.ts +44 -0
- package/src/zones/zone-operations.test.ts +800 -0
- package/src/zones/zone-operations.ts +306 -0
- package/src/zones/zone-state-helpers.test.ts +337 -0
- package/src/zones/zone-state-helpers.ts +128 -0
- package/src/zones/zone-visibility.test.ts +156 -0
- package/src/zones/zone-visibility.ts +36 -0
- package/src/zones/zone.test.ts +186 -0
- package/src/zones/zone.ts +66 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { createTestState } from "./test-state-builder";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tests for createTestState - State builder for tests
|
|
6
|
+
*
|
|
7
|
+
* Task 2.1: Write tests for test builders (createTestState)
|
|
8
|
+
*
|
|
9
|
+
* Tests verify:
|
|
10
|
+
* - Creating state with default values
|
|
11
|
+
* - Creating state with partial overrides
|
|
12
|
+
* - Creating state with deep overrides
|
|
13
|
+
* - Type safety and inference
|
|
14
|
+
* - Immutability of defaults
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
type TestGameState = {
|
|
18
|
+
turn: number;
|
|
19
|
+
phase: "setup" | "play" | "end";
|
|
20
|
+
players: Array<{
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
health: number;
|
|
24
|
+
}>;
|
|
25
|
+
deck: string[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
describe("createTestState", () => {
|
|
29
|
+
describe("Basic Functionality", () => {
|
|
30
|
+
it("should create state with defaults", () => {
|
|
31
|
+
const defaults: TestGameState = {
|
|
32
|
+
turn: 1,
|
|
33
|
+
phase: "setup",
|
|
34
|
+
players: [],
|
|
35
|
+
deck: [],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const state = createTestState(defaults);
|
|
39
|
+
|
|
40
|
+
expect(state).toEqual(defaults);
|
|
41
|
+
expect(state.turn).toBe(1);
|
|
42
|
+
expect(state.phase).toBe("setup");
|
|
43
|
+
expect(state.players).toHaveLength(0);
|
|
44
|
+
expect(state.deck).toHaveLength(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should override specific fields", () => {
|
|
48
|
+
const defaults: TestGameState = {
|
|
49
|
+
turn: 1,
|
|
50
|
+
phase: "setup",
|
|
51
|
+
players: [],
|
|
52
|
+
deck: [],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const state = createTestState(defaults, {
|
|
56
|
+
turn: 5,
|
|
57
|
+
phase: "play",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(state.turn).toBe(5);
|
|
61
|
+
expect(state.phase).toBe("play");
|
|
62
|
+
expect(state.players).toHaveLength(0);
|
|
63
|
+
expect(state.deck).toHaveLength(0);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should override nested fields", () => {
|
|
67
|
+
const defaults: TestGameState = {
|
|
68
|
+
turn: 1,
|
|
69
|
+
phase: "setup",
|
|
70
|
+
players: [
|
|
71
|
+
{ id: "p1", name: "Player 1", health: 20 },
|
|
72
|
+
{ id: "p2", name: "Player 2", health: 20 },
|
|
73
|
+
],
|
|
74
|
+
deck: ["card1", "card2"],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const state = createTestState(defaults, {
|
|
78
|
+
players: [
|
|
79
|
+
{ id: "p1", name: "Alice", health: 15 },
|
|
80
|
+
{ id: "p2", name: "Bob", health: 18 },
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(state.players[0]?.name).toBe("Alice");
|
|
85
|
+
expect(state.players[0]?.health).toBe(15);
|
|
86
|
+
expect(state.players[1]?.name).toBe("Bob");
|
|
87
|
+
expect(state.players[1]?.health).toBe(18);
|
|
88
|
+
expect(state.turn).toBe(1);
|
|
89
|
+
expect(state.phase).toBe("setup");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should override array fields", () => {
|
|
93
|
+
const defaults: TestGameState = {
|
|
94
|
+
turn: 1,
|
|
95
|
+
phase: "setup",
|
|
96
|
+
players: [],
|
|
97
|
+
deck: ["card1", "card2"],
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const state = createTestState(defaults, {
|
|
101
|
+
deck: ["card3", "card4", "card5"],
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(state.deck).toEqual(["card3", "card4", "card5"]);
|
|
105
|
+
expect(state.turn).toBe(1);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("Immutability", () => {
|
|
110
|
+
it("should not modify defaults when creating state", () => {
|
|
111
|
+
const defaults: TestGameState = {
|
|
112
|
+
turn: 1,
|
|
113
|
+
phase: "setup",
|
|
114
|
+
players: [{ id: "p1", name: "Player 1", health: 20 }],
|
|
115
|
+
deck: ["card1"],
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
createTestState(defaults, {
|
|
119
|
+
turn: 2,
|
|
120
|
+
players: [{ id: "p1", name: "Modified", health: 10 }],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Defaults should remain unchanged
|
|
124
|
+
expect(defaults.turn).toBe(1);
|
|
125
|
+
expect(defaults.players[0]?.name).toBe("Player 1");
|
|
126
|
+
expect(defaults.players[0]?.health).toBe(20);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should not modify defaults when creating multiple states", () => {
|
|
130
|
+
const defaults: TestGameState = {
|
|
131
|
+
turn: 1,
|
|
132
|
+
phase: "setup",
|
|
133
|
+
players: [],
|
|
134
|
+
deck: [],
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const state1 = createTestState(defaults, { turn: 2 });
|
|
138
|
+
const state2 = createTestState(defaults, { turn: 3 });
|
|
139
|
+
|
|
140
|
+
expect(defaults.turn).toBe(1);
|
|
141
|
+
expect(state1.turn).toBe(2);
|
|
142
|
+
expect(state2.turn).toBe(3);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should create independent state copies", () => {
|
|
146
|
+
const defaults: TestGameState = {
|
|
147
|
+
turn: 1,
|
|
148
|
+
phase: "setup",
|
|
149
|
+
players: [{ id: "p1", name: "Player 1", health: 20 }],
|
|
150
|
+
deck: ["card1"],
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const state1 = createTestState(defaults);
|
|
154
|
+
const state2 = createTestState(defaults);
|
|
155
|
+
|
|
156
|
+
// Modify state1
|
|
157
|
+
state1.players[0]!.health = 10;
|
|
158
|
+
state1.deck.push("card2");
|
|
159
|
+
|
|
160
|
+
// state2 should be unaffected
|
|
161
|
+
expect(state2.players[0]?.health).toBe(20);
|
|
162
|
+
expect(state2.deck).toHaveLength(1);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("Type Safety", () => {
|
|
167
|
+
it("should infer correct type from defaults", () => {
|
|
168
|
+
type SimpleState = {
|
|
169
|
+
value: number;
|
|
170
|
+
label: string;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const defaults: SimpleState = {
|
|
174
|
+
value: 0,
|
|
175
|
+
label: "test",
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const state = createTestState(defaults, { value: 42 });
|
|
179
|
+
|
|
180
|
+
// TypeScript should infer state as SimpleState
|
|
181
|
+
expect(state.value).toBe(42);
|
|
182
|
+
expect(state.label).toBe("test");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should allow partial overrides", () => {
|
|
186
|
+
const defaults: TestGameState = {
|
|
187
|
+
turn: 1,
|
|
188
|
+
phase: "setup",
|
|
189
|
+
players: [],
|
|
190
|
+
deck: [],
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// Should accept partial overrides without type errors
|
|
194
|
+
const state1 = createTestState(defaults, { turn: 5 });
|
|
195
|
+
const state2 = createTestState(defaults, { phase: "play" });
|
|
196
|
+
const state3 = createTestState(defaults, {});
|
|
197
|
+
|
|
198
|
+
expect(state1.turn).toBe(5);
|
|
199
|
+
expect(state2.phase).toBe("play");
|
|
200
|
+
expect(state3).toEqual(defaults);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe("Edge Cases", () => {
|
|
205
|
+
it("should handle empty overrides", () => {
|
|
206
|
+
const defaults: TestGameState = {
|
|
207
|
+
turn: 1,
|
|
208
|
+
phase: "setup",
|
|
209
|
+
players: [],
|
|
210
|
+
deck: [],
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const state = createTestState(defaults, {});
|
|
214
|
+
|
|
215
|
+
expect(state).toEqual(defaults);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("should handle undefined overrides", () => {
|
|
219
|
+
const defaults: TestGameState = {
|
|
220
|
+
turn: 1,
|
|
221
|
+
phase: "setup",
|
|
222
|
+
players: [],
|
|
223
|
+
deck: [],
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const state = createTestState(defaults);
|
|
227
|
+
|
|
228
|
+
expect(state).toEqual(defaults);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("should handle complex nested structures", () => {
|
|
232
|
+
type ComplexState = {
|
|
233
|
+
meta: {
|
|
234
|
+
gameId: string;
|
|
235
|
+
created: number;
|
|
236
|
+
};
|
|
237
|
+
config: {
|
|
238
|
+
rules: {
|
|
239
|
+
maxPlayers: number;
|
|
240
|
+
turnLimit: number;
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const defaults: ComplexState = {
|
|
246
|
+
meta: {
|
|
247
|
+
gameId: "game-1",
|
|
248
|
+
created: 1000,
|
|
249
|
+
},
|
|
250
|
+
config: {
|
|
251
|
+
rules: {
|
|
252
|
+
maxPlayers: 4,
|
|
253
|
+
turnLimit: 100,
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const state = createTestState(defaults, {
|
|
259
|
+
config: {
|
|
260
|
+
rules: {
|
|
261
|
+
maxPlayers: 6,
|
|
262
|
+
turnLimit: 200,
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(state.config.rules.maxPlayers).toBe(6);
|
|
268
|
+
expect(state.config.rules.turnLimit).toBe(200);
|
|
269
|
+
expect(state.meta.gameId).toBe("game-1");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("should handle arrays of primitives", () => {
|
|
273
|
+
type ArrayState = {
|
|
274
|
+
numbers: number[];
|
|
275
|
+
strings: string[];
|
|
276
|
+
booleans: boolean[];
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const defaults: ArrayState = {
|
|
280
|
+
numbers: [1, 2, 3],
|
|
281
|
+
strings: ["a", "b"],
|
|
282
|
+
booleans: [true, false],
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const state = createTestState(defaults, {
|
|
286
|
+
numbers: [4, 5, 6, 7],
|
|
287
|
+
strings: ["x"],
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
expect(state.numbers).toEqual([4, 5, 6, 7]);
|
|
291
|
+
expect(state.strings).toEqual(["x"]);
|
|
292
|
+
expect(state.booleans).toEqual([true, false]);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("should handle null and undefined values", () => {
|
|
296
|
+
type NullableState = {
|
|
297
|
+
optional?: string;
|
|
298
|
+
nullable: string | null;
|
|
299
|
+
value: number;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const defaults: NullableState = {
|
|
303
|
+
optional: "default",
|
|
304
|
+
nullable: null,
|
|
305
|
+
value: 0,
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const state = createTestState(defaults, {
|
|
309
|
+
optional: undefined,
|
|
310
|
+
nullable: "not-null",
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
expect(state.optional).toBeUndefined();
|
|
314
|
+
expect(state.nullable).toBe("not-null");
|
|
315
|
+
expect(state.value).toBe(0);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
describe("Practical Test Scenarios", () => {
|
|
320
|
+
it("should simplify test setup for game states", () => {
|
|
321
|
+
const defaults: TestGameState = {
|
|
322
|
+
turn: 1,
|
|
323
|
+
phase: "setup",
|
|
324
|
+
players: [
|
|
325
|
+
{ id: "p1", name: "Player 1", health: 20 },
|
|
326
|
+
{ id: "p2", name: "Player 2", health: 20 },
|
|
327
|
+
],
|
|
328
|
+
deck: Array.from({ length: 40 }, (_, i) => `card${i}`),
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// Test scenario: mid-game state
|
|
332
|
+
const midGameState = createTestState(defaults, {
|
|
333
|
+
turn: 5,
|
|
334
|
+
phase: "play",
|
|
335
|
+
deck: defaults.deck.slice(10),
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
expect(midGameState.turn).toBe(5);
|
|
339
|
+
expect(midGameState.phase).toBe("play");
|
|
340
|
+
expect(midGameState.deck).toHaveLength(30);
|
|
341
|
+
expect(midGameState.players).toHaveLength(2);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("should support testing edge cases with minimal setup", () => {
|
|
345
|
+
const defaults: TestGameState = {
|
|
346
|
+
turn: 1,
|
|
347
|
+
phase: "setup",
|
|
348
|
+
players: [
|
|
349
|
+
{ id: "p1", name: "Player 1", health: 20 },
|
|
350
|
+
{ id: "p2", name: "Player 2", health: 20 },
|
|
351
|
+
],
|
|
352
|
+
deck: ["card1", "card2"],
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// Test scenario: player at low health
|
|
356
|
+
const lowHealthState = createTestState(defaults, {
|
|
357
|
+
players: [
|
|
358
|
+
{ id: "p1", name: "Player 1", health: 1 },
|
|
359
|
+
{ id: "p2", name: "Player 2", health: 20 },
|
|
360
|
+
],
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
expect(lowHealthState.players[0]?.health).toBe(1);
|
|
364
|
+
|
|
365
|
+
// Test scenario: empty deck
|
|
366
|
+
const emptyDeckState = createTestState(defaults, {
|
|
367
|
+
deck: [],
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
expect(emptyDeckState.deck).toHaveLength(0);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test State Builder
|
|
3
|
+
*
|
|
4
|
+
* Task 2.5: Implement createTestState<T>(overrides?)
|
|
5
|
+
*
|
|
6
|
+
* Creates test state objects with defaults and selective overrides.
|
|
7
|
+
* Simplifies test setup by allowing partial state specifications.
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Deep merge of defaults and overrides
|
|
11
|
+
* - Type-safe: overrides must match state structure
|
|
12
|
+
* - Immutable: doesn't modify defaults
|
|
13
|
+
* - Supports nested objects and arrays
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const defaults = {
|
|
18
|
+
* turn: 1,
|
|
19
|
+
* phase: 'setup',
|
|
20
|
+
* players: [
|
|
21
|
+
* { id: 'p1', health: 20 }
|
|
22
|
+
* ]
|
|
23
|
+
* };
|
|
24
|
+
*
|
|
25
|
+
* // Use defaults
|
|
26
|
+
* const state = createTestState(defaults);
|
|
27
|
+
*
|
|
28
|
+
* // Override specific fields
|
|
29
|
+
* const midGameState = createTestState(defaults, {
|
|
30
|
+
* turn: 5,
|
|
31
|
+
* phase: 'play'
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // Override nested fields
|
|
35
|
+
* const lowHealthState = createTestState(defaults, {
|
|
36
|
+
* players: [{ id: 'p1', health: 1 }]
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function createTestState<T>(defaults: T, overrides?: Partial<T>): T {
|
|
41
|
+
// Use structuredClone for deep cloning to ensure immutability
|
|
42
|
+
// This preserves complex types (Date, Map, Set, etc.) better than JSON
|
|
43
|
+
const clonedDefaults = structuredClone(defaults);
|
|
44
|
+
|
|
45
|
+
if (!overrides) {
|
|
46
|
+
return clonedDefaults;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Deep merge overrides into cloned defaults
|
|
50
|
+
return deepMerge(clonedDefaults, overrides);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Deep merge utility
|
|
55
|
+
*
|
|
56
|
+
* Recursively merges source into target.
|
|
57
|
+
* Arrays are replaced entirely, not merged element-wise.
|
|
58
|
+
*
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
function deepMerge<T>(target: T, source: Partial<T>): T {
|
|
62
|
+
const result = { ...target };
|
|
63
|
+
|
|
64
|
+
for (const key in source) {
|
|
65
|
+
if (Object.hasOwn(source, key)) {
|
|
66
|
+
const sourceValue = source[key];
|
|
67
|
+
const targetValue = target[key];
|
|
68
|
+
|
|
69
|
+
if (sourceValue === undefined) {
|
|
70
|
+
// Allow explicit undefined to override
|
|
71
|
+
// biome-ignore lint/suspicious/noExplicitAny: Safe type assertion for deep merge
|
|
72
|
+
result[key] = sourceValue as any;
|
|
73
|
+
} else if (
|
|
74
|
+
isObject(sourceValue) &&
|
|
75
|
+
isObject(targetValue) &&
|
|
76
|
+
!Array.isArray(sourceValue)
|
|
77
|
+
) {
|
|
78
|
+
// Recursively merge objects (but not arrays)
|
|
79
|
+
// biome-ignore lint/suspicious/noExplicitAny: Safe type assertion for deep merge
|
|
80
|
+
result[key] = deepMerge(targetValue, sourceValue as any) as any;
|
|
81
|
+
} else {
|
|
82
|
+
// Replace primitives, arrays, null, etc.
|
|
83
|
+
// biome-ignore lint/suspicious/noExplicitAny: Safe type assertion for deep merge
|
|
84
|
+
result[key] = sourceValue as any;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Type guard for objects
|
|
94
|
+
*
|
|
95
|
+
* @internal
|
|
96
|
+
*/
|
|
97
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
98
|
+
return typeof value === "object" && value !== null;
|
|
99
|
+
}
|