1import { describe, it } from "@effect/vitest"
2import {
3 assertEquals,
4 assertFalse,
5 assertNone,
6 assertSome,
7 assertTrue,
8 deepStrictEqual,
9 doesNotThrow,
10 strictEqual,
11 throws
12} from "@effect/vitest/utils"
13import type { Predicate } from "effect"
14import { Array as Arr, Chunk, Equal, Equivalence, Option, Order, Result } from "effect"
15import { identity, pipe } from "effect/Function"
16import * as fc from "fast-check"
17
18const assertTuple = <A, B>(
19 actual: [Chunk.Chunk<A>, Chunk.Chunk<B>],
20 expected: [Chunk.Chunk<A>, Chunk.Chunk<B>]
21) => {
22 assertEquals(actual[0], expected[0])
23 assertEquals(actual[1], expected[1])
24}
25
26describe("Chunk", () => {
27 it("Equal.equals", () => {
28 assertTrue(Equal.equals(Chunk.make(0), Chunk.make(0)))
29 assertTrue(Equal.equals(Chunk.make(1, 2, 3), Chunk.make(1, 2, 3)))
30 assertFalse(Equal.equals(Chunk.make(1, 2, 3), Chunk.make(1, 2)))
31 assertFalse(Equal.equals(Chunk.make(1, 2), Chunk.make(1, 2, 3)))
32 assertFalse(Equal.equals(Chunk.make(1, 2, 3), Chunk.make(1, "a", 3)))
33 assertFalse(Equal.equals(Chunk.make(0), [0]))
34 })
35
36 it("toString", () => {
37 strictEqual(
38 String(Chunk.make(0, 1, 2)),
39 `Chunk([0,1,2])`
40 )
41 strictEqual(
42 String(Chunk.make(Chunk.make(1, 2, 3))),
43 `Chunk([Chunk([1,2,3])])`
44 )
45 })
46
47 it("toJSON", () => {
48 deepStrictEqual(Chunk.make(0, 1, 2).toJSON(), { _id: "Chunk", values: [0, 1, 2] })
49 deepStrictEqual(Chunk.make(Chunk.make(1, 2, 3)).toJSON(), {
50 _id: "Chunk",
51 values: [{ _id: "Chunk", values: [1, 2, 3] }]
52 })
53 })
54
55 it("inspect", () => {
56 if (typeof window === "undefined") {
57 // oxlint-disable-next-line @typescript-eslint/no-require-imports
58 const { inspect } = require("node:util")
59 assertEquals(inspect(Chunk.make(0, 1, 2)), inspect({ _id: "Chunk", values: [0, 1, 2] }))
60 }
61 })
62
63 it("modify", () => {
64 assertNone(pipe(Chunk.empty(), Chunk.modify(0, (n: number) => n * 2)))
65 assertSome(
66 pipe(Chunk.make(1, 2, 3), Chunk.modify(0, (n: number) => n * 2)),
67 Chunk.make(2, 2, 3)
68 )
69 })
70
71 it("replace", () => {
72 assertNone(pipe(Chunk.empty(), Chunk.replace(0, 2)))
73 assertSome(pipe(Chunk.make(1, 2, 3), Chunk.replace(0, 2)), Chunk.make(2, 2, 3))
74 })
75
76 it("remove", () => {
77 assertEquals(pipe(Chunk.empty(), Chunk.remove(0)), Chunk.empty())
78 assertEquals(pipe(Chunk.make(1, 2, 3), Chunk.remove(0)), Chunk.make(2, 3))
79 })
80
81 it("chunksOf", () => {
82 assertEquals(pipe(Chunk.empty(), Chunk.chunksOf(2)), Chunk.empty())
83 assertEquals(
84 pipe(Chunk.make(1, 2, 3, 4, 5), Chunk.chunksOf(2)),
85 Chunk.make(Chunk.make(1, 2), Chunk.make(3, 4), Chunk.make(5))
86 )
87 assertEquals(
88 pipe(Chunk.make(1, 2, 3, 4, 5, 6), Chunk.chunksOf(2)),
89 Chunk.make(Chunk.make(1, 2), Chunk.make(3, 4), Chunk.make(5, 6))
90 )
91 assertEquals(
92 pipe(Chunk.make(1, 2, 3, 4, 5), Chunk.chunksOf(1)),
93 Chunk.make(Chunk.make(1), Chunk.make(2), Chunk.make(3), Chunk.make(4), Chunk.make(5))
94 )
95 assertEquals(
96 pipe(Chunk.make(1, 2, 3), Chunk.chunksOf(1.5)),
97 Chunk.make(Chunk.make(1), Chunk.make(2), Chunk.make(3))
98 )
99 assertEquals(pipe(Chunk.make(1, 2, 3, 4, 5), Chunk.chunksOf(5)), Chunk.make(Chunk.make(1, 2, 3, 4, 5)))
100 // out of bounds
101 assertEquals(
102 pipe(Chunk.make(1, 2, 3, 4, 5), Chunk.chunksOf(0)),
103 Chunk.make(Chunk.make(1), Chunk.make(2), Chunk.make(3), Chunk.make(4), Chunk.make(5))
104 )
105 assertEquals(
106 pipe(Chunk.make(1, 2, 3, 4, 5), Chunk.chunksOf(-1)),
107 Chunk.make(Chunk.make(1), Chunk.make(2), Chunk.make(3), Chunk.make(4), Chunk.make(5))
108 )
109 assertEquals(
110 pipe(Chunk.make(1, 2, 3, 4, 5), Chunk.chunksOf(Number.NaN)),
111 Chunk.make(Chunk.make(1), Chunk.make(2), Chunk.make(3), Chunk.make(4), Chunk.make(5))
112 )
113 assertEquals(pipe(Chunk.make(1, 2, 3, 4, 5), Chunk.chunksOf(10)), Chunk.make(Chunk.make(1, 2, 3, 4, 5)))
114 })
115
116 it(".pipe() method", () => {
117 assertEquals(Chunk.empty().pipe(Chunk.append(1)), Chunk.make(1))
118 })
119
120 describe("toArray", () => {
121 it("should return an empty array for an empty chunk", () => {
122 deepStrictEqual(Chunk.toArray(Chunk.empty()), [])
123 })
124
125 it("should return an array with the elements of the chunk", () => {
126 deepStrictEqual(Chunk.toArray(Chunk.make(1, 2, 3)), [1, 2, 3])
127 })
128
129 it("should not affect the original chunk when the array is mutated", () => {
130 const chunk = Chunk.make(1, 2, 3)
131 const arr = Chunk.toArray(chunk)
132 // mutate the array
133 arr[1] = 4
134 // the chunk should not be affected
135 deepStrictEqual(Chunk.toArray(chunk), [1, 2, 3])
136 })
137 })
138
139 describe("toReadonlyArray", () => {
140 describe("Given an empty Chunk", () => {
141 const chunk = Chunk.empty()
142 it("should give back an empty readonly array", () => {
143 deepStrictEqual(Chunk.toReadonlyArray(chunk), [])
144 })
145 })
146
147 describe("Given a large Chunk", () => {
148 const len = 100_000
149 let chunk = Chunk.empty<number>()
150 for (let i = 0; i < len; i++) chunk = Chunk.appendAll(Chunk.of(i), chunk)
151
152 it("gives back a readonly array", () => {
153 doesNotThrow(() => Chunk.toReadonlyArray(chunk))
154 deepStrictEqual(Chunk.toReadonlyArray(chunk), Arr.reverse(Arr.range(0, len - 1)))
155 })
156 })
157
158 describe(`Given an imbalanced left and right chunk`, () => {
159 const len = 1_000
160 let rchunk = Chunk.empty<number>()
161 let lchunk = Chunk.empty<number>()
162 for (let i = 0; i < len; i++) {
163 rchunk = Chunk.appendAll(Chunk.of(i), rchunk)
164 lchunk = Chunk.appendAll(lchunk, Chunk.of(i))
165 }
166 it("should have depth of +/- 3", () => {
167 assertTrue(rchunk.depth >= lchunk.depth - 3)
168 assertTrue(rchunk.depth <= lchunk.depth + 3)
169 })
170 })
171 })
172
173 describe("isChunk", () => {
174 describe("Given a chunk", () => {
175 const chunk = Chunk.make(0, 1)
176 it("should be true", () => {
177 assertTrue(Chunk.isChunk(chunk))
178 })
179 })
180 describe("Given an object", () => {
181 const object = {}
182 it("should be false", () => {
183 assertFalse(Chunk.isChunk(object))
184 })
185 })
186 })
187
188 describe("fromIterable", () => {
189 describe("Given an iterable", () => {
190 const myIterable = {
191 [Symbol.iterator]() {
192 let i = 0
193
194 return {
195 next() {
196 i++
197 return { value: i, done: i > 5 }
198 }
199 }
200 }
201 }
202
203 it("should process it", () => {
204 assertEquals(Chunk.fromIterable(myIterable), Chunk.fromArrayUnsafe([1, 2, 3, 4, 5]))
205 })
206 })
207
208 it("should return the same reference if the input is a Chunk", () => {
209 const expected = Chunk.make(1, 2, 3)
210 const actual = Chunk.fromIterable(expected)
211 assertTrue(actual === expected)
212 })
213 })
214
215 describe("get", () => {
216 describe("Given a Chunk and an index within the bounds", () => {
217 const chunk = Chunk.fromArrayUnsafe([1, 2, 3])
218 const index = 0
219
220 it("should a Some with the value", () => {
221 deepStrictEqual(pipe(chunk, Chunk.get(index)), Option.some(1))
222 })
223 })
224
225 describe("Given a Chunk and an index out of bounds", () => {
226 const chunk = Chunk.fromArrayUnsafe([1, 2, 3])
227
228 it("should return a None", () => {
229 assertNone(pipe(chunk, Chunk.get(4)))
230 })
231 })
232 })
233
234 describe("getUnsafe", () => {
235 describe("Given an empty Chunk and an index", () => {
236 const chunk = Chunk.empty()
237 const index = 4
238
239 it("should throw", () => {
240 throws(() => pipe(chunk, Chunk.getUnsafe(index)))
241 })
242 })
243
244 describe("Given an appended Chunk and an index out of bounds", () => {
245 const chunk = pipe(Chunk.empty(), Chunk.append(1))
246 const index = 4
247
248 it("should throw", () => {
249 throws(() => pipe(chunk, Chunk.getUnsafe(index)))
250 })
251 })
252
253 describe("Given an appended Chunk and an index in bounds", () => {
254 it("should return the value", () => {
255 const chunk = pipe(Chunk.make(0, 1, 2), Chunk.append(3))
256 strictEqual(Chunk.getUnsafe(1)(chunk), 1)
257 })
258 })
259
260 describe("Given a prepended Chunk and an index out of bounds", () => {
261 it("should throw", () => {
262 fc.assert(fc.property(fc.array(fc.anything()), (array) => {
263 let chunk: Chunk.Chunk<unknown> = Chunk.empty()
264 array.forEach((e) => {
265 chunk = pipe(chunk, Chunk.prepend(e))
266 })
267 throws(() => pipe(chunk, Chunk.getUnsafe(array.length)))
268 }))
269 })
270 })
271
272 describe("Given a prepended Chunk and an index in bounds", () => {
273 it("should return the value", () => {
274 const chunk = pipe(Chunk.make(0, 1, 2), Chunk.prepend(3))
275 strictEqual(Chunk.getUnsafe(1)(chunk), 0)
276 })
277 })
278
279 describe("Given a singleton Chunk and an index out of bounds", () => {
280 const chunk = Chunk.make(1)
281 const index = 4
282
283 it("should throw", () => {
284 throws(() => pipe(chunk, Chunk.getUnsafe(index)))
285 })
286 })
287
288 describe("Given an array Chunk and an index out of bounds", () => {
289 const chunk = Chunk.fromArrayUnsafe([1, 2])
290 const index = 4
291
292 it("should throw", () => {
293 throws(() => pipe(chunk, Chunk.getUnsafe(index)))
294 })
295 })
296
297 describe("Given a concat Chunk and an index out of bounds", () => {
298 it("should throw", () => {
299 fc.assert(fc.property(fc.array(fc.anything()), fc.array(fc.anything()), (arr1, arr2) => {
300 const chunk: Chunk.Chunk<unknown> = Chunk.appendAll(Chunk.fromIterable(arr2))(Chunk.fromArrayUnsafe(arr1))
301 throws(() => pipe(chunk, Chunk.getUnsafe(arr1.length + arr2.length)))
302 }))
303 })
304 })
305
306 describe("Given an appended Chunk and an index in bounds", () => {
307 const chunk = pipe(Chunk.empty(), Chunk.append(1), Chunk.append(2))
308 const index = 1
309
310 it("should return the value", () => {
311 strictEqual(pipe(chunk, Chunk.getUnsafe(index)), 2)
312 })
313 })
314
315 describe("Given a prepended Chunk and an index in bounds", () => {
316 const chunk = pipe(Chunk.empty(), Chunk.prepend(2), Chunk.prepend(1))
317 const index = 1
318
319 it("should return the value", () => {
320 strictEqual(pipe(chunk, Chunk.getUnsafe(index)), 2)
321 })
322 })
323
324 describe("Given a singleton Chunk and an index in bounds", () => {
325 const chunk = Chunk.make(1)
326 const index = 0
327
328 it("should return the value", () => {
329 strictEqual(pipe(chunk, Chunk.getUnsafe(index)), 1)
330 })
331 })
332
333 describe("Given an array Chunk and an index in bounds", () => {
334 const chunk = pipe(Chunk.fromArrayUnsafe([1, 2, 3]))
335 const index = 1
336
337 it("should return the value", () => {
338 strictEqual(pipe(chunk, Chunk.getUnsafe(index)), 2)
339 })
340 })
341
342 describe("Given a concat Chunk and an index in bounds", () => {
343 it("should return the value", () => {
344 fc.assert(fc.property(fc.array(fc.anything()), fc.array(fc.anything()), (a, b) => {
345 const c = [...a, ...b]
346 const d = Chunk.appendAll(Chunk.fromArrayUnsafe(b))(Chunk.fromArrayUnsafe(a))
347 for (let i = 0; i < c.length; i++) {
348 deepStrictEqual(Chunk.getUnsafe(i)(d), c[i])
349 }
350 }))
351 })
352 })
353 })
354
355 it("append", () => {
356 fc.assert(
357 fc.property(
358 fc.array(fc.integer()),
359 fc.array(fc.integer(), { minLength: 0, maxLength: 120, size: "xlarge" }),
360 (a, b) => {
361 let chunk = Chunk.fromArrayUnsafe(a)
362 b.forEach((e) => {
363 chunk = Chunk.append(e)(chunk)
364 })
365 deepStrictEqual(Chunk.toReadonlyArray(chunk), [...a, ...b])
366 }
367 )
368 )
369 })
370
371 it("prependAll", () => {
372 assertEquals(pipe(Chunk.empty(), Chunk.prependAll(Chunk.make(1))), Chunk.make(1))
373 assertEquals(pipe(Chunk.make(1), Chunk.prependAll(Chunk.empty())), Chunk.make(1))
374
375 assertEquals(pipe(Chunk.empty(), Chunk.prependAll(Chunk.make(1, 2))), Chunk.make(1, 2))
376 assertEquals(pipe(Chunk.make(1, 2), Chunk.prependAll(Chunk.empty())), Chunk.make(1, 2))
377
378 assertEquals(pipe(Chunk.make(2, 3), Chunk.prependAll(Chunk.make(1))), Chunk.make(1, 2, 3))
379 assertEquals(pipe(Chunk.make(3), Chunk.prependAll(Chunk.make(1, 2))), Chunk.make(1, 2, 3))
380 })
381
382 it("prepend", () => {
383 fc.assert(
384 fc.property(
385 fc.array(fc.integer()),
386 fc.array(fc.integer(), { minLength: 0, maxLength: 120, size: "xlarge" }),
387 (a, b) => {
388 let chunk = Chunk.fromArrayUnsafe(a)
389 for (let i = b.length - 1; i >= 0; i--) {
390 chunk = Chunk.prepend(b[i])(chunk)
391 }
392 deepStrictEqual(Chunk.toReadonlyArray(chunk), [...b, ...a])
393 }
394 )
395 )
396 })
397
398 describe("count normalization", () => {
399 it("normalizes NaN to a zero count", () => {
400 const chunk = Chunk.make(1, 2, 3)
401 assertEquals(Chunk.take(chunk, Number.NaN), Chunk.empty())
402 assertEquals(Chunk.drop(chunk, Number.NaN), chunk)
403 assertEquals(Chunk.takeRight(chunk, Number.NaN), Chunk.empty())
404 assertEquals(Chunk.dropRight(chunk, Number.NaN), chunk)
405 assertTuple(Chunk.splitAt(chunk, Number.NaN), [Chunk.empty(), chunk])
406 })
407
408 it("produces valid chunks for fractional counts", () => {
409 const chunk = Chunk.make(1, 2, 3)
410 deepStrictEqual(Chunk.toArray(Chunk.take(chunk, 1.5)), [1])
411 deepStrictEqual(Chunk.toArray(Chunk.drop(chunk, 1.5)), [2, 3])
412 deepStrictEqual(Chunk.toArray(Chunk.takeRight(chunk, 1.5)), [3])
413 deepStrictEqual(Chunk.toArray(Chunk.dropRight(chunk, 1.5)), [1, 2])
414 })
415 })
416
417 describe("take", () => {
418 describe("Given a Chunk with more elements than the amount taken", () => {
419 it("should return the subset", () => {
420 assertEquals(pipe(Chunk.fromArrayUnsafe([1, 2, 3]), Chunk.take(2)), Chunk.fromArrayUnsafe([1, 2]))
421 })
422 })
423
424 describe("Given a Chunk with fewer elements than the amount taken", () => {
425 const chunk = Chunk.fromArrayUnsafe([1, 2, 3])
426 const amount = 5
427
428 it("should return the available subset", () => {
429 assertEquals(pipe(chunk, Chunk.take(amount)), Chunk.fromArrayUnsafe([1, 2, 3]))
430 })
431 })
432
433 describe("Given a slice Chunk with and an amount", () => {
434 const chunk = pipe(Chunk.fromArrayUnsafe([1, 2, 3, 4, 5]), Chunk.take(4))
435 const amount = 3
436
437 it("should return the available subset", () => {
438 assertEquals(pipe(chunk, Chunk.take(amount)), Chunk.fromArrayUnsafe([1, 2, 3]))
439 })
440 })
441
442 describe("Given a singleton Chunk with and an amount > 1", () => {
443 const chunk = Chunk.make(1)
444 const amount = 2
445
446 it("should return the available subset", () => {
447 assertEquals(pipe(chunk, Chunk.take(amount)), Chunk.fromArrayUnsafe([1]))
448 })
449 })
450
451 describe("Given a concatenated Chunk and an amount > 1", () => {
452 const chunk = pipe(Chunk.of(1), Chunk.appendAll(Chunk.make(2, 3, 4)))
453 const amount = 2
454
455 it("should return the available subset", () => {
456 deepStrictEqual(pipe(chunk, Chunk.take(amount), Chunk.toReadonlyArray), [1, 2])
457 })
458 })
459
460 describe("Given a concatenated Chunk and an amount <= self.left", () => {
461 it("should return the available subset", () => {
462 const chunk = Chunk.appendAll(Chunk.make(2, 3, 4), Chunk.of(1))
463 deepStrictEqual(pipe(chunk, Chunk.take(2), Chunk.toReadonlyArray), [2, 3])
464 deepStrictEqual(pipe(chunk, Chunk.take(3), Chunk.toReadonlyArray), [2, 3, 4])
465 })
466 })
467 })
468
469 describe("make", () => {
470 it("should return a NonEmptyChunk", () => {
471 strictEqual(Chunk.make(0, 1).length, 2)
472 })
473 })
474
475 describe("singleton", () => {
476 it("should return a NonEmptyChunk", () => {
477 strictEqual(Chunk.of(1).length, 1)
478 })
479 it("should return a ISingleton", () => {
480 strictEqual(Chunk.of(1).backing._tag, "ISingleton")
481 })
482 })
483
484 describe("drop", () => {
485 it("should return self on 0", () => {
486 const self = Chunk.make(0, 1)
487 strictEqual(Chunk.drop(0)(self), self)
488 })
489 it("should drop twice", () => {
490 const self = Chunk.make(0, 1, 2, 3)
491 deepStrictEqual(Chunk.toReadonlyArray(Chunk.drop(1)(Chunk.drop(1)(self))), [2, 3])
492 })
493 it("should handle concatenated chunks", () => {
494 const self = pipe(Chunk.make(1), Chunk.appendAll(Chunk.make(2, 3, 4)))
495 deepStrictEqual(pipe(self, Chunk.drop(2), Chunk.toReadonlyArray), [3, 4])
496 })
497 })
498
499 describe("dropRight", () => {
500 describe("Given a Chunk and an amount to drop below the length", () => {
501 const chunk = Chunk.fromArrayUnsafe([1, 2, 3])
502 const toDrop = 1
503
504 it("should remove the given amount of items", () => {
505 assertEquals(pipe(chunk, Chunk.dropRight(toDrop)), Chunk.fromArrayUnsafe([1, 2]))
506 })
507 })
508
509 describe("Given a Chunk and an amount to drop above the length", () => {
510 const chunk = Chunk.fromArrayUnsafe([1, 2])
511 const toDrop = 3
512
513 it("should return an empty chunk", () => {
514 assertEquals(pipe(chunk, Chunk.dropRight(toDrop)), Chunk.fromArrayUnsafe([]))
515 })
516 })
517 })
518
519 describe("dropWhile", () => {
520 describe("Given a Chunk and a criteria that applies to part of the chunk", () => {
521 const chunk = Chunk.fromArrayUnsafe([1, 2, 3])
522 const criteria = (n: number) => n < 3
523
524 it("should return the subset that doesn't pass the criteria", () => {
525 assertEquals(pipe(chunk, Chunk.dropWhile(criteria)), Chunk.fromArrayUnsafe([3]))
526 })
527 })
528
529 describe("Given a Chunk and a criteria that applies to the whole chunk", () => {
530 const chunk = Chunk.fromArrayUnsafe([1, 2, 3])
531 const criteria = (n: number) => n < 4
532
533 it("should return an empty chunk", () => {
534 assertEquals(pipe(chunk, Chunk.dropWhile(criteria)), Chunk.fromArrayUnsafe([]))
535 })
536 })
537 })
538
539 describe("concat", () => {
540 it("preserves a sliced chunk when appending after a prefix", () => {
541 const chunk = pipe(
542 Chunk.make(2, 3, 4),
543 Chunk.take(2),
544 Chunk.prependAll(Chunk.of(1)),
545 Chunk.append(5)
546 )
547 deepStrictEqual(Chunk.toArray(chunk), [1, 2, 3, 5])
548 })
549
550 describe("Given 2 chunks of the same length", () => {
551 const chunk1 = Chunk.fromArrayUnsafe([0, 1])
552 const chunk2 = Chunk.fromArrayUnsafe([2, 3])
553
554 it("should concatenate them following order", () => {
555 assertEquals(pipe(chunk1, Chunk.appendAll(chunk2)), Chunk.fromArrayUnsafe([0, 1, 2, 3]))
556 })
557 })
558
559 describe("Given 2 chunks where the first one has more elements than the second one", () => {
560 const chunk1 = Chunk.fromArrayUnsafe([1, 2])
561 const chunk2 = Chunk.fromArrayUnsafe([3])
562
563 it("should concatenate them following order", () => {
564 assertEquals(pipe(chunk1, Chunk.appendAll(chunk2)), Chunk.fromArrayUnsafe([1, 2, 3]))
565 })
566 })
567
568 describe("Given 2 chunks where the first one has fewer elements than the second one", () => {
569 const chunk1 = Chunk.fromArrayUnsafe([1])
570 const chunk2 = Chunk.fromArrayUnsafe([2, 3, 4])
571
572 it("should concatenate them following order", () => {
573 assertEquals(pipe(chunk1, Chunk.appendAll(chunk2)), Chunk.fromArrayUnsafe([1, 2, 3, 4]))
574 })
575 })
576
577 describe("Given 2 chunks where the first one is appended", () => {
578 const chunk1 = pipe(
579 Chunk.empty(),
580 Chunk.append(1)
581 )
582 const chunk2 = Chunk.fromArrayUnsafe([2, 3, 4])
583
584 it("should concatenate them following order", () => {
585 assertEquals(pipe(chunk1, Chunk.appendAll(chunk2)), Chunk.fromArrayUnsafe([1, 2, 3, 4]))
586 })
587 })
588
589 describe("Given 2 chunks where the second one is appended", () => {
590 const chunk1 = Chunk.fromArrayUnsafe([1])
591 const chunk2 = pipe(
592 Chunk.empty(),
593 Chunk.prepend(2)
594 )
595
596 it("should concatenate them following order", () => {
597 assertEquals(pipe(chunk1, Chunk.appendAll(chunk2)), Chunk.fromArrayUnsafe([1, 2]))
598 })
599 })
600
601 describe("Given 2 chunks where the first one is empty", () => {
602 const chunk1 = Chunk.empty()
603 const chunk2 = Chunk.fromArrayUnsafe([1, 2])
604
605 it("should concatenate them following order", () => {
606 assertEquals(pipe(chunk1, Chunk.appendAll(chunk2)), Chunk.fromArrayUnsafe([1, 2]))
607 })
608 })
609
610 describe("Given 2 chunks where the second one is empty", () => {
611 const chunk1 = Chunk.fromArrayUnsafe([1, 2])
612 const chunk2 = Chunk.empty()
613
614 it("should concatenate them following order", () => {
615 assertEquals(pipe(chunk1, Chunk.appendAll(chunk2)), Chunk.fromArrayUnsafe([1, 2]))
616 })
617 })
618
619 describe("Given several chunks concatenated with each", () => {
620 const chunk1 = Chunk.empty()
621 const chunk2 = Chunk.fromArrayUnsafe([1])
622 const chunk3 = Chunk.fromArrayUnsafe([2])
623 const chunk4 = Chunk.fromArrayUnsafe([3, 4])
624 const chunk5 = Chunk.fromArrayUnsafe([5, 6])
625
626 it("should concatenate them following order", () => {
627 assertEquals(
628 pipe(
629 chunk1,
630 Chunk.appendAll(chunk2),
631 Chunk.appendAll(chunk3),
632 Chunk.appendAll(chunk4),
633 Chunk.appendAll(chunk5)
634 ),
635 Chunk.fromArrayUnsafe([1, 2, 3, 4, 5, 6])
636 )
637 })
638 })
639 })
640
641 it("zip", () => {
642 assertEquals(Chunk.zip(Chunk.empty(), Chunk.empty()), Chunk.empty())
643 assertEquals(Chunk.zip(Chunk.make(1), Chunk.empty()), Chunk.empty())
644 assertEquals(Chunk.zip(Chunk.empty(), Chunk.make(1)), Chunk.empty())
645 deepStrictEqual(Chunk.toArray(Chunk.zip(Chunk.make(1), Chunk.make(2))), [[1, 2]])
646 })
647
648 describe("Given two non-materialized chunks of different sizes", () => {
649 it("should zip the chunks together and drop the leftover", () => {
650 // Create two non-materialized Chunks
651 const left = pipe(Chunk.make(-1, 0, 1), Chunk.drop(1))
652 const right = pipe(Chunk.make(1, 0, 0, 1), Chunk.drop(1))
653 const zipped = pipe(left, Chunk.zipWith(pipe(right, Chunk.take(left.length)), (a, b) => [a, b]))
654 deepStrictEqual(Array.from(zipped), [[0, 0], [1, 0]])
655 })
656 })
657
658 it("last", () => {
659 assertNone(Chunk.last(Chunk.empty()))
660 assertSome(Chunk.last(Chunk.make(1, 2, 3)), 3)
661 })
662
663 it("map", () => {
664 assertEquals(Chunk.map(Chunk.empty(), (n) => n + 1), Chunk.empty())
665 assertEquals(Chunk.map(Chunk.of(1), (n) => n + 1), Chunk.of(2))
666 assertEquals(Chunk.map(Chunk.make(1, 2, 3), (n) => n + 1), Chunk.make(2, 3, 4))
667 assertEquals(Chunk.map(Chunk.make(1, 2, 3), (n, i) => n + i), Chunk.make(1, 3, 5))
668 })
669
670 it("mapAccum", () => {
671 deepStrictEqual(Chunk.mapAccum(Chunk.make(1, 2, 3), "-", (s, a) => [s + a, a + 1]), ["-123", Chunk.make(2, 3, 4)])
672 })
673
674 it("partition", () => {
675 assertTuple(
676 Chunk.partition(Chunk.empty<number>(), (n) => n > 2 ? Result.succeed(n) : Result.fail(n)),
677 [Chunk.empty(), Chunk.empty()]
678 )
679 assertTuple(
680 Chunk.partition(Chunk.make(1, 3), (n) => n > 2 ? Result.succeed(n) : Result.fail(n)),
681 [Chunk.make(3), Chunk.make(1)]
682 )
683
684 assertTuple(
685 Chunk.partition(
686 Chunk.empty<number>(),
687 (n, i) => n + i > 2 ? Result.succeed(n + i) : Result.fail(`negative:${n + i}`)
688 ),
689 [Chunk.empty(), Chunk.empty()]
690 )
691 assertTuple(
692 Chunk.partition(Chunk.make(1, 2), (n, i) => n + i > 2 ? Result.succeed(n + i) : Result.fail(`negative:${n + i}`)),
693 [Chunk.make(3), Chunk.make("negative:1")]
694 )
695 })
696
697 it("partition (identity)", () => {
698 assertTuple(Chunk.partition(Chunk.empty(), identity), [Chunk.empty(), Chunk.empty()])
699 assertTuple(Chunk.partition(Chunk.make(Result.succeed(1), Result.fail("a"), Result.succeed(2)), identity), [
700 Chunk.make(1, 2),
701 Chunk.make("a")
702 ])
703 })
704
705 it("separate", () => {
706 assertTuple(Chunk.separate(Chunk.empty()), [Chunk.empty(), Chunk.empty()])
707 assertTuple(Chunk.separate(Chunk.make(Result.succeed(1), Result.fail("e"), Result.succeed(2))), [
708 Chunk.make(1, 2),
709 Chunk.make("e")
710 ])
711 })
712
713 it("size", () => {
714 strictEqual(Chunk.size(Chunk.empty()), 0)
715 strictEqual(Chunk.size(Chunk.make(1, 2, 3)), 3)
716 })
717
718 it("split", () => {
719 assertEquals(pipe(Chunk.empty(), Chunk.split(2)), Chunk.empty())
720 assertEquals(pipe(Chunk.make(1), Chunk.split(2)), Chunk.make(Chunk.make(1)))
721 assertEquals(pipe(Chunk.make(1, 2, 3), Chunk.split(Number.NaN)), Chunk.make(Chunk.make(1, 2, 3)))
722 assertEquals(pipe(Chunk.make(1, 2), Chunk.split(2)), Chunk.make(Chunk.make(1), Chunk.make(2)))
723 assertEquals(pipe(Chunk.make(1, 2, 3, 4, 5), Chunk.split(2)), Chunk.make(Chunk.make(1, 2, 3), Chunk.make(4, 5)))
724 assertEquals(
725 pipe(Chunk.make(1, 2, 3, 4, 5), Chunk.split(3)),
726 Chunk.make(Chunk.make(1, 2), Chunk.make(3, 4), Chunk.make(5))
727 )
728 })
729
730 it("tail", () => {
731 assertNone(Chunk.tail(Chunk.empty()))
732 const tail = Chunk.tail(Chunk.make(1, 2, 3))
733 assertTrue(Option.isSome(tail))
734 assertEquals(tail.value, Chunk.make(2, 3))
735 })
736
737 it("filter", () => {
738 assertEquals(Chunk.filter(Chunk.make(1, 2, 3), (n) => n % 2 === 1), Chunk.make(1, 3))
739 assertEquals(
740 Chunk.filter(Chunk.make(Option.some(3), Option.some(2), Option.some(1)), Option.isSome),
741 Chunk.make(Option.some(3), Option.some(2), Option.some(1)) as any
742 )
743 assertEquals(
744 Chunk.filter(Chunk.make(Option.some(3), Option.none(), Option.some(1)), Option.isSome),
745 Chunk.make(Option.some(3), Option.some(1)) as any
746 )
747 })
748
749 it("combinators read sparse backing holes as undefined", () => {
750 const sparse: Array<number | undefined> = new Array(3)
751 sparse[0] = 1
752 sparse[2] = 3
753 const chunk = Chunk.fromArrayUnsafe(sparse)
754 const isUndefined = (a: unknown) => a === undefined
755 deepStrictEqual(Chunk.toArray(Chunk.filter(chunk, isUndefined)), [undefined])
756 deepStrictEqual(
757 Chunk.toArray(Chunk.filterMap(chunk, (a) => a === undefined ? Result.succeed("hole") : Result.failVoid)),
758 ["hole"]
759 )
760 deepStrictEqual(Chunk.toArray(Chunk.zipWith(chunk, chunk, (a, b) => [a, b])), [
761 [1, 1],
762 [undefined, undefined],
763 [3, 3]
764 ])
765 assertSome(Chunk.findLastIndex(chunk, isUndefined), 1)
766 assertTrue(Chunk.some(chunk, isUndefined))
767 assertFalse(Chunk.every(chunk, (a) => a !== undefined))
768 assertSome(Chunk.findLast(chunk, isUndefined), undefined)
769 deepStrictEqual(Chunk.toArray(Chunk.difference(Chunk.make(1, undefined, 2), chunk)), [2])
770 deepStrictEqual(Chunk.toArray(Chunk.difference(chunk, Chunk.make(3))), [1, undefined])
771 })
772
773 it("findLast", () => {
774 assertSome(Chunk.findLast(Chunk.make(1, 2, 3, 4), (n) => n % 2 === 1), 3)
775 assertNone(Chunk.findLast(Chunk.make(2, 4), (n) => n % 2 === 1))
776 assertNone(Chunk.findLast(Chunk.empty<number>(), (n) => n % 2 === 1))
777 })
778
779 it("filterMap", () => {
780 assertEquals(
781 Chunk.filterMap(Chunk.make(1, 2, 3, 4), (n) => n % 2 === 0 ? Result.succeed(n * 2) : Result.failVoid),
782 Chunk.make(4, 8)
783 )
784 assertEquals(
785 Chunk.filterMap(Chunk.make(1, 2, 3, 4), (n, i) => i % 2 === 0 ? Result.succeed(n + i) : Result.failVoid),
786 Chunk.make(1, 5)
787 )
788 })
789
790 it("filterMapWhile", () => {
791 assertEquals(
792 Chunk.filterMapWhile(Chunk.make(1, 3, 4, 5), (n) => n % 2 === 1 ? Result.succeed(n) : Result.failVoid),
793 Chunk.make(1, 3)
794 )
795 })
796
797 it("compact", () => {
798 assertEquals(Chunk.compact(Chunk.empty()), Chunk.empty())
799 assertEquals(Chunk.compact(Chunk.make(Option.some(1), Option.some(2), Option.some(3))), Chunk.make(1, 2, 3))
800 assertEquals(Chunk.compact(Chunk.make(Option.some(1), Option.none(), Option.some(3))), Chunk.make(1, 3))
801 })
802
803 it("dedupeAdjacent", () => {
804 assertEquals(Chunk.dedupeAdjacent(Chunk.empty()), Chunk.empty())
805 assertEquals(Chunk.dedupeAdjacent(Chunk.make(1, 2, 3)), Chunk.make(1, 2, 3))
806 assertEquals(Chunk.dedupeAdjacent(Chunk.make(1, 2, 2, 3, 3)), Chunk.make(1, 2, 3))
807 })
808
809 it("flatMap", () => {
810 assertEquals(Chunk.flatMap(Chunk.make(1), (n) => Chunk.make(n, n + 1)), Chunk.make(1, 2))
811 assertEquals(Chunk.flatMap(Chunk.make(1, 2, 3), (n) => Chunk.make(n, n + 1)), Chunk.make(1, 2, 2, 3, 3, 4))
812 })
813
814 it("flatMap preserves order and callback indices across chunk backings", () => {
815 const indices: Array<number> = []
816 const outer = Chunk.drop(Chunk.make(0, 1, 2, 3), 1)
817 const result = Chunk.flatMap(outer, (n, i) => {
818 indices.push(i)
819 return n === 2 ? Chunk.empty() : Chunk.appendAll(Chunk.of(n), Chunk.of(n + 10))
820 })
821 deepStrictEqual(Array.from(result), [1, 11, 3, 13])
822 deepStrictEqual(indices, [0, 1, 2])
823 })
824
825 it("flatMap and join read the holes of a sparse backing array as undefined", () => {
826 const sparse: Array<string> = new Array(3)
827 sparse[0] = "a"
828 sparse[2] = "c"
829 const chunk = Chunk.fromArrayUnsafe(sparse)
830 const doubled = Chunk.flatMap(chunk, (a) => Chunk.make(a, a))
831 deepStrictEqual(Array.from(doubled), ["a", "a", undefined, undefined, "c", "c"])
832 strictEqual(Chunk.join(chunk, "-"), "a--c")
833 })
834
835 it("union", () => {
836 assertEquals(Chunk.union(Chunk.make(1, 2, 3), Chunk.empty()), Chunk.make(1, 2, 3))
837 assertEquals(Chunk.union(Chunk.empty(), Chunk.make(1, 2, 3)), Chunk.make(1, 2, 3))
838 assertEquals(Chunk.union(Chunk.make(1, 2, 3), Chunk.make(2, 3, 4)), Chunk.make(1, 2, 3, 4))
839 })
840
841 it("intersection", () => {
842 assertEquals(Chunk.intersection(Chunk.make(1, 2, 3), Chunk.empty()), Chunk.empty())
843 assertEquals(Chunk.intersection(Chunk.empty(), Chunk.make(2, 3, 4)), Chunk.empty())
844 assertEquals(Chunk.intersection(Chunk.make(1, 2, 3), Chunk.make(2, 3, 4)), Chunk.make(2, 3))
845 })
846
847 it("isEmpty", () => {
848 assertTrue(Chunk.isEmpty(Chunk.empty()))
849 assertFalse(Chunk.isEmpty(Chunk.make(1)))
850 })
851
852 it("lastUnsafe", () => {
853 strictEqual(Chunk.lastUnsafe(Chunk.make(1)), 1)
854 strictEqual(Chunk.lastUnsafe(Chunk.make(1, 2, 3)), 3)
855 throws(() => Chunk.lastUnsafe(Chunk.empty()), new Error("Index out of bounds: -1"))
856 })
857
858 it("splitNonEmptyAt", () => {
859 assertTuple(Chunk.splitNonEmptyAt(Chunk.make(1, 2, 3, 4), 2), [Chunk.make(1, 2), Chunk.make(3, 4)])
860 assertTuple(Chunk.splitNonEmptyAt(Chunk.make(1, 2, 3, 4), 10), [Chunk.make(1, 2, 3, 4), Chunk.empty()])
861 assertTuple(Chunk.splitNonEmptyAt(Chunk.make(1, 2, 3, 4), Number.NaN), [Chunk.make(1), Chunk.make(2, 3, 4)])
862 })
863
864 it("splitWhere", () => {
865 assertTuple(Chunk.splitWhere(Chunk.empty(), (n) => n > 1), [Chunk.empty(), Chunk.empty()])
866 assertTuple(Chunk.splitWhere(Chunk.make(1, 2, 3), (n) => n > 1), [Chunk.make(1), Chunk.make(2, 3)])
867 })
868
869 it("takeWhile", () => {
870 assertEquals(Chunk.takeWhile(Chunk.empty(), (n) => n <= 2), Chunk.empty())
871 assertEquals(Chunk.takeWhile(Chunk.make(1, 2, 3), (n) => n <= 2), Chunk.make(1, 2))
872 })
873
874 it("dedupe", () => {
875 assertEquals(Chunk.dedupe(Chunk.empty()), Chunk.empty())
876 assertEquals(Chunk.dedupe(Chunk.make(1, 2, 3)), Chunk.make(1, 2, 3))
877 assertEquals(Chunk.dedupe(Chunk.make(1, 2, 3, 2, 1, 3)), Chunk.make(1, 2, 3))
878 })
879
880 it("unzip", () => {
881 assertTuple(Chunk.unzip(Chunk.empty()), [Chunk.empty(), Chunk.empty()])
882 assertTuple(Chunk.unzip(Chunk.make(["a", 1] as const, ["b", 2] as const)), [
883 Chunk.make("a", "b"),
884 Chunk.make(1, 2)
885 ])
886 })
887
888 it("reverse", () => {
889 assertEquals(Chunk.reverse(Chunk.empty()), Chunk.empty())
890 assertEquals(Chunk.reverse(Chunk.make(1, 2, 3)), Chunk.make(3, 2, 1))
891 assertEquals(Chunk.reverse(Chunk.take(Chunk.make(1, 2, 3, 4), 3)), Chunk.make(3, 2, 1))
892 })
893
894 it("flatten", () => {
895 assertEquals(Chunk.flatten(Chunk.make(Chunk.make(1), Chunk.make(2), Chunk.make(3))), Chunk.make(1, 2, 3))
896 })
897
898 it("makeBy", () => {
899 assertEquals(Chunk.makeBy(5, (n) => n * 2), Chunk.make(0, 2, 4, 6, 8))
900 assertEquals(Chunk.makeBy(2.2, (n) => n * 2), Chunk.make(0, 2))
901 assertEquals(Chunk.makeBy(Number.NaN, (n) => n), Chunk.make(0))
902 })
903
904 it("range", () => {
905 assertEquals(Chunk.range(0, 0), Chunk.make(0))
906 assertEquals(Chunk.range(0, 1), Chunk.make(0, 1))
907 assertEquals(Chunk.range(1, 5), Chunk.make(1, 2, 3, 4, 5))
908 assertEquals(Chunk.range(10, 15), Chunk.make(10, 11, 12, 13, 14, 15))
909 assertEquals(Chunk.range(-1, 0), Chunk.make(-1, 0))
910 assertEquals(Chunk.range(-5, -1), Chunk.make(-5, -4, -3, -2, -1))
911 // out of bound
912 assertEquals(Chunk.range(2, 1), Chunk.make(2))
913 assertEquals(Chunk.range(-1, -2), Chunk.make(-1))
914 })
915
916 it("some", () => {
917 const isPositive: Predicate.Predicate<number> = (n) => n > 0
918 assertTrue(Chunk.some(Chunk.make(-1, -2, 3), isPositive))
919 assertFalse(Chunk.some(Chunk.make(-1, -2, -3), isPositive))
920 })
921
922 it("forEach", () => {
923 const as: Array<string> = []
924 Chunk.forEach(Chunk.make(1, 2, 3, 4), (n, i) => as.push(`${n}-${i}`))
925 deepStrictEqual(as, ["1-0", "2-1", "3-2", "4-3"])
926 })
927
928 it("sortWith", () => {
929 type X = {
930 a: string
931 b: number
932 }
933 const chunk: Chunk.Chunk<X> = Chunk.make({ a: "a", b: 2 }, { a: "b", b: 1 })
934 deepStrictEqual(Chunk.sortWith(chunk, (x) => x.b, Order.Number), Chunk.make({ a: "b", b: 1 }, { a: "a", b: 2 }))
935 })
936
937 it("makeEquivalence", () => {
938 const equivalence = Chunk.makeEquivalence(Equivalence.strictEqual<number>())
939 assertTrue(equivalence(Chunk.empty(), Chunk.empty()))
940 assertTrue(equivalence(Chunk.make(1, 2, 3), Chunk.make(1, 2, 3)))
941 assertFalse(equivalence(Chunk.make(1, 2, 3), Chunk.make(1, 2)))
942 assertFalse(equivalence(Chunk.make(1, 2, 3), Chunk.make(1, 2, 4)))
943 })
944
945 it("makeEquivalence is symmetric on a sparse backing array, whose holes read as undefined", () => {
946 const equivalence = Chunk.makeEquivalence(Equivalence.strictEqual<number | undefined>())
947 const holey: Array<number | undefined> = new Array(2)
948 holey[1] = 1
949 const sparse = Chunk.fromArrayUnsafe(holey)
950 assertFalse(equivalence(sparse, Chunk.make(5, 1)))
951 assertFalse(equivalence(Chunk.make(5, 1), sparse))
952 assertTrue(equivalence(sparse, Chunk.make(undefined, 1)))
953 assertTrue(equivalence(Chunk.make(undefined, 1), sparse))
954 })
955
956 it("differenceWith", () => {
957 const eq = <E extends { id: number }>(a: E, b: E) => a.id === b.id
958 const differenceWith = pipe(eq, Chunk.differenceWith)
959
960 const chunk = Chunk.make({ id: 1 }, { id: 2 }, { id: 3 })
961
962 deepStrictEqual(differenceWith(chunk, Chunk.make({ id: 1 }, { id: 2 })), Chunk.make({ id: 3 }))
963 assertEquals(differenceWith(Chunk.empty(), chunk), Chunk.empty())
964 assertEquals(differenceWith(chunk, Chunk.empty()), chunk)
965 assertEquals(differenceWith(chunk, chunk), Chunk.empty())
966 })
967
968 it("difference", () => {
969 const curr = Chunk.make(1, 3, 5, 7, 9)
970
971 assertEquals(Chunk.difference(curr, Chunk.make(1, 2, 3, 4, 5)), Chunk.make(7, 9))
972 assertEquals(Chunk.difference(Chunk.empty(), curr), Chunk.empty())
973 assertEquals(Chunk.difference(curr, Chunk.empty()), curr)
974 assertEquals(Chunk.difference(curr, curr), Chunk.empty())
975 })
976})