ts-data-forge 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +534 -0
  3. package/package.json +101 -0
  4. package/src/array/array-utils-creation.test.mts +443 -0
  5. package/src/array/array-utils-modification.test.mts +197 -0
  6. package/src/array/array-utils-overload-type-error.test.mts +149 -0
  7. package/src/array/array-utils-reducing-value.test.mts +425 -0
  8. package/src/array/array-utils-search.test.mts +169 -0
  9. package/src/array/array-utils-set-op.test.mts +335 -0
  10. package/src/array/array-utils-slice-clamped.test.mts +113 -0
  11. package/src/array/array-utils-slicing.test.mts +316 -0
  12. package/src/array/array-utils-transformation.test.mts +790 -0
  13. package/src/array/array-utils-validation.test.mts +492 -0
  14. package/src/array/array-utils.mts +4000 -0
  15. package/src/array/array.test.mts +146 -0
  16. package/src/array/index.mts +2 -0
  17. package/src/array/tuple-utils.mts +519 -0
  18. package/src/array/tuple-utils.test.mts +518 -0
  19. package/src/collections/imap-mapped.mts +801 -0
  20. package/src/collections/imap-mapped.test.mts +860 -0
  21. package/src/collections/imap.mts +651 -0
  22. package/src/collections/imap.test.mts +932 -0
  23. package/src/collections/index.mts +6 -0
  24. package/src/collections/iset-mapped.mts +889 -0
  25. package/src/collections/iset-mapped.test.mts +1187 -0
  26. package/src/collections/iset.mts +682 -0
  27. package/src/collections/iset.test.mts +1084 -0
  28. package/src/collections/queue.mts +390 -0
  29. package/src/collections/queue.test.mts +282 -0
  30. package/src/collections/stack.mts +423 -0
  31. package/src/collections/stack.test.mts +225 -0
  32. package/src/expect-type.mts +206 -0
  33. package/src/functional/index.mts +4 -0
  34. package/src/functional/match.mts +300 -0
  35. package/src/functional/match.test.mts +177 -0
  36. package/src/functional/optional.mts +733 -0
  37. package/src/functional/optional.test.mts +619 -0
  38. package/src/functional/pipe.mts +212 -0
  39. package/src/functional/pipe.test.mts +85 -0
  40. package/src/functional/result.mts +1134 -0
  41. package/src/functional/result.test.mts +777 -0
  42. package/src/globals.d.mts +38 -0
  43. package/src/guard/has-key.mts +119 -0
  44. package/src/guard/has-key.test.mts +219 -0
  45. package/src/guard/index.mts +7 -0
  46. package/src/guard/is-non-empty-string.mts +108 -0
  47. package/src/guard/is-non-empty-string.test.mts +91 -0
  48. package/src/guard/is-non-null-object.mts +106 -0
  49. package/src/guard/is-non-null-object.test.mts +90 -0
  50. package/src/guard/is-primitive.mts +165 -0
  51. package/src/guard/is-primitive.test.mts +102 -0
  52. package/src/guard/is-record.mts +153 -0
  53. package/src/guard/is-record.test.mts +112 -0
  54. package/src/guard/is-type.mts +450 -0
  55. package/src/guard/is-type.test.mts +496 -0
  56. package/src/guard/key-is-in.mts +163 -0
  57. package/src/guard/key-is-in.test.mts +19 -0
  58. package/src/index.mts +10 -0
  59. package/src/iterator/index.mts +1 -0
  60. package/src/iterator/range.mts +120 -0
  61. package/src/iterator/range.test.mts +33 -0
  62. package/src/json/index.mts +1 -0
  63. package/src/json/json.mts +711 -0
  64. package/src/json/json.test.mts +628 -0
  65. package/src/number/branded-types/finite-number.mts +354 -0
  66. package/src/number/branded-types/finite-number.test.mts +135 -0
  67. package/src/number/branded-types/index.mts +26 -0
  68. package/src/number/branded-types/int.mts +278 -0
  69. package/src/number/branded-types/int.test.mts +140 -0
  70. package/src/number/branded-types/int16.mts +192 -0
  71. package/src/number/branded-types/int16.test.mts +170 -0
  72. package/src/number/branded-types/int32.mts +193 -0
  73. package/src/number/branded-types/int32.test.mts +170 -0
  74. package/src/number/branded-types/non-negative-finite-number.mts +223 -0
  75. package/src/number/branded-types/non-negative-finite-number.test.mts +188 -0
  76. package/src/number/branded-types/non-negative-int16.mts +187 -0
  77. package/src/number/branded-types/non-negative-int16.test.mts +201 -0
  78. package/src/number/branded-types/non-negative-int32.mts +187 -0
  79. package/src/number/branded-types/non-negative-int32.test.mts +204 -0
  80. package/src/number/branded-types/non-zero-finite-number.mts +229 -0
  81. package/src/number/branded-types/non-zero-finite-number.test.mts +198 -0
  82. package/src/number/branded-types/non-zero-int.mts +167 -0
  83. package/src/number/branded-types/non-zero-int.test.mts +177 -0
  84. package/src/number/branded-types/non-zero-int16.mts +196 -0
  85. package/src/number/branded-types/non-zero-int16.test.mts +195 -0
  86. package/src/number/branded-types/non-zero-int32.mts +196 -0
  87. package/src/number/branded-types/non-zero-int32.test.mts +197 -0
  88. package/src/number/branded-types/non-zero-safe-int.mts +196 -0
  89. package/src/number/branded-types/non-zero-safe-int.test.mts +232 -0
  90. package/src/number/branded-types/non-zero-uint16.mts +189 -0
  91. package/src/number/branded-types/non-zero-uint16.test.mts +199 -0
  92. package/src/number/branded-types/non-zero-uint32.mts +189 -0
  93. package/src/number/branded-types/non-zero-uint32.test.mts +199 -0
  94. package/src/number/branded-types/positive-finite-number.mts +241 -0
  95. package/src/number/branded-types/positive-finite-number.test.mts +204 -0
  96. package/src/number/branded-types/positive-int.mts +304 -0
  97. package/src/number/branded-types/positive-int.test.mts +176 -0
  98. package/src/number/branded-types/positive-int16.mts +188 -0
  99. package/src/number/branded-types/positive-int16.test.mts +197 -0
  100. package/src/number/branded-types/positive-int32.mts +188 -0
  101. package/src/number/branded-types/positive-int32.test.mts +197 -0
  102. package/src/number/branded-types/positive-safe-int.mts +187 -0
  103. package/src/number/branded-types/positive-safe-int.test.mts +210 -0
  104. package/src/number/branded-types/positive-uint16.mts +188 -0
  105. package/src/number/branded-types/positive-uint16.test.mts +203 -0
  106. package/src/number/branded-types/positive-uint32.mts +188 -0
  107. package/src/number/branded-types/positive-uint32.test.mts +203 -0
  108. package/src/number/branded-types/safe-int.mts +291 -0
  109. package/src/number/branded-types/safe-int.test.mts +170 -0
  110. package/src/number/branded-types/safe-uint.mts +187 -0
  111. package/src/number/branded-types/safe-uint.test.mts +176 -0
  112. package/src/number/branded-types/uint.mts +179 -0
  113. package/src/number/branded-types/uint.test.mts +158 -0
  114. package/src/number/branded-types/uint16.mts +186 -0
  115. package/src/number/branded-types/uint16.test.mts +170 -0
  116. package/src/number/branded-types/uint32.mts +218 -0
  117. package/src/number/branded-types/uint32.test.mts +170 -0
  118. package/src/number/enum/index.mts +2 -0
  119. package/src/number/enum/int8.mts +344 -0
  120. package/src/number/enum/int8.test.mts +180 -0
  121. package/src/number/enum/uint8.mts +293 -0
  122. package/src/number/enum/uint8.test.mts +164 -0
  123. package/src/number/index.mts +4 -0
  124. package/src/number/num.mts +604 -0
  125. package/src/number/num.test.mts +242 -0
  126. package/src/number/refined-number-utils.mts +566 -0
  127. package/src/object/index.mts +1 -0
  128. package/src/object/object.mts +447 -0
  129. package/src/object/object.test.mts +124 -0
  130. package/src/others/cast-mutable.mts +113 -0
  131. package/src/others/cast-readonly.mts +192 -0
  132. package/src/others/cast-readonly.test.mts +89 -0
  133. package/src/others/if-then.mts +98 -0
  134. package/src/others/if-then.test.mts +75 -0
  135. package/src/others/index.mts +7 -0
  136. package/src/others/map-nullable.mts +172 -0
  137. package/src/others/map-nullable.test.mts +297 -0
  138. package/src/others/memoize-function.mts +196 -0
  139. package/src/others/memoize-function.test.mts +168 -0
  140. package/src/others/tuple.mts +160 -0
  141. package/src/others/tuple.test.mts +11 -0
  142. package/src/others/unknown-to-string.mts +215 -0
  143. package/src/others/unknown-to-string.test.mts +114 -0
@@ -0,0 +1,149 @@
1
+ import { expectType } from '../expect-type.mjs';
2
+ import { pipe } from '../functional/index.mjs';
3
+ import { Arr } from './array-utils.mjs';
4
+
5
+ // Tests for ensuring type errors occur with incorrect usage of overloaded functions
6
+
7
+ describe('Array overloaded functions - type error validation', () => {
8
+ const testArray = [1, 2, 3, 4, 5] as const;
9
+ const predicate = (x: number): boolean => x > 3;
10
+
11
+ describe('Arr.findIndex type safety', () => {
12
+ test('findIndex with correct arguments should work', () => {
13
+ // These should work fine
14
+ const _result1 = Arr.findIndex(testArray, predicate);
15
+ const _result2 = Arr.findIndex(predicate);
16
+ const _result3 = Arr.findIndex(predicate)(testArray);
17
+
18
+ expectType<typeof _result1, SizeType.Arr | -1>('=');
19
+ expectType<
20
+ typeof _result2,
21
+ (array: readonly number[]) => SizeType.Arr | -1
22
+ >('=');
23
+ expectType<typeof _result3, SizeType.Arr | -1>('=');
24
+ });
25
+
26
+ test('findIndex type constraints work correctly', () => {
27
+ // Test that type constraints exist for predicate function
28
+ // @ts-expect-error - Predicate should return boolean, not string
29
+ Arr.findIndex(testArray, (x: number) => x.toString());
30
+
31
+ // @ts-expect-error - Array element type should match predicate parameter
32
+ Arr.findIndex(['a', 'b', 'c'], (x: number) => x > 0);
33
+
34
+ expect(true).toBe(true);
35
+ });
36
+ });
37
+
38
+ describe('Arr.filterNot type safety', () => {
39
+ test('filterNot with correct arguments should work', () => {
40
+ // These should work fine
41
+ const _result1 = Arr.filterNot(testArray, predicate);
42
+ const _result2 = Arr.filterNot(predicate);
43
+ const _result3 = Arr.filterNot(predicate)(testArray);
44
+
45
+ expectType<typeof _result1, readonly number[]>('<=');
46
+ expectType<
47
+ typeof _result2,
48
+ (array: readonly number[]) => readonly number[]
49
+ >('<=');
50
+ expectType<typeof _result3, readonly number[]>('<=');
51
+ });
52
+
53
+ test('filterNot type constraints work correctly', () => {
54
+ // @ts-expect-error - Predicate should return boolean, not string
55
+ Arr.filterNot(testArray, (x: number) => x.toString());
56
+
57
+ // @ts-expect-error - Array element type should match predicate parameter
58
+ Arr.filterNot(['a', 'b', 'c'], (x: number) => x > 0);
59
+
60
+ expect(true).toBe(true);
61
+ });
62
+ });
63
+
64
+ describe('Arr.partition type safety', () => {
65
+ test('partition with correct arguments should work', () => {
66
+ // These should work fine
67
+ const _result1 = Arr.partition(testArray, 2);
68
+ const _result2 = Arr.partition(2);
69
+ const _result3 = Arr.partition(2)(testArray);
70
+
71
+ expectType<typeof _result1, readonly (readonly number[])[]>('<=');
72
+ expectType<
73
+ typeof _result2,
74
+ <A>(array: readonly A[]) => readonly (readonly A[])[]
75
+ >('<=');
76
+ expectType<typeof _result3, readonly (readonly number[])[]>('<=');
77
+ });
78
+
79
+ test('partition type constraints work correctly', () => {
80
+ // @ts-expect-error - Partition size should be number, not string
81
+ expect(() => Arr.partition(testArray, 'invalid')).toThrowError();
82
+
83
+ // @ts-expect-error - Negative partition size should not be allowed
84
+ Arr.partition(testArray, -1);
85
+ });
86
+ });
87
+
88
+ describe('Arr.range type safety', () => {
89
+ test('range with correct arguments should work', () => {
90
+ // These should work fine
91
+ const _result1 = Arr.range(1, 5);
92
+ const _result2 = Arr.range(1, 5, 1);
93
+
94
+ expectType<typeof _result1, readonly [1, 2, 3, 4]>('=');
95
+ expectType<typeof _result2, readonly [1, 2, 3, 4]>('=');
96
+ });
97
+
98
+ test('range type constraints work correctly', () => {
99
+ // @ts-expect-error - Range bounds should be numbers, not strings
100
+ expect(() => Arr.range('1', '5')).toThrowError();
101
+
102
+ // @ts-expect-error - Step should be number, not string
103
+ Arr.range(1, 5, 'invalid');
104
+ });
105
+ });
106
+
107
+ describe('Type-safe spread operations', () => {
108
+ test('spread with correct tuple types should work', () => {
109
+ // Correct usage with spread
110
+ const correctArgs1 = [testArray, predicate] as const;
111
+ const correctArgs2 = [predicate] as const;
112
+
113
+ const _result1 = Arr.findIndex(...correctArgs1);
114
+ const _result2 = Arr.findIndex(...correctArgs2);
115
+
116
+ expectType<typeof _result1, SizeType.Arr | -1>('<=');
117
+ expectType<
118
+ typeof _result2,
119
+ (array: readonly number[]) => SizeType.Arr | -1
120
+ >('<=');
121
+ });
122
+
123
+ test('spread with incorrect types should cause errors', () => {
124
+ const invalidArgs: [string[], (x: string) => number] = [
125
+ ['a'],
126
+ (x) => x.length,
127
+ ];
128
+
129
+ // @ts-expect-error - Wrong types in tuple for spread
130
+ Arr.findIndex(...invalidArgs);
131
+
132
+ expect(true).toBe(true);
133
+ });
134
+ });
135
+
136
+ describe('Function composition with overloaded functions', () => {
137
+ test('composition should preserve type safety', () => {
138
+ // This should work
139
+ const findPositive = Arr.findIndex((x: number) => x > 0);
140
+ const filterNegative = Arr.filterNot((x: number) => x < 0);
141
+
142
+ const _composedResult = pipe(testArray)
143
+ .map(filterNegative)
144
+ .map(findPositive).value;
145
+
146
+ expectType<typeof _composedResult, SizeType.Arr | -1>('=');
147
+ });
148
+ });
149
+ });
@@ -0,0 +1,425 @@
1
+ import { IMap } from '../collections/index.mjs';
2
+ import { expectType } from '../expect-type.mjs';
3
+ import { Optional, Result } from '../functional/index.mjs';
4
+ import { Arr } from './array-utils.mjs';
5
+
6
+ describe('Arr reducing value', () => {
7
+ describe('min', () => {
8
+ {
9
+ const xs = [3, 5, 4] as const;
10
+ const result = Arr.min(xs);
11
+
12
+ expectType<typeof result, Optional.Some<3 | 4 | 5>>('=');
13
+
14
+ test('case 1', () => {
15
+ expect(Optional.isSome(result)).toBe(true);
16
+ if (Optional.isSome(result)) {
17
+ expect(result.value).toBe(3);
18
+ }
19
+ });
20
+ }
21
+ {
22
+ const xs = [3, 5, 4] as const;
23
+ const result = Arr.min(xs, (a, b) => a - b);
24
+
25
+ expectType<typeof result, Optional.Some<3 | 4 | 5>>('=');
26
+
27
+ test('case 2', () => {
28
+ expect(Optional.isSome(result)).toBe(true);
29
+ if (Optional.isSome(result)) {
30
+ expect(result.value).toBe(3);
31
+ }
32
+ });
33
+ }
34
+ {
35
+ const xs: readonly (3 | 4 | 5)[] = [3, 5, 4] as const;
36
+ const result = Arr.min(xs, (a, b) => a - b);
37
+
38
+ expectType<typeof result, Optional<3 | 4 | 5>>('=');
39
+
40
+ test('case 3', () => {
41
+ expect(Optional.isSome(result)).toBe(true);
42
+ if (Optional.isSome(result)) {
43
+ expect(result.value).toBe(3);
44
+ }
45
+ });
46
+ }
47
+ });
48
+
49
+ describe('max', () => {
50
+ const xs = [3, 5, 4] as const;
51
+ const result = Arr.max(xs, (a, b) => a - b);
52
+
53
+ expectType<typeof result, Optional.Some<3 | 4 | 5>>('=');
54
+
55
+ test('case 1', () => {
56
+ expect(Optional.isSome(result)).toBe(true);
57
+ if (Optional.isSome(result)) {
58
+ expect(result.value).toBe(5);
59
+ }
60
+ });
61
+
62
+ test('case 2: no comparator', () => {
63
+ const res = Arr.max(xs);
64
+ expectType<typeof res, Optional.Some<3 | 4 | 5>>('=');
65
+ expect(Optional.isSome(res)).toBe(true);
66
+ if (Optional.isSome(res)) {
67
+ expect(res.value).toBe(5);
68
+ }
69
+ });
70
+
71
+ test('case 3: readonly array', () => {
72
+ const arr: readonly number[] = [1, 5, 2];
73
+ const res = Arr.max(arr);
74
+ expectType<typeof res, Optional<number>>('=');
75
+ expect(Optional.isSome(res)).toBe(true);
76
+ if (Optional.isSome(res)) {
77
+ expect(res.value).toBe(5);
78
+ }
79
+ });
80
+
81
+ test('case 4: empty array', () => {
82
+ const arr: readonly number[] = [];
83
+ const res = Arr.max(arr);
84
+ expectType<typeof res, Optional<number>>('=');
85
+ expect(Optional.isNone(res)).toBe(true);
86
+ });
87
+ });
88
+
89
+ describe('minBy', () => {
90
+ const xs: NonEmptyArray<
91
+ | Readonly<{ x: 1; y: 2 }>
92
+ | Readonly<{ x: 2; y: 3 }>
93
+ | Readonly<{ x: 3; y: 2 }>
94
+ | Readonly<{ x: 4; y: 1 }>
95
+ | Readonly<{ x: 5; y: 1 }>
96
+ | Readonly<{ x: 6; y: 1 }>
97
+ > = [
98
+ { x: 5, y: 1 },
99
+ { x: 4, y: 1 },
100
+ { x: 6, y: 1 },
101
+ { x: 3, y: 2 },
102
+ { x: 1, y: 2 },
103
+ { x: 2, y: 3 },
104
+ ] as const;
105
+
106
+ const result = Arr.minBy(xs, (a) => a.x);
107
+
108
+ expectType<
109
+ typeof result,
110
+ Optional.Some<
111
+ | Readonly<{ x: 1; y: 2 }>
112
+ | Readonly<{ x: 2; y: 3 }>
113
+ | Readonly<{ x: 3; y: 2 }>
114
+ | Readonly<{ x: 4; y: 1 }>
115
+ | Readonly<{ x: 5; y: 1 }>
116
+ | Readonly<{ x: 6; y: 1 }>
117
+ >
118
+ >('=');
119
+
120
+ test('case 1', () => {
121
+ expect(Optional.isSome(result)).toBe(true);
122
+ if (Optional.isSome(result)) {
123
+ expect(result.value).toStrictEqual({ x: 1, y: 2 });
124
+ }
125
+ });
126
+
127
+ test('case 2: empty array', () => {
128
+ const arr: readonly { x: number }[] = [];
129
+ const res = Arr.minBy(arr, (a) => a.x);
130
+ expectType<typeof res, Optional<{ x: number }>>('=');
131
+ expect(Optional.isNone(res)).toBe(true);
132
+ });
133
+
134
+ test('case 3: custom comparator', () => {
135
+ const arr = [
136
+ { name: 'apple', score: 10 },
137
+ { name: 'banana', score: 5 },
138
+ { name: 'cherry', score: 12 },
139
+ ] as const;
140
+
141
+ const res = Arr.minBy(
142
+ arr,
143
+ (item) => item.name,
144
+ (a, b) => a.localeCompare(b),
145
+ );
146
+
147
+ expectType<
148
+ typeof res,
149
+ Optional<
150
+ | Readonly<{ name: 'apple'; score: 10 }>
151
+ | Readonly<{ name: 'banana'; score: 5 }>
152
+ | Readonly<{ name: 'cherry'; score: 12 }>
153
+ >
154
+ >('=');
155
+
156
+ expect(Optional.isSome(res)).toBe(true);
157
+ if (Optional.isSome(res)) {
158
+ expect(res.value).toStrictEqual({ name: 'apple', score: 10 });
159
+ }
160
+ });
161
+ });
162
+
163
+ describe('maxBy', () => {
164
+ const xs: NonEmptyArray<
165
+ | Readonly<{ x: 1; y: 2 }>
166
+ | Readonly<{ x: 2; y: 3 }>
167
+ | Readonly<{ x: 3; y: 2 }>
168
+ | Readonly<{ x: 4; y: 1 }>
169
+ | Readonly<{ x: 5; y: 1 }>
170
+ | Readonly<{ x: 6; y: 1 }>
171
+ > = [
172
+ { x: 5, y: 1 },
173
+ { x: 4, y: 1 },
174
+ { x: 6, y: 1 },
175
+ { x: 3, y: 2 },
176
+ { x: 1, y: 2 },
177
+ { x: 2, y: 3 },
178
+ ] as const;
179
+
180
+ const result = Arr.maxBy(xs, (a) => a.x);
181
+
182
+ expectType<
183
+ typeof result,
184
+ Optional.Some<
185
+ | Readonly<{ x: 1; y: 2 }>
186
+ | Readonly<{ x: 2; y: 3 }>
187
+ | Readonly<{ x: 3; y: 2 }>
188
+ | Readonly<{ x: 4; y: 1 }>
189
+ | Readonly<{ x: 5; y: 1 }>
190
+ | Readonly<{ x: 6; y: 1 }>
191
+ >
192
+ >('=');
193
+
194
+ test('case 1', () => {
195
+ expect(Optional.isSome(result)).toBe(true);
196
+ if (Optional.isSome(result)) {
197
+ expect(result.value).toStrictEqual({ x: 6, y: 1 });
198
+ }
199
+ });
200
+
201
+ test('case 2: empty array', () => {
202
+ const arr: readonly { x: number }[] = [];
203
+ const res = Arr.maxBy(arr, (a) => a.x);
204
+ expectType<typeof res, Optional<{ x: number }>>('=');
205
+ expect(Optional.isNone(res)).toBe(true);
206
+ });
207
+
208
+ test('case 3: custom comparator', () => {
209
+ const arr = [
210
+ { name: 'apple', score: 10 },
211
+ { name: 'banana', score: 5 },
212
+ { name: 'cherry', score: 12 },
213
+ ] as const;
214
+
215
+ const res = Arr.maxBy(
216
+ arr,
217
+ (item) => item.name,
218
+ (a, b) => a.localeCompare(b),
219
+ );
220
+
221
+ expectType<
222
+ typeof res,
223
+ Optional<
224
+ | Readonly<{ name: 'apple'; score: 10 }>
225
+ | Readonly<{ name: 'banana'; score: 5 }>
226
+ | Readonly<{ name: 'cherry'; score: 12 }>
227
+ >
228
+ >('=');
229
+
230
+ expect(Optional.isSome(res)).toBe(true);
231
+ if (Optional.isSome(res)) {
232
+ expect(res.value).toStrictEqual({ name: 'cherry', score: 12 });
233
+ }
234
+ });
235
+ });
236
+
237
+ describe('count', () => {
238
+ const xs = [
239
+ { x: 1, y: 1 },
240
+ { x: 2, y: 1 },
241
+ { x: 3, y: 1 },
242
+ { x: 1, y: 2 },
243
+ { x: 2, y: 2 },
244
+ { x: 1, y: 3 },
245
+ ] as const;
246
+
247
+ const result = Arr.count(xs, (a) => a.x === 2);
248
+
249
+ expectType<typeof result, Uint32>('=');
250
+
251
+ test('case 1', () => {
252
+ expect(result).toBe(2);
253
+ });
254
+
255
+ test('case 2: empty array', () => {
256
+ const arr: readonly number[] = [];
257
+ const res = Arr.count(arr, (x) => x > 0);
258
+ expectType<typeof res, Uint32>('=');
259
+ expect(res).toBe(0);
260
+ });
261
+ });
262
+
263
+ describe('countBy', () => {
264
+ const xs = [
265
+ { x: 1, y: 1 },
266
+ { x: 2, y: 1 },
267
+ { x: 3, y: 1 },
268
+ { x: 1, y: 2 },
269
+ { x: 2, y: 2 },
270
+ { x: 1, y: 3 },
271
+ ] as const;
272
+
273
+ const result = Arr.countBy(xs, (a) => a.x);
274
+
275
+ expectType<typeof result, IMap<1 | 2 | 3, Uint32>>('=');
276
+
277
+ test('case 1', () => {
278
+ expect(result).toStrictEqual(
279
+ IMap.create<1 | 2 | 3, number>([
280
+ [1, 3],
281
+ [2, 2],
282
+ [3, 1],
283
+ ]),
284
+ );
285
+ });
286
+
287
+ test('case 2: empty array', () => {
288
+ const arr: readonly { x: number }[] = [];
289
+ const res = Arr.countBy(arr, (a) => a.x);
290
+ expectType<typeof res, IMap<number, Uint32>>('=');
291
+ expect(res.size).toBe(0);
292
+ });
293
+ });
294
+
295
+ describe('foldl', () => {
296
+ test('empty array', () => {
297
+ const result = Arr.foldl([], (acc, curr: number) => acc + curr, 0);
298
+ expectType<typeof result, number>('=');
299
+ expect(result).toBe(0);
300
+ });
301
+
302
+ test('sum numbers', () => {
303
+ const result = Arr.foldl(
304
+ [1, 2, 3] as const,
305
+ (acc, curr) => acc + curr,
306
+ 0,
307
+ );
308
+ expectType<typeof result, number>('=');
309
+ expect(result).toBe(6);
310
+ });
311
+
312
+ test('concatenate strings', () => {
313
+ const result = Arr.foldl(
314
+ ['a', 'b', 'c'] as const,
315
+ (acc, curr) => acc + curr,
316
+ '',
317
+ );
318
+ expectType<typeof result, string>('=');
319
+ expect(result).toBe('abc');
320
+ });
321
+
322
+ test('alias reduce', () => {
323
+ expect(Arr.reduce).toBe(Arr.foldl);
324
+ });
325
+ });
326
+
327
+ describe('foldr', () => {
328
+ test('empty array', () => {
329
+ const result = Arr.foldr([], (acc, curr: number) => acc + curr, 0);
330
+ expectType<typeof result, number>('=');
331
+ expect(result).toBe(0);
332
+ });
333
+
334
+ test('subtract numbers from right', () => {
335
+ // (1 - (2 - (3 - 0))) = 1 - (2 - 3) = 1 - (-1) = 2
336
+ const result = Arr.foldr(
337
+ [1, 2, 3] as const,
338
+ (acc, curr) => curr - acc,
339
+ 0,
340
+ );
341
+ expectType<typeof result, number>('=');
342
+ expect(result).toBe(2); // 3 - (2 - (1 - 0)) = 3 - (2 - 1) = 3 - 1 = 2. No, this is (acc, curr) => acc - curr.
343
+ // The callback is (previousValue: S, currentValue: A) => S
344
+ // So it's initialValue for S.
345
+ // Iteration 1: prev = 0, curr = 3. Result = 3 - 0 = 3.
346
+ // Iteration 2: prev = 3, curr = 2. Result = 2 - 3 = -1.
347
+ // Iteration 3: prev = -1, curr = 1. Result = 1 - (-1) = 2.
348
+ });
349
+
350
+ test('concatenate strings from right', () => {
351
+ const result = Arr.foldr(
352
+ ['a', 'b', 'c'] as const,
353
+ (acc, curr) => curr + acc,
354
+ '',
355
+ );
356
+ expectType<typeof result, string>('=');
357
+ expect(result).toBe('abc'); // c + (b + (a + "")) = cba. No, it's curr + acc.
358
+ // Iteration 1: prev = "", curr = "c". Result = "c" + "" = "c".
359
+ // Iteration 2: prev = "c", curr = "b". Result = "b" + "c" = "bc".
360
+ // Iteration 3: prev = "bc", curr = "a". Result = "a" + "bc" = "abc".
361
+ });
362
+
363
+ test('alias reduceRight', () => {
364
+ expect(Arr.reduceRight).toBe(Arr.foldr);
365
+ });
366
+ });
367
+
368
+ describe('sum', () => {
369
+ test('empty array', () => {
370
+ const result = Arr.sum([]);
371
+ expectType<typeof result, 0>('=');
372
+ expect(result).toBe(0);
373
+ });
374
+
375
+ test('one element array', () => {
376
+ const result = Arr.sum([23]);
377
+ expectType<typeof result, 23>('=');
378
+ expect(result).toBe(23);
379
+ });
380
+
381
+ test('positive numbers', () => {
382
+ const result = Arr.sum([1, 2, 3, 4, 5] as const);
383
+ expectType<typeof result, number>('=');
384
+ expect(result).toBe(15);
385
+ });
386
+
387
+ test('mixed numbers', () => {
388
+ const result = Arr.sum([1, -2, 3, 0, -5] as const);
389
+ expectType<typeof result, number>('=');
390
+ expect(result).toBe(-3);
391
+ });
392
+ });
393
+
394
+ describe('join', () => {
395
+ test('should join array elements', () => {
396
+ const arr = ['Hello', 'World'];
397
+ const result = Arr.join(arr, ' ');
398
+
399
+ expect(Result.isOk(result)).toBe(true);
400
+ if (Result.isOk(result)) {
401
+ expect(result.value).toBe('Hello World');
402
+ }
403
+ });
404
+
405
+ test('should handle empty separator', () => {
406
+ const arr = ['a', 'b', 'c'];
407
+ const result = Arr.join(arr, '');
408
+
409
+ expect(Result.isOk(result)).toBe(true);
410
+ if (Result.isOk(result)) {
411
+ expect(result.value).toBe('abc');
412
+ }
413
+ });
414
+
415
+ test('should handle undefined separator', () => {
416
+ const arr = ['a', 'b', 'c'];
417
+ const result = Arr.join(arr);
418
+
419
+ expect(Result.isOk(result)).toBe(true);
420
+ if (Result.isOk(result)) {
421
+ expect(result.value).toBe('a,b,c');
422
+ }
423
+ });
424
+ });
425
+ });