@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.
Files changed (157) hide show
  1. package/README.md +882 -0
  2. package/package.json +58 -0
  3. package/src/__tests__/alpha-clash-engine-definition.test.ts +319 -0
  4. package/src/__tests__/createMockAlphaClashGame.ts +462 -0
  5. package/src/__tests__/createMockGrandArchiveGame.ts +373 -0
  6. package/src/__tests__/createMockGundamGame.ts +379 -0
  7. package/src/__tests__/createMockLorcanaGame.ts +328 -0
  8. package/src/__tests__/createMockOnePieceGame.ts +429 -0
  9. package/src/__tests__/createMockRiftboundGame.ts +462 -0
  10. package/src/__tests__/grand-archive-engine-definition.test.ts +118 -0
  11. package/src/__tests__/gundam-engine-definition.test.ts +110 -0
  12. package/src/__tests__/integration-complete-game.test.ts +508 -0
  13. package/src/__tests__/integration-network-sync.test.ts +469 -0
  14. package/src/__tests__/lorcana-engine-definition.test.ts +100 -0
  15. package/src/__tests__/move-enumeration.test.ts +725 -0
  16. package/src/__tests__/multiplayer-engine.test.ts +555 -0
  17. package/src/__tests__/one-piece-engine-definition.test.ts +114 -0
  18. package/src/__tests__/riftbound-engine-definition.test.ts +124 -0
  19. package/src/actions/action-definition.test.ts +201 -0
  20. package/src/actions/action-definition.ts +122 -0
  21. package/src/actions/action-timing.test.ts +490 -0
  22. package/src/actions/action-timing.ts +257 -0
  23. package/src/cards/card-definition.test.ts +268 -0
  24. package/src/cards/card-definition.ts +27 -0
  25. package/src/cards/card-instance.test.ts +422 -0
  26. package/src/cards/card-instance.ts +49 -0
  27. package/src/cards/computed-properties.test.ts +530 -0
  28. package/src/cards/computed-properties.ts +84 -0
  29. package/src/cards/conditional-modifiers.test.ts +390 -0
  30. package/src/cards/modifiers.test.ts +286 -0
  31. package/src/cards/modifiers.ts +51 -0
  32. package/src/engine/MULTIPLAYER.md +425 -0
  33. package/src/engine/__tests__/rule-engine-flow.test.ts +348 -0
  34. package/src/engine/__tests__/rule-engine-history.test.ts +535 -0
  35. package/src/engine/__tests__/rule-engine-moves.test.ts +488 -0
  36. package/src/engine/__tests__/rule-engine.test.ts +366 -0
  37. package/src/engine/index.ts +14 -0
  38. package/src/engine/multiplayer-engine.example.ts +571 -0
  39. package/src/engine/multiplayer-engine.ts +409 -0
  40. package/src/engine/rule-engine.test.ts +286 -0
  41. package/src/engine/rule-engine.ts +1539 -0
  42. package/src/engine/tracker-system.ts +172 -0
  43. package/src/examples/__tests__/coin-flip-game.test.ts +641 -0
  44. package/src/filtering/card-filter.test.ts +230 -0
  45. package/src/filtering/card-filter.ts +91 -0
  46. package/src/filtering/card-query.test.ts +901 -0
  47. package/src/filtering/card-query.ts +273 -0
  48. package/src/filtering/filter-matching.test.ts +944 -0
  49. package/src/filtering/filter-matching.ts +315 -0
  50. package/src/flow/SERIALIZATION.md +428 -0
  51. package/src/flow/__tests__/flow-definition.test.ts +427 -0
  52. package/src/flow/__tests__/flow-manager.test.ts +756 -0
  53. package/src/flow/__tests__/flow-serialization.test.ts +565 -0
  54. package/src/flow/flow-definition.ts +453 -0
  55. package/src/flow/flow-manager.ts +1044 -0
  56. package/src/flow/index.ts +35 -0
  57. package/src/game-definition/__tests__/game-definition-validation.test.ts +359 -0
  58. package/src/game-definition/__tests__/game-definition.test.ts +291 -0
  59. package/src/game-definition/__tests__/move-definitions.test.ts +328 -0
  60. package/src/game-definition/game-definition.ts +261 -0
  61. package/src/game-definition/index.ts +28 -0
  62. package/src/game-definition/move-definitions.ts +188 -0
  63. package/src/game-definition/validation.ts +183 -0
  64. package/src/history/history-manager.test.ts +497 -0
  65. package/src/history/history-manager.ts +312 -0
  66. package/src/history/history-operations.ts +122 -0
  67. package/src/history/index.ts +9 -0
  68. package/src/history/types.ts +255 -0
  69. package/src/index.ts +32 -0
  70. package/src/logging/index.ts +27 -0
  71. package/src/logging/log-formatter.ts +187 -0
  72. package/src/logging/logger.ts +276 -0
  73. package/src/logging/types.ts +148 -0
  74. package/src/moves/create-move.test.ts +331 -0
  75. package/src/moves/create-move.ts +64 -0
  76. package/src/moves/move-enumeration.ts +228 -0
  77. package/src/moves/move-executor.test.ts +431 -0
  78. package/src/moves/move-executor.ts +195 -0
  79. package/src/moves/move-system.test.ts +380 -0
  80. package/src/moves/move-system.ts +463 -0
  81. package/src/moves/standard-moves.ts +231 -0
  82. package/src/operations/card-operations.test.ts +236 -0
  83. package/src/operations/card-operations.ts +116 -0
  84. package/src/operations/card-registry-impl.test.ts +251 -0
  85. package/src/operations/card-registry-impl.ts +70 -0
  86. package/src/operations/card-registry.test.ts +234 -0
  87. package/src/operations/card-registry.ts +106 -0
  88. package/src/operations/counter-operations.ts +152 -0
  89. package/src/operations/game-operations.test.ts +280 -0
  90. package/src/operations/game-operations.ts +140 -0
  91. package/src/operations/index.ts +24 -0
  92. package/src/operations/operations-impl.test.ts +354 -0
  93. package/src/operations/operations-impl.ts +468 -0
  94. package/src/operations/zone-operations.test.ts +295 -0
  95. package/src/operations/zone-operations.ts +223 -0
  96. package/src/rng/seeded-rng.test.ts +339 -0
  97. package/src/rng/seeded-rng.ts +123 -0
  98. package/src/targeting/index.ts +48 -0
  99. package/src/targeting/target-definition.test.ts +273 -0
  100. package/src/targeting/target-definition.ts +37 -0
  101. package/src/targeting/target-dsl.ts +279 -0
  102. package/src/targeting/target-resolver.ts +486 -0
  103. package/src/targeting/target-validation.test.ts +994 -0
  104. package/src/targeting/target-validation.ts +286 -0
  105. package/src/telemetry/events.ts +202 -0
  106. package/src/telemetry/index.ts +21 -0
  107. package/src/telemetry/telemetry-manager.ts +127 -0
  108. package/src/telemetry/types.ts +68 -0
  109. package/src/testing/__tests__/testing-utilities-integration.test.ts +161 -0
  110. package/src/testing/index.ts +88 -0
  111. package/src/testing/test-assertions.test.ts +341 -0
  112. package/src/testing/test-assertions.ts +256 -0
  113. package/src/testing/test-card-factory.test.ts +228 -0
  114. package/src/testing/test-card-factory.ts +111 -0
  115. package/src/testing/test-context-factory.ts +187 -0
  116. package/src/testing/test-end-assertions.test.ts +262 -0
  117. package/src/testing/test-end-assertions.ts +95 -0
  118. package/src/testing/test-engine-builder.test.ts +389 -0
  119. package/src/testing/test-engine-builder.ts +46 -0
  120. package/src/testing/test-flow-assertions.test.ts +284 -0
  121. package/src/testing/test-flow-assertions.ts +115 -0
  122. package/src/testing/test-player-builder.test.ts +132 -0
  123. package/src/testing/test-player-builder.ts +46 -0
  124. package/src/testing/test-replay-assertions.test.ts +356 -0
  125. package/src/testing/test-replay-assertions.ts +164 -0
  126. package/src/testing/test-rng-helpers.test.ts +260 -0
  127. package/src/testing/test-rng-helpers.ts +190 -0
  128. package/src/testing/test-state-builder.test.ts +373 -0
  129. package/src/testing/test-state-builder.ts +99 -0
  130. package/src/testing/test-zone-factory.test.ts +295 -0
  131. package/src/testing/test-zone-factory.ts +224 -0
  132. package/src/types/branded-utils.ts +54 -0
  133. package/src/types/branded.test.ts +175 -0
  134. package/src/types/branded.ts +33 -0
  135. package/src/types/index.ts +8 -0
  136. package/src/types/state.test.ts +198 -0
  137. package/src/types/state.ts +154 -0
  138. package/src/validation/card-type-guards.test.ts +242 -0
  139. package/src/validation/card-type-guards.ts +179 -0
  140. package/src/validation/index.ts +40 -0
  141. package/src/validation/schema-builders.test.ts +403 -0
  142. package/src/validation/schema-builders.ts +345 -0
  143. package/src/validation/type-guard-builder.test.ts +216 -0
  144. package/src/validation/type-guard-builder.ts +109 -0
  145. package/src/validation/validator-builder.test.ts +375 -0
  146. package/src/validation/validator-builder.ts +273 -0
  147. package/src/zones/index.ts +28 -0
  148. package/src/zones/zone-factory.test.ts +183 -0
  149. package/src/zones/zone-factory.ts +44 -0
  150. package/src/zones/zone-operations.test.ts +800 -0
  151. package/src/zones/zone-operations.ts +306 -0
  152. package/src/zones/zone-state-helpers.test.ts +337 -0
  153. package/src/zones/zone-state-helpers.ts +128 -0
  154. package/src/zones/zone-visibility.test.ts +156 -0
  155. package/src/zones/zone-visibility.ts +36 -0
  156. package/src/zones/zone.test.ts +186 -0
  157. package/src/zones/zone.ts +66 -0
@@ -0,0 +1,161 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { RuleEngine } from "../../engine/rule-engine";
3
+ import type { GameDefinition } from "../../game-definition/game-definition";
4
+ import type { GameMoveDefinitions } from "../../game-definition/move-definitions";
5
+ import { createPlayerId, type PlayerId } from "../../types";
6
+ import {
7
+ createTestCards,
8
+ createTestDeck,
9
+ createTestHand,
10
+ expectDeterministicReplay,
11
+ expectGameEnd,
12
+ expectGameNotEnded,
13
+ expectMoveFailure,
14
+ expectMoveSuccess,
15
+ expectPhaseTransition,
16
+ expectStateProperty,
17
+ withSeed,
18
+ } from "../index";
19
+
20
+ /**
21
+ * Integration test demonstrating @drmxrcy/tcg-core/testing utilities
22
+ */
23
+
24
+ type TestGameState = {
25
+ players: Array<{
26
+ id: PlayerId;
27
+ name: string;
28
+ health: number;
29
+ hand: string[];
30
+ }>;
31
+ phase: "draw" | "main";
32
+ winner?: PlayerId;
33
+ };
34
+
35
+ type TestGameMoves = {
36
+ drawCard: Record<string, never>;
37
+ attack: Record<string, never>;
38
+ endPhase: Record<string, never>;
39
+ };
40
+
41
+ describe("Testing Utilities Integration", () => {
42
+ it("demonstrates testing utilities workflow", () => {
43
+ // 1. Test factories
44
+ const cards = createTestCards(5);
45
+ expect(cards.length).toBe(5);
46
+
47
+ const deck = createTestDeck([], createPlayerId("p1"));
48
+ expect(deck.config.visibility).toBe("secret");
49
+
50
+ const hand = createTestHand([], createPlayerId("p1"));
51
+ expect(hand.config.visibility).toBe("private");
52
+
53
+ // 2. Test RNG
54
+ const shuffled1 = withSeed("test", (rng) => rng.shuffle([1, 2, 3]));
55
+ const shuffled2 = withSeed("test", (rng) => rng.shuffle([1, 2, 3]));
56
+ expect(shuffled1).toEqual(shuffled2);
57
+
58
+ // 3. Test game
59
+ const moves: GameMoveDefinitions<TestGameState, TestGameMoves> = {
60
+ drawCard: {
61
+ condition: (state) => state.phase === "draw",
62
+ reducer: (draft) => {
63
+ const player = draft.players[0];
64
+ if (player) {
65
+ player.hand.push("card");
66
+ }
67
+ },
68
+ },
69
+ attack: {
70
+ condition: (state) => state.phase === "main",
71
+ reducer: (draft) => {
72
+ const target = draft.players[1];
73
+ if (target) {
74
+ target.health -= 1;
75
+ if (target.health <= 0) {
76
+ draft.winner = draft.players[0]?.id;
77
+ }
78
+ }
79
+ },
80
+ },
81
+ endPhase: {
82
+ reducer: (draft) => {
83
+ draft.phase = draft.phase === "draw" ? "main" : "draw";
84
+ },
85
+ },
86
+ };
87
+
88
+ const gameDefinition: GameDefinition<TestGameState, TestGameMoves> = {
89
+ name: "Test Game",
90
+ setup: (players) => ({
91
+ players: players.map((p) => ({
92
+ id: p.id as PlayerId,
93
+ name: p.name || "Player",
94
+ health: 3,
95
+ hand: [],
96
+ })),
97
+ phase: "draw" as const,
98
+ }),
99
+ moves,
100
+ endIf: (state) => {
101
+ if (state.winner) {
102
+ return { winner: state.winner, reason: "Victory" };
103
+ }
104
+ return undefined;
105
+ },
106
+ };
107
+
108
+ const engine = new RuleEngine(
109
+ gameDefinition,
110
+ [
111
+ { id: createPlayerId("p1"), name: "Alice" },
112
+ { id: createPlayerId("p2"), name: "Bob" },
113
+ ],
114
+ { seed: "test-seed" },
115
+ );
116
+
117
+ // 4. Test assertions
118
+ expectStateProperty(engine, "phase", "draw");
119
+ expectStateProperty(engine, "players[0].health", 3);
120
+
121
+ expectMoveSuccess(engine, "drawCard", {
122
+ playerId: createPlayerId("p1"),
123
+ params: {},
124
+ });
125
+ expectStateProperty(engine, "players[0].hand.length", 1);
126
+
127
+ expectPhaseTransition(
128
+ engine,
129
+ "endPhase",
130
+ { playerId: createPlayerId("p1"), params: {} },
131
+ "draw",
132
+ "main",
133
+ );
134
+
135
+ expectMoveFailure(
136
+ engine,
137
+ "drawCard",
138
+ { playerId: createPlayerId("p1"), params: {} },
139
+ "CONDITION_FAILED",
140
+ );
141
+
142
+ expectGameNotEnded(engine);
143
+
144
+ // Attack to end game
145
+ expectMoveSuccess(engine, "attack", {
146
+ playerId: createPlayerId("p1"),
147
+ params: {},
148
+ });
149
+ expectMoveSuccess(engine, "attack", {
150
+ playerId: createPlayerId("p1"),
151
+ params: {},
152
+ });
153
+ expectMoveSuccess(engine, "attack", {
154
+ playerId: createPlayerId("p1"),
155
+ params: {},
156
+ });
157
+
158
+ expectGameEnd(engine, createPlayerId("p1"));
159
+ expectDeterministicReplay(engine);
160
+ });
161
+ });
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @drmxrcy/tcg-core/testing
3
+ *
4
+ * Testing utilities for game engine development
5
+ *
6
+ * Provides assertions, factories, and helpers for:
7
+ * - Move execution testing
8
+ * - State verification
9
+ * - Flow and phase transitions
10
+ * - Game end conditions
11
+ * - Card and zone creation
12
+ * - Deterministic RNG testing
13
+ * - Replay verification
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import {
18
+ * expectMoveSuccess,
19
+ * expectStateProperty,
20
+ * createTestCard,
21
+ * createTestDeck,
22
+ * withSeed,
23
+ * expectDeterministicReplay
24
+ * } from '@drmxrcy/tcg-core/testing';
25
+ *
26
+ * // Test move execution
27
+ * expectMoveSuccess(engine, 'playCard', {
28
+ * playerId: 'p1',
29
+ * data: { cardId: 'card-123' }
30
+ * });
31
+ *
32
+ * // Verify state
33
+ * expectStateProperty(engine, 'players[0].score', 10);
34
+ *
35
+ * // Create test data
36
+ * const card = createTestCard({ type: 'creature', basePower: 3 });
37
+ * const deck = createTestDeck(['card1', 'card2', 'card3'], 'player1');
38
+ *
39
+ * // Test with deterministic RNG
40
+ * const result = withSeed('test-seed', (rng) => {
41
+ * return rng.shuffle([1, 2, 3, 4, 5]);
42
+ * });
43
+ *
44
+ * // Verify deterministic replay
45
+ * expectDeterministicReplay(engine);
46
+ * ```
47
+ *
48
+ * @module @drmxrcy/tcg-core/testing
49
+ */
50
+
51
+ // Move assertions
52
+ export {
53
+ expectMoveFailure,
54
+ expectMoveSuccess,
55
+ expectStateProperty,
56
+ } from "./test-assertions";
57
+ // Card factory
58
+ export {
59
+ createTestCard,
60
+ createTestCards,
61
+ resetCardCounter,
62
+ } from "./test-card-factory";
63
+
64
+ // End assertions
65
+ export { expectGameEnd, expectGameNotEnded } from "./test-end-assertions";
66
+ // Flow assertions
67
+ export { expectPhaseTransition } from "./test-flow-assertions";
68
+ // Replay assertions
69
+ export { expectDeterministicReplay } from "./test-replay-assertions";
70
+
71
+ // RNG helpers
72
+ export {
73
+ createDeterministicRNG,
74
+ createMultipleRNGs,
75
+ createPredictableSequence,
76
+ expectDeterministicBehavior,
77
+ testWithMultipleSeeds,
78
+ withSeed,
79
+ } from "./test-rng-helpers";
80
+ // Zone factory
81
+ export {
82
+ createTestDeck,
83
+ createTestGraveyard,
84
+ createTestHand,
85
+ createTestPlayArea,
86
+ createTestZone,
87
+ resetZoneCounter,
88
+ } from "./test-zone-factory";
@@ -0,0 +1,341 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { RuleEngine } from "../engine/rule-engine";
3
+ import type { GameDefinition } from "../game-definition/game-definition";
4
+ import type { GameMoveDefinitions } from "../game-definition/move-definitions";
5
+ import { createPlayerId, type PlayerId } from "../types";
6
+ import {
7
+ expectMoveFailure,
8
+ expectMoveSuccess,
9
+ expectStateProperty,
10
+ } from "./test-assertions";
11
+ import { createMockContext } from "./test-context-factory";
12
+
13
+ /**
14
+ * Test state for assertions
15
+ */
16
+ type TestGameState = {
17
+ players: Array<{
18
+ id: PlayerId;
19
+ name: string;
20
+ score: number;
21
+ hand: string[];
22
+ }>;
23
+ currentPlayerIndex: number;
24
+ turnNumber: number;
25
+ phase: "main" | "end";
26
+ nested: {
27
+ deep: {
28
+ value: number;
29
+ };
30
+ };
31
+ };
32
+
33
+ type TestMoves = {
34
+ addScore: { amount: number };
35
+ drawCard: Record<string, never>;
36
+ invalidMove: Record<string, never>;
37
+ };
38
+
39
+ describe("test-assertions", () => {
40
+ function createTestEngine() {
41
+ const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
42
+ addScore: {
43
+ reducer: (draft, context) => {
44
+ const player = draft.players[draft.currentPlayerIndex];
45
+ if (player && context.params?.amount) {
46
+ player.score += context.params.amount as number;
47
+ }
48
+ },
49
+ },
50
+ drawCard: {
51
+ reducer: (draft) => {
52
+ const player = draft.players[draft.currentPlayerIndex];
53
+ if (player) {
54
+ player.hand.push("card");
55
+ }
56
+ },
57
+ },
58
+ invalidMove: {
59
+ condition: () => false, // Always fails
60
+ reducer: () => {},
61
+ },
62
+ };
63
+
64
+ const gameDefinition: GameDefinition<TestGameState, TestMoves> = {
65
+ name: "Test Game",
66
+ setup: (players) => ({
67
+ players: players.map((p) => ({
68
+ id: p.id as PlayerId,
69
+ name: p.name || "Player",
70
+ score: 0,
71
+ hand: [] as string[],
72
+ })),
73
+ currentPlayerIndex: 0,
74
+ turnNumber: 1,
75
+ phase: "main" as const,
76
+ nested: {
77
+ deep: {
78
+ value: 42,
79
+ },
80
+ },
81
+ }),
82
+ moves,
83
+ };
84
+
85
+ const players = [
86
+ { id: createPlayerId("p1"), name: "Alice" },
87
+ { id: createPlayerId("p2"), name: "Bob" },
88
+ ];
89
+
90
+ return new RuleEngine(gameDefinition, players);
91
+ }
92
+
93
+ describe("expectMoveSuccess", () => {
94
+ it("should pass when move succeeds", () => {
95
+ const engine = createTestEngine();
96
+
97
+ // Should not throw
98
+ expectMoveSuccess(engine, "addScore", {
99
+ playerId: createPlayerId("p1"),
100
+ params: { amount: 5 },
101
+ });
102
+ });
103
+
104
+ it("should fail when move does not succeed", () => {
105
+ const engine = createTestEngine();
106
+
107
+ // Should throw because condition fails
108
+ expect(() => {
109
+ expectMoveSuccess(engine, "invalidMove", {
110
+ playerId: createPlayerId("p1"),
111
+ params: {},
112
+ });
113
+ }).toThrow();
114
+ });
115
+
116
+ it("should return move result for further assertions", () => {
117
+ const engine = createTestEngine();
118
+
119
+ const result = expectMoveSuccess(engine, "addScore", {
120
+ playerId: createPlayerId("p1"),
121
+ params: { amount: 5 },
122
+ });
123
+
124
+ expect(result.success).toBe(true);
125
+ if (result.success) {
126
+ expect(result.patches.length).toBeGreaterThan(0);
127
+ }
128
+ });
129
+
130
+ it("should throw with descriptive error message on failure", () => {
131
+ const engine = createTestEngine();
132
+
133
+ expect(() => {
134
+ expectMoveSuccess(engine, "invalidMove", {
135
+ playerId: createPlayerId("p1"),
136
+ params: {},
137
+ });
138
+ }).toThrow(/Expected move 'invalidMove' to succeed/);
139
+ });
140
+ });
141
+
142
+ describe("expectMoveFailure", () => {
143
+ it("should pass when move fails", () => {
144
+ const engine = createTestEngine();
145
+
146
+ // Should not throw
147
+ expectMoveFailure(engine, "invalidMove", {
148
+ playerId: createPlayerId("p1"),
149
+ params: {},
150
+ });
151
+ });
152
+
153
+ it("should fail when move succeeds", () => {
154
+ const engine = createTestEngine();
155
+
156
+ // Should throw because move succeeds
157
+ expect(() => {
158
+ expectMoveFailure(engine, "addScore", {
159
+ playerId: createPlayerId("p1"),
160
+ params: { amount: 5 },
161
+ });
162
+ }).toThrow();
163
+ });
164
+
165
+ it("should return failure result with error", () => {
166
+ const engine = createTestEngine();
167
+
168
+ const result = expectMoveFailure(engine, "invalidMove", {
169
+ playerId: createPlayerId("p1"),
170
+ params: {},
171
+ });
172
+
173
+ expect(result.success).toBe(false);
174
+ if (!result.success) {
175
+ expect(result.error).toBeDefined();
176
+ expect(result.errorCode).toBeDefined();
177
+ }
178
+ });
179
+
180
+ it("should optionally check error code", () => {
181
+ const engine = createTestEngine();
182
+
183
+ // Should not throw because error code matches
184
+ expectMoveFailure(
185
+ engine,
186
+ "invalidMove",
187
+ { playerId: createPlayerId("p1"), params: {} },
188
+ "CONDITION_FAILED",
189
+ );
190
+ });
191
+
192
+ it("should throw when error code does not match", () => {
193
+ const engine = createTestEngine();
194
+
195
+ expect(() => {
196
+ expectMoveFailure(
197
+ engine,
198
+ "invalidMove",
199
+ { playerId: createPlayerId("p1"), params: {} },
200
+ "WRONG_CODE",
201
+ );
202
+ }).toThrow(/Expected error code 'WRONG_CODE'/);
203
+ });
204
+
205
+ it("should throw with descriptive error message when move succeeds", () => {
206
+ const engine = createTestEngine();
207
+
208
+ expect(() => {
209
+ expectMoveFailure(engine, "addScore", {
210
+ playerId: createPlayerId("p1"),
211
+ params: { amount: 5 },
212
+ });
213
+ }).toThrow(/Expected move 'addScore' to fail/);
214
+ });
215
+ });
216
+
217
+ describe("expectStateProperty", () => {
218
+ it("should verify top-level property", () => {
219
+ const engine = createTestEngine();
220
+
221
+ // Should not throw
222
+ expectStateProperty(engine, "turnNumber", 1);
223
+ expectStateProperty(engine, "phase", "main");
224
+ });
225
+
226
+ it("should verify nested property with dot notation", () => {
227
+ const engine = createTestEngine();
228
+
229
+ // Should not throw
230
+ expectStateProperty(engine, "nested.deep.value", 42);
231
+ });
232
+
233
+ it("should verify array element", () => {
234
+ const engine = createTestEngine();
235
+
236
+ expectStateProperty(engine, "players[0].name", "Alice");
237
+ expectStateProperty(engine, "players[1].name", "Bob");
238
+ });
239
+
240
+ it("should verify array length", () => {
241
+ const engine = createTestEngine();
242
+
243
+ expectStateProperty(engine, "players.length", 2);
244
+ });
245
+
246
+ it("should throw when property value does not match", () => {
247
+ const engine = createTestEngine();
248
+
249
+ expect(() => {
250
+ expectStateProperty(engine, "turnNumber", 99);
251
+ }).toThrow(/Expected state.turnNumber to be 99/);
252
+ });
253
+
254
+ it("should throw when property path does not exist", () => {
255
+ const engine = createTestEngine();
256
+
257
+ expect(() => {
258
+ expectStateProperty(
259
+ engine,
260
+ "nonexistent.path" as any,
261
+ "anything" as any,
262
+ );
263
+ }).toThrow(/Property path 'nonexistent.path' not found/);
264
+ });
265
+
266
+ it("should work with undefined values", () => {
267
+ const engine = createTestEngine();
268
+ engine.executeMove("addScore", {
269
+ playerId: createPlayerId("p1"),
270
+ params: { amount: 5 },
271
+ });
272
+
273
+ // Non-existent property should be undefined
274
+ expect(() => {
275
+ expectStateProperty(
276
+ engine,
277
+ "players[0].nonexistent" as any,
278
+ undefined as any,
279
+ );
280
+ }).toThrow();
281
+ });
282
+
283
+ it("should verify state changes after moves", () => {
284
+ const engine = createTestEngine();
285
+
286
+ expectStateProperty(engine, "players[0].score", 0);
287
+
288
+ engine.executeMove("addScore", {
289
+ playerId: createPlayerId("p1"),
290
+ params: { amount: 10 },
291
+ });
292
+
293
+ expectStateProperty(engine, "players[0].score", 10);
294
+ });
295
+
296
+ it("should handle complex nested paths", () => {
297
+ const engine = createTestEngine();
298
+
299
+ // Draw some cards
300
+ engine.executeMove("drawCard", {
301
+ playerId: createPlayerId("p1"),
302
+ params: {},
303
+ });
304
+ engine.executeMove("drawCard", {
305
+ playerId: createPlayerId("p1"),
306
+ params: {},
307
+ });
308
+
309
+ expectStateProperty(engine, "players[0].hand.length", 2);
310
+ expectStateProperty(engine, "players[0].hand[0]", "card");
311
+ });
312
+ });
313
+
314
+ describe("integration with RuleEngine", () => {
315
+ it("should work together in a typical test scenario", () => {
316
+ const engine = createTestEngine();
317
+
318
+ // Initial state assertions
319
+ expectStateProperty(engine, "turnNumber", 1);
320
+ expectStateProperty(engine, "players[0].score", 0);
321
+
322
+ // Execute and verify success
323
+ expectMoveSuccess(engine, "addScore", {
324
+ playerId: createPlayerId("p1"),
325
+ params: { amount: 5 },
326
+ });
327
+
328
+ // Verify state changed
329
+ expectStateProperty(engine, "players[0].score", 5);
330
+
331
+ // Verify invalid move fails
332
+ expectMoveFailure(engine, "invalidMove", {
333
+ playerId: createPlayerId("p1"),
334
+ params: {},
335
+ });
336
+
337
+ // State should be unchanged after failed move
338
+ expectStateProperty(engine, "players[0].score", 5);
339
+ });
340
+ });
341
+ });