1import { describe, it } from "@effect/vitest"
2import { assertFalse, assertTrue, deepStrictEqual, strictEqual } from "@effect/vitest/utils"
3import { Array as Arr, Order, pipe } from "effect"
4
5describe("Order", () => {
6 it("Struct", () => {
7 const O = Order.Struct({ a: Order.String, b: Order.String })
8 strictEqual(O({ a: "a", b: "b" }, { a: "a", b: "c" }), -1)
9 strictEqual(O({ a: "a", b: "b" }, { a: "a", b: "b" }), 0)
10 strictEqual(O({ a: "a", b: "c" }, { a: "a", b: "b" }), 1)
11 })
12
13 it("Tuple", () => {
14 const O = Order.Tuple([Order.String, Order.String])
15 strictEqual(O(["a", "b"], ["a", "c"]), -1)
16 strictEqual(O(["a", "b"], ["a", "b"]), 0)
17 strictEqual(O(["a", "b"], ["a", "a"]), 1)
18 strictEqual(O(["a", "b"], ["b", "a"]), -1)
19 })
20
21 it("mapInput", () => {
22 const O = Order.mapInput(Order.Number, (s: string) => s.length)
23 strictEqual(O("a", "b"), 0)
24 strictEqual(O("a", "bb"), -1)
25 strictEqual(O("aa", "b"), 1)
26 })
27
28 it("Number compares numeric values and treats NaN as the lowest value", () => {
29 const O = Order.Number
30 strictEqual(O(1, 1), 0)
31 strictEqual(O(1, 2), -1)
32 strictEqual(O(2, 1), 1)
33 strictEqual(O(0, -0), 0)
34
35 strictEqual(O(NaN, NaN), 0)
36 strictEqual(O(NaN, 1), -1)
37 strictEqual(O(1, NaN), 1)
38 })
39
40 it("Date compares by timestamp", () => {
41 const O = Order.Date
42 strictEqual(O(new Date(0), new Date(1)), -1)
43 strictEqual(O(new Date(1), new Date(1)), 0)
44 strictEqual(O(new Date(1), new Date(0)), 1)
45 })
46
47 it("clamp keeps values inside inclusive bounds", () => {
48 const clamp = Order.clamp(Order.Number)({ minimum: 1, maximum: 10 })
49 strictEqual(clamp(2), 2)
50 strictEqual(clamp(10), 10)
51 strictEqual(clamp(20), 10)
52 strictEqual(clamp(1), 1)
53 strictEqual(clamp(-10), 1)
54
55 strictEqual(Order.clamp(Order.Number)({ minimum: 1, maximum: 10 })(2), 2)
56 })
57
58 it("isBetween checks inclusive bounds", () => {
59 const between = Order.isBetween(Order.Number)({ minimum: 1, maximum: 10 })
60 assertTrue(between(2))
61 assertTrue(between(10))
62 assertFalse(between(20))
63 assertTrue(between(1))
64 assertFalse(between(-10))
65
66 assertTrue(Order.isBetween(Order.Number)(2, { minimum: 1, maximum: 10 }))
67 })
68
69 it("flip reverses an existing order", () => {
70 const O = Order.flip(Order.Number)
71 strictEqual(O(1, 2), 1)
72 strictEqual(O(2, 1), -1)
73 strictEqual(O(2, 2), 0)
74 })
75
76 it("isLessThan returns true only for strictly smaller values", () => {
77 const lessThan = Order.isLessThan(Order.Number)
78 assertTrue(lessThan(0, 1))
79 assertFalse(lessThan(1, 1))
80 assertFalse(lessThan(2, 1))
81 })
82
83 it("isLessThanOrEqualTo returns true for smaller or equal values", () => {
84 const lessThanOrEqualTo = Order.isLessThanOrEqualTo(Order.Number)
85 assertTrue(lessThanOrEqualTo(0, 1))
86 assertTrue(lessThanOrEqualTo(1, 1))
87 assertFalse(lessThanOrEqualTo(2, 1))
88 })
89
90 it("isGreaterThan returns true only for strictly greater values", () => {
91 const greaterThan = Order.isGreaterThan(Order.Number)
92 assertFalse(greaterThan(0, 1))
93 assertFalse(greaterThan(1, 1))
94 assertTrue(greaterThan(2, 1))
95 })
96
97 it("isGreaterThanOrEqualTo returns true for greater or equal values", () => {
98 const greaterThanOrEqualTo = Order.isGreaterThanOrEqualTo(Order.Number)
99 assertFalse(greaterThanOrEqualTo(0, 1))
100 assertTrue(greaterThanOrEqualTo(1, 1))
101 assertTrue(greaterThanOrEqualTo(2, 1))
102 })
103
104 it("min", () => {
105 type A = { a: number }
106 const min = Order.min(
107 pipe(
108 Order.Number,
109 Order.mapInput((a: A) => a.a)
110 )
111 )
112 deepStrictEqual(min({ a: 1 }, { a: 2 }), { a: 1 })
113 deepStrictEqual(min({ a: 2 }, { a: 1 }), { a: 1 })
114 const first = { a: 1 }
115 const second = { a: 1 }
116 deepStrictEqual(min(first, second), first)
117 })
118
119 it("max", () => {
120 type A = { a: number }
121 const max = Order.max(
122 pipe(
123 Order.Number,
124 Order.mapInput((a: A) => a.a)
125 )
126 )
127 deepStrictEqual(max({ a: 1 }, { a: 2 }), { a: 2 })
128 deepStrictEqual(max({ a: 2 }, { a: 1 }), { a: 2 })
129 const first = { a: 1 }
130 const second = { a: 1 }
131 deepStrictEqual(max(first, second), first)
132 })
133
134 it("combineAll reuses finite criteria across comparisons", () => {
135 const O = Order.combineAll([Order.Number].values())
136 strictEqual(O(1, 2), -1)
137 strictEqual(O(1, 2), -1)
138 })
139
140 it("combine", () => {
141 type T = [number, string]
142 const tuples: Array<T> = [
143 [2, "c"],
144 [1, "b"],
145 [2, "a"],
146 [1, "c"]
147 ]
148 const sortByFst = pipe(
149 Order.Number,
150 Order.mapInput((x: T) => x[0])
151 )
152 const sortBySnd = pipe(
153 Order.String,
154 Order.mapInput((x: T) => x[1])
155 )
156 deepStrictEqual(Arr.sort(Order.combine(sortByFst, sortBySnd))(tuples), [
157 [1, "b"],
158 [1, "c"],
159 [2, "a"],
160 [2, "c"]
161 ])
162 deepStrictEqual(Arr.sort(Order.combine(sortBySnd, sortByFst))(tuples), [
163 [2, "a"],
164 [1, "b"],
165 [1, "c"],
166 [2, "c"]
167 ])
168 })
169})