1import { describe, it } from "@effect/vitest"
2import { deepStrictEqual, strictEqual } from "@effect/vitest/utils"
3import { MutableList } from "effect"
4
5describe("MutableList", () => {
6 it("prependAll with no values preserves existing elements", () => {
7 const list = MutableList.make<number>()
8 MutableList.appendAll(list, [1, 2])
9 MutableList.prependAll(list, [])
10 strictEqual(list.length, 2)
11 strictEqual(MutableList.take(list), 1)
12 strictEqual(MutableList.take(list), 2)
13 strictEqual(list.length, 0)
14 })
15
16 it("prependAll with no values keeps an empty list appendable", () => {
17 const list = MutableList.make<number>()
18 MutableList.prependAll(list, [])
19 strictEqual(list.length, 0)
20 strictEqual(list.head, undefined)
21 strictEqual(list.tail, undefined)
22 MutableList.appendAll(list, [3])
23 strictEqual(MutableList.take(list), 3)
24 })
25
26 it("bounds the retained slots of a list that never fully drains", () => {
27 const takes: Array<[batch: number, take: (list: MutableList.MutableList<number>) => void]> = [
28 [1, (list) => MutableList.take(list)],
29 [7, (list) => MutableList.takeN(list, 7)],
30 [3, (list) => MutableList.takeNVoid(list, 3)]
31 ]
32 for (const [batch, take] of takes) {
33 const list = MutableList.make<number>()
34 MutableList.append(list, -1)
35 for (let i = 0; i < 10_000; i++) {
36 for (let j = 0; j < batch; j++) MutableList.append(list, i * batch + j)
37 take(list)
38 }
39 const slots = list.head!.array.length
40 strictEqual(slots <= 2048, true, `retained ${slots} slots for one element`)
41 deepStrictEqual(MutableList.takeAll(list), [10_000 * batch - 1])
42 }
43 })
44
45 it("keeps later buckets intact when consuming a large prefix", () => {
46 const list = MutableList.make<number>()
47 for (let i = 0; i < 2048; i++) MutableList.append(list, i)
48 MutableList.appendAll(list, [2048])
49 MutableList.takeNVoid(list, 1920)
50 deepStrictEqual(MutableList.takeAll(list), Array.from({ length: 129 }, (_, i) => i + 1920))
51 })
52
53 it("takeN advances past a drained appendAll bucket", () => {
54 const list = MutableList.make<number>()
55 MutableList.appendAll(list, [1, 2])
56 MutableList.appendAll(list, [3])
57
58 deepStrictEqual(MutableList.takeN(list, 2), [1, 2])
59 strictEqual(list.length, 1)
60 deepStrictEqual(MutableList.toArray(list), [3])
61 strictEqual(MutableList.take(list), 3)
62 strictEqual(list.length, 0)
63 strictEqual(MutableList.take(list), MutableList.Empty)
64 MutableList.append(list, 4)
65 strictEqual(MutableList.take(list), 4)
66 })
67
68 it("takeN advances past a drained append bucket but not a partially drained bucket", () => {
69 const list = MutableList.make<number>()
70 MutableList.append(list, 1)
71 MutableList.appendAll(list, [2, 3, 4])
72
73 deepStrictEqual(MutableList.takeN(list, 1), [1])
74 strictEqual(MutableList.take(list), 2)
75 deepStrictEqual(MutableList.takeN(list, 1), [3])
76 strictEqual(MutableList.take(list), 4)
77 strictEqual(list.length, 0)
78 })
79
80 it("preserves a prepended element when appending to the list", () => {
81 const list = MutableList.make<number>()
82 MutableList.prepend(list, 1)
83 MutableList.append(list, 2)
84
85 strictEqual(MutableList.toArray(list).join(","), "1,2")
86 strictEqual(list.length, 2)
87 })
88
89 it("preserves bulk-prepended values when appending to the list", () => {
90 const list = MutableList.make<number>()
91 MutableList.prependAll(list, [1, 2])
92 MutableList.append(list, 3)
93
94 deepStrictEqual(MutableList.toArray(list), [1, 2, 3], "bulk-prepended values should be preserved")
95 })
96
97 it("returns an empty snapshot for a negative bound", () => {
98 const list = MutableList.make<number>()
99 MutableList.append(list, 1)
100
101 deepStrictEqual(MutableList.toArrayN(list, -1), [])
102 })
103
104 it("normalizes bounded operation counts", () => {
105 const takeNaN = MutableList.make<number>()
106 MutableList.appendAll(takeNaN, [1, 2, 3])
107 deepStrictEqual(MutableList.takeN(takeNaN, Number.NaN), [])
108 deepStrictEqual(MutableList.toArray(takeNaN), [1, 2, 3])
109
110 const takeFraction = MutableList.make<number>()
111 MutableList.appendAll(takeFraction, [1, 2, 3])
112 deepStrictEqual(MutableList.takeN(takeFraction, 1.9), [1])
113 deepStrictEqual(MutableList.toArray(takeFraction), [2, 3])
114
115 const discardNaN = MutableList.make<number>()
116 MutableList.appendAll(discardNaN, [1, 2, 3])
117 MutableList.takeNVoid(discardNaN, Number.NaN)
118 deepStrictEqual(MutableList.toArray(discardNaN), [1, 2, 3])
119
120 const discardFraction = MutableList.make<number>()
121 MutableList.appendAll(discardFraction, [1, 2, 3])
122 MutableList.takeNVoid(discardFraction, 1.9)
123 deepStrictEqual(MutableList.toArray(discardFraction), [2, 3])
124
125 const snapshot = MutableList.make<number>()
126 MutableList.appendAll(snapshot, [1, 2, 3])
127 deepStrictEqual(MutableList.toArrayN(snapshot, Number.NaN), [])
128 deepStrictEqual(MutableList.toArrayN(snapshot, 1.9), [1])
129 })
130
131 it("appendAll returns 0 and leaves an empty list empty", () => {
132 const list = MutableList.make<number>()
133
134 strictEqual(MutableList.appendAll(list, []), 0)
135 strictEqual(list.length, 0)
136 strictEqual(list.head, undefined)
137 strictEqual(list.tail, undefined)
138 strictEqual(MutableList.take(list), MutableList.Empty)
139 })
140
141 it("appendAll with empty iterables preserves later append order", () => {
142 const list = MutableList.make<number>()
143
144 MutableList.appendAll(list, [])
145 MutableList.append(list, 1)
146 MutableList.appendAll(list, [])
147 MutableList.append(list, 2)
148
149 deepStrictEqual(MutableList.takeAll(list), [1, 2])
150 strictEqual(MutableList.take(list), MutableList.Empty)
151 })
152
153 it("appendAllUnsafe with an empty array is a no-op", () => {
154 const list = MutableList.make<number>()
155
156 MutableList.appendAll(list, [1])
157 strictEqual(MutableList.appendAllUnsafe(list, []), 0)
158 MutableList.append(list, 2)
159
160 deepStrictEqual(MutableList.takeAll(list), [1, 2])
161 strictEqual(MutableList.take(list), MutableList.Empty)
162 })
163
164 it("filter keeps matching values in place and updates length", () => {
165 const list = MutableList.make<number>()
166 MutableList.appendAll(list, [1, 2, 3, 4, 5])
167
168 MutableList.filter(list, (n) => n % 2 === 0)
169
170 deepStrictEqual(MutableList.toArrayN(list, 2), [2, 4])
171 strictEqual(list.length, 2)
172 })
173
174 it("filter passes list indices to the predicate across offsets and buckets", () => {
175 const list = MutableList.make<number>()
176 MutableList.appendAll(list, [0, 1, 2])
177 MutableList.appendAll(list, [3, 4])
178 MutableList.take(list)
179 const indices: Array<number> = []
180 MutableList.filter(list, (_, i) => {
181 indices.push(i)
182 return i % 2 === 0
183 })
184 deepStrictEqual(indices, [0, 1, 2, 3])
185 deepStrictEqual(MutableList.toArray(list), [1, 3])
186 })
187
188 it("filter restores the empty list state when no values match", () => {
189 const list = MutableList.make<number>()
190 MutableList.append(list, 1)
191
192 MutableList.filter(list, () => false)
193
194 strictEqual(list.length, 0)
195 strictEqual(list.head, undefined)
196 strictEqual(list.tail, undefined)
197 strictEqual(MutableList.take(list), MutableList.Empty)
198 })
199
200 it("remove deletes all strictly equal values and updates length", () => {
201 const list = MutableList.make<string>()
202 MutableList.appendAll(list, ["apple", "banana", "apple", "cherry", "apple"])
203
204 MutableList.remove(list, "apple")
205
206 deepStrictEqual(MutableList.toArrayN(list, 2), ["banana", "cherry"])
207 strictEqual(list.length, 2)
208 })
209})