@voidhash/mimic 0.0.1-alpha.1 → 0.0.1-alpha.10
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/.turbo/turbo-build.log +51 -0
- package/LICENSE.md +663 -0
- package/dist/Document-ChuFrTk1.cjs +571 -0
- package/dist/Document-CwiAFTIq.mjs +438 -0
- package/dist/Document-CwiAFTIq.mjs.map +1 -0
- package/dist/Presence-DKKP4v5X.d.cts +91 -0
- package/dist/Presence-DKKP4v5X.d.cts.map +1 -0
- package/dist/Presence-DdMVKcOv.mjs +110 -0
- package/dist/Presence-DdMVKcOv.mjs.map +1 -0
- package/dist/Presence-N8u7Eppr.d.mts +91 -0
- package/dist/Presence-N8u7Eppr.d.mts.map +1 -0
- package/dist/Presence-gWrmGBeu.cjs +126 -0
- package/dist/Primitive-CvFVxR8_.d.cts +1175 -0
- package/dist/Primitive-CvFVxR8_.d.cts.map +1 -0
- package/dist/Primitive-lEhQyGVL.d.mts +1175 -0
- package/dist/Primitive-lEhQyGVL.d.mts.map +1 -0
- package/dist/chunk-CLMFDpHK.mjs +18 -0
- package/dist/client/index.cjs +1456 -0
- package/dist/client/index.d.cts +692 -0
- package/dist/client/index.d.cts.map +1 -0
- package/dist/client/index.d.mts +692 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +1413 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/index.cjs +2577 -0
- package/dist/index.d.cts +143 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +143 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2526 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server/index.cjs +191 -0
- package/dist/server/index.d.cts +148 -0
- package/dist/server/index.d.cts.map +1 -0
- package/dist/server/index.d.mts +148 -0
- package/dist/server/index.d.mts.map +1 -0
- package/dist/server/index.mjs +182 -0
- package/dist/server/index.mjs.map +1 -0
- package/package.json +25 -13
- package/src/EffectSchema.ts +374 -0
- package/src/Primitive.ts +3 -0
- package/src/client/ClientDocument.ts +1 -1
- package/src/client/errors.ts +10 -10
- package/src/index.ts +1 -0
- package/src/primitives/Array.ts +57 -22
- package/src/primitives/Boolean.ts +33 -19
- package/src/primitives/Either.ts +379 -0
- package/src/primitives/Lazy.ts +16 -2
- package/src/primitives/Literal.ts +33 -20
- package/src/primitives/Number.ts +39 -26
- package/src/primitives/String.ts +40 -25
- package/src/primitives/Struct.ts +126 -29
- package/src/primitives/Tree.ts +119 -32
- package/src/primitives/TreeNode.ts +77 -30
- package/src/primitives/Union.ts +56 -29
- package/src/primitives/shared.ts +111 -9
- package/src/server/errors.ts +6 -6
- package/tests/EffectSchema.test.ts +546 -0
- package/tests/primitives/Array.test.ts +108 -0
- package/tests/primitives/Either.test.ts +707 -0
- package/tests/primitives/Struct.test.ts +250 -0
- package/tests/primitives/Tree.test.ts +250 -0
- package/tsdown.config.ts +1 -1
|
@@ -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
|
+
|