@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,354 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import type { CardId, PlayerId, ZoneId } from "../types";
|
|
3
|
+
import type { InternalState } from "../types/state";
|
|
4
|
+
import type { CardZoneConfig } from "../zones";
|
|
5
|
+
import { createCardOperations, createZoneOperations } from "./operations-impl";
|
|
6
|
+
|
|
7
|
+
describe("Operations Implementation", () => {
|
|
8
|
+
type TestCardDef = { id: string; name: string };
|
|
9
|
+
type TestCardMeta = {
|
|
10
|
+
damage?: number;
|
|
11
|
+
exerted?: boolean;
|
|
12
|
+
counters?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const createTestInternalState = (): InternalState<
|
|
16
|
+
TestCardDef,
|
|
17
|
+
TestCardMeta
|
|
18
|
+
> => {
|
|
19
|
+
const handZone: CardZoneConfig = {
|
|
20
|
+
id: "hand" as ZoneId,
|
|
21
|
+
name: "Hand",
|
|
22
|
+
visibility: "private",
|
|
23
|
+
ordered: false,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const deckZone: CardZoneConfig = {
|
|
27
|
+
id: "deck" as ZoneId,
|
|
28
|
+
name: "Deck",
|
|
29
|
+
visibility: "secret",
|
|
30
|
+
ordered: true,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
zones: {
|
|
35
|
+
hand: {
|
|
36
|
+
config: handZone,
|
|
37
|
+
cardIds: ["card-1", "card-2"] as unknown as CardId[],
|
|
38
|
+
},
|
|
39
|
+
deck: {
|
|
40
|
+
config: deckZone,
|
|
41
|
+
cardIds: ["card-3", "card-4", "card-5"] as unknown as CardId[],
|
|
42
|
+
},
|
|
43
|
+
play: {
|
|
44
|
+
config: {
|
|
45
|
+
id: "play" as ZoneId,
|
|
46
|
+
name: "Play",
|
|
47
|
+
visibility: "public",
|
|
48
|
+
ordered: false,
|
|
49
|
+
},
|
|
50
|
+
cardIds: [],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
cards: {
|
|
54
|
+
"card-1": {
|
|
55
|
+
definitionId: "monster-1",
|
|
56
|
+
owner: "player-1" as unknown as PlayerId,
|
|
57
|
+
controller: "player-1" as unknown as PlayerId,
|
|
58
|
+
zone: "hand" as ZoneId,
|
|
59
|
+
},
|
|
60
|
+
"card-2": {
|
|
61
|
+
definitionId: "monster-2",
|
|
62
|
+
owner: "player-1" as unknown as PlayerId,
|
|
63
|
+
controller: "player-1" as unknown as PlayerId,
|
|
64
|
+
zone: "hand" as ZoneId,
|
|
65
|
+
},
|
|
66
|
+
"card-3": {
|
|
67
|
+
definitionId: "spell-1",
|
|
68
|
+
owner: "player-1" as unknown as PlayerId,
|
|
69
|
+
controller: "player-1" as unknown as PlayerId,
|
|
70
|
+
zone: "deck" as ZoneId,
|
|
71
|
+
position: 0,
|
|
72
|
+
},
|
|
73
|
+
"card-4": {
|
|
74
|
+
definitionId: "spell-2",
|
|
75
|
+
owner: "player-1" as unknown as PlayerId,
|
|
76
|
+
controller: "player-1" as unknown as PlayerId,
|
|
77
|
+
zone: "deck" as ZoneId,
|
|
78
|
+
position: 1,
|
|
79
|
+
},
|
|
80
|
+
"card-5": {
|
|
81
|
+
definitionId: "spell-3",
|
|
82
|
+
owner: "player-1" as unknown as PlayerId,
|
|
83
|
+
controller: "player-1" as unknown as PlayerId,
|
|
84
|
+
zone: "deck" as ZoneId,
|
|
85
|
+
position: 2,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
cardMetas: {
|
|
89
|
+
"card-1": { damage: 0, exerted: false },
|
|
90
|
+
"card-2": { damage: 3, exerted: true, counters: 2 },
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
describe("ZoneOperations Implementation", () => {
|
|
96
|
+
describe("moveCard", () => {
|
|
97
|
+
it("should move card from hand to play", () => {
|
|
98
|
+
const state = createTestInternalState();
|
|
99
|
+
const ops = createZoneOperations(state);
|
|
100
|
+
|
|
101
|
+
ops.moveCard({
|
|
102
|
+
cardId: "card-1" as CardId,
|
|
103
|
+
targetZoneId: "play" as ZoneId,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(state.zones.hand.cardIds).not.toContain("card-1");
|
|
107
|
+
expect(state.zones.play.cardIds).toContain("card-1");
|
|
108
|
+
expect(state.cards["card-1"].zone).toBe("play" as ZoneId);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should move card to top of deck", () => {
|
|
112
|
+
const state = createTestInternalState();
|
|
113
|
+
const ops = createZoneOperations(state);
|
|
114
|
+
|
|
115
|
+
ops.moveCard({
|
|
116
|
+
cardId: "card-1" as CardId,
|
|
117
|
+
targetZoneId: "deck" as ZoneId,
|
|
118
|
+
position: "top",
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(state.zones.deck.cardIds[0]).toBe("card-1" as unknown as CardId);
|
|
122
|
+
expect(state.cards["card-1"].position).toBe(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("should move card to bottom of deck", () => {
|
|
126
|
+
const state = createTestInternalState();
|
|
127
|
+
const ops = createZoneOperations(state);
|
|
128
|
+
|
|
129
|
+
ops.moveCard({
|
|
130
|
+
cardId: "card-1" as CardId,
|
|
131
|
+
targetZoneId: "deck" as ZoneId,
|
|
132
|
+
position: "bottom",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const lastIndex = state.zones.deck.cardIds.length - 1;
|
|
136
|
+
expect(state.zones.deck.cardIds[lastIndex]).toBe(
|
|
137
|
+
"card-1" as unknown as CardId,
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should update positions in ordered zones", () => {
|
|
142
|
+
const state = createTestInternalState();
|
|
143
|
+
const ops = createZoneOperations(state);
|
|
144
|
+
|
|
145
|
+
ops.moveCard({
|
|
146
|
+
cardId: "card-1" as CardId,
|
|
147
|
+
targetZoneId: "deck" as ZoneId,
|
|
148
|
+
position: 1,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
expect(state.cards["card-1"].position).toBe(1);
|
|
152
|
+
expect(state.cards["card-4"].position).toBe(2); // Shifted down
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("getCardsInZone", () => {
|
|
157
|
+
it("should return all cards in a zone", () => {
|
|
158
|
+
const state = createTestInternalState();
|
|
159
|
+
const ops = createZoneOperations(state);
|
|
160
|
+
|
|
161
|
+
const handCards = ops.getCardsInZone("hand" as ZoneId);
|
|
162
|
+
|
|
163
|
+
expect(handCards).toHaveLength(2);
|
|
164
|
+
expect(handCards).toContain("card-1");
|
|
165
|
+
expect(handCards).toContain("card-2");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should return empty array for empty zone", () => {
|
|
169
|
+
const state = createTestInternalState();
|
|
170
|
+
const ops = createZoneOperations(state);
|
|
171
|
+
|
|
172
|
+
const playCards = ops.getCardsInZone("play" as ZoneId);
|
|
173
|
+
|
|
174
|
+
expect(playCards).toHaveLength(0);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should filter by owner", () => {
|
|
178
|
+
const state = createTestInternalState();
|
|
179
|
+
// Add card for different player
|
|
180
|
+
state.zones.hand.cardIds.push("card-6" as CardId);
|
|
181
|
+
state.cards["card-6"] = {
|
|
182
|
+
definitionId: "monster-3",
|
|
183
|
+
owner: "player-2" as unknown as PlayerId,
|
|
184
|
+
controller: "player-2" as unknown as PlayerId,
|
|
185
|
+
zone: "hand" as ZoneId,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const ops = createZoneOperations(state);
|
|
189
|
+
|
|
190
|
+
const player1Cards = ops.getCardsInZone(
|
|
191
|
+
"hand" as ZoneId,
|
|
192
|
+
"player-1" as unknown as PlayerId,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
expect(player1Cards).toHaveLength(2);
|
|
196
|
+
expect(player1Cards).not.toContain("card-6");
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("shuffleZone", () => {
|
|
201
|
+
it("should shuffle cards in a zone", () => {
|
|
202
|
+
const state = createTestInternalState();
|
|
203
|
+
const ops = createZoneOperations(state);
|
|
204
|
+
|
|
205
|
+
const beforeShuffle = [...state.zones.deck.cardIds];
|
|
206
|
+
ops.shuffleZone("deck" as ZoneId);
|
|
207
|
+
const afterShuffle = state.zones.deck.cardIds;
|
|
208
|
+
|
|
209
|
+
// Should have same cards
|
|
210
|
+
expect(afterShuffle).toHaveLength(beforeShuffle.length);
|
|
211
|
+
for (const card of beforeShuffle) {
|
|
212
|
+
expect(afterShuffle).toContain(card);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Should update positions
|
|
216
|
+
for (let i = 0; i < afterShuffle.length; i++) {
|
|
217
|
+
const cardId = afterShuffle[i] as string;
|
|
218
|
+
expect(state.cards[cardId].position).toBe(i);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe("getCardZone", () => {
|
|
224
|
+
it("should return zone containing a card", () => {
|
|
225
|
+
const state = createTestInternalState();
|
|
226
|
+
const ops = createZoneOperations(state);
|
|
227
|
+
|
|
228
|
+
const zone = ops.getCardZone("card-3" as CardId);
|
|
229
|
+
|
|
230
|
+
expect(zone).toBe("deck" as ZoneId);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should return undefined for nonexistent card", () => {
|
|
234
|
+
const state = createTestInternalState();
|
|
235
|
+
const ops = createZoneOperations(state);
|
|
236
|
+
|
|
237
|
+
const zone = ops.getCardZone("nonexistent" as CardId);
|
|
238
|
+
|
|
239
|
+
expect(zone).toBeUndefined();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("CardOperations Implementation", () => {
|
|
245
|
+
describe("getCardMeta", () => {
|
|
246
|
+
it("should return card metadata", () => {
|
|
247
|
+
const state = createTestInternalState();
|
|
248
|
+
const ops = createCardOperations(state);
|
|
249
|
+
|
|
250
|
+
const meta = ops.getCardMeta("card-2" as CardId);
|
|
251
|
+
|
|
252
|
+
expect(meta.damage).toBe(3);
|
|
253
|
+
expect(meta.exerted).toBe(true);
|
|
254
|
+
expect(meta.counters).toBe(2);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should return empty object for card without metadata", () => {
|
|
258
|
+
const state = createTestInternalState();
|
|
259
|
+
const ops = createCardOperations(state);
|
|
260
|
+
|
|
261
|
+
const meta = ops.getCardMeta("card-3" as CardId);
|
|
262
|
+
|
|
263
|
+
expect(meta).toEqual({});
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe("updateCardMeta", () => {
|
|
268
|
+
it("should merge metadata", () => {
|
|
269
|
+
const state = createTestInternalState();
|
|
270
|
+
const ops = createCardOperations(state);
|
|
271
|
+
|
|
272
|
+
ops.updateCardMeta("card-1" as CardId, { damage: 5 });
|
|
273
|
+
|
|
274
|
+
expect(state.cardMetas["card-1"].damage).toBe(5);
|
|
275
|
+
expect(state.cardMetas["card-1"].exerted).toBe(false); // Preserved
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("should create metadata for new card", () => {
|
|
279
|
+
const state = createTestInternalState();
|
|
280
|
+
const ops = createCardOperations(state);
|
|
281
|
+
|
|
282
|
+
ops.updateCardMeta("card-3" as CardId, { damage: 2 });
|
|
283
|
+
|
|
284
|
+
expect(state.cardMetas["card-3"].damage).toBe(2);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe("setCardMeta", () => {
|
|
289
|
+
it("should replace metadata completely", () => {
|
|
290
|
+
const state = createTestInternalState();
|
|
291
|
+
const ops = createCardOperations(state);
|
|
292
|
+
|
|
293
|
+
ops.setCardMeta("card-2" as CardId, { damage: 10 });
|
|
294
|
+
|
|
295
|
+
expect(state.cardMetas["card-2"].damage).toBe(10);
|
|
296
|
+
expect(state.cardMetas["card-2"].exerted).toBeUndefined();
|
|
297
|
+
expect(state.cardMetas["card-2"].counters).toBeUndefined();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe("getCardOwner", () => {
|
|
302
|
+
it("should return card owner", () => {
|
|
303
|
+
const state = createTestInternalState();
|
|
304
|
+
const ops = createCardOperations(state);
|
|
305
|
+
|
|
306
|
+
const owner = ops.getCardOwner("card-1" as CardId);
|
|
307
|
+
|
|
308
|
+
expect(owner).toBe("player-1" as unknown as PlayerId);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("should return undefined for nonexistent card", () => {
|
|
312
|
+
const state = createTestInternalState();
|
|
313
|
+
const ops = createCardOperations(state);
|
|
314
|
+
|
|
315
|
+
const owner = ops.getCardOwner("nonexistent" as CardId);
|
|
316
|
+
|
|
317
|
+
expect(owner).toBeUndefined();
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe("queryCards", () => {
|
|
322
|
+
it("should find cards by predicate", () => {
|
|
323
|
+
const state = createTestInternalState();
|
|
324
|
+
const ops = createCardOperations(state);
|
|
325
|
+
|
|
326
|
+
const exerted = ops.queryCards((cardId, meta) => meta.exerted === true);
|
|
327
|
+
|
|
328
|
+
expect(exerted).toHaveLength(1);
|
|
329
|
+
expect(exerted).toContain("card-2");
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("should support complex predicates", () => {
|
|
333
|
+
const state = createTestInternalState();
|
|
334
|
+
const ops = createCardOperations(state);
|
|
335
|
+
|
|
336
|
+
const results = ops.queryCards(
|
|
337
|
+
(cardId, meta) => (meta.damage ?? 0) > 0 && (meta.counters ?? 0) >= 2,
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
expect(results).toHaveLength(1);
|
|
341
|
+
expect(results).toContain("card-2");
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("should return empty array when no matches", () => {
|
|
345
|
+
const state = createTestInternalState();
|
|
346
|
+
const ops = createCardOperations(state);
|
|
347
|
+
|
|
348
|
+
const results = ops.queryCards((cardId, meta) => meta.counters === 999);
|
|
349
|
+
|
|
350
|
+
expect(results).toHaveLength(0);
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
});
|