1import { Equivalence, Number, pipe, Schema, String, Tuple } from "effect"
2import { TestSchema } from "effect/testing"
3import { deepStrictEqual, strictEqual } from "node:assert"
4import { describe, it } from "vitest"
5
6const tuple = ["a", 2, true] as [string, number, boolean]
7
8describe("Tuple", () => {
9 it("make creates a tuple from ordered values", () => {
10 deepStrictEqual(Tuple.make("a", 2, true), ["a", 2, true])
11 })
12
13 it("get", () => {
14 strictEqual(pipe(tuple, Tuple.get(0)), "a")
15 strictEqual(pipe(tuple, Tuple.get(1)), 2)
16
17 strictEqual(Tuple.get(tuple, 0), "a")
18 strictEqual(Tuple.get(tuple, 1), 2)
19 })
20
21 it("pick", () => {
22 deepStrictEqual(pipe(tuple, Tuple.pick([0, 2])), ["a", true])
23 deepStrictEqual(Tuple.pick(tuple, [0, 2]), ["a", true])
24 })
25
26 it("omit", () => {
27 deepStrictEqual(pipe(tuple, Tuple.omit([1])), ["a", true])
28 deepStrictEqual(Tuple.omit(tuple, [1]), ["a", true])
29 })
30
31 it("evolve", () => {
32 deepStrictEqual(
33 pipe(
34 tuple,
35 Tuple.evolve([
36 (s) => s.length,
37 undefined,
38 (b) => `b: ${b}`
39 ])
40 ),
41 [1, 2, "b: true"]
42 )
43 deepStrictEqual(
44 Tuple.evolve(
45 tuple,
46 [
47 (s) => s.length,
48 undefined,
49 (b) => `b: ${b}`
50 ] as const
51 ),
52 [1, 2, "b: true"]
53 )
54 })
55
56 describe("renameIndices", () => {
57 it("partial index mapping", () => {
58 deepStrictEqual(pipe(tuple, Tuple.renameIndices(["1", "0"])), [2, "a", true])
59 deepStrictEqual(Tuple.renameIndices(tuple, ["1", "0"]), [2, "a", true])
60 })
61
62 it("full index mapping", () => {
63 deepStrictEqual(pipe(tuple, Tuple.renameIndices(["2", "1", "0"])), [true, 2, "a"])
64 deepStrictEqual(Tuple.renameIndices(tuple, ["2", "1", "0"]), [true, 2, "a"])
65 })
66 })
67
68 it("map transforms every schema element with a lambda", () => {
69 const tuple = [Schema.String, Schema.Number, Schema.Boolean] as const
70 TestSchema.Asserts.ast.elements.equals(pipe(tuple, Tuple.map(Schema.NullOr)), [
71 Schema.NullOr(Schema.String),
72 Schema.NullOr(Schema.Number),
73 Schema.NullOr(Schema.Boolean)
74 ])
75 TestSchema.Asserts.ast.elements.equals(Tuple.map(tuple, Schema.NullOr), [
76 Schema.NullOr(Schema.String),
77 Schema.NullOr(Schema.Number),
78 Schema.NullOr(Schema.Boolean)
79 ])
80 })
81
82 it("mapPick transforms only the selected schema elements", () => {
83 const tuple = [Schema.String, Schema.Number, Schema.Boolean] as const
84 TestSchema.Asserts.ast.elements.equals(pipe(tuple, Tuple.mapPick([0, 2], Schema.NullOr)), [
85 Schema.NullOr(Schema.String),
86 Schema.Number,
87 Schema.NullOr(Schema.Boolean)
88 ])
89 TestSchema.Asserts.ast.elements.equals(Tuple.mapPick(tuple, [0, 2], Schema.NullOr), [
90 Schema.NullOr(Schema.String),
91 Schema.Number,
92 Schema.NullOr(Schema.Boolean)
93 ])
94 })
95
96 it("mapOmit transforms every schema element except the omitted indices", () => {
97 const tuple = [Schema.String, Schema.Number, Schema.Boolean] as const
98 TestSchema.Asserts.ast.elements.equals(pipe(tuple, Tuple.mapOmit([1], Schema.NullOr)), [
99 Schema.NullOr(Schema.String),
100 Schema.Number,
101 Schema.NullOr(Schema.Boolean)
102 ])
103 TestSchema.Asserts.ast.elements.equals(Tuple.mapOmit(tuple, [1], Schema.NullOr), [
104 Schema.NullOr(Schema.String),
105 Schema.Number,
106 Schema.NullOr(Schema.Boolean)
107 ])
108 })
109
110 it("makeCombiner combines tuple positions independently", () => {
111 const C = Tuple.makeCombiner([
112 Number.ReducerSum,
113 String.ReducerConcat
114 ])
115
116 deepStrictEqual(C.combine([1, "a"], [2, "b"]), [3, "ab"])
117 })
118
119 it("makeReducer derives a tuple-shaped initial value and combines position-wise", () => {
120 const R = Tuple.makeReducer([
121 Number.ReducerSum,
122 String.ReducerConcat
123 ])
124
125 deepStrictEqual(R.initialValue, [0, ""])
126 })
127
128 it("appendElement appends a single element in both data-first and data-last forms", () => {
129 deepStrictEqual(pipe(Tuple.make(1, 2), Tuple.appendElement("end")), [1, 2, "end"])
130 deepStrictEqual(Tuple.appendElement(Tuple.make(1, 2), "end"), [1, 2, "end"])
131 })
132
133 it("appendElements concatenates a tuple in both data-first and data-last forms", () => {
134 deepStrictEqual(pipe(Tuple.make(1, 2), Tuple.appendElements(["a", "b"] as const)), [1, 2, "a", "b"])
135 deepStrictEqual(Tuple.appendElements(Tuple.make(1, 2), ["a", "b"] as const), [1, 2, "a", "b"])
136 })
137
138 it("makeEquivalence compares tuple positions independently", () => {
139 const eq = Tuple.makeEquivalence([
140 Equivalence.strictEqual<string>(),
141 Equivalence.strictEqual<number>()
142 ])
143
144 strictEqual(eq(["Alice", 30], ["Alice", 30]), true)
145 strictEqual(eq(["Alice", 30], ["Bob", 30]), false)
146 strictEqual(eq(["Alice", 30], ["Alice", 31]), false)
147 })
148
149 it("makeOrder orders tuple positions lexicographically", () => {
150 const ord = Tuple.makeOrder([String.Order, Number.Order])
151
152 strictEqual(ord(["Alice", 30], ["Bob", 25]), -1)
153 strictEqual(ord(["Alice", 30], ["Alice", 30]), 0)
154 strictEqual(ord(["Bob", 25], ["Alice", 30]), 1)
155 strictEqual(ord(["Alice", 30], ["Alice", 25]), 1)
156 })
157})