@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,403 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
composeSchemas,
|
|
5
|
+
createCardSchema,
|
|
6
|
+
createOptionalSchema,
|
|
7
|
+
createStrictSchema,
|
|
8
|
+
extendSchema,
|
|
9
|
+
mergeSchemas,
|
|
10
|
+
} from "./schema-builders";
|
|
11
|
+
|
|
12
|
+
describe("schema-builders", () => {
|
|
13
|
+
describe("createCardSchema", () => {
|
|
14
|
+
it("should create a basic card schema", () => {
|
|
15
|
+
const schema = createCardSchema({
|
|
16
|
+
id: z.string(),
|
|
17
|
+
name: z.string(),
|
|
18
|
+
type: z.string(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const validCard = {
|
|
22
|
+
id: "dragon-1",
|
|
23
|
+
name: "Dragon",
|
|
24
|
+
type: "creature",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const result = schema.safeParse(validCard);
|
|
28
|
+
expect(result.success).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should validate required fields", () => {
|
|
32
|
+
const schema = createCardSchema({
|
|
33
|
+
id: z.string(),
|
|
34
|
+
name: z.string(),
|
|
35
|
+
type: z.string(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const invalidCard = {
|
|
39
|
+
id: "dragon-1",
|
|
40
|
+
// missing name
|
|
41
|
+
type: "creature",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const result = schema.safeParse(invalidCard);
|
|
45
|
+
expect(result.success).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should support optional fields", () => {
|
|
49
|
+
const schema = createCardSchema({
|
|
50
|
+
id: z.string(),
|
|
51
|
+
name: z.string(),
|
|
52
|
+
type: z.string(),
|
|
53
|
+
basePower: z.number().optional(),
|
|
54
|
+
baseToughness: z.number().optional(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const cardWithOptional = {
|
|
58
|
+
id: "dragon-1",
|
|
59
|
+
name: "Dragon",
|
|
60
|
+
type: "creature",
|
|
61
|
+
basePower: 5,
|
|
62
|
+
baseToughness: 5,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const cardWithoutOptional = {
|
|
66
|
+
id: "bolt-1",
|
|
67
|
+
name: "Lightning Bolt",
|
|
68
|
+
type: "instant",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
expect(schema.safeParse(cardWithOptional).success).toBe(true);
|
|
72
|
+
expect(schema.safeParse(cardWithoutOptional).success).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("extendSchema", () => {
|
|
77
|
+
it("should extend base schema with additional fields", () => {
|
|
78
|
+
const baseSchema = createCardSchema({
|
|
79
|
+
id: z.string(),
|
|
80
|
+
name: z.string(),
|
|
81
|
+
type: z.string(),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const extendedSchema = extendSchema(baseSchema, {
|
|
85
|
+
basePower: z.number(),
|
|
86
|
+
baseToughness: z.number(),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const validCard = {
|
|
90
|
+
id: "dragon-1",
|
|
91
|
+
name: "Dragon",
|
|
92
|
+
type: "creature",
|
|
93
|
+
basePower: 5,
|
|
94
|
+
baseToughness: 5,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const result = extendedSchema.safeParse(validCard);
|
|
98
|
+
expect(result.success).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should require extended fields", () => {
|
|
102
|
+
const baseSchema = createCardSchema({
|
|
103
|
+
id: z.string(),
|
|
104
|
+
name: z.string(),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const extendedSchema = extendSchema(baseSchema, {
|
|
108
|
+
power: z.number(),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const cardWithoutPower = {
|
|
112
|
+
id: "dragon-1",
|
|
113
|
+
name: "Dragon",
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const result = extendedSchema.safeParse(cardWithoutPower);
|
|
117
|
+
expect(result.success).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("mergeSchemas", () => {
|
|
122
|
+
it("should merge two schemas", () => {
|
|
123
|
+
const schema1 = createCardSchema({
|
|
124
|
+
id: z.string(),
|
|
125
|
+
name: z.string(),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const schema2 = createCardSchema({
|
|
129
|
+
type: z.string(),
|
|
130
|
+
cost: z.number(),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const merged = mergeSchemas(schema1, schema2);
|
|
134
|
+
|
|
135
|
+
const validCard = {
|
|
136
|
+
id: "dragon-1",
|
|
137
|
+
name: "Dragon",
|
|
138
|
+
type: "creature",
|
|
139
|
+
cost: 5,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
expect(merged.safeParse(validCard).success).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should merge multiple schemas", () => {
|
|
146
|
+
const baseSchema = createCardSchema({
|
|
147
|
+
id: z.string(),
|
|
148
|
+
name: z.string(),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const typeSchema = createCardSchema({
|
|
152
|
+
type: z.string(),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const statsSchema = createCardSchema({
|
|
156
|
+
power: z.number(),
|
|
157
|
+
toughness: z.number(),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const merged = mergeSchemas(baseSchema, typeSchema, statsSchema);
|
|
161
|
+
|
|
162
|
+
const validCard = {
|
|
163
|
+
id: "dragon-1",
|
|
164
|
+
name: "Dragon",
|
|
165
|
+
type: "creature",
|
|
166
|
+
power: 5,
|
|
167
|
+
toughness: 5,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
expect(merged.safeParse(validCard).success).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("composeSchemas", () => {
|
|
175
|
+
it("should compose schemas with transformations", () => {
|
|
176
|
+
const schema1 = z.object({
|
|
177
|
+
name: z.string(),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const schema2 = z.object({
|
|
181
|
+
type: z.string(),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const composed = composeSchemas([schema1, schema2]);
|
|
185
|
+
|
|
186
|
+
const validData = {
|
|
187
|
+
name: "Dragon",
|
|
188
|
+
type: "creature",
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
expect(composed.safeParse(validData).success).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("should validate all schemas in composition", () => {
|
|
195
|
+
const schema1 = z.object({
|
|
196
|
+
name: z.string().min(1),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const schema2 = z.object({
|
|
200
|
+
type: z.string().min(1),
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const composed = composeSchemas([schema1, schema2]);
|
|
204
|
+
|
|
205
|
+
const invalidData = {
|
|
206
|
+
name: "",
|
|
207
|
+
type: "creature",
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
expect(composed.safeParse(invalidData).success).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe("createOptionalSchema", () => {
|
|
215
|
+
it("should make all fields optional", () => {
|
|
216
|
+
const requiredSchema = createCardSchema({
|
|
217
|
+
id: z.string(),
|
|
218
|
+
name: z.string(),
|
|
219
|
+
type: z.string(),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const optionalSchema = createOptionalSchema(requiredSchema);
|
|
223
|
+
|
|
224
|
+
const partialCard = {
|
|
225
|
+
id: "dragon-1",
|
|
226
|
+
// missing name and type
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
expect(optionalSchema.safeParse(partialCard).success).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should still validate provided fields", () => {
|
|
233
|
+
const requiredSchema = createCardSchema({
|
|
234
|
+
id: z.string(),
|
|
235
|
+
power: z.number(),
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const optionalSchema = createOptionalSchema(requiredSchema);
|
|
239
|
+
|
|
240
|
+
const invalidCard = {
|
|
241
|
+
id: "dragon-1",
|
|
242
|
+
power: "not-a-number", // wrong type
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
expect(optionalSchema.safeParse(invalidCard).success).toBe(false);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe("createStrictSchema", () => {
|
|
250
|
+
it("should not allow extra fields", () => {
|
|
251
|
+
const schema = createStrictSchema({
|
|
252
|
+
id: z.string(),
|
|
253
|
+
name: z.string(),
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const cardWithExtra = {
|
|
257
|
+
id: "dragon-1",
|
|
258
|
+
name: "Dragon",
|
|
259
|
+
extraField: "not allowed",
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const result = schema.safeParse(cardWithExtra);
|
|
263
|
+
expect(result.success).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("should allow defined fields", () => {
|
|
267
|
+
const schema = createStrictSchema({
|
|
268
|
+
id: z.string(),
|
|
269
|
+
name: z.string(),
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const validCard = {
|
|
273
|
+
id: "dragon-1",
|
|
274
|
+
name: "Dragon",
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
expect(schema.safeParse(validCard).success).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe("game-specific schemas", () => {
|
|
282
|
+
it("should create Gundam card schema", () => {
|
|
283
|
+
const gundamCardSchema = createCardSchema({
|
|
284
|
+
id: z.string(),
|
|
285
|
+
name: z.string(),
|
|
286
|
+
type: z.enum(["unit", "command", "character", "base"]),
|
|
287
|
+
cost: z.number().min(0),
|
|
288
|
+
power: z.number().min(0).optional(),
|
|
289
|
+
deployText: z.string().optional(),
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const validGundamCard = {
|
|
293
|
+
id: "gundam-1",
|
|
294
|
+
name: "RX-78-2 Gundam",
|
|
295
|
+
type: "unit",
|
|
296
|
+
cost: 6,
|
|
297
|
+
power: 7,
|
|
298
|
+
deployText: "【Deploy】Draw 2 cards",
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
expect(gundamCardSchema.safeParse(validGundamCard).success).toBe(true);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("should create Lorcana card schema", () => {
|
|
305
|
+
const lorcanaCardSchema = createCardSchema({
|
|
306
|
+
id: z.string(),
|
|
307
|
+
name: z.string(),
|
|
308
|
+
type: z.enum(["character", "action", "item", "location"]),
|
|
309
|
+
cost: z.number().min(0),
|
|
310
|
+
inkable: z.boolean(),
|
|
311
|
+
strength: z.number().optional(),
|
|
312
|
+
willpower: z.number().optional(),
|
|
313
|
+
lore: z.number().optional(),
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const validLorcanaCard = {
|
|
317
|
+
id: "mickey-1",
|
|
318
|
+
name: "Mickey Mouse - Brave Little Tailor",
|
|
319
|
+
type: "character",
|
|
320
|
+
cost: 5,
|
|
321
|
+
inkable: true,
|
|
322
|
+
strength: 4,
|
|
323
|
+
willpower: 5,
|
|
324
|
+
lore: 2,
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
expect(lorcanaCardSchema.safeParse(validLorcanaCard).success).toBe(true);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe("schema extension patterns", () => {
|
|
332
|
+
it("should extend base card with game-specific fields", () => {
|
|
333
|
+
const baseCardSchema = createCardSchema({
|
|
334
|
+
id: z.string(),
|
|
335
|
+
name: z.string(),
|
|
336
|
+
type: z.string(),
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const creatureSchema = extendSchema(baseCardSchema, {
|
|
340
|
+
power: z.number().min(0),
|
|
341
|
+
toughness: z.number().min(0),
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const validCreature = {
|
|
345
|
+
id: "dragon-1",
|
|
346
|
+
name: "Dragon",
|
|
347
|
+
type: "creature",
|
|
348
|
+
power: 5,
|
|
349
|
+
toughness: 5,
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
expect(creatureSchema.safeParse(validCreature).success).toBe(true);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("should compose multiple feature schemas", () => {
|
|
356
|
+
const baseSchema = createCardSchema({
|
|
357
|
+
id: z.string(),
|
|
358
|
+
name: z.string(),
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const typeSchema = createCardSchema({
|
|
362
|
+
type: z.enum(["creature", "instant", "sorcery"]),
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const costSchema = createCardSchema({
|
|
366
|
+
cost: z.number().min(0).max(20),
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const fullSchema = composeSchemas([baseSchema, typeSchema, costSchema]);
|
|
370
|
+
|
|
371
|
+
const validCard = {
|
|
372
|
+
id: "dragon-1",
|
|
373
|
+
name: "Dragon",
|
|
374
|
+
type: "creature",
|
|
375
|
+
cost: 5,
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
expect(fullSchema.safeParse(validCard).success).toBe(true);
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
describe("validation error messages", () => {
|
|
383
|
+
it("should provide meaningful error messages", () => {
|
|
384
|
+
const schema = createCardSchema({
|
|
385
|
+
id: z.string(),
|
|
386
|
+
name: z.string().min(1, "Name cannot be empty"),
|
|
387
|
+
power: z.number().min(0, "Power must be non-negative"),
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const invalidCard = {
|
|
391
|
+
id: "dragon-1",
|
|
392
|
+
name: "",
|
|
393
|
+
power: -1,
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const result = schema.safeParse(invalidCard);
|
|
397
|
+
expect(result.success).toBe(false);
|
|
398
|
+
if (!result.success) {
|
|
399
|
+
expect(result.error.errors.length).toBeGreaterThan(0);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
});
|