fp_test.gno
14.20 Kb · 667 lines
1package fp
2
3import (
4 "testing"
5
6 "gno.land/p/nt/ufmt/v0"
7)
8
9func TestMap(t *testing.T) {
10 tests := []struct {
11 name string
12 input []any
13 fn func(any) any
14 expected []any
15 }{
16 {
17 name: "multiply numbers by 2",
18 input: []any{1, 2, 3},
19 fn: func(v any) any { return v.(int) * 2 },
20 expected: []any{2, 4, 6},
21 },
22 {
23 name: "empty slice",
24 input: []any{},
25 fn: func(v any) any { return v.(int) * 2 },
26 expected: []any{},
27 },
28 {
29 name: "convert numbers to strings",
30 input: []any{1, 2, 3},
31 fn: func(v any) any { return ufmt.Sprintf("%d", v.(int)) },
32 expected: []any{"1", "2", "3"},
33 },
34 }
35
36 for _, tt := range tests {
37 t.Run(tt.name, func(t *testing.T) {
38 result := Map(tt.input, tt.fn)
39 if !equalSlices(result, tt.expected) {
40 t.Errorf("Map failed, expected %v, got %v", tt.expected, result)
41 }
42 })
43 }
44}
45
46func TestFilter(t *testing.T) {
47 tests := []struct {
48 name string
49 input []any
50 fn func(any) bool
51 expected []any
52 }{
53 {
54 name: "filter even numbers",
55 input: []any{1, 2, 3, 4},
56 fn: func(v any) bool { return v.(int)%2 == 0 },
57 expected: []any{2, 4},
58 },
59 {
60 name: "empty slice",
61 input: []any{},
62 fn: func(v any) bool { return v.(int)%2 == 0 },
63 expected: []any{},
64 },
65 {
66 name: "no matches",
67 input: []any{1, 3, 5},
68 fn: func(v any) bool { return v.(int)%2 == 0 },
69 expected: []any{},
70 },
71 {
72 name: "all matches",
73 input: []any{2, 4, 6},
74 fn: func(v any) bool { return v.(int)%2 == 0 },
75 expected: []any{2, 4, 6},
76 },
77 }
78
79 for _, tt := range tests {
80 t.Run(tt.name, func(t *testing.T) {
81 result := Filter(tt.input, tt.fn)
82 if !equalSlices(result, tt.expected) {
83 t.Errorf("Filter failed, expected %v, got %v", tt.expected, result)
84 }
85 })
86 }
87}
88
89func TestReduce(t *testing.T) {
90 tests := []struct {
91 name string
92 input []any
93 fn func(any, any) any
94 initial any
95 expected any
96 }{
97 {
98 name: "sum numbers",
99 input: []any{1, 2, 3},
100 fn: func(a, b any) any { return a.(int) + b.(int) },
101 initial: 0,
102 expected: 6,
103 },
104 {
105 name: "empty slice",
106 input: []any{},
107 fn: func(a, b any) any { return a.(int) + b.(int) },
108 initial: 0,
109 expected: 0,
110 },
111 {
112 name: "concatenate strings",
113 input: []any{"a", "b", "c"},
114 fn: func(a, b any) any { return a.(string) + b.(string) },
115 initial: "",
116 expected: "abc",
117 },
118 }
119
120 for _, tt := range tests {
121 t.Run(tt.name, func(t *testing.T) {
122 result := Reduce(tt.input, tt.fn, tt.initial)
123 if result != tt.expected {
124 t.Errorf("Reduce failed, expected %v, got %v", tt.expected, result)
125 }
126 })
127 }
128}
129
130func TestFlatMap(t *testing.T) {
131 tests := []struct {
132 name string
133 input []any
134 fn func(any) any
135 expected []any
136 }{
137 {
138 name: "split words into chars",
139 input: []any{"go", "fn"},
140 fn: func(word any) any {
141 chars := []any{}
142 for _, c := range word.(string) {
143 chars = append(chars, string(c))
144 }
145 return chars
146 },
147 expected: []any{"g", "o", "f", "n"},
148 },
149 {
150 name: "empty string handling",
151 input: []any{"", "a", ""},
152 fn: func(word any) any {
153 chars := []any{}
154 for _, c := range word.(string) {
155 chars = append(chars, string(c))
156 }
157 return chars
158 },
159 expected: []any{"a"},
160 },
161 {
162 name: "nil handling",
163 input: []any{nil, "a", nil},
164 fn: func(word any) any {
165 if word == nil {
166 return []any{}
167 }
168 return []any{word}
169 },
170 expected: []any{"a"},
171 },
172 {
173 name: "empty slice result",
174 input: []any{"", "", ""},
175 fn: func(word any) any {
176 return []any{}
177 },
178 expected: []any{},
179 },
180 {
181 name: "nested array flattening",
182 input: []any{1, 2, 3},
183 fn: func(n any) any {
184 return []any{n, n}
185 },
186 expected: []any{1, 1, 2, 2, 3, 3},
187 },
188 }
189
190 for _, tt := range tests {
191 t.Run(tt.name, func(t *testing.T) {
192 result := FlatMap(tt.input, tt.fn)
193 if !equalSlices(result, tt.expected) {
194 t.Errorf("FlatMap failed, expected %v, got %v", tt.expected, result)
195 }
196 })
197 }
198}
199
200func TestAllAnyNone(t *testing.T) {
201 tests := []struct {
202 name string
203 input []any
204 fn func(any) bool
205 expectedAll bool
206 expectedAny bool
207 expectedNone bool
208 }{
209 {
210 name: "all even numbers",
211 input: []any{2, 4, 6, 8},
212 fn: func(x any) bool { return x.(int)%2 == 0 },
213 expectedAll: true,
214 expectedAny: true,
215 expectedNone: false,
216 },
217 {
218 name: "no even numbers",
219 input: []any{1, 3, 5, 7},
220 fn: func(x any) bool { return x.(int)%2 == 0 },
221 expectedAll: false,
222 expectedAny: false,
223 expectedNone: true,
224 },
225 {
226 name: "mixed even/odd numbers",
227 input: []any{1, 2, 3, 4},
228 fn: func(x any) bool { return x.(int)%2 == 0 },
229 expectedAll: false,
230 expectedAny: true,
231 expectedNone: false,
232 },
233 {
234 name: "empty slice",
235 input: []any{},
236 fn: func(x any) bool { return x.(int)%2 == 0 },
237 expectedAll: true, // vacuously true
238 expectedAny: false, // vacuously false
239 expectedNone: true, // vacuously true
240 },
241 {
242 name: "nil predicate handling",
243 input: []any{nil, nil, nil},
244 fn: func(x any) bool { return x == nil },
245 expectedAll: true,
246 expectedAny: true,
247 expectedNone: false,
248 },
249 }
250
251 for _, tt := range tests {
252 t.Run(tt.name, func(t *testing.T) {
253 resultAll := All(tt.input, tt.fn)
254 if resultAll != tt.expectedAll {
255 t.Errorf("All failed, expected %v, got %v", tt.expectedAll, resultAll)
256 }
257
258 resultAny := Any(tt.input, tt.fn)
259 if resultAny != tt.expectedAny {
260 t.Errorf("Any failed, expected %v, got %v", tt.expectedAny, resultAny)
261 }
262
263 resultNone := None(tt.input, tt.fn)
264 if resultNone != tt.expectedNone {
265 t.Errorf("None failed, expected %v, got %v", tt.expectedNone, resultNone)
266 }
267 })
268 }
269}
270
271func TestChunk(t *testing.T) {
272 tests := []struct {
273 name string
274 input []any
275 size int
276 expected [][]any
277 }{
278 {
279 name: "normal chunks",
280 input: []any{1, 2, 3, 4, 5},
281 size: 2,
282 expected: [][]any{{1, 2}, {3, 4}, {5}},
283 },
284 {
285 name: "empty slice",
286 input: []any{},
287 size: 2,
288 expected: [][]any{},
289 },
290 {
291 name: "chunk size equals length",
292 input: []any{1, 2, 3},
293 size: 3,
294 expected: [][]any{{1, 2, 3}},
295 },
296 {
297 name: "chunk size larger than length",
298 input: []any{1, 2},
299 size: 3,
300 expected: [][]any{{1, 2}},
301 },
302 }
303
304 for _, tt := range tests {
305 t.Run(tt.name, func(t *testing.T) {
306 result := Chunk(tt.input, tt.size)
307 if !equalNestedSlices(result, tt.expected) {
308 t.Errorf("Chunk failed, expected %v, got %v", tt.expected, result)
309 }
310 })
311 }
312}
313
314func TestFind(t *testing.T) {
315 tests := []struct {
316 name string
317 input []any
318 fn func(any) bool
319 expected any
320 shouldFound bool
321 }{
322 {
323 name: "find first number greater than 2",
324 input: []any{1, 2, 3, 4},
325 fn: func(v any) bool { return v.(int) > 2 },
326 expected: 3,
327 shouldFound: true,
328 },
329 {
330 name: "empty slice",
331 input: []any{},
332 fn: func(v any) bool { return v.(int) > 2 },
333 expected: nil,
334 shouldFound: false,
335 },
336 {
337 name: "no match",
338 input: []any{1, 2},
339 fn: func(v any) bool { return v.(int) > 10 },
340 expected: nil,
341 shouldFound: false,
342 },
343 }
344
345 for _, tt := range tests {
346 t.Run(tt.name, func(t *testing.T) {
347 result, found := Find(tt.input, tt.fn)
348 if found != tt.shouldFound {
349 t.Errorf("Find failed, expected found=%v, got found=%v", tt.shouldFound, found)
350 }
351 if found && result != tt.expected {
352 t.Errorf("Find failed, expected %v, got %v", tt.expected, result)
353 }
354 })
355 }
356}
357
358func TestReverse(t *testing.T) {
359 tests := []struct {
360 name string
361 input []any
362 expected []any
363 }{
364 {
365 name: "normal sequence",
366 input: []any{1, 2, 3, 4},
367 expected: []any{4, 3, 2, 1},
368 },
369 {
370 name: "empty slice",
371 input: []any{},
372 expected: []any{},
373 },
374 {
375 name: "single element",
376 input: []any{1},
377 expected: []any{1},
378 },
379 {
380 name: "mixed types",
381 input: []any{1, "a", true, 2.5},
382 expected: []any{2.5, true, "a", 1},
383 },
384 }
385
386 for _, tt := range tests {
387 t.Run(tt.name, func(t *testing.T) {
388 result := Reverse(tt.input)
389 if !equalSlices(result, tt.expected) {
390 t.Errorf("Reverse failed, expected %v, got %v", tt.expected, result)
391 }
392 })
393 }
394}
395
396func TestZipUnzip(t *testing.T) {
397 tests := []struct {
398 name string
399 a []any
400 b []any
401 expectedZip [][2]any
402 expectedA []any
403 expectedB []any
404 }{
405 {
406 name: "normal case",
407 a: []any{1, 2, 3},
408 b: []any{"a", "b", "c"},
409 expectedZip: [][2]any{{1, "a"}, {2, "b"}, {3, "c"}},
410 expectedA: []any{1, 2, 3},
411 expectedB: []any{"a", "b", "c"},
412 },
413 {
414 name: "empty slices",
415 a: []any{},
416 b: []any{},
417 expectedZip: [][2]any{},
418 expectedA: []any{},
419 expectedB: []any{},
420 },
421 {
422 name: "different lengths - a shorter",
423 a: []any{1, 2},
424 b: []any{"a", "b", "c"},
425 expectedZip: [][2]any{{1, "a"}, {2, "b"}},
426 expectedA: []any{1, 2},
427 expectedB: []any{"a", "b"},
428 },
429 {
430 name: "different lengths - b shorter",
431 a: []any{1, 2, 3},
432 b: []any{"a"},
433 expectedZip: [][2]any{{1, "a"}},
434 expectedA: []any{1},
435 expectedB: []any{"a"},
436 },
437 {
438 name: "mixed types",
439 a: []any{1, true, "x"},
440 b: []any{2.5, false, "y"},
441 expectedZip: [][2]any{{1, 2.5}, {true, false}, {"x", "y"}},
442 expectedA: []any{1, true, "x"},
443 expectedB: []any{2.5, false, "y"},
444 },
445 }
446
447 for _, tt := range tests {
448 t.Run(tt.name, func(t *testing.T) {
449 zipped := Zip(tt.a, tt.b)
450 if len(zipped) != len(tt.expectedZip) {
451 t.Errorf("Zip failed, expected length %v, got %v", len(tt.expectedZip), len(zipped))
452 }
453 for i, pair := range zipped {
454 if pair[0] != tt.expectedZip[i][0] || pair[1] != tt.expectedZip[i][1] {
455 t.Errorf("Zip failed at index %d, expected %v, got %v", i, tt.expectedZip[i], pair)
456 }
457 }
458
459 unzippedA, unzippedB := Unzip(zipped)
460 if !equalSlices(unzippedA, tt.expectedA) {
461 t.Errorf("Unzip failed for slice A, expected %v, got %v", tt.expectedA, unzippedA)
462 }
463 if !equalSlices(unzippedB, tt.expectedB) {
464 t.Errorf("Unzip failed for slice B, expected %v, got %v", tt.expectedB, unzippedB)
465 }
466 })
467 }
468}
469
470func TestGroupBy(t *testing.T) {
471 tests := []struct {
472 name string
473 input []any
474 fn func(any) any
475 expected map[any][]any
476 }{
477 {
478 name: "group by even/odd",
479 input: []any{1, 2, 3, 4, 5, 6},
480 fn: func(v any) any { return v.(int) % 2 },
481 expected: map[any][]any{
482 0: {2, 4, 6},
483 1: {1, 3, 5},
484 },
485 },
486 {
487 name: "empty slice",
488 input: []any{},
489 fn: func(v any) any { return v.(int) % 2 },
490 expected: map[any][]any{},
491 },
492 {
493 name: "single group",
494 input: []any{2, 4, 6},
495 fn: func(v any) any { return v.(int) % 2 },
496 expected: map[any][]any{
497 0: {2, 4, 6},
498 },
499 },
500 {
501 name: "group by type",
502 input: []any{1, "a", 2, "b", true},
503 fn: func(v any) any {
504 switch v.(type) {
505 case int:
506 return "int"
507 case string:
508 return "string"
509 default:
510 return "other"
511 }
512 },
513 expected: map[any][]any{
514 "int": {1, 2},
515 "string": {"a", "b"},
516 "other": {true},
517 },
518 },
519 }
520
521 for _, tt := range tests {
522 t.Run(tt.name, func(t *testing.T) {
523 result := GroupBy(tt.input, tt.fn)
524 if len(result) != len(tt.expected) {
525 t.Errorf("GroupBy failed, expected %d groups, got %d", len(tt.expected), len(result))
526 }
527 for k, v := range tt.expected {
528 if !equalSlices(result[k], v) {
529 t.Errorf("GroupBy failed for key %v, expected %v, got %v", k, v, result[k])
530 }
531 }
532 })
533 }
534}
535
536func TestFlatten(t *testing.T) {
537 tests := []struct {
538 name string
539 input [][]any
540 expected []any
541 }{
542 {
543 name: "normal nested slices",
544 input: [][]any{{1, 2}, {3, 4}, {5}},
545 expected: []any{1, 2, 3, 4, 5},
546 },
547 {
548 name: "empty outer slice",
549 input: [][]any{},
550 expected: []any{},
551 },
552 {
553 name: "empty inner slices",
554 input: [][]any{{}, {}, {}},
555 expected: []any{},
556 },
557 {
558 name: "mixed types",
559 input: [][]any{{1, "a"}, {true, 2.5}, {nil}},
560 expected: []any{1, "a", true, 2.5, nil},
561 },
562 {
563 name: "single element slices",
564 input: [][]any{{1}, {2}, {3}},
565 expected: []any{1, 2, 3},
566 },
567 }
568
569 for _, tt := range tests {
570 t.Run(tt.name, func(t *testing.T) {
571 result := Flatten(tt.input)
572 if !equalSlices(result, tt.expected) {
573 t.Errorf("Flatten failed, expected %v, got %v", tt.expected, result)
574 }
575 })
576 }
577}
578
579func TestContains(t *testing.T) {
580 tests := []struct {
581 name string
582 slice []any
583 item any
584 expected bool
585 }{
586 {
587 name: "contains integer",
588 slice: []any{1, 2, 3},
589 item: 2,
590 expected: true,
591 },
592 {
593 name: "does not contain integer",
594 slice: []any{1, 2, 3},
595 item: 4,
596 expected: false,
597 },
598 {
599 name: "contains string",
600 slice: []any{"a", "b", "c"},
601 item: "b",
602 expected: true,
603 },
604 {
605 name: "empty slice",
606 slice: []any{},
607 item: 1,
608 expected: false,
609 },
610 {
611 name: "contains nil",
612 slice: []any{1, nil, 3},
613 item: nil,
614 expected: true,
615 },
616 {
617 name: "mixed types",
618 slice: []any{1, "a", true},
619 item: true,
620 expected: true,
621 },
622 }
623
624 for _, tt := range tests {
625 t.Run(tt.name, func(t *testing.T) {
626 result := contains(tt.slice, tt.item)
627 if result != tt.expected {
628 t.Errorf("contains failed, expected %v, got %v", tt.expected, result)
629 }
630 })
631 }
632}
633
634// Helper function for testing
635func contains(slice []any, item any) bool {
636 for _, v := range slice {
637 if v == item {
638 return true
639 }
640 }
641 return false
642}
643
644// Helper functions for comparing slices
645func equalSlices(a, b []any) bool {
646 if len(a) != len(b) {
647 return false
648 }
649 for i := range a {
650 if a[i] != b[i] {
651 return false
652 }
653 }
654 return true
655}
656
657func equalNestedSlices(a, b [][]any) bool {
658 if len(a) != len(b) {
659 return false
660 }
661 for i := range a {
662 if !equalSlices(a[i], b[i]) {
663 return false
664 }
665 }
666 return true
667}