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,492 @@
1
+ import { expectType } from '../expect-type.mjs';
2
+ import { Arr } from './array-utils.mjs';
3
+
4
+ describe('Arr validations', () => {
5
+ describe('isArray', () => {
6
+ test('should return true for arrays', () => {
7
+ expect(Arr.isArray([1, 2, 3])).toBe(true);
8
+ expect(Arr.isArray([])).toBe(true);
9
+ expect(Arr.isArray(['a', 'b'])).toBe(true);
10
+ });
11
+
12
+ test('should return false for non-arrays', () => {
13
+ expect(Arr.isArray('hello')).toBe(false);
14
+ expect(Arr.isArray(123)).toBe(false);
15
+ expect(Arr.isArray(null)).toBe(false);
16
+ expect(Arr.isArray(undefined)).toBe(false);
17
+ expect(Arr.isArray({})).toBe(false);
18
+ expect(Arr.isArray(new Set())).toBe(false);
19
+ });
20
+
21
+ test('should refine union types correctly', () => {
22
+ function processValue(value: string | readonly number[] | null): number {
23
+ if (Arr.isArray(value)) {
24
+ // value should be typed as number[]
25
+ expectType<typeof value, readonly number[]>('=');
26
+ return value.length;
27
+ }
28
+ return 0;
29
+ }
30
+
31
+ expect(processValue([1, 2, 3])).toBe(3);
32
+ expect(processValue('hello')).toBe(0);
33
+ expect(processValue(null)).toBe(0);
34
+ });
35
+
36
+ test('should work with readonly arrays', () => {
37
+ const readonlyArray: readonly number[] = [1, 2, 3];
38
+ if (Arr.isArray(readonlyArray)) {
39
+ expectType<typeof readonlyArray, readonly number[]>('=');
40
+ expect(readonlyArray.length).toBe(3);
41
+ }
42
+ });
43
+
44
+ test('should work with mutable arrays', () => {
45
+ const mutableArray: number[] = [1, 2, 3];
46
+ if (Arr.isArray(mutableArray)) {
47
+ expectType<typeof mutableArray, number[]>('=');
48
+ expect(mutableArray.length).toBe(3);
49
+ }
50
+ });
51
+
52
+ test('should exclude impossible array types from unions', () => {
53
+ function checkUnion(
54
+ value: string | boolean | readonly number[] | { readonly a: number },
55
+ ): number {
56
+ if (Arr.isArray(value)) {
57
+ // Only number[] should remain
58
+ expectType<typeof value, readonly number[]>('=');
59
+ return value.length;
60
+ }
61
+ // Non-array types
62
+ expectType<typeof value, string | boolean | { readonly a: number }>(
63
+ '=',
64
+ );
65
+ return -1;
66
+ }
67
+
68
+ expect(checkUnion([1, 2])).toBe(2);
69
+ expect(checkUnion('test')).toBe(-1);
70
+ expect(checkUnion(true)).toBe(-1);
71
+ expect(checkUnion({ a: 1 })).toBe(-1);
72
+ });
73
+
74
+ test('should exclude impossible array types from unions (including unknown)', () => {
75
+ function checkUnion(
76
+ value:
77
+ | string
78
+ | boolean
79
+ | readonly number[]
80
+ | { readonly a: number }
81
+ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
82
+ | unknown
83
+ // eslint-disable-next-line @typescript-eslint/no-restricted-types
84
+ | object,
85
+ ): number {
86
+ if (Arr.isArray(value)) {
87
+ // Only number[] should remain
88
+ expectType<typeof value, readonly unknown[]>('=');
89
+ return value.length;
90
+ }
91
+ // Non-array types
92
+ expectType<
93
+ typeof value,
94
+ | string
95
+ | boolean
96
+ | { readonly a: number }
97
+ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
98
+ | unknown
99
+ // eslint-disable-next-line @typescript-eslint/no-restricted-types
100
+ | object
101
+ >('=');
102
+ return -1;
103
+ }
104
+
105
+ expect(checkUnion([1, 2])).toBe(2);
106
+ expect(checkUnion('test')).toBe(-1);
107
+ expect(checkUnion(true)).toBe(-1);
108
+ expect(checkUnion({ a: 1 })).toBe(-1);
109
+ });
110
+
111
+ test('should return true for arrays (additional)', () => {
112
+ expect(Arr.isArray([])).toBe(true);
113
+ expect(Arr.isArray([1, 2, 3])).toBe(true);
114
+ expect(Arr.isArray(['a', 'b'])).toBe(true);
115
+ });
116
+
117
+ test('should return false for non-arrays (additional)', () => {
118
+ expect(Arr.isArray('string')).toBe(false);
119
+ expect(Arr.isArray(123)).toBe(false);
120
+ expect(Arr.isArray({})).toBe(false);
121
+ expect(Arr.isArray(null)).toBe(false);
122
+ expect(Arr.isArray(undefined)).toBe(false);
123
+ });
124
+
125
+ test('should work as type guard (additional)', () => {
126
+ const value: unknown = [1, 2, 3];
127
+ if (Arr.isArray(value)) {
128
+ expectType<typeof value, readonly unknown[]>('=');
129
+ expect(value.length).toBe(3);
130
+ }
131
+ });
132
+
133
+ test('should handle array-like objects', () => {
134
+ const arrayLike = { 0: 'a', 1: 'b', length: 2 };
135
+ expect(Arr.isArray(arrayLike)).toBe(false);
136
+ });
137
+
138
+ describe('comprehensive type guard tests', () => {
139
+ test('should narrow unknown type to array', () => {
140
+ const value: unknown = [1, 2, 3];
141
+ if (Arr.isArray(value)) {
142
+ expectType<typeof value, readonly unknown[]>('=');
143
+ } else {
144
+ expectType<typeof value, unknown>('=');
145
+ }
146
+ });
147
+
148
+ test('should handle any type', () => {
149
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
150
+ const value: any = [1, 2, 3];
151
+ if (Arr.isArray(value)) {
152
+ expectType<typeof value, readonly unknown[]>('=');
153
+ }
154
+ });
155
+
156
+ test('should work with nested arrays', () => {
157
+ const nested: readonly (readonly number[])[] = [[1], [2], [3]];
158
+ if (Arr.isArray(nested)) {
159
+ expectType<typeof nested, readonly (readonly number[])[]>('=');
160
+ }
161
+ });
162
+
163
+ test('should distinguish between array and tuple types', () => {
164
+ const tuple: readonly [1, 2, 3] = [1, 2, 3];
165
+ if (Arr.isArray(tuple)) {
166
+ expectType<typeof tuple, readonly [1, 2, 3]>('=');
167
+ }
168
+ });
169
+
170
+ test('should work with empty tuple type', () => {
171
+ const emptyTuple: readonly [] = [];
172
+ if (Arr.isArray(emptyTuple)) {
173
+ expectType<typeof emptyTuple, readonly []>('=');
174
+ }
175
+ });
176
+
177
+ test('should handle union of array types', () => {
178
+ type MixedArrayUnion =
179
+ | readonly string[]
180
+ | readonly number[]
181
+ | readonly boolean[];
182
+ const mixedArray: MixedArrayUnion = [1, 2, 3];
183
+ if (Arr.isArray(mixedArray)) {
184
+ expectType<typeof mixedArray, MixedArrayUnion>('<=');
185
+ }
186
+ });
187
+
188
+ test('should work with generic function', () => {
189
+ function processGeneric<T>(value: T | readonly number[]): number {
190
+ if (Arr.isArray(value)) {
191
+ // Type is narrowed to array type within this block
192
+ return value.length;
193
+ }
194
+ return 0;
195
+ }
196
+ expect(processGeneric([1, 2, 3])).toBe(3);
197
+ expect(processGeneric('hello')).toBe(0);
198
+ });
199
+
200
+ test('should handle never type correctly', () => {
201
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
202
+ const neverValue = undefined as never;
203
+ if (Arr.isArray(neverValue)) {
204
+ expectType<typeof neverValue, never>('=');
205
+ }
206
+ });
207
+
208
+ test('should work with conditional types', () => {
209
+ type ArrayOrValue<T> = T extends readonly unknown[] ? T : readonly T[];
210
+ function makeArray<T>(value: T): ArrayOrValue<T> {
211
+ if (Arr.isArray(value)) {
212
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
213
+ return value as ArrayOrValue<T>;
214
+ }
215
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
216
+ return [value] as ArrayOrValue<T>;
217
+ }
218
+ expect(makeArray([1, 2, 3])).toStrictEqual([1, 2, 3]);
219
+ expect(makeArray(5)).toStrictEqual([5]);
220
+ });
221
+
222
+ test('should handle intersection types', () => {
223
+ type TaggedArray = readonly number[] & { tag: string };
224
+ const tagged = Object.assign([1, 2, 3], { tag: 'test' }) as TaggedArray;
225
+ if (Arr.isArray(tagged)) {
226
+ expectType<typeof tagged, TaggedArray>('=');
227
+ expect(tagged.tag).toBe('test');
228
+ }
229
+ });
230
+
231
+ test('should work with branded types', () => {
232
+ type BrandedArray = readonly number[] & {
233
+ readonly __brand: unique symbol;
234
+ };
235
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
236
+ const branded = [1, 2, 3] as unknown as BrandedArray;
237
+ if (Arr.isArray(branded)) {
238
+ expectType<typeof branded, BrandedArray>('=');
239
+ }
240
+ });
241
+
242
+ test('should handle complex union discrimination', () => {
243
+ type ComplexUnion =
244
+ | { type: 'array'; data: readonly string[] }
245
+ | { type: 'object'; data: Record<string, unknown> }
246
+ | readonly number[]
247
+ | string
248
+ | null;
249
+
250
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
251
+ function processComplex(value: ComplexUnion): number {
252
+ if (Arr.isArray(value)) {
253
+ expectType<typeof value, readonly number[]>('=');
254
+ return value.length;
255
+ }
256
+ if (typeof value === 'string') {
257
+ expectType<typeof value, string>('=');
258
+ return value.length;
259
+ }
260
+ if (value === null) {
261
+ expectType<typeof value, null>('=');
262
+ return 0;
263
+ }
264
+ expectType<
265
+ typeof value,
266
+ | { type: 'array'; data: readonly string[] }
267
+ | { type: 'object'; data: Record<string, unknown> }
268
+ >('=');
269
+ return -1;
270
+ }
271
+
272
+ expect(processComplex([1, 2, 3])).toBe(3);
273
+ expect(processComplex('test')).toBe(4);
274
+ expect(processComplex(null)).toBe(0);
275
+ expect(processComplex({ type: 'array', data: ['a', 'b'] })).toBe(-1);
276
+ });
277
+
278
+ test('should preserve literal types in arrays', () => {
279
+ const literalArray = [1, 2, 3] as const;
280
+ if (Arr.isArray(literalArray)) {
281
+ expectType<typeof literalArray, readonly [1, 2, 3]>('=');
282
+ }
283
+ });
284
+
285
+ test('should handle arrays with mixed element types', () => {
286
+ const mixed: readonly (string | number | boolean)[] = [1, 'two', true];
287
+ if (Arr.isArray(mixed)) {
288
+ expectType<typeof mixed, readonly (string | number | boolean)[]>('=');
289
+ }
290
+ });
291
+
292
+ test('should work with symbol-keyed arrays', () => {
293
+ const sym = Symbol('test');
294
+ const arrWithSymbol = Object.assign([1, 2, 3], { [sym]: 'value' });
295
+ if (Arr.isArray(arrWithSymbol)) {
296
+ expect(arrWithSymbol.length).toBe(3);
297
+ }
298
+ });
299
+ });
300
+ });
301
+
302
+ describe('isEmpty', () => {
303
+ const xs = [1, 2, 3] as const;
304
+ const result = Arr.isEmpty(xs);
305
+
306
+ expectType<typeof result, boolean>('=');
307
+
308
+ test('case 1', () => {
309
+ expect(result).toBe(false);
310
+ });
311
+
312
+ test('case 2', () => {
313
+ expect(Arr.isEmpty([])).toBe(true);
314
+ });
315
+ });
316
+
317
+ describe('isNonEmpty', () => {
318
+ const xs = [1, 2, 3] as const;
319
+ const result = Arr.isNonEmpty(xs);
320
+
321
+ expectType<typeof result, boolean>('=');
322
+
323
+ test('case 1', () => {
324
+ expect(result).toBe(true);
325
+ });
326
+
327
+ test('case 2', () => {
328
+ expect(Arr.isNonEmpty([])).toBe(false);
329
+ });
330
+ });
331
+
332
+ describe('isArrayOfLength', () => {
333
+ test('should return true if array has specified length', () => {
334
+ const arr = [1, 2, 3] as const;
335
+ expect(Arr.isArrayOfLength(arr, 3)).toBe(true);
336
+ if (Arr.isArrayOfLength(arr, 3)) {
337
+ expectType<typeof arr, readonly [1, 2, 3]>('=');
338
+ }
339
+ });
340
+
341
+ test('should return false if array does not have specified length', () => {
342
+ const arr = [1, 2, 3] as const;
343
+ expect(Arr.isArrayOfLength(arr, 2)).toBe(false);
344
+ });
345
+
346
+ test('should return true for empty array and length 0', () => {
347
+ const arr = [] as const;
348
+ expect(Arr.isArrayOfLength(arr, 0)).toBe(true);
349
+ if (Arr.isArrayOfLength(arr, 0)) {
350
+ expectType<typeof arr, readonly []>('=');
351
+ }
352
+ });
353
+
354
+ test('should return false for non-empty array and length 0', () => {
355
+ const arr = [1] as const;
356
+ expect(Arr.isArrayOfLength(arr, 0)).toBe(false);
357
+ });
358
+
359
+ test('should work with unknown array type', () => {
360
+ const arr: number[] = [1, 2];
361
+ expect(Arr.isArrayOfLength(arr, 2)).toBe(true);
362
+ if (Arr.isArrayOfLength(arr, 2)) {
363
+ expectType<typeof arr, number[] & ArrayOfLength<2, number>>('=');
364
+ }
365
+ expect(Arr.isArrayOfLength(arr, 3)).toBe(false);
366
+ });
367
+
368
+ test('should work with unknown readonly array type', () => {
369
+ const arr: readonly number[] = [1, 2];
370
+ expect(Arr.isArrayOfLength(arr, 2)).toBe(true);
371
+ if (Arr.isArrayOfLength(arr, 2)) {
372
+ expectType<typeof arr, ArrayOfLength<2, number>>('=');
373
+ }
374
+ expect(Arr.isArrayOfLength(arr, 3)).toBe(false);
375
+ });
376
+
377
+ test('should return true for arrays of exact length (additional)', () => {
378
+ expect(Arr.isArrayOfLength([1, 2, 3], 3)).toBe(true);
379
+ expect(Arr.isArrayOfLength([], 0)).toBe(true);
380
+ expect(Arr.isArrayOfLength(['a'], 1)).toBe(true);
381
+ });
382
+
383
+ test('should return false for arrays of different length (additional)', () => {
384
+ expect(Arr.isArrayOfLength([1, 2, 3], 2)).toBe(false);
385
+ expect(Arr.isArrayOfLength([1, 2, 3], 4)).toBe(false);
386
+ expect(Arr.isArrayOfLength([], 1)).toBe(false);
387
+ });
388
+
389
+ test('should work as type guard with exact length (additional)', () => {
390
+ const array: readonly number[] = [1, 2, 3];
391
+ if (Arr.isArrayOfLength(array, 3)) {
392
+ expectType<typeof array, ArrayOfLength<3, number>>('=');
393
+ expect(array.length).toBe(3);
394
+ }
395
+ });
396
+ });
397
+
398
+ describe('isArrayAtLeastLength', () => {
399
+ test('should return true if array length is greater than or equal to specified length', () => {
400
+ const arr = [1, 2, 3] as const;
401
+ expect(Arr.isArrayAtLeastLength(arr, 3)).toBe(true);
402
+ if (Arr.isArrayAtLeastLength(arr, 3)) {
403
+ expectType<typeof arr, readonly [1, 2, 3]>('=');
404
+ }
405
+ expect(Arr.isArrayAtLeastLength(arr, 2)).toBe(true);
406
+ if (Arr.isArrayAtLeastLength(arr, 2)) {
407
+ expectType<typeof arr, readonly [1, 2, 3]>('=');
408
+ }
409
+ });
410
+
411
+ test('should return false if array length is less than specified length', () => {
412
+ const arr = [1, 2, 3] as const;
413
+ expect(Arr.isArrayAtLeastLength(arr, 4)).toBe(false);
414
+ });
415
+
416
+ test('should return true for empty array and length 0', () => {
417
+ const arr = [] as const;
418
+ expect(Arr.isArrayAtLeastLength(arr, 0)).toBe(true);
419
+ if (Arr.isArrayAtLeastLength(arr, 0)) {
420
+ expectType<typeof arr, readonly []>('=');
421
+ }
422
+ });
423
+
424
+ test('should return false for empty array and positive length', () => {
425
+ const arr = [] as const;
426
+ expect(Arr.isArrayAtLeastLength(arr, 1)).toBe(false);
427
+ });
428
+
429
+ test('should work with unknown array type', () => {
430
+ const arr: number[] = [1, 2];
431
+ expect(Arr.isArrayAtLeastLength(arr, 2)).toBe(true);
432
+ if (Arr.isArrayAtLeastLength(arr, 2)) {
433
+ expectType<typeof arr, number[] & ArrayAtLeastLen<2, number>>('=');
434
+ }
435
+ expect(Arr.isArrayAtLeastLength(arr, 1)).toBe(true);
436
+ if (Arr.isArrayAtLeastLength(arr, 1)) {
437
+ expectType<typeof arr, number[] & ArrayAtLeastLen<1, number>>('=');
438
+ }
439
+ expect(Arr.isArrayAtLeastLength(arr, 3)).toBe(false);
440
+ });
441
+
442
+ test('should return true for arrays of at least specified length (additional)', () => {
443
+ expect(Arr.isArrayAtLeastLength([1, 2, 3], 3)).toBe(true);
444
+ expect(Arr.isArrayAtLeastLength([1, 2, 3], 2)).toBe(true);
445
+ expect(Arr.isArrayAtLeastLength([1, 2, 3], 1)).toBe(true);
446
+ expect(Arr.isArrayAtLeastLength([1, 2, 3], 0)).toBe(true);
447
+ });
448
+
449
+ test('should return false for arrays shorter than specified length (additional)', () => {
450
+ expect(Arr.isArrayAtLeastLength([1, 2, 3], 4)).toBe(false);
451
+ expect(Arr.isArrayAtLeastLength([], 1)).toBe(false);
452
+ });
453
+
454
+ test('should work as type guard for at least length (additional)', () => {
455
+ const array: readonly number[] = [1, 2, 3];
456
+ if (Arr.isArrayAtLeastLength(array, 2)) {
457
+ expectType<typeof array, ArrayAtLeastLen<2, number>>('=');
458
+ expect(array.length >= 2).toBe(true);
459
+ }
460
+ });
461
+ });
462
+
463
+ describe('indexIsInRange', () => {
464
+ test('should return true for valid indices', () => {
465
+ const array = ['a', 'b', 'c'];
466
+ expect(Arr.indexIsInRange(array, 0)).toBe(true);
467
+ expect(Arr.indexIsInRange(array, 1)).toBe(true);
468
+ expect(Arr.indexIsInRange(array, 2)).toBe(true);
469
+ });
470
+
471
+ test('should return false for invalid indices', () => {
472
+ const array = ['a', 'b', 'c'];
473
+ expect(Arr.indexIsInRange(array, 3)).toBe(false);
474
+ expect(Arr.indexIsInRange(array, 10)).toBe(false);
475
+ });
476
+
477
+ test('should work with empty array', () => {
478
+ const empty: readonly string[] = [];
479
+ expect(Arr.indexIsInRange(empty, 0)).toBe(false);
480
+ // @ts-expect-error negative indices should not be allowed
481
+ expect(Arr.indexIsInRange(empty, -1)).toBe(false);
482
+ });
483
+
484
+ test('should be type error with floating point indices', () => {
485
+ const array = [1, 2, 3];
486
+ // @ts-expect-error floating point indices should not be allowed
487
+ expect(Arr.indexIsInRange(array, 1.5)).toBe(true); // JavaScript arrays accept floating point indices
488
+ // @ts-expect-error floating point indices should not be allowed
489
+ expect(Arr.indexIsInRange(array, 3.1)).toBe(false);
490
+ });
491
+ });
492
+ });