@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,256 @@
1
+ import type { MoveExecutionResult, RuleEngine } from "../engine/rule-engine";
2
+ import type { MoveContext, MoveContextInput } from "../moves/move-system";
3
+
4
+ /**
5
+ * Test Assertions
6
+ *
7
+ * Assertion helpers for testing game moves and state
8
+ */
9
+
10
+ /**
11
+ * Assert that a move executes successfully
12
+ *
13
+ * Executes the move and throws an error if it fails.
14
+ * Returns the success result for further assertions on patches.
15
+ *
16
+ * @param engine - Rule engine instance
17
+ * @param moveId - Move to execute
18
+ * @param context - Move context
19
+ * @returns Move execution result (success)
20
+ * @throws Error if move fails
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const result = expectMoveSuccess(engine, 'playCard', {
25
+ * playerId: 'p1',
26
+ * data: { cardId: 'card-123' }
27
+ * });
28
+ * expect(result.patches.length).toBeGreaterThan(0);
29
+ * ```
30
+ */
31
+ export function expectMoveSuccess<TState, TMoves extends Record<string, any>>(
32
+ engine: RuleEngine<TState, TMoves>,
33
+ moveId: string,
34
+ context: MoveContextInput,
35
+ ): Extract<MoveExecutionResult, { success: true }> {
36
+ const result = engine.executeMove(moveId, context);
37
+
38
+ if (!result.success) {
39
+ throw new Error(
40
+ `Expected move '${moveId}' to succeed, but it failed with: ${result.error}` +
41
+ (result.errorCode ? ` (code: ${result.errorCode})` : ""),
42
+ );
43
+ }
44
+
45
+ return result;
46
+ }
47
+
48
+ /**
49
+ * Assert that a move fails
50
+ *
51
+ * Executes the move and throws an error if it succeeds.
52
+ * Returns the failure result for assertions on error details.
53
+ *
54
+ * @param engine - Rule engine instance
55
+ * @param moveId - Move to execute
56
+ * @param context - Move context
57
+ * @param expectedErrorCode - Optional expected error code
58
+ * @returns Move execution result (failure)
59
+ * @throws Error if move succeeds or error code doesn't match
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const result = expectMoveFailure(engine, 'invalidMove', {
64
+ * playerId: 'p1'
65
+ * }, 'CONDITION_FAILED');
66
+ * expect(result.error).toContain('not met');
67
+ * ```
68
+ */
69
+ export function expectMoveFailure<TState, TMoves extends Record<string, any>>(
70
+ engine: RuleEngine<TState, TMoves>,
71
+ moveId: string,
72
+ context: MoveContextInput,
73
+ expectedErrorCode?: string,
74
+ ): Extract<MoveExecutionResult, { success: false }> {
75
+ const result = engine.executeMove(moveId, context);
76
+
77
+ if (result.success) {
78
+ throw new Error(
79
+ `Expected move '${moveId}' to fail, but it succeeded with ${result.patches.length} patches`,
80
+ );
81
+ }
82
+
83
+ if (expectedErrorCode && result.errorCode !== expectedErrorCode) {
84
+ throw new Error(
85
+ `Expected error code '${expectedErrorCode}', but got '${result.errorCode}': ${result.error}`,
86
+ );
87
+ }
88
+
89
+ return result;
90
+ }
91
+
92
+ /**
93
+ * Assert that a state property has a specific value
94
+ *
95
+ * Supports dot notation and array indexing for nested properties.
96
+ * Throws an error if the property doesn't match the expected value.
97
+ *
98
+ * @param engine - Rule engine instance
99
+ * @param path - Property path (e.g., 'players[0].score' or 'nested.deep.value')
100
+ * @param expectedValue - Expected value
101
+ * @throws Error if property doesn't match or path is invalid
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * expectStateProperty(engine, 'turnNumber', 1);
106
+ * expectStateProperty(engine, 'players[0].score', 10);
107
+ * expectStateProperty(engine, 'nested.deep.value', 42);
108
+ * ```
109
+ */
110
+ export function expectStateProperty<TState, TMoves extends Record<string, any>>(
111
+ engine: RuleEngine<TState, TMoves>,
112
+ path: string,
113
+ expectedValue: any,
114
+ ): void {
115
+ const state = engine.getState();
116
+ const actualValue = getPropertyByPath(state, path);
117
+
118
+ if (actualValue === undefined && !hasProperty(state, path)) {
119
+ throw new Error(
120
+ `Property path '${path}' not found in state. Available paths: ${getAvailablePaths(state).join(", ")}`,
121
+ );
122
+ }
123
+
124
+ if (actualValue !== expectedValue) {
125
+ throw new Error(
126
+ `Expected state.${path} to be ${JSON.stringify(expectedValue)}, but got ${JSON.stringify(actualValue)}`,
127
+ );
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Get property value by path with dot notation and array indexing
133
+ *
134
+ * Supports paths like:
135
+ * - 'players[0].name'
136
+ * - 'nested.deep.value'
137
+ * - 'array.length'
138
+ *
139
+ * @param obj - Object to traverse
140
+ * @param path - Property path
141
+ * @returns Property value or undefined
142
+ */
143
+ function getPropertyByPath(obj: any, path: string): any {
144
+ // Split by dots but preserve array brackets
145
+ const parts = path.split(/\.(?![^[]*\])/);
146
+
147
+ let current = obj;
148
+
149
+ for (const part of parts) {
150
+ if (current === null || current === undefined) {
151
+ return undefined;
152
+ }
153
+
154
+ // Handle array indexing: players[0]
155
+ const arrayMatch = part.match(/^(.+?)\[(\d+)\]$/);
156
+ if (arrayMatch) {
157
+ const [, arrayName, index] = arrayMatch;
158
+ current = current[arrayName as string];
159
+ if (current === null || current === undefined) {
160
+ return undefined;
161
+ }
162
+ current = current[Number.parseInt(index as string, 10)];
163
+ } else {
164
+ current = current[part];
165
+ }
166
+ }
167
+
168
+ return current;
169
+ }
170
+
171
+ /**
172
+ * Check if a property path exists in an object
173
+ *
174
+ * @param obj - Object to check
175
+ * @param path - Property path
176
+ * @returns True if path exists
177
+ */
178
+ function hasProperty(obj: any, path: string): boolean {
179
+ const parts = path.split(/\.(?![^[]*\])/);
180
+ let current = obj;
181
+
182
+ for (const part of parts) {
183
+ if (current === null || current === undefined) {
184
+ return false;
185
+ }
186
+
187
+ // Handle array indexing
188
+ const arrayMatch = part.match(/^(.+?)\[(\d+)\]$/);
189
+ if (arrayMatch) {
190
+ const [, arrayName, index] = arrayMatch;
191
+ if (!(arrayName in current)) {
192
+ return false;
193
+ }
194
+ current = current[arrayName as string];
195
+ if (current === null || current === undefined) {
196
+ return false;
197
+ }
198
+ const idx = Number.parseInt(index as string, 10);
199
+ if (!Array.isArray(current) || idx >= current.length) {
200
+ return false;
201
+ }
202
+ current = current[idx];
203
+ } else {
204
+ if (!(part in current)) {
205
+ return false;
206
+ }
207
+ current = current[part];
208
+ }
209
+ }
210
+
211
+ return true;
212
+ }
213
+
214
+ /**
215
+ * Get available property paths in an object (for error messages)
216
+ *
217
+ * @param obj - Object to inspect
218
+ * @param prefix - Path prefix for recursion
219
+ * @param maxDepth - Maximum depth to traverse
220
+ * @returns Array of property paths
221
+ */
222
+ function getAvailablePaths(obj: any, prefix = "", maxDepth = 2): string[] {
223
+ if (maxDepth <= 0 || obj === null || typeof obj !== "object") {
224
+ return [];
225
+ }
226
+
227
+ const paths: string[] = [];
228
+
229
+ for (const key of Object.keys(obj)) {
230
+ const path = prefix ? `${prefix}.${key}` : key;
231
+ paths.push(path);
232
+
233
+ // Recursively get nested paths
234
+ if (typeof obj[key] === "object" && obj[key] !== null) {
235
+ if (Array.isArray(obj[key])) {
236
+ // Show array length
237
+ paths.push(`${path}.length`);
238
+ // Show first few elements
239
+ for (let i = 0; i < Math.min(3, obj[key].length); i++) {
240
+ paths.push(`${path}[${i}]`);
241
+ const nestedPaths = getAvailablePaths(
242
+ obj[key][i],
243
+ `${path}[${i}]`,
244
+ maxDepth - 1,
245
+ );
246
+ paths.push(...nestedPaths);
247
+ }
248
+ } else {
249
+ const nestedPaths = getAvailablePaths(obj[key], path, maxDepth - 1);
250
+ paths.push(...nestedPaths);
251
+ }
252
+ }
253
+ }
254
+
255
+ return paths.slice(0, 50); // Limit for readability
256
+ }
@@ -0,0 +1,228 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import type { CardDefinition } from "../cards/card-definition";
3
+ import { createTestCard, createTestCards } from "./test-card-factory";
4
+
5
+ describe("test-card-factory", () => {
6
+ describe("createTestCard", () => {
7
+ it("should create card with default values", () => {
8
+ const card = createTestCard();
9
+
10
+ expect(card.id).toBeDefined();
11
+ expect(card.name).toBeDefined();
12
+ expect(card.type).toBeDefined();
13
+ });
14
+
15
+ it("should generate unique IDs for each card", () => {
16
+ const card1 = createTestCard();
17
+ const card2 = createTestCard();
18
+ const card3 = createTestCard();
19
+
20
+ expect(card1.id).not.toBe(card2.id);
21
+ expect(card2.id).not.toBe(card3.id);
22
+ expect(card1.id).not.toBe(card3.id);
23
+ });
24
+
25
+ it("should override provided properties", () => {
26
+ const card = createTestCard({
27
+ name: "Custom Card",
28
+ type: "spell",
29
+ basePower: 10,
30
+ });
31
+
32
+ expect(card.name).toBe("Custom Card");
33
+ expect(card.type).toBe("spell");
34
+ expect(card.basePower).toBe(10);
35
+ });
36
+
37
+ it("should keep default values for non-overridden properties", () => {
38
+ const card = createTestCard({
39
+ name: "Custom Card",
40
+ });
41
+
42
+ expect(card.name).toBe("Custom Card");
43
+ expect(card.type).toBeDefined(); // Should still have default type
44
+ expect(card.id).toBeDefined(); // Should still have generated ID
45
+ });
46
+
47
+ it("should create creature card with power and toughness", () => {
48
+ const creature = createTestCard({
49
+ type: "creature",
50
+ basePower: 3,
51
+ baseToughness: 4,
52
+ });
53
+
54
+ expect(creature.type).toBe("creature");
55
+ expect(creature.basePower).toBe(3);
56
+ expect(creature.baseToughness).toBe(4);
57
+ });
58
+
59
+ it("should create spell card with cost", () => {
60
+ const spell = createTestCard({
61
+ type: "spell",
62
+ baseCost: 5,
63
+ });
64
+
65
+ expect(spell.type).toBe("spell");
66
+ expect(spell.baseCost).toBe(5);
67
+ });
68
+
69
+ it("should support abilities", () => {
70
+ const card = createTestCard({
71
+ abilities: ["flying", "haste"],
72
+ });
73
+
74
+ expect(card.abilities).toEqual(["flying", "haste"]);
75
+ });
76
+
77
+ it("should handle empty overrides", () => {
78
+ const card = createTestCard({});
79
+
80
+ expect(card.id).toBeDefined();
81
+ expect(card.name).toBeDefined();
82
+ expect(card.type).toBeDefined();
83
+ });
84
+
85
+ it("should create valid card definition", () => {
86
+ const card = createTestCard();
87
+
88
+ // Verify it's a valid CardDefinition
89
+ const validate = (def: CardDefinition) => def;
90
+ expect(() => validate(card)).not.toThrow();
91
+ });
92
+ });
93
+
94
+ describe("createTestCards", () => {
95
+ it("should create multiple cards with default count", () => {
96
+ const cards = createTestCards();
97
+
98
+ expect(cards.length).toBe(3); // Default count
99
+ expect(cards[0]).toBeDefined();
100
+ expect(cards[1]).toBeDefined();
101
+ expect(cards[2]).toBeDefined();
102
+ });
103
+
104
+ it("should create specified number of cards", () => {
105
+ const cards = createTestCards(5);
106
+
107
+ expect(cards.length).toBe(5);
108
+ });
109
+
110
+ it("should create cards with unique IDs", () => {
111
+ const cards = createTestCards(10);
112
+
113
+ const ids = cards.map((c) => c.id);
114
+ const uniqueIds = new Set(ids);
115
+
116
+ expect(uniqueIds.size).toBe(10); // All IDs should be unique
117
+ });
118
+
119
+ it("should apply overrides to all cards", () => {
120
+ const cards = createTestCards(3, {
121
+ type: "creature",
122
+ basePower: 2,
123
+ });
124
+
125
+ for (const card of cards) {
126
+ expect(card.type).toBe("creature");
127
+ expect(card.basePower).toBe(2);
128
+ }
129
+ });
130
+
131
+ it("should still generate unique IDs even with overrides", () => {
132
+ const cards = createTestCards(3, {
133
+ name: "Same Name",
134
+ type: "creature",
135
+ });
136
+
137
+ const ids = cards.map((c) => c.id);
138
+ const uniqueIds = new Set(ids);
139
+
140
+ expect(uniqueIds.size).toBe(3);
141
+ expect(cards[0]?.name).toBe("Same Name");
142
+ expect(cards[1]?.name).toBe("Same Name");
143
+ expect(cards[2]?.name).toBe("Same Name");
144
+ });
145
+
146
+ it("should handle count of 0", () => {
147
+ const cards = createTestCards(0);
148
+
149
+ expect(cards.length).toBe(0);
150
+ expect(cards).toEqual([]);
151
+ });
152
+
153
+ it("should handle count of 1", () => {
154
+ const cards = createTestCards(1);
155
+
156
+ expect(cards.length).toBe(1);
157
+ expect(cards[0]).toBeDefined();
158
+ });
159
+
160
+ it("should create cards with different base stats", () => {
161
+ const cards = createTestCards(3, {
162
+ type: "creature",
163
+ basePower: 5,
164
+ baseToughness: 5,
165
+ baseCost: 3,
166
+ });
167
+
168
+ for (const card of cards) {
169
+ expect(card.type).toBe("creature");
170
+ expect(card.basePower).toBe(5);
171
+ expect(card.baseToughness).toBe(5);
172
+ expect(card.baseCost).toBe(3);
173
+ }
174
+ });
175
+ });
176
+
177
+ describe("integration", () => {
178
+ it("should create cards useful for testing", () => {
179
+ // Create a test deck
180
+ const creatures = createTestCards(20, {
181
+ type: "creature",
182
+ basePower: 2,
183
+ baseToughness: 2,
184
+ });
185
+
186
+ const spells = createTestCards(10, {
187
+ type: "spell",
188
+ baseCost: 3,
189
+ });
190
+
191
+ const deck = [...creatures, ...spells];
192
+
193
+ expect(deck.length).toBe(30);
194
+
195
+ // Verify creatures
196
+ const creatureCards = deck.filter((c) => c.type === "creature");
197
+ expect(creatureCards.length).toBe(20);
198
+
199
+ // Verify spells
200
+ const spellCards = deck.filter((c) => c.type === "spell");
201
+ expect(spellCards.length).toBe(10);
202
+
203
+ // Verify all unique
204
+ const ids = deck.map((c) => c.id);
205
+ const uniqueIds = new Set(ids);
206
+ expect(uniqueIds.size).toBe(30);
207
+ });
208
+
209
+ it("should work with card registry", () => {
210
+ const cards = createTestCards(5);
211
+
212
+ // Simulate registry usage
213
+ const registry = new Map<string, CardDefinition>();
214
+ for (const card of cards) {
215
+ registry.set(card.id, card);
216
+ }
217
+
218
+ expect(registry.size).toBe(5);
219
+
220
+ // Can retrieve cards by ID
221
+ const firstCard = cards[0];
222
+ if (firstCard) {
223
+ const retrieved = registry.get(firstCard.id);
224
+ expect(retrieved).toEqual(firstCard);
225
+ }
226
+ });
227
+ });
228
+ });
@@ -0,0 +1,111 @@
1
+ import type { CardDefinition } from "../cards/card-definition";
2
+
3
+ /**
4
+ * Test Card Factory
5
+ *
6
+ * Factory functions for creating test card definitions
7
+ */
8
+
9
+ let cardCounter = 0;
10
+
11
+ /**
12
+ * Create a test card definition with optional overrides
13
+ *
14
+ * Generates a card with sensible defaults that can be customized.
15
+ * Each card gets a unique ID automatically.
16
+ *
17
+ * @param overrides - Optional properties to override defaults
18
+ * @returns Card definition for testing
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // Create default test card
23
+ * const card = createTestCard();
24
+ *
25
+ * // Create custom creature
26
+ * const creature = createTestCard({
27
+ * name: 'Dragon',
28
+ * type: 'creature',
29
+ * basePower: 5,
30
+ * baseToughness: 5,
31
+ * baseCost: 7
32
+ * });
33
+ *
34
+ * // Create spell
35
+ * const spell = createTestCard({
36
+ * type: 'spell',
37
+ * baseCost: 3
38
+ * });
39
+ * ```
40
+ */
41
+ export function createTestCard(
42
+ overrides?: Partial<CardDefinition>,
43
+ ): CardDefinition {
44
+ const id = `test-card-${cardCounter++}`;
45
+
46
+ return {
47
+ id,
48
+ name: `Test Card ${cardCounter}`,
49
+ type: "creature",
50
+ basePower: 1,
51
+ baseToughness: 1,
52
+ baseCost: 1,
53
+ ...overrides,
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Create multiple test cards with optional overrides
59
+ *
60
+ * Generates an array of test cards, each with a unique ID.
61
+ * All cards share the same overridden properties but have unique IDs.
62
+ *
63
+ * @param count - Number of cards to create (default: 3)
64
+ * @param overrides - Optional properties to override defaults for all cards
65
+ * @returns Array of card definitions
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * // Create 5 default cards
70
+ * const cards = createTestCards(5);
71
+ *
72
+ * // Create 10 creatures with same stats
73
+ * const creatures = createTestCards(10, {
74
+ * type: 'creature',
75
+ * basePower: 2,
76
+ * baseToughness: 2
77
+ * });
78
+ *
79
+ * // Create deck of mixed cards
80
+ * const deck = [
81
+ * ...createTestCards(20, { type: 'creature' }),
82
+ * ...createTestCards(10, { type: 'spell' })
83
+ * ];
84
+ * ```
85
+ */
86
+ export function createTestCards(
87
+ count = 3,
88
+ overrides?: Partial<CardDefinition>,
89
+ ): CardDefinition[] {
90
+ const cards: CardDefinition[] = [];
91
+
92
+ for (let i = 0; i < count; i++) {
93
+ cards.push(createTestCard(overrides));
94
+ }
95
+
96
+ return cards;
97
+ }
98
+
99
+ /**
100
+ * Reset the card counter (useful for deterministic test IDs)
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * resetCardCounter();
105
+ * const card1 = createTestCard(); // test-card-0
106
+ * const card2 = createTestCard(); // test-card-1
107
+ * ```
108
+ */
109
+ export function resetCardCounter(): void {
110
+ cardCounter = 0;
111
+ }