@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,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
|
+
}
|