1import { describe, it } from "@effect/vitest"
2import { assertFalse, assertTrue } from "@effect/vitest/utils"
3import { Equivalence, pipe } from "effect"
4
5describe("Equivalence", () => {
6 it("String", () => {
7 assertTrue(Equivalence.String("a", "a"))
8 assertFalse(Equivalence.String("a", "b"))
9 })
10
11 it("Number", () => {
12 assertTrue(Equivalence.Number(1, 1))
13 assertFalse(Equivalence.Number(1, 2))
14 assertTrue(Equivalence.Number(NaN, NaN))
15 assertFalse(Equivalence.Number(NaN, 1))
16 assertFalse(Equivalence.Number(1, NaN))
17 assertTrue(Equivalence.Number(0, -0))
18 })
19
20 it("Boolean", () => {
21 assertTrue(Equivalence.Boolean(true, true))
22 assertTrue(Equivalence.Boolean(false, false))
23 assertFalse(Equivalence.Boolean(true, false))
24 assertFalse(Equivalence.Boolean(false, true))
25 })
26
27 it("BigInt", () => {
28 assertTrue(Equivalence.BigInt(1n, 1n))
29 assertFalse(Equivalence.BigInt(1n, 2n))
30 })
31
32 it("mapInput", () => {
33 interface Person {
34 readonly name: string
35 readonly age: number
36 }
37 const eqPerson = pipe(Equivalence.strictEqual<string>(), Equivalence.mapInput((p: Person) => p.name))
38 assertTrue(eqPerson({ name: "a", age: 1 }, { name: "a", age: 2 }))
39 assertTrue(eqPerson({ name: "a", age: 1 }, { name: "a", age: 1 }))
40 assertFalse(eqPerson({ name: "a", age: 1 }, { name: "b", age: 1 }))
41 assertFalse(eqPerson({ name: "a", age: 1 }, { name: "b", age: 2 }))
42 })
43
44 it("combine", () => {
45 type T = readonly [string, number, boolean]
46 const E0: Equivalence.Equivalence<T> = Equivalence.mapInput((x: T) => x[0])(Equivalence.strictEqual<string>())
47 const E1: Equivalence.Equivalence<T> = Equivalence.mapInput((x: T) => x[1])(Equivalence.strictEqual<number>())
48 const eqE0E1 = Equivalence.combine(E0, E1)
49 assertTrue(eqE0E1(["a", 1, true], ["a", 1, true]))
50 assertTrue(eqE0E1(["a", 1, true], ["a", 1, false]))
51 assertFalse(eqE0E1(["a", 1, true], ["b", 1, true]))
52 assertFalse(eqE0E1(["a", 1, true], ["a", 2, false]))
53 })
54
55 it("combineAll", () => {
56 type T = readonly [string, number, boolean]
57 const E0: Equivalence.Equivalence<T> = Equivalence.mapInput((x: T) => x[0])(Equivalence.strictEqual<string>())
58 const E1: Equivalence.Equivalence<T> = Equivalence.mapInput((x: T) => x[1])(Equivalence.strictEqual<number>())
59 const E2: Equivalence.Equivalence<T> = Equivalence.mapInput((x: T) => x[2])(Equivalence.strictEqual<boolean>())
60 const eqE0E1E2 = Equivalence.combineAll([E0, E1, E2])
61 assertTrue(eqE0E1E2(["a", 1, true], ["a", 1, true]))
62 assertFalse(eqE0E1E2(["a", 1, true], ["b", 1, true]))
63 assertFalse(eqE0E1E2(["a", 1, true], ["a", 2, true]))
64 assertFalse(eqE0E1E2(["a", 1, true], ["a", 1, false]))
65 })
66
67 it("Tuple", () => {
68 const eq = Equivalence.Tuple([Equivalence.strictEqual<string>(), Equivalence.strictEqual<number>()])
69
70 assertTrue(eq(["a", 1], ["a", 1]))
71 assertFalse(eq(["a", 1], ["c", 1]))
72 assertFalse(eq(["a", 1], ["a", 2]))
73 })
74
75 it("Array", () => {
76 const eq = Equivalence.Array(Equivalence.strictEqual<number>())
77
78 assertTrue(eq([], []))
79 assertTrue(eq([1, 2, 3], [1, 2, 3]))
80 assertFalse(eq([1, 2, 3], [1, 2, 4]))
81 assertFalse(eq([1, 2, 3], [1, 2]))
82 })
83
84 describe("Struct", () => {
85 it("string keys", () => {
86 const eq = Equivalence.Struct({
87 a: Equivalence.strictEqual<string>(),
88 b: Equivalence.strictEqual<number>()
89 })
90
91 assertTrue(eq({ a: "a", b: 1 }, { a: "a", b: 1 }))
92 assertFalse(eq({ a: "a", b: 1 }, { a: "c", b: 1 }))
93 assertFalse(eq({ a: "a", b: 1 }, { a: "a", b: 2 }))
94 })
95
96 it("symbol keys", () => {
97 const a = Symbol.for("a")
98 const b = Symbol.for("b")
99 const eq = Equivalence.Struct({
100 [a]: Equivalence.strictEqual<string>(),
101 [b]: Equivalence.strictEqual<number>()
102 })
103
104 assertTrue(eq({ [a]: "a", [b]: 1 }, { [a]: "a", [b]: 1 }))
105 assertFalse(eq({ [a]: "a", [b]: 1 }, { [a]: "c", [b]: 1 }))
106 assertFalse(eq({ [a]: "a", [b]: 1 }, { [a]: "a", [b]: 2 }))
107 })
108
109 it("mixed keys", () => {
110 const b = Symbol.for("b")
111 const eq = Equivalence.Struct({
112 a: Equivalence.strictEqual<string>(),
113 [b]: Equivalence.strictEqual<number>()
114 })
115
116 assertTrue(eq({ a: "a", [b]: 1 }, { a: "a", [b]: 1 }))
117 assertFalse(eq({ a: "a", [b]: 1 }, { a: "c", [b]: 1 }))
118 assertFalse(eq({ a: "a", [b]: 1 }, { a: "a", [b]: 2 }))
119 })
120 })
121
122 describe("Record", () => {
123 it("string keys", () => {
124 const eq = Equivalence.Record(Equivalence.strictEqual<number>())
125
126 assertTrue(eq({ a: 1, b: 2 }, { a: 1, b: 2 }))
127 assertFalse(eq({ a: 1, b: 2 }, { a: 1, b: 3 }))
128 assertFalse(eq({ a: 1, b: 2 }, { a: 1 }))
129 assertFalse(eq({ a: 1 }, { a: 1, b: 2 }))
130 })
131
132 it("symbol keys", () => {
133 const a = Symbol.for("a")
134 const b = Symbol.for("b")
135 const eq = Equivalence.Record(Equivalence.strictEqual<number>())
136
137 assertTrue(eq({ [a]: 1, [b]: 2 }, { [a]: 1, [b]: 2 }))
138 assertFalse(eq({ [a]: 1, [b]: 2 }, { [a]: 1, [b]: 3 }))
139 assertFalse(eq({ [a]: 1, [b]: 2 }, { [a]: 1 }))
140 assertFalse(eq({ [a]: 1 }, { [a]: 1, [b]: 2 }))
141 })
142
143 it("mixed keys", () => {
144 const b = Symbol.for("b")
145 const eq = Equivalence.Record(Equivalence.strictEqual<number>())
146
147 assertTrue(eq({ a: 1, [b]: 2 }, { a: 1, [b]: 2 }))
148 assertFalse(eq({ a: 1, [b]: 2 }, { a: 1, [b]: 3 }))
149 assertFalse(eq({ a: 1, [b]: 2 }, { a: 1 }))
150 assertFalse(eq({ a: 1 }, { a: 1, [b]: 2 }))
151 })
152 })
153
154 it("Date", () => {
155 const d1 = new Date("2020-01-01T00:00:00.000Z")
156 const d2 = new Date("2020-01-01T00:00:00.000Z")
157 const d3 = new Date("2021-01-01T00:00:00.000Z")
158
159 assertTrue(Equivalence.Date(d1, d2))
160 assertFalse(Equivalence.Date(d1, d3))
161 assertTrue(Equivalence.Date(d1, d1))
162
163 const invalid1 = new Date("foo")
164 const invalid2 = new Date("bar")
165
166 assertTrue(Equivalence.Date(invalid1, invalid2))
167 assertFalse(Equivalence.Date(invalid1, d1))
168 assertFalse(Equivalence.Date(d1, invalid2))
169 })
170})