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