1import {
2 BigDecimal,
3 DateTime,
4 Duration,
5 Equivalence,
6 HashMap,
7 Option,
8 Redacted,
9 Result,
10 Schema,
11 SchemaGetter
12} from "effect"
13import { describe, it } from "vitest"
14import { assertFalse, assertTrue, strictEqual } from "../utils/assert.ts"
15
16const Modulo2 = Schema.Number.annotate({
17 toEquivalence: (): Equivalence.Equivalence<number> => Equivalence.make((a, b) => a % 2 === b % 2)
18})
19
20const Modulo3 = Schema.Number.annotate({
21 toEquivalence: (): Equivalence.Equivalence<number> => Equivalence.make((a, b) => a % 3 === b % 3)
22})
23
24describe("toEquivalence", () => {
25 it("Never", () => {
26 const equivalence = Schema.toEquivalence(Schema.Never)
27 const value = {} as never
28
29 assertTrue(equivalence(value, value))
30 assertFalse(equivalence({} as never, {} as never))
31 })
32
33 it("String", () => {
34 const schema = Schema.String
35 const equivalence = Schema.toEquivalence(schema)
36 assertTrue(equivalence("a", "a"))
37 assertFalse(equivalence("a", "b"))
38 })
39
40 describe("Tuple", () => {
41 it("should fail on non-array inputs", () => {
42 const schema = Schema.Tuple([Schema.String, Schema.Number])
43 const equivalence = Schema.toEquivalence(schema)
44 assertFalse(equivalence(["a", 1], null as never))
45 })
46
47 it("empty", () => {
48 const schema = Schema.Tuple([])
49 const equivalence = Schema.toEquivalence(schema)
50 assertTrue(equivalence([], []))
51 })
52
53 it("required elements", () => {
54 const schema = Schema.Tuple([Schema.String, Schema.Number])
55 const equivalence = Schema.toEquivalence(schema)
56 assertTrue(equivalence(["a", 1], ["a", 1]))
57 assertFalse(equivalence(["a", 1], ["b", 1]))
58 })
59
60 it("optionalKey elements", () => {
61 const schema = Schema.Tuple([Schema.String, Schema.optionalKey(Schema.Number)])
62 const equivalence = Schema.toEquivalence(schema)
63 assertTrue(equivalence(["a", 1], ["a", 1]))
64 assertTrue(equivalence(["a"], ["a"]))
65 assertFalse(equivalence(["a", 1], ["b", 1]))
66 assertFalse(equivalence(["a"], ["b"]))
67 })
68
69 it("optional elements", () => {
70 const schema = Schema.Tuple([Schema.String, Schema.optional(Schema.Number)])
71 const equivalence = Schema.toEquivalence(schema)
72 assertTrue(equivalence(["a", 1], ["a", 1]))
73 assertTrue(equivalence(["a"], ["a"]))
74 assertTrue(equivalence(["a", undefined], ["a", undefined]))
75 assertFalse(equivalence(["a", 1], ["b", 1]))
76 assertFalse(equivalence(["a"], ["b"]))
77 assertFalse(equivalence(["a", undefined], ["b", undefined]))
78 })
79 })
80
81 it("Array", () => {
82 const schema = Schema.Array(Schema.String)
83 const equivalence = Schema.toEquivalence(schema)
84 assertTrue(equivalence(["a", "b", "c"], ["a", "b", "c"]))
85 assertFalse(equivalence(["a", "b", "c"], ["a", "b", "d"]))
86 assertFalse(equivalence(["a", "b", "c"], ["a", "b"]))
87 assertFalse(equivalence(["a", "b", "c"], ["a", "b", "c", "d"]))
88 })
89
90 it("TupleWithRest", () => {
91 const schema = Schema.TupleWithRest(Schema.Tuple([Schema.String, Schema.Number]), [Schema.String, Schema.Number])
92 const equivalence = Schema.toEquivalence(schema)
93 assertTrue(equivalence(["a", 1, 2], ["a", 1, 2]))
94 assertTrue(equivalence(["a", 1, "b", 2], ["a", 1, "b", 2]))
95
96 assertFalse(equivalence(["a", 1, 2], ["a", 2, 2]))
97 assertFalse(equivalence(["a", 1, 2], ["a", 1, 3]))
98 assertFalse(equivalence(["a", 1, "b", 2], ["c", 1, "b", 2]))
99 assertFalse(equivalence(["a", 1, "b", 2], ["a", 1, "c", 2]))
100 assertFalse(equivalence(["a", 1, "b", 2], ["a", 2, "b", 2]))
101 assertFalse(equivalence(["a", 1, "b", 2], ["a", 1, "b", 3]))
102 })
103
104 it("TupleWithRest with multiple post-rest elements", () => {
105 const schema = Schema.TupleWithRest(Schema.Tuple([Schema.String]), [
106 Schema.String,
107 Schema.Number,
108 Schema.Boolean,
109 Schema.String
110 ])
111 const equivalence = Schema.toEquivalence(schema)
112 assertTrue(equivalence(["head", "tail", 1, true, "last"], ["head", "tail", 1, true, "last"]))
113 assertFalse(equivalence(["head", "tail", 1, true, "A"], ["head", "tail", 1, true, "B"]))
114 })
115
116 describe("rest elements", () => {
117 // Pairwise distinct, so a rest element read at the wrong index is observable.
118 const Mod = (n: number) =>
119 Schema.Number.annotate({
120 toEquivalence: (): Equivalence.Equivalence<number> => Equivalence.make((a, b) => a % n === b % n)
121 })
122
123 it("one trailing element after the rest element", () => {
124 const schema = Schema.TupleWithRest(Schema.Tuple([Mod(2)]), [Mod(3), Mod(5)])
125 const equivalence = Schema.toEquivalence(schema)
126 assertTrue(equivalence([2, 5], [4, 10]))
127 assertFalse(equivalence([2, 5], [4, 7]))
128 assertTrue(equivalence([2, 3, 5], [4, 6, 10]))
129 assertFalse(equivalence([2, 3, 5], [4, 7, 10]))
130 assertFalse(equivalence([2, 3, 5], [4, 6, 7]))
131 assertTrue(equivalence([2, 3, 6, 5], [4, 6, 9, 10]))
132 assertFalse(equivalence([2, 3, 6, 5], [4, 6, 10, 10]))
133 })
134
135 it("multiple trailing elements after the rest element", () => {
136 const equivalence = Schema.toEquivalence(
137 Schema.TupleWithRest(Schema.Tuple([Mod(2)]), [Mod(3), Mod(5), Mod(7)])
138 )
139 assertTrue(equivalence([2, 5, 7], [4, 10, 14]))
140 assertFalse(equivalence([2, 5, 7], [4, 7, 14]))
141 assertFalse(equivalence([2, 5, 7], [4, 10, 9]))
142 assertTrue(equivalence([2, 3, 5, 7], [4, 6, 10, 14]))
143 assertFalse(equivalence([2, 3, 5, 7], [4, 7, 10, 14]))
144 })
145 })
146
147 describe("Struct", () => {
148 it("should fail on non-record inputs", () => {
149 const schema = Schema.Struct({ a: Schema.String })
150 const equivalence = Schema.toEquivalence(schema)
151 assertFalse(equivalence({ a: "a" }, 1 as never))
152 })
153
154 it("empty", () => {
155 const schema = Schema.Struct({})
156 const equivalence = Schema.toEquivalence(schema)
157 const a = {}
158 assertTrue(equivalence(a, a))
159 assertTrue(equivalence({}, {})) // Now supports structural equality
160 })
161
162 it("required fields", () => {
163 const schema = Schema.Struct({
164 a: Schema.String,
165 b: Schema.Number
166 })
167 const equivalence = Schema.toEquivalence(schema)
168 assertTrue(equivalence({ a: "a", b: 1 }, { a: "a", b: 1 }))
169 assertFalse(equivalence({ a: "a", b: 1 }, { a: "b", b: 1 }))
170 assertFalse(equivalence({ a: "a", b: 1 }, { a: "a", b: 2 }))
171 })
172
173 it("symbol keys", () => {
174 const a = Symbol.for("a")
175 const b = Symbol.for("b")
176 const schema = Schema.Struct({
177 [a]: Schema.String,
178 [b]: Schema.Number
179 })
180 const equivalence = Schema.toEquivalence(schema)
181 assertTrue(
182 equivalence({ [a]: "a", [b]: 1 }, { [a]: "a", [b]: 1 })
183 )
184 assertFalse(
185 equivalence({ [a]: "a", [b]: 1 }, { [a]: "b", [b]: 1 })
186 )
187 assertFalse(
188 equivalence({ [a]: "a", [b]: 1 }, { [a]: "a", [b]: 2 })
189 )
190 })
191
192 it("optionalKey fields", () => {
193 const schema = Schema.Struct({
194 a: Schema.String,
195 b: Schema.optionalKey(Schema.Number)
196 })
197 const equivalence = Schema.toEquivalence(schema)
198 assertTrue(equivalence({ a: "a", b: 1 }, { a: "a", b: 1 }))
199 assertTrue(equivalence({ a: "a" }, { a: "a" }))
200 assertFalse(equivalence({ a: "a" }, { a: "b" }))
201 assertFalse(equivalence({ a: "a", b: 1 }, { a: "b", b: 1 }))
202 assertFalse(equivalence({ a: "a", b: 1 }, { a: "a", b: 2 }))
203 })
204
205 it("optional fields", () => {
206 const schema = Schema.Struct({
207 a: Schema.String,
208 b: Schema.optional(Schema.Number)
209 })
210 const equivalence = Schema.toEquivalence(schema)
211 assertTrue(equivalence({ a: "a", b: 1 }, { a: "a", b: 1 }))
212 assertTrue(equivalence({ a: "a" }, { a: "a" }))
213 assertTrue(equivalence({ a: "a", b: undefined }, { a: "a", b: undefined }))
214 assertFalse(equivalence({ a: "a", b: 1 }, { a: "b", b: 1 }))
215 assertFalse(equivalence({ a: "a", b: 1 }, { a: "a", b: 2 }))
216 assertFalse(equivalence({ a: "a", b: 1 }, { a: "a", b: undefined }))
217 assertFalse(equivalence({ a: "a", b: undefined }, { a: "a", b: 1 }))
218 })
219 })
220
221 it("Class", () => {
222 class A extends Schema.Class<A>("A")({
223 value: Schema.String
224 }) {}
225
226 const equivalence = Schema.toEquivalence(A)
227 const a = new A({ value: "a" })
228 const b = new A({ value: "a" })
229 ;(b as A & { metadata?: number }).metadata = 1
230
231 assertTrue(equivalence(a, b))
232 assertFalse(equivalence(a, new A({ value: "b" })))
233 })
234
235 describe("Record", () => {
236 it("Record(String, Number)", () => {
237 const schema = Schema.Record(Schema.String, Schema.Number)
238 const equivalence = Schema.toEquivalence(schema)
239 assertTrue(equivalence({ a: 1, b: 2 }, { a: 1, b: 2 }))
240 assertFalse(equivalence({ a: 1, b: 2 }, { a: 1, b: 3 }))
241 assertFalse(equivalence({ a: 1, b: 2 }, { a: 2, b: 2 }))
242 assertFalse(equivalence({ a: 1, b: 2 }, { a: 1, b: 2, c: 3 }))
243 assertFalse(equivalence({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 }))
244 })
245
246 it("Record(String, UndefinedOr(Number))", () => {
247 const schema = Schema.Record(Schema.String, Schema.UndefinedOr(Schema.Number))
248 const equivalence = Schema.toEquivalence(schema)
249 assertTrue(equivalence({ a: 1, b: undefined }, { a: 1, b: undefined }))
250 assertFalse(equivalence({ a: 1, b: undefined }, { a: 1 }))
251 assertFalse(equivalence({ a: 1 }, { a: 1, b: undefined }))
252 })
253
254 it("Record(String.check, Number) should use the key checks to select keys", () => {
255 const schema = Schema.Record(Schema.String.check(Schema.isPattern(/^a/)), Schema.Number)
256 const equivalence = Schema.toEquivalence(schema)
257 assertTrue(equivalence({ a: 1, b: 1 }, { a: 1, b: 2 }))
258 assertFalse(equivalence({ a: 1 }, { a: 2 }))
259 })
260
261 it("Record(Symbol, Number)", () => {
262 const a = Symbol.for("a")
263 const b = Symbol.for("b")
264 const c = Symbol.for("c")
265 const schema = Schema.Record(Schema.Symbol, Schema.Number)
266 const equivalence = Schema.toEquivalence(schema)
267 assertTrue(
268 equivalence({ [a]: 1, [b]: 2 }, { [a]: 1, [b]: 2 })
269 )
270 assertFalse(
271 equivalence({ [a]: 1, [b]: 2 }, { [a]: 1, [b]: 3 })
272 )
273 assertFalse(
274 equivalence({ [a]: 1, [b]: 2 }, { [a]: 2, [b]: 2 })
275 )
276 assertFalse(
277 equivalence({ [a]: 1, [b]: 2 }, {
278 [a]: 1,
279 [b]: 2,
280 [c]: 3
281 })
282 )
283 assertFalse(
284 equivalence({ [a]: 1, [b]: 2, [c]: 3 }, {
285 [a]: 1,
286 [b]: 2
287 })
288 )
289 })
290 })
291
292 describe("suspend", () => {
293 it("reuses the compiled recursive body for deeper values", () => {
294 interface Tree {
295 readonly value: number
296 readonly children: ReadonlyArray<Tree>
297 }
298 let derivations = 0
299 const value = Schema.Number.annotate({
300 toEquivalence: () => {
301 derivations++
302 return Equivalence.strictEqual<number>()
303 }
304 })
305 const schema = Schema.Struct({
306 value,
307 children: Schema.Array(Schema.suspend((): Schema.Codec<Tree> => schema))
308 })
309 const make = (depth: number, leafValue = 0): Tree => ({
310 value: depth === 0 ? leafValue : depth,
311 children: depth === 0 ? [] : [make(depth - 1, leafValue)]
312 })
313 const equivalence = Schema.toEquivalence(schema)
314
315 strictEqual(derivations, 1)
316 for (const depth of [1, 8, 32]) {
317 assertTrue(equivalence(make(depth), make(depth)))
318 assertFalse(equivalence(make(depth), make(depth, -1)))
319 strictEqual(derivations, 1)
320 }
321 })
322
323 it("recursive schema", () => {
324 interface A {
325 readonly a: string
326 readonly as: ReadonlyArray<A>
327 }
328 const schema = Schema.Struct({
329 a: Schema.String,
330 as: Schema.Array(Schema.suspend((): Schema.Codec<A> => schema))
331 })
332 const equivalence = Schema.toEquivalence(schema)
333 assertTrue(equivalence({ a: "a", as: [] }, { a: "a", as: [] }))
334 assertFalse(equivalence({ a: "a", as: [] }, { a: "b", as: [] }))
335 assertFalse(equivalence({ a: "a", as: [{ a: "a", as: [] }] }, { a: "a", as: [] }))
336 assertFalse(equivalence({ a: "a", as: [] }, { a: "a", as: [{ a: "a", as: [] }] }))
337 })
338
339 it("mutually recursive schemas", () => {
340 interface Expression {
341 readonly type: "expression"
342 readonly value: number | Operation
343 }
344
345 interface Operation {
346 readonly type: "operation"
347 readonly operator: "+" | "-"
348 readonly left: Expression
349 readonly right: Expression
350 }
351
352 const Expression = Schema.Struct({
353 type: Schema.Literal("expression"),
354 value: Schema.Union([Schema.Finite, Schema.suspend((): Schema.Codec<Operation> => Operation)])
355 })
356
357 const Operation = Schema.Struct({
358 type: Schema.Literal("operation"),
359 operator: Schema.Literals(["+", "-"]),
360 left: Expression,
361 right: Expression
362 })
363
364 const schema = Operation
365 const equivalence = Schema.toEquivalence(schema)
366 assertTrue(
367 equivalence({
368 type: "operation",
369 operator: "+",
370 left: { type: "expression", value: 1 },
371 right: { type: "expression", value: 2 }
372 }, {
373 type: "operation",
374 operator: "+",
375 left: { type: "expression", value: 1 },
376 right: { type: "expression", value: 2 }
377 })
378 )
379 assertFalse(
380 equivalence({
381 type: "operation",
382 operator: "+",
383 left: { type: "expression", value: 1 },
384 right: { type: "expression", value: 2 }
385 }, {
386 type: "operation",
387 operator: "+",
388 left: { type: "expression", value: 1 },
389 right: { type: "expression", value: 3 }
390 })
391 )
392 assertFalse(
393 equivalence({
394 type: "operation",
395 operator: "+",
396 left: { type: "expression", value: 1 },
397 right: { type: "expression", value: 2 }
398 }, {
399 type: "operation",
400 operator: "-",
401 left: { type: "expression", value: 1 },
402 right: { type: "expression", value: 2 }
403 })
404 )
405 assertFalse(
406 equivalence({
407 type: "operation",
408 operator: "+",
409 left: { type: "expression", value: 1 },
410 right: { type: "expression", value: 2 }
411 }, {
412 type: "operation",
413 operator: "+",
414 left: { type: "expression", value: 2 },
415 right: { type: "expression", value: 2 }
416 })
417 )
418 })
419 })
420
421 it("precompiles Union members", () => {
422 let derivations = 0
423 const member = Schema.Struct({
424 tag: Schema.Literal("a"),
425 value: Schema.String
426 }).pipe(
427 Schema.overrideToEquivalence(() => {
428 derivations++
429 return Equivalence.make((a, b) => a.value === b.value)
430 })
431 )
432 const equivalence = Schema.toEquivalence(Schema.Union([member, Schema.Never]))
433
434 strictEqual(derivations, 1)
435 assertTrue(equivalence({ tag: "a", value: "a" }, { tag: "a", value: "a" }))
436 assertFalse(equivalence({ tag: "a", value: "a" }, { tag: "a", value: "b" }))
437 strictEqual(derivations, 1)
438 })
439
440 it("selects transformed Union members on the Type side", () => {
441 const Target = Schema.Struct({ value: Schema.String }).pipe(
442 Schema.overrideToEquivalence(() => Equivalence.make((a, b) => a.value === b.value))
443 )
444 const Transformed = Schema.String.pipe(
445 Schema.decodeTo(Target, {
446 decode: SchemaGetter.transform((s) => ({ value: s })),
447 encode: SchemaGetter.transform((a) => a.value)
448 })
449 )
450 const equivalence = Schema.toEquivalence(Schema.Union([Transformed, Schema.Boolean]))
451
452 assertTrue(equivalence({ value: "a" }, { value: "a" }))
453 assertFalse(equivalence({ value: "a" }, { value: "b" }))
454 })
455
456 it("preserves Union member equivalence annotations", () => {
457 const member = Schema.String.pipe(
458 Schema.flip,
459 Schema.check(Schema.makeFilter(() => true)),
460 Schema.flip,
461 Schema.overrideToEquivalence(() => Equivalence.make((a, b) => a[0] === b[0]))
462 )
463 const equivalence = Schema.toEquivalence(Schema.Union([member, Schema.Number]))
464
465 assertTrue(equivalence("ab", "ac"))
466 assertFalse(equivalence("ab", "bc"))
467 })
468
469 it("Date", () => {
470 const schema = Schema.Date
471 const equivalence = Schema.toEquivalence(schema)
472 assertTrue(equivalence(new Date(0), new Date(0)))
473 assertFalse(equivalence(new Date(0), new Date(1)))
474 })
475
476 it("URL", () => {
477 const schema = Schema.URL
478 const equivalence = Schema.toEquivalence(schema)
479 assertTrue(equivalence(new URL("https://example.com"), new URL("https://example.com")))
480 assertFalse(equivalence(new URL("https://example.com"), new URL("https://example.org")))
481 })
482
483 it("RegExp", () => {
484 const schema = Schema.RegExp
485 const equivalence = Schema.toEquivalence(schema)
486 assertTrue(equivalence(new RegExp("a"), new RegExp("a")))
487 assertTrue(equivalence(new RegExp("a", "i"), new RegExp("a", "i")))
488 assertFalse(equivalence(new RegExp("a"), new RegExp("b")))
489 assertFalse(equivalence(new RegExp("a", "i"), new RegExp("a", "g")))
490 })
491
492 it("Redacted(String)", () => {
493 const schema = Schema.Redacted(Schema.String)
494 const equivalence = Schema.toEquivalence(schema)
495 assertTrue(equivalence(Redacted.make("a"), Redacted.make("a")))
496 assertFalse(equivalence(Redacted.make("a"), Redacted.make("b")))
497 })
498
499 it("Option(Modulo2)", () => {
500 const schema = Schema.Option(Modulo2)
501 const equivalence = Schema.toEquivalence(schema)
502
503 assertTrue(equivalence(Option.none(), Option.none()))
504 assertTrue(equivalence(Option.some(0), Option.some(2)))
505 assertTrue(equivalence(Option.some(1), Option.some(3)))
506
507 assertFalse(equivalence(Option.none(), Option.some(0)))
508 assertFalse(equivalence(Option.some(0), Option.none()))
509 assertFalse(equivalence(Option.some(0), Option.some(1)))
510 })
511
512 it("Result(Modulo2, Modulo3)", () => {
513 const schema = Schema.Result(Modulo2, Modulo3)
514 const equivalence = Schema.toEquivalence(schema)
515
516 assertTrue(equivalence(Result.succeed(0), Result.succeed(2)))
517 assertTrue(equivalence(Result.succeed(1), Result.succeed(3)))
518 assertTrue(equivalence(Result.fail(0), Result.fail(3)))
519 assertTrue(equivalence(Result.fail(1), Result.fail(4)))
520 assertTrue(equivalence(Result.fail(2), Result.fail(5)))
521
522 assertFalse(equivalence(Result.succeed(0), Result.fail(2)))
523 assertFalse(equivalence(Result.fail(0), Result.succeed(3)))
524 })
525
526 it("ReadonlySet(Modulo2)", () => {
527 const schema = Schema.ReadonlySet(Modulo2)
528 const equivalence = Schema.toEquivalence(schema)
529
530 assertTrue(equivalence(new Set(), new Set()))
531 assertTrue(equivalence(new Set([0]), new Set([0])))
532 assertTrue(equivalence(new Set([0]), new Set([2])))
533 assertTrue(equivalence(new Set([0, 1]), new Set([1, 0])))
534 assertTrue(equivalence(new Set([0, 1]), new Set([2, 3])))
535
536 assertFalse(equivalence(new Set([0]), new Set([1])))
537 assertFalse(equivalence(new Set([0, 1]), new Set([2, 2])))
538 })
539
540 it("ReadonlyMap(Modulo2, Modulo3)", () => {
541 const schema = Schema.ReadonlyMap(Modulo2, Modulo3)
542 const equivalence = Schema.toEquivalence(schema)
543
544 assertTrue(equivalence(new Map(), new Map()))
545 assertTrue(equivalence(new Map([[0, 1]]), new Map([[0, 1]])))
546 assertTrue(equivalence(new Map([[0, 1]]), new Map([[2, 4]])))
547 assertTrue(equivalence(new Map([[0, 1], [1, 2]]), new Map([[0, 1], [1, 2]])))
548 assertTrue(equivalence(new Map([[0, 1], [1, 2]]), new Map([[1, 2], [0, 1]])))
549
550 assertFalse(equivalence(new Map([[0, 1]]), new Map([[1, 1]])))
551 assertFalse(equivalence(new Map([[0, 1]]), new Map([[0, 2]])))
552 assertFalse(equivalence(new Map([[0, 1], [1, 2]]), new Map([[0, 1], [1, 3]])))
553 assertFalse(equivalence(new Map([[0, 1], [1, 2]]), new Map([[0, 1], [2, 2]])))
554 })
555
556 it("HashMap(Modulo2, Modulo3)", () => {
557 const schema = Schema.HashMap(Modulo2, Modulo3)
558 const equivalence = Schema.toEquivalence(schema)
559
560 assertTrue(equivalence(HashMap.empty(), HashMap.empty()))
561 assertTrue(equivalence(HashMap.make([0, 1]), HashMap.make([0, 1])))
562 assertTrue(equivalence(HashMap.make([0, 1]), HashMap.make([2, 4])))
563 assertTrue(equivalence(HashMap.make([0, 1], [1, 2]), HashMap.make([0, 1], [1, 2])))
564 assertTrue(equivalence(HashMap.make([0, 1], [1, 2]), HashMap.make([1, 2], [0, 1])))
565
566 assertFalse(equivalence(HashMap.make([0, 1]), HashMap.make([1, 1])))
567 assertFalse(equivalence(HashMap.make([0, 1]), HashMap.make([0, 2])))
568 assertFalse(equivalence(HashMap.make([0, 1], [1, 2]), HashMap.make([0, 1], [1, 3])))
569 assertFalse(equivalence(HashMap.make([0, 1], [1, 2]), HashMap.make([0, 1], [2, 2])))
570 })
571
572 it("Duration", () => {
573 const schema = Schema.Duration
574 const equivalence = Schema.toEquivalence(schema)
575 assertTrue(equivalence(Duration.millis(1), Duration.millis(1)))
576 assertFalse(equivalence(Duration.millis(1), Duration.millis(2)))
577 assertTrue(equivalence(Duration.nanos(1n), Duration.nanos(1n)))
578 assertTrue(equivalence(Duration.millis(1), Duration.nanos(1_000_000n)))
579 assertFalse(equivalence(Duration.nanos(1n), Duration.nanos(2n)))
580 assertTrue(equivalence(Duration.infinity, Duration.infinity))
581 assertFalse(equivalence(Duration.infinity, Duration.millis(1)))
582 assertTrue(equivalence(Duration.negativeInfinity, Duration.negativeInfinity))
583 assertFalse(equivalence(Duration.negativeInfinity, Duration.infinity))
584 })
585
586 it("BigDecimal", () => {
587 const schema = Schema.BigDecimal
588 const equivalence = Schema.toEquivalence(schema)
589 assertTrue(equivalence(BigDecimal.fromStringUnsafe("1.5"), BigDecimal.fromStringUnsafe("1.50")))
590 assertFalse(equivalence(BigDecimal.fromStringUnsafe("1.5"), BigDecimal.fromStringUnsafe("2")))
591 assertTrue(equivalence(BigDecimal.fromStringUnsafe("0"), BigDecimal.fromStringUnsafe("0")))
592 })
593
594 it("DateTimeUtc", () => {
595 const schema = Schema.DateTimeUtc
596 const equivalence = Schema.toEquivalence(schema)
597 assertTrue(
598 equivalence(DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"), DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"))
599 )
600 assertFalse(
601 equivalence(DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"), DateTime.makeUnsafe("2021-01-01T00:00:00.001Z"))
602 )
603 })
604
605 it("TimeZoneOffset", () => {
606 const equivalence = Schema.toEquivalence(Schema.TimeZoneOffset)
607 assertTrue(
608 equivalence(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000), DateTime.zoneMakeOffset(3 * 60 * 60 * 1000))
609 )
610 assertFalse(
611 equivalence(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000), DateTime.zoneMakeOffset(4 * 60 * 60 * 1000))
612 )
613 })
614
615 it("TimeZoneNamed", () => {
616 const equivalence = Schema.toEquivalence(Schema.TimeZoneNamed)
617 assertTrue(
618 equivalence(DateTime.zoneMakeNamedUnsafe("Europe/London"), DateTime.zoneMakeNamedUnsafe("Europe/London"))
619 )
620 assertFalse(
621 equivalence(DateTime.zoneMakeNamedUnsafe("Europe/London"), DateTime.zoneMakeNamedUnsafe("America/New_York"))
622 )
623 })
624
625 it("TimeZone", () => {
626 const equivalence = Schema.toEquivalence(Schema.TimeZone)
627 assertTrue(
628 equivalence(DateTime.zoneMakeOffset(0), DateTime.zoneMakeOffset(0))
629 )
630 assertFalse(
631 equivalence(DateTime.zoneMakeOffset(0), DateTime.zoneMakeOffset(3 * 60 * 60 * 1000))
632 )
633 })
634
635 it("DateTimeZoned", () => {
636 const equivalence = Schema.toEquivalence(Schema.DateTimeZoned)
637 const z1 = DateTime.makeZonedUnsafe("2024-01-01T00:00:00.000Z", { timeZone: "Europe/London" })
638 const sameInstant = DateTime.makeZonedUnsafe("2024-01-01T00:00:00.000Z", { timeZone: "America/New_York" })
639 const z2 = DateTime.makeZonedUnsafe("2024-01-02T00:00:00.000Z", { timeZone: "Europe/London" })
640 assertTrue(equivalence(z1, z1))
641 assertTrue(equivalence(z1, sameInstant))
642 assertFalse(equivalence(z1, z2))
643 })
644
645 describe("Annotations", () => {
646 describe("overrideToEquivalence", () => {
647 it("String", () => {
648 const schema = Schema.String.pipe(
649 Schema.overrideToEquivalence(() => Equivalence.make((a, b) => a.substring(0, 1) === b.substring(0, 1)))
650 )
651 const equivalence = Schema.toEquivalence(schema)
652 assertTrue(equivalence("ab", "ac"))
653 })
654
655 it("String & isMinLength(1)", () => {
656 const schema = Schema.String.check(Schema.isMinLength(1)).pipe(
657 Schema.overrideToEquivalence(() => Equivalence.make((a, b) => a.substring(0, 1) === b.substring(0, 1)))
658 )
659 const equivalence = Schema.toEquivalence(schema)
660 assertTrue(equivalence("ab", "ac"))
661 })
662
663 it("overrides a compiler-owned Declaration", () => {
664 const schema = Schema.Option(Schema.Number).pipe(
665 Schema.overrideToEquivalence(() => () => true)
666 )
667 const equivalence = Schema.toEquivalence(schema)
668 assertTrue(equivalence(Option.none(), Option.some(1)))
669 })
670 })
671 })
672})