@voidhash/mimic 0.0.1-alpha.9 → 0.0.1

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.
@@ -0,0 +1,546 @@
1
+ import { describe, expect, it } from "@effect/vitest";
2
+ import { Schema } from "effect";
3
+ import * as EffectSchema from "../src/EffectSchema";
4
+ import * as Primitive from "../src/Primitive";
5
+
6
+ // =============================================================================
7
+ // Simple Primitive Tests
8
+ // =============================================================================
9
+
10
+ describe("EffectSchema - Simple Primitives", () => {
11
+ describe("toSetSchema", () => {
12
+ it("converts StringPrimitive to Schema.String", () => {
13
+ const primitive = Primitive.String();
14
+ const schema = EffectSchema.toSetSchema(primitive);
15
+
16
+ // Verify it accepts valid strings
17
+ expect(Schema.decodeUnknownSync(schema)("hello")).toBe("hello");
18
+ });
19
+
20
+ it("converts NumberPrimitive to Schema.Number", () => {
21
+ const primitive = Primitive.Number();
22
+ const schema = EffectSchema.toSetSchema(primitive);
23
+
24
+ expect(Schema.decodeUnknownSync(schema)(42)).toBe(42);
25
+ });
26
+
27
+ it("converts BooleanPrimitive to Schema.Boolean", () => {
28
+ const primitive = Primitive.Boolean();
29
+ const schema = EffectSchema.toSetSchema(primitive);
30
+
31
+ expect(Schema.decodeUnknownSync(schema)(true)).toBe(true);
32
+ expect(Schema.decodeUnknownSync(schema)(false)).toBe(false);
33
+ });
34
+
35
+ it("converts LiteralPrimitive to Schema.Literal", () => {
36
+ const primitive = Primitive.Literal("active");
37
+ const schema = EffectSchema.toSetSchema(primitive);
38
+
39
+ expect(Schema.decodeUnknownSync(schema)("active")).toBe("active");
40
+
41
+ // Should reject non-matching literals
42
+ expect(() => Schema.decodeUnknownSync(schema)("inactive")).toThrow();
43
+ });
44
+ });
45
+
46
+ describe("toUpdateSchema", () => {
47
+ it("update schema for simple primitives is same as set schema", () => {
48
+ const stringPrimitive = Primitive.String();
49
+ const setSchema = EffectSchema.toSetSchema(stringPrimitive);
50
+ const updateSchema = EffectSchema.toUpdateSchema(stringPrimitive);
51
+
52
+ // Both should accept strings
53
+ expect(Schema.decodeUnknownSync(setSchema)("test")).toBe("test");
54
+ expect(Schema.decodeUnknownSync(updateSchema)("test")).toBe("test");
55
+ });
56
+ });
57
+ });
58
+
59
+ // =============================================================================
60
+ // Struct Primitive Tests
61
+ // =============================================================================
62
+
63
+ describe("EffectSchema - Struct Primitives", () => {
64
+ describe("toSetSchema", () => {
65
+ it("required fields are non-optional", () => {
66
+ const primitive = Primitive.Struct({
67
+ name: Primitive.String().required(),
68
+ });
69
+
70
+ const schema = EffectSchema.toSetSchema(primitive);
71
+
72
+ // Should accept object with required field
73
+ expect(Schema.decodeUnknownSync(schema)({ name: "Alice" })).toEqual({ name: "Alice" });
74
+
75
+ // Should reject missing required field
76
+ expect(() => Schema.decodeUnknownSync(schema)({})).toThrow();
77
+ });
78
+
79
+ it("fields with defaults are optional", () => {
80
+ const primitive = Primitive.Struct({
81
+ name: Primitive.String().default("default"),
82
+ });
83
+
84
+ const schema = EffectSchema.toSetSchema(primitive);
85
+
86
+ // Should accept object with field
87
+ expect(Schema.decodeUnknownSync(schema)({ name: "Alice" })).toEqual({ name: "Alice" });
88
+
89
+ // Should accept object without field (field is optional)
90
+ expect(Schema.decodeUnknownSync(schema)({})).toEqual({});
91
+ });
92
+
93
+ it("non-required fields are optional", () => {
94
+ const primitive = Primitive.Struct({
95
+ email: Primitive.String(),
96
+ });
97
+
98
+ const schema = EffectSchema.toSetSchema(primitive);
99
+
100
+ // Should accept object with field
101
+ expect(Schema.decodeUnknownSync(schema)({ email: "test@example.com" })).toEqual({ email: "test@example.com" });
102
+
103
+ // Should accept object without field
104
+ expect(Schema.decodeUnknownSync(schema)({})).toEqual({});
105
+ });
106
+
107
+ it("handles mixed required/optional fields", () => {
108
+ const primitive = Primitive.Struct({
109
+ name: Primitive.String().required(),
110
+ age: Primitive.Number().default(0),
111
+ email: Primitive.String(),
112
+ });
113
+
114
+ const schema = EffectSchema.toSetSchema(primitive);
115
+
116
+ // Should accept with only required field
117
+ expect(Schema.decodeUnknownSync(schema)({ name: "Alice" })).toEqual({ name: "Alice" });
118
+
119
+ // Should accept with all fields
120
+ expect(Schema.decodeUnknownSync(schema)({
121
+ name: "Alice",
122
+ age: 30,
123
+ email: "alice@example.com"
124
+ })).toEqual({
125
+ name: "Alice",
126
+ age: 30,
127
+ email: "alice@example.com"
128
+ });
129
+
130
+ // Should reject missing required field
131
+ expect(() => Schema.decodeUnknownSync(schema)({ age: 30 })).toThrow();
132
+ });
133
+ });
134
+
135
+ describe("toUpdateSchema", () => {
136
+ it("all fields are optional for updates", () => {
137
+ const primitive = Primitive.Struct({
138
+ name: Primitive.String().required(),
139
+ age: Primitive.Number().default(0),
140
+ email: Primitive.String(),
141
+ });
142
+
143
+ const schema = EffectSchema.toUpdateSchema(primitive);
144
+
145
+ // Should accept empty object
146
+ expect(Schema.decodeUnknownSync(schema)({})).toEqual({});
147
+
148
+ // Should accept partial updates
149
+ expect(Schema.decodeUnknownSync(schema)({ name: "Alice" })).toEqual({ name: "Alice" });
150
+ expect(Schema.decodeUnknownSync(schema)({ age: 30 })).toEqual({ age: 30 });
151
+
152
+ // Should accept full object
153
+ expect(Schema.decodeUnknownSync(schema)({
154
+ name: "Alice",
155
+ age: 30,
156
+ email: "alice@example.com"
157
+ })).toEqual({
158
+ name: "Alice",
159
+ age: 30,
160
+ email: "alice@example.com"
161
+ });
162
+ });
163
+ });
164
+ });
165
+
166
+ // =============================================================================
167
+ // Nested Struct Tests
168
+ // =============================================================================
169
+
170
+ describe("EffectSchema - Nested Structs", () => {
171
+ describe("toSetSchema", () => {
172
+ it("handles nested struct with required fields", () => {
173
+ const primitive = Primitive.Struct({
174
+ user: Primitive.Struct({
175
+ name: Primitive.String().required(),
176
+ age: Primitive.Number(),
177
+ }),
178
+ });
179
+
180
+ const schema = EffectSchema.toSetSchema(primitive);
181
+
182
+ // Should accept valid nested structure
183
+ expect(Schema.decodeUnknownSync(schema)({
184
+ user: { name: "Alice" }
185
+ })).toEqual({
186
+ user: { name: "Alice" }
187
+ });
188
+
189
+ // Should reject missing required nested field
190
+ expect(() => Schema.decodeUnknownSync(schema)({ user: {} })).toThrow();
191
+ });
192
+ });
193
+
194
+ describe("toUpdateSchema", () => {
195
+ it("nested struct fields are also optional for updates", () => {
196
+ const primitive = Primitive.Struct({
197
+ user: Primitive.Struct({
198
+ name: Primitive.String().required(),
199
+ age: Primitive.Number(),
200
+ }),
201
+ });
202
+
203
+ const schema = EffectSchema.toUpdateSchema(primitive);
204
+
205
+ // Should accept empty object
206
+ expect(Schema.decodeUnknownSync(schema)({})).toEqual({});
207
+
208
+ // Should accept partial nested update
209
+ expect(Schema.decodeUnknownSync(schema)({
210
+ user: { name: "Alice" }
211
+ })).toEqual({
212
+ user: { name: "Alice" }
213
+ });
214
+
215
+ // Should accept nested struct with empty object (all fields optional)
216
+ expect(Schema.decodeUnknownSync(schema)({ user: {} })).toEqual({ user: {} });
217
+ });
218
+ });
219
+ });
220
+
221
+ // =============================================================================
222
+ // Array Primitive Tests
223
+ // =============================================================================
224
+
225
+ describe("EffectSchema - Array Primitives", () => {
226
+ describe("toSetSchema", () => {
227
+ it("converts simple array to Schema.Array", () => {
228
+ const primitive = Primitive.Array(Primitive.String());
229
+ const schema = EffectSchema.toSetSchema(primitive);
230
+
231
+ expect(Schema.decodeUnknownSync(schema)(["a", "b", "c"])).toEqual(["a", "b", "c"]);
232
+ });
233
+
234
+ it("converts array of structs with proper field handling", () => {
235
+ const primitive = Primitive.Array(
236
+ Primitive.Struct({
237
+ name: Primitive.String().required(),
238
+ age: Primitive.Number(),
239
+ })
240
+ );
241
+
242
+ const schema = EffectSchema.toSetSchema(primitive);
243
+
244
+ // Should accept array with valid elements
245
+ expect(Schema.decodeUnknownSync(schema)([
246
+ { name: "Alice" },
247
+ { name: "Bob", age: 30 },
248
+ ])).toEqual([
249
+ { name: "Alice" },
250
+ { name: "Bob", age: 30 },
251
+ ]);
252
+
253
+ // Should reject element missing required field
254
+ expect(() => Schema.decodeUnknownSync(schema)([{ age: 30 }])).toThrow();
255
+ });
256
+ });
257
+ });
258
+
259
+ // =============================================================================
260
+ // Union Primitive Tests
261
+ // =============================================================================
262
+
263
+ describe("EffectSchema - Union Primitives", () => {
264
+ describe("toSetSchema", () => {
265
+ it("creates union schema for variants", () => {
266
+ const primitive = Primitive.Union({
267
+ variants: {
268
+ text: Primitive.Struct({
269
+ type: Primitive.Literal("text"),
270
+ content: Primitive.String().required(),
271
+ }),
272
+ image: Primitive.Struct({
273
+ type: Primitive.Literal("image"),
274
+ url: Primitive.String().required(),
275
+ }),
276
+ },
277
+ });
278
+
279
+ const schema = EffectSchema.toSetSchema(primitive);
280
+
281
+ // Should accept text variant
282
+ expect(Schema.decodeUnknownSync(schema)({
283
+ type: "text",
284
+ content: "Hello"
285
+ })).toEqual({
286
+ type: "text",
287
+ content: "Hello"
288
+ });
289
+
290
+ // Should accept image variant
291
+ expect(Schema.decodeUnknownSync(schema)({
292
+ type: "image",
293
+ url: "https://example.com/image.png"
294
+ })).toEqual({
295
+ type: "image",
296
+ url: "https://example.com/image.png"
297
+ });
298
+ });
299
+ });
300
+
301
+ describe("toUpdateSchema", () => {
302
+ it("all variant fields are optional for updates", () => {
303
+ const primitive = Primitive.Union({
304
+ variants: {
305
+ text: Primitive.Struct({
306
+ type: Primitive.Literal("text"),
307
+ content: Primitive.String().required(),
308
+ }),
309
+ },
310
+ });
311
+
312
+ const schema = EffectSchema.toUpdateSchema(primitive);
313
+
314
+ // Should accept partial variant
315
+ expect(Schema.decodeUnknownSync(schema)({ type: "text" })).toEqual({ type: "text" });
316
+ });
317
+ });
318
+ });
319
+
320
+ // =============================================================================
321
+ // Either Primitive Tests
322
+ // =============================================================================
323
+
324
+ describe("EffectSchema - Either Primitives", () => {
325
+ describe("toSetSchema", () => {
326
+ it("creates union of scalar types", () => {
327
+ const primitive = Primitive.Either(
328
+ Primitive.String(),
329
+ Primitive.Number()
330
+ );
331
+
332
+ const schema = EffectSchema.toSetSchema(primitive);
333
+
334
+ // Should accept string
335
+ expect(Schema.decodeUnknownSync(schema)("hello")).toBe("hello");
336
+
337
+ // Should accept number
338
+ expect(Schema.decodeUnknownSync(schema)(42)).toBe(42);
339
+
340
+ // Should reject non-matching types
341
+ expect(() => Schema.decodeUnknownSync(schema)(true)).toThrow();
342
+ });
343
+ });
344
+ });
345
+
346
+ // =============================================================================
347
+ // TreeNode Primitive Tests
348
+ // =============================================================================
349
+
350
+ describe("EffectSchema - TreeNode Primitives", () => {
351
+ describe("toSetSchema", () => {
352
+ it("delegates to data struct for set schema", () => {
353
+ const CardNode = Primitive.TreeNode("card", {
354
+ data: Primitive.Struct({
355
+ title: Primitive.String().required(),
356
+ description: Primitive.String(),
357
+ }),
358
+ children: [Primitive.TreeNodeSelf],
359
+ });
360
+
361
+ const schema = EffectSchema.toSetSchema(CardNode);
362
+
363
+ // Should accept valid data with required field
364
+ expect(Schema.decodeUnknownSync(schema)({
365
+ title: "My Card"
366
+ })).toEqual({
367
+ title: "My Card"
368
+ });
369
+
370
+ // Should accept valid data with all fields
371
+ expect(Schema.decodeUnknownSync(schema)({
372
+ title: "My Card",
373
+ description: "Card description"
374
+ })).toEqual({
375
+ title: "My Card",
376
+ description: "Card description"
377
+ });
378
+
379
+ // Should reject missing required field
380
+ expect(() => Schema.decodeUnknownSync(schema)({})).toThrow();
381
+ expect(() => Schema.decodeUnknownSync(schema)({ description: "no title" })).toThrow();
382
+ });
383
+ });
384
+
385
+ describe("toUpdateSchema", () => {
386
+ it("all data fields are optional for updates", () => {
387
+ const CardNode = Primitive.TreeNode("card", {
388
+ data: Primitive.Struct({
389
+ title: Primitive.String().required(),
390
+ description: Primitive.String(),
391
+ }),
392
+ children: [Primitive.TreeNodeSelf],
393
+ });
394
+
395
+ const schema = EffectSchema.toUpdateSchema(CardNode);
396
+
397
+ // Should accept empty update
398
+ expect(Schema.decodeUnknownSync(schema)({})).toEqual({});
399
+
400
+ // Should accept partial update
401
+ expect(Schema.decodeUnknownSync(schema)({
402
+ title: "Updated Title"
403
+ })).toEqual({
404
+ title: "Updated Title"
405
+ });
406
+
407
+ // Should accept update with only optional field
408
+ expect(Schema.decodeUnknownSync(schema)({
409
+ description: "New description"
410
+ })).toEqual({
411
+ description: "New description"
412
+ });
413
+ });
414
+ });
415
+ });
416
+
417
+ // =============================================================================
418
+ // Tree Primitive Tests
419
+ // =============================================================================
420
+
421
+ describe("EffectSchema - Tree Primitives", () => {
422
+ describe("toSetSchema", () => {
423
+ it("returns array of TreeNodeState schema", () => {
424
+ const FolderNode = Primitive.TreeNode("folder", {
425
+ data: Primitive.Struct({
426
+ name: Primitive.String().required(),
427
+ }),
428
+ children: [Primitive.TreeNodeSelf],
429
+ });
430
+
431
+ const treePrimitive = Primitive.Tree({
432
+ root: FolderNode,
433
+ });
434
+
435
+ const schema = EffectSchema.toSetSchema(treePrimitive);
436
+
437
+ // Should accept valid tree state
438
+ expect(Schema.decodeUnknownSync(schema)([
439
+ {
440
+ id: "node-1",
441
+ type: "folder",
442
+ parentId: null,
443
+ pos: "a0",
444
+ data: { name: "Root" },
445
+ },
446
+ ])).toEqual([
447
+ {
448
+ id: "node-1",
449
+ type: "folder",
450
+ parentId: null,
451
+ pos: "a0",
452
+ data: { name: "Root" },
453
+ },
454
+ ]);
455
+ });
456
+ });
457
+ });
458
+
459
+ // =============================================================================
460
+ // TreeNodeStateSchema Export Tests
461
+ // =============================================================================
462
+
463
+ describe("EffectSchema - TreeNodeStateSchema", () => {
464
+ it("validates tree node state structure", () => {
465
+ const validNodeState = {
466
+ id: "node-123",
467
+ type: "card",
468
+ parentId: "parent-456",
469
+ pos: "a0",
470
+ data: { title: "Test" },
471
+ };
472
+
473
+ expect(Schema.decodeUnknownSync(EffectSchema.TreeNodeStateSchema)(validNodeState)).toEqual(validNodeState);
474
+ });
475
+
476
+ it("accepts null parentId for root nodes", () => {
477
+ const rootNodeState = {
478
+ id: "root-1",
479
+ type: "folder",
480
+ parentId: null,
481
+ pos: "a0",
482
+ data: { name: "Root" },
483
+ };
484
+
485
+ expect(Schema.decodeUnknownSync(EffectSchema.TreeNodeStateSchema)(rootNodeState)).toEqual(rootNodeState);
486
+ });
487
+
488
+ it("rejects invalid node state", () => {
489
+ const invalidNodeState = {
490
+ id: 123, // should be string
491
+ type: "card",
492
+ parentId: null,
493
+ pos: "a0",
494
+ data: {},
495
+ };
496
+
497
+ expect(() => Schema.decodeUnknownSync(EffectSchema.TreeNodeStateSchema)(invalidNodeState)).toThrow();
498
+ });
499
+ });
500
+
501
+ // =============================================================================
502
+ // Complex Example Tests
503
+ // =============================================================================
504
+
505
+ describe("EffectSchema - Complex Examples", () => {
506
+ it("handles the example schema from the plan", () => {
507
+ const UserSchema = Primitive.Struct({
508
+ name: Primitive.String().required(),
509
+ age: Primitive.Number().default(0),
510
+ email: Primitive.String(),
511
+ });
512
+
513
+ const setSchema = EffectSchema.toSetSchema(UserSchema);
514
+ const updateSchema = EffectSchema.toUpdateSchema(UserSchema);
515
+
516
+ // Set schema: name required, others optional
517
+ expect(Schema.decodeUnknownSync(setSchema)({ name: "Alice" })).toEqual({ name: "Alice" });
518
+ expect(() => Schema.decodeUnknownSync(setSchema)({})).toThrow();
519
+
520
+ // Update schema: all optional
521
+ expect(Schema.decodeUnknownSync(updateSchema)({})).toEqual({});
522
+ expect(Schema.decodeUnknownSync(updateSchema)({ age: 30 })).toEqual({ age: 30 });
523
+ });
524
+
525
+ it("handles the TreeNode example from the plan", () => {
526
+ const CardNode = Primitive.TreeNode("card", {
527
+ data: Primitive.Struct({
528
+ title: Primitive.String().required(),
529
+ description: Primitive.String(),
530
+ }),
531
+ children: [Primitive.TreeNodeSelf],
532
+ });
533
+
534
+ const setSchema = EffectSchema.toSetSchema(CardNode);
535
+ const updateSchema = EffectSchema.toUpdateSchema(CardNode);
536
+
537
+ // Set schema: title required, description optional
538
+ expect(Schema.decodeUnknownSync(setSchema)({ title: "Test" })).toEqual({ title: "Test" });
539
+ expect(() => Schema.decodeUnknownSync(setSchema)({})).toThrow();
540
+
541
+ // Update schema: all optional
542
+ expect(Schema.decodeUnknownSync(updateSchema)({})).toEqual({});
543
+ expect(Schema.decodeUnknownSync(updateSchema)({ description: "Updated" })).toEqual({ description: "Updated" });
544
+ });
545
+ });
546
+