@voidhash/mimic 0.0.1-alpha.2 → 0.0.1-alpha.4

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,707 @@
1
+ import { describe, expect, it } from "@effect/vitest";
2
+ import * as Primitive from "../../src/Primitive";
3
+ import * as ProxyEnvironment from "../../src/ProxyEnvironment";
4
+ import * as OperationPath from "../../src/OperationPath";
5
+ import * as Operation from "../../src/Operation";
6
+
7
+ describe("EitherPrimitive", () => {
8
+ describe("proxy", () => {
9
+ describe("set()", () => {
10
+ it("generates correct operation with string payload", () => {
11
+ const operations: Operation.Operation<any, any, any>[] = [];
12
+ const env = ProxyEnvironment.make((op) => operations.push(op));
13
+
14
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
15
+ const proxy = either._internal.createProxy(env, OperationPath.make(""));
16
+
17
+ proxy.set("hello");
18
+
19
+ expect(operations).toHaveLength(1);
20
+ expect(operations[0]!.kind).toBe("either.set");
21
+ expect(operations[0]!.payload).toBe("hello");
22
+ });
23
+
24
+ it("generates correct operation with number payload", () => {
25
+ const operations: Operation.Operation<any, any, any>[] = [];
26
+ const env = ProxyEnvironment.make((op) => operations.push(op));
27
+
28
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
29
+ const proxy = either._internal.createProxy(env, OperationPath.make(""));
30
+
31
+ proxy.set(42);
32
+
33
+ expect(operations).toHaveLength(1);
34
+ expect(operations[0]!.kind).toBe("either.set");
35
+ expect(operations[0]!.payload).toBe(42);
36
+ });
37
+
38
+ it("generates correct operation with boolean payload", () => {
39
+ const operations: Operation.Operation<any, any, any>[] = [];
40
+ const env = ProxyEnvironment.make((op) => operations.push(op));
41
+
42
+ const either = Primitive.Either(Primitive.String(), Primitive.Boolean());
43
+ const proxy = either._internal.createProxy(env, OperationPath.make(""));
44
+
45
+ proxy.set(true);
46
+
47
+ expect(operations).toHaveLength(1);
48
+ expect(operations[0]!.kind).toBe("either.set");
49
+ expect(operations[0]!.payload).toBe(true);
50
+ });
51
+
52
+ it("includes the correct path in operation", () => {
53
+ const operations: Operation.Operation<any, any, any>[] = [];
54
+ const env = ProxyEnvironment.make((op) => operations.push(op));
55
+
56
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
57
+ const proxy = either._internal.createProxy(env, OperationPath.make("status"));
58
+
59
+ proxy.set("active");
60
+
61
+ expect(operations[0]!.path.toTokens()).toEqual(["status"]);
62
+ });
63
+ });
64
+
65
+ describe("get()", () => {
66
+ it("returns undefined when no value is set and no default", () => {
67
+ const env = ProxyEnvironment.make(() => {});
68
+
69
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
70
+ const proxy = either._internal.createProxy(env, OperationPath.make(""));
71
+
72
+ expect(proxy.get()).toBeUndefined();
73
+ });
74
+
75
+ it("returns default value when no state is set", () => {
76
+ const env = ProxyEnvironment.make(() => {});
77
+
78
+ const either = Primitive.Either(Primitive.String(), Primitive.Number()).default("pending");
79
+ const proxy = either._internal.createProxy(env, OperationPath.make(""));
80
+
81
+ expect(proxy.get()).toBe("pending");
82
+ });
83
+ });
84
+
85
+ describe("toSnapshot()", () => {
86
+ it("returns undefined when no value is set and no default", () => {
87
+ const env = ProxyEnvironment.make(() => {});
88
+
89
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
90
+ const proxy = either._internal.createProxy(env, OperationPath.make(""));
91
+
92
+ expect(proxy.toSnapshot()).toBeUndefined();
93
+ });
94
+
95
+ it("returns default value when no state is set", () => {
96
+ const env = ProxyEnvironment.make(() => {});
97
+
98
+ const either = Primitive.Either(Primitive.String(), Primitive.Number()).default(100);
99
+ const proxy = either._internal.createProxy(env, OperationPath.make(""));
100
+
101
+ expect(proxy.toSnapshot()).toBe(100);
102
+ });
103
+ });
104
+
105
+ describe("match()", () => {
106
+ it("returns undefined when value is undefined", () => {
107
+ const env = ProxyEnvironment.make(() => {});
108
+
109
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
110
+ const proxy = either._internal.createProxy(env, OperationPath.make(""));
111
+
112
+ const result = proxy.match({
113
+ string: (s) => `string: ${s}`,
114
+ number: (n) => `number: ${n}`,
115
+ });
116
+
117
+ expect(result).toBeUndefined();
118
+ });
119
+
120
+ it("routes to string handler when value is a string", () => {
121
+ const state: Record<string, unknown> = { value: "hello" };
122
+ const env = ProxyEnvironment.make({
123
+ onOperation: () => {},
124
+ getState: (path) => {
125
+ const tokens = path.toTokens().filter((t) => t !== "");
126
+ if (tokens.length === 0) return undefined;
127
+ return state[tokens[0]!];
128
+ },
129
+ });
130
+
131
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
132
+ const proxy = either._internal.createProxy(env, OperationPath.make("value"));
133
+
134
+ const result = proxy.match({
135
+ string: (s) => `string: ${s}`,
136
+ number: (n) => `number: ${n}`,
137
+ });
138
+
139
+ expect(result).toBe("string: hello");
140
+ });
141
+
142
+ it("routes to number handler when value is a number", () => {
143
+ const state: Record<string, unknown> = { value: 42 };
144
+ const env = ProxyEnvironment.make({
145
+ onOperation: () => {},
146
+ getState: (path) => {
147
+ const tokens = path.toTokens().filter((t) => t !== "");
148
+ if (tokens.length === 0) return undefined;
149
+ return state[tokens[0]!];
150
+ },
151
+ });
152
+
153
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
154
+ const proxy = either._internal.createProxy(env, OperationPath.make("value"));
155
+
156
+ const result = proxy.match({
157
+ string: (s) => `string: ${s}`,
158
+ number: (n) => `number: ${n}`,
159
+ });
160
+
161
+ expect(result).toBe("number: 42");
162
+ });
163
+
164
+ it("routes to boolean handler when value is a boolean", () => {
165
+ const state: Record<string, unknown> = { value: true };
166
+ const env = ProxyEnvironment.make({
167
+ onOperation: () => {},
168
+ getState: (path) => {
169
+ const tokens = path.toTokens().filter((t) => t !== "");
170
+ if (tokens.length === 0) return undefined;
171
+ return state[tokens[0]!];
172
+ },
173
+ });
174
+
175
+ const either = Primitive.Either(Primitive.String(), Primitive.Boolean());
176
+ const proxy = either._internal.createProxy(env, OperationPath.make("value"));
177
+
178
+ const result = proxy.match({
179
+ string: (s) => `string: ${s}`,
180
+ boolean: (b) => `boolean: ${b}`,
181
+ });
182
+
183
+ expect(result).toBe("boolean: true");
184
+ });
185
+
186
+ it("routes to literal handler when value matches a literal", () => {
187
+ const state: Record<string, unknown> = { value: "auto" };
188
+ const env = ProxyEnvironment.make({
189
+ onOperation: () => {},
190
+ getState: (path) => {
191
+ const tokens = path.toTokens().filter((t) => t !== "");
192
+ if (tokens.length === 0) return undefined;
193
+ return state[tokens[0]!];
194
+ },
195
+ });
196
+
197
+ const either = Primitive.Either(Primitive.Literal("auto"), Primitive.Literal("manual"));
198
+ const proxy = either._internal.createProxy(env, OperationPath.make("value"));
199
+
200
+ const result = proxy.match({
201
+ literal: (v) => `literal: ${v}`,
202
+ });
203
+
204
+ expect(result).toBe("literal: auto");
205
+ });
206
+
207
+ it("uses default value when state is undefined", () => {
208
+ const env = ProxyEnvironment.make(() => {});
209
+
210
+ const either = Primitive.Either(Primitive.String(), Primitive.Number()).default("default");
211
+ const proxy = either._internal.createProxy(env, OperationPath.make(""));
212
+
213
+ const result = proxy.match({
214
+ string: (s) => `string: ${s}`,
215
+ number: (n) => `number: ${n}`,
216
+ });
217
+
218
+ expect(result).toBe("string: default");
219
+ });
220
+
221
+ it("returns undefined when no handler matches", () => {
222
+ const state: Record<string, unknown> = { value: "hello" };
223
+ const env = ProxyEnvironment.make({
224
+ onOperation: () => {},
225
+ getState: (path) => {
226
+ const tokens = path.toTokens().filter((t) => t !== "");
227
+ if (tokens.length === 0) return undefined;
228
+ return state[tokens[0]!];
229
+ },
230
+ });
231
+
232
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
233
+ const proxy = either._internal.createProxy(env, OperationPath.make("value"));
234
+
235
+ const result = proxy.match({
236
+ number: (n) => `number: ${n}`,
237
+ });
238
+
239
+ expect(result).toBeUndefined();
240
+ });
241
+ });
242
+ });
243
+
244
+ describe("applyOperation", () => {
245
+ it("accepts string payload when String is a variant", () => {
246
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
247
+ const operation: Operation.Operation<any, any, any> = {
248
+ kind: "either.set",
249
+ path: OperationPath.make(""),
250
+ payload: "hello",
251
+ };
252
+
253
+ const result = either._internal.applyOperation(undefined, operation);
254
+ expect(result).toBe("hello");
255
+ });
256
+
257
+ it("accepts number payload when Number is a variant", () => {
258
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
259
+ const operation: Operation.Operation<any, any, any> = {
260
+ kind: "either.set",
261
+ path: OperationPath.make(""),
262
+ payload: 42,
263
+ };
264
+
265
+ const result = either._internal.applyOperation(undefined, operation);
266
+ expect(result).toBe(42);
267
+ });
268
+
269
+ it("accepts boolean payload when Boolean is a variant", () => {
270
+ const either = Primitive.Either(Primitive.String(), Primitive.Boolean());
271
+ const operation: Operation.Operation<any, any, any> = {
272
+ kind: "either.set",
273
+ path: OperationPath.make(""),
274
+ payload: true,
275
+ };
276
+
277
+ const result = either._internal.applyOperation(undefined, operation);
278
+ expect(result).toBe(true);
279
+ });
280
+
281
+ it("accepts literal payload when Literal is a variant", () => {
282
+ const either = Primitive.Either(Primitive.Literal("auto"), Primitive.Literal("manual"));
283
+ const operation: Operation.Operation<any, any, any> = {
284
+ kind: "either.set",
285
+ path: OperationPath.make(""),
286
+ payload: "auto",
287
+ };
288
+
289
+ const result = either._internal.applyOperation(undefined, operation);
290
+ expect(result).toBe("auto");
291
+ });
292
+
293
+ it("replaces existing state with new value", () => {
294
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
295
+ const operation: Operation.Operation<any, any, any> = {
296
+ kind: "either.set",
297
+ path: OperationPath.make(""),
298
+ payload: 100,
299
+ };
300
+
301
+ const result = either._internal.applyOperation("old value", operation);
302
+ expect(result).toBe(100);
303
+ });
304
+
305
+ it("throws ValidationError for payload not matching any variant", () => {
306
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
307
+ const operation: Operation.Operation<any, any, any> = {
308
+ kind: "either.set",
309
+ path: OperationPath.make(""),
310
+ payload: true, // boolean not allowed
311
+ };
312
+
313
+ expect(() => either._internal.applyOperation(undefined, operation)).toThrow(
314
+ Primitive.ValidationError
315
+ );
316
+ });
317
+
318
+ it("throws ValidationError for wrong operation kind", () => {
319
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
320
+ const operation: Operation.Operation<any, any, any> = {
321
+ kind: "string.set",
322
+ path: OperationPath.make(""),
323
+ payload: "hello",
324
+ };
325
+
326
+ expect(() => either._internal.applyOperation(undefined, operation)).toThrow(
327
+ Primitive.ValidationError
328
+ );
329
+ });
330
+
331
+ it("throws ValidationError for object payload", () => {
332
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
333
+ const operation: Operation.Operation<any, any, any> = {
334
+ kind: "either.set",
335
+ path: OperationPath.make(""),
336
+ payload: { invalid: true },
337
+ };
338
+
339
+ expect(() => either._internal.applyOperation(undefined, operation)).toThrow(
340
+ Primitive.ValidationError
341
+ );
342
+ });
343
+ });
344
+
345
+ describe("getInitialState", () => {
346
+ it("returns undefined when no default is set", () => {
347
+ const either = Primitive.Either(Primitive.String(), Primitive.Number());
348
+ expect(either._internal.getInitialState()).toBeUndefined();
349
+ });
350
+
351
+ it("returns the string default value when set", () => {
352
+ const either = Primitive.Either(Primitive.String(), Primitive.Number()).default("pending");
353
+ expect(either._internal.getInitialState()).toBe("pending");
354
+ });
355
+
356
+ it("returns the number default value when set", () => {
357
+ const either = Primitive.Either(Primitive.String(), Primitive.Number()).default(0);
358
+ expect(either._internal.getInitialState()).toBe(0);
359
+ });
360
+
361
+ it("returns the boolean default value when set", () => {
362
+ const either = Primitive.Either(Primitive.String(), Primitive.Boolean()).default(false);
363
+ expect(either._internal.getInitialState()).toBe(false);
364
+ });
365
+ });
366
+
367
+ describe("schema modifiers", () => {
368
+ it("required() returns a new EitherPrimitive", () => {
369
+ const original = Primitive.Either(Primitive.String(), Primitive.Number());
370
+ const required = original.required();
371
+
372
+ expect(required).toBeInstanceOf(Primitive.EitherPrimitive);
373
+ expect(required).not.toBe(original);
374
+ });
375
+
376
+ it("default() returns a new EitherPrimitive with default value", () => {
377
+ const original = Primitive.Either(Primitive.String(), Primitive.Number());
378
+ const withDefault = original.default("default");
379
+
380
+ expect(withDefault).toBeInstanceOf(Primitive.EitherPrimitive);
381
+ expect(withDefault._internal.getInitialState()).toBe("default");
382
+ });
383
+
384
+ it("preserves variants after required()", () => {
385
+ const original = Primitive.Either(Primitive.String(), Primitive.Number());
386
+ const required = original.required();
387
+
388
+ expect(required.variants).toEqual(original.variants);
389
+ });
390
+
391
+ it("preserves variants after default()", () => {
392
+ const original = Primitive.Either(Primitive.String(), Primitive.Number());
393
+ const withDefault = original.default("default");
394
+
395
+ expect(withDefault.variants).toEqual(original.variants);
396
+ });
397
+ });
398
+
399
+ describe("multi-variant tests", () => {
400
+ it("accepts all three scalar types", () => {
401
+ const either = Primitive.Either(Primitive.String(), Primitive.Number(), Primitive.Boolean());
402
+
403
+ // String
404
+ const stringOp: Operation.Operation<any, any, any> = {
405
+ kind: "either.set",
406
+ path: OperationPath.make(""),
407
+ payload: "test",
408
+ };
409
+ expect(either._internal.applyOperation(undefined, stringOp)).toBe("test");
410
+
411
+ // Number
412
+ const numberOp: Operation.Operation<any, any, any> = {
413
+ kind: "either.set",
414
+ path: OperationPath.make(""),
415
+ payload: 123,
416
+ };
417
+ expect(either._internal.applyOperation(undefined, numberOp)).toBe(123);
418
+
419
+ // Boolean
420
+ const booleanOp: Operation.Operation<any, any, any> = {
421
+ kind: "either.set",
422
+ path: OperationPath.make(""),
423
+ payload: false,
424
+ };
425
+ expect(either._internal.applyOperation(undefined, booleanOp)).toBe(false);
426
+ });
427
+
428
+ it("works with mixed literal and scalar types", () => {
429
+ const either = Primitive.Either(
430
+ Primitive.Literal("auto"),
431
+ Primitive.Literal("manual"),
432
+ Primitive.Number()
433
+ );
434
+
435
+ // Literal "auto"
436
+ const autoOp: Operation.Operation<any, any, any> = {
437
+ kind: "either.set",
438
+ path: OperationPath.make(""),
439
+ payload: "auto",
440
+ };
441
+ expect(either._internal.applyOperation(undefined, autoOp)).toBe("auto");
442
+
443
+ // Literal "manual"
444
+ const manualOp: Operation.Operation<any, any, any> = {
445
+ kind: "either.set",
446
+ path: OperationPath.make(""),
447
+ payload: "manual",
448
+ };
449
+ expect(either._internal.applyOperation(undefined, manualOp)).toBe("manual");
450
+
451
+ // Number
452
+ const numberOp: Operation.Operation<any, any, any> = {
453
+ kind: "either.set",
454
+ path: OperationPath.make(""),
455
+ payload: 50,
456
+ };
457
+ expect(either._internal.applyOperation(undefined, numberOp)).toBe(50);
458
+
459
+ // Other string should fail (not a literal)
460
+ const invalidOp: Operation.Operation<any, any, any> = {
461
+ kind: "either.set",
462
+ path: OperationPath.make(""),
463
+ payload: "other",
464
+ };
465
+ expect(() => either._internal.applyOperation(undefined, invalidOp)).toThrow(
466
+ Primitive.ValidationError
467
+ );
468
+ });
469
+ });
470
+
471
+ describe("creation validation", () => {
472
+ it("throws when created with no variants", () => {
473
+ expect(() => Primitive.Either()).toThrow(Primitive.ValidationError);
474
+ });
475
+ });
476
+
477
+ describe("variant validator delegation", () => {
478
+ it("validates string min length from variant", () => {
479
+ const either = Primitive.Either(
480
+ Primitive.String().min(2),
481
+ Primitive.Number()
482
+ );
483
+
484
+ // Valid string (length >= 2)
485
+ const validOp: Operation.Operation<any, any, any> = {
486
+ kind: "either.set",
487
+ path: OperationPath.make(""),
488
+ payload: "hello",
489
+ };
490
+ expect(either._internal.applyOperation(undefined, validOp)).toBe("hello");
491
+
492
+ // Invalid string (length < 2)
493
+ const invalidOp: Operation.Operation<any, any, any> = {
494
+ kind: "either.set",
495
+ path: OperationPath.make(""),
496
+ payload: "a",
497
+ };
498
+ expect(() => either._internal.applyOperation(undefined, invalidOp)).toThrow(
499
+ Primitive.ValidationError
500
+ );
501
+ });
502
+
503
+ it("validates string max length from variant", () => {
504
+ const either = Primitive.Either(
505
+ Primitive.String().max(5),
506
+ Primitive.Number()
507
+ );
508
+
509
+ // Valid string (length <= 5)
510
+ const validOp: Operation.Operation<any, any, any> = {
511
+ kind: "either.set",
512
+ path: OperationPath.make(""),
513
+ payload: "hello",
514
+ };
515
+ expect(either._internal.applyOperation(undefined, validOp)).toBe("hello");
516
+
517
+ // Invalid string (length > 5)
518
+ const invalidOp: Operation.Operation<any, any, any> = {
519
+ kind: "either.set",
520
+ path: OperationPath.make(""),
521
+ payload: "hello world",
522
+ };
523
+ expect(() => either._internal.applyOperation(undefined, invalidOp)).toThrow(
524
+ Primitive.ValidationError
525
+ );
526
+ });
527
+
528
+ it("validates number max from variant", () => {
529
+ const either = Primitive.Either(
530
+ Primitive.String(),
531
+ Primitive.Number().max(255)
532
+ );
533
+
534
+ // Valid number (<= 255)
535
+ const validOp: Operation.Operation<any, any, any> = {
536
+ kind: "either.set",
537
+ path: OperationPath.make(""),
538
+ payload: 100,
539
+ };
540
+ expect(either._internal.applyOperation(undefined, validOp)).toBe(100);
541
+
542
+ // Invalid number (> 255)
543
+ const invalidOp: Operation.Operation<any, any, any> = {
544
+ kind: "either.set",
545
+ path: OperationPath.make(""),
546
+ payload: 300,
547
+ };
548
+ expect(() => either._internal.applyOperation(undefined, invalidOp)).toThrow(
549
+ Primitive.ValidationError
550
+ );
551
+ });
552
+
553
+ it("validates number min from variant", () => {
554
+ const either = Primitive.Either(
555
+ Primitive.String(),
556
+ Primitive.Number().min(0)
557
+ );
558
+
559
+ // Valid number (>= 0)
560
+ const validOp: Operation.Operation<any, any, any> = {
561
+ kind: "either.set",
562
+ path: OperationPath.make(""),
563
+ payload: 0,
564
+ };
565
+ expect(either._internal.applyOperation(undefined, validOp)).toBe(0);
566
+
567
+ // Invalid number (< 0)
568
+ const invalidOp: Operation.Operation<any, any, any> = {
569
+ kind: "either.set",
570
+ path: OperationPath.make(""),
571
+ payload: -5,
572
+ };
573
+ expect(() => either._internal.applyOperation(undefined, invalidOp)).toThrow(
574
+ Primitive.ValidationError
575
+ );
576
+ });
577
+
578
+ it("validates combined string and number constraints", () => {
579
+ const either = Primitive.Either(
580
+ Primitive.String().min(2).max(50),
581
+ Primitive.Number().max(255)
582
+ );
583
+
584
+ // Valid string
585
+ const validStringOp: Operation.Operation<any, any, any> = {
586
+ kind: "either.set",
587
+ path: OperationPath.make(""),
588
+ payload: "valid",
589
+ };
590
+ expect(either._internal.applyOperation(undefined, validStringOp)).toBe("valid");
591
+
592
+ // Valid number
593
+ const validNumberOp: Operation.Operation<any, any, any> = {
594
+ kind: "either.set",
595
+ path: OperationPath.make(""),
596
+ payload: 200,
597
+ };
598
+ expect(either._internal.applyOperation(undefined, validNumberOp)).toBe(200);
599
+
600
+ // Invalid string (too short)
601
+ const invalidStringOp: Operation.Operation<any, any, any> = {
602
+ kind: "either.set",
603
+ path: OperationPath.make(""),
604
+ payload: "a",
605
+ };
606
+ expect(() => either._internal.applyOperation(undefined, invalidStringOp)).toThrow(
607
+ Primitive.ValidationError
608
+ );
609
+
610
+ // Invalid number (too large)
611
+ const invalidNumberOp: Operation.Operation<any, any, any> = {
612
+ kind: "either.set",
613
+ path: OperationPath.make(""),
614
+ payload: 500,
615
+ };
616
+ expect(() => either._internal.applyOperation(undefined, invalidNumberOp)).toThrow(
617
+ Primitive.ValidationError
618
+ );
619
+ });
620
+
621
+ it("validates string regex pattern from variant", () => {
622
+ const either = Primitive.Either(
623
+ Primitive.String().regex(/^[a-z]+$/),
624
+ Primitive.Number()
625
+ );
626
+
627
+ // Valid string (lowercase only)
628
+ const validOp: Operation.Operation<any, any, any> = {
629
+ kind: "either.set",
630
+ path: OperationPath.make(""),
631
+ payload: "hello",
632
+ };
633
+ expect(either._internal.applyOperation(undefined, validOp)).toBe("hello");
634
+
635
+ // Invalid string (has uppercase)
636
+ const invalidOp: Operation.Operation<any, any, any> = {
637
+ kind: "either.set",
638
+ path: OperationPath.make(""),
639
+ payload: "Hello",
640
+ };
641
+ expect(() => either._internal.applyOperation(undefined, invalidOp)).toThrow(
642
+ Primitive.ValidationError
643
+ );
644
+ });
645
+
646
+ it("validates number positive constraint from variant", () => {
647
+ const either = Primitive.Either(
648
+ Primitive.String(),
649
+ Primitive.Number().positive()
650
+ );
651
+
652
+ // Valid positive number
653
+ const validOp: Operation.Operation<any, any, any> = {
654
+ kind: "either.set",
655
+ path: OperationPath.make(""),
656
+ payload: 1,
657
+ };
658
+ expect(either._internal.applyOperation(undefined, validOp)).toBe(1);
659
+
660
+ // Invalid (zero is not positive)
661
+ const zeroOp: Operation.Operation<any, any, any> = {
662
+ kind: "either.set",
663
+ path: OperationPath.make(""),
664
+ payload: 0,
665
+ };
666
+ expect(() => either._internal.applyOperation(undefined, zeroOp)).toThrow(
667
+ Primitive.ValidationError
668
+ );
669
+
670
+ // Invalid negative number
671
+ const negativeOp: Operation.Operation<any, any, any> = {
672
+ kind: "either.set",
673
+ path: OperationPath.make(""),
674
+ payload: -1,
675
+ };
676
+ expect(() => either._internal.applyOperation(undefined, negativeOp)).toThrow(
677
+ Primitive.ValidationError
678
+ );
679
+ });
680
+
681
+ it("validates number int constraint from variant", () => {
682
+ const either = Primitive.Either(
683
+ Primitive.String(),
684
+ Primitive.Number().int()
685
+ );
686
+
687
+ // Valid integer
688
+ const validOp: Operation.Operation<any, any, any> = {
689
+ kind: "either.set",
690
+ path: OperationPath.make(""),
691
+ payload: 42,
692
+ };
693
+ expect(either._internal.applyOperation(undefined, validOp)).toBe(42);
694
+
695
+ // Invalid (decimal number)
696
+ const decimalOp: Operation.Operation<any, any, any> = {
697
+ kind: "either.set",
698
+ path: OperationPath.make(""),
699
+ payload: 3.14,
700
+ };
701
+ expect(() => either._internal.applyOperation(undefined, decimalOp)).toThrow(
702
+ Primitive.ValidationError
703
+ );
704
+ });
705
+ });
706
+ });
707
+