ts-data-forge 13.0.0 → 14.0.1

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 (93) hide show
  1. package/README.md +4 -3
  2. package/dist/array/impl/array-utils-creation.d.mts +2 -2
  3. package/dist/array/impl/array-utils-creation.d.mts.map +1 -1
  4. package/dist/array/impl/array-utils-creation.mjs.map +1 -1
  5. package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts +56 -29
  6. package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts.map +1 -1
  7. package/dist/array/impl/array-utils-length-bounded-array-cast.mjs +23 -15
  8. package/dist/array/impl/array-utils-length-bounded-array-cast.mjs.map +1 -1
  9. package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts +126 -28
  10. package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts.map +1 -1
  11. package/dist/array/impl/array-utils-length-bounded-array-guard.mjs +39 -5
  12. package/dist/array/impl/array-utils-length-bounded-array-guard.mjs.map +1 -1
  13. package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts +167 -0
  14. package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts.map +1 -0
  15. package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs +87 -0
  16. package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs.map +1 -0
  17. package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts +135 -0
  18. package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts.map +1 -0
  19. package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs +41 -0
  20. package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs.map +1 -0
  21. package/dist/array/impl/array-utils-modification.d.mts +45 -18
  22. package/dist/array/impl/array-utils-modification.d.mts.map +1 -1
  23. package/dist/array/impl/array-utils-modification.mjs.map +1 -1
  24. package/dist/array/impl/array-utils-reducing-value.d.mts +3 -3
  25. package/dist/array/impl/array-utils-reducing-value.d.mts.map +1 -1
  26. package/dist/array/impl/array-utils-reducing-value.mjs +1 -1
  27. package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
  28. package/dist/array/impl/array-utils-slice-clamped.d.mts.map +1 -1
  29. package/dist/array/impl/array-utils-slice-clamped.mjs.map +1 -1
  30. package/dist/array/impl/array-utils-slicing.d.mts +11 -11
  31. package/dist/array/impl/array-utils-slicing.d.mts.map +1 -1
  32. package/dist/array/impl/array-utils-slicing.mjs +1 -1
  33. package/dist/array/impl/array-utils-slicing.mjs.map +1 -1
  34. package/dist/array/impl/array-utils-transformation.d.mts +36 -19
  35. package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
  36. package/dist/array/impl/array-utils-transformation.mjs +2 -2
  37. package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
  38. package/dist/array/impl/array-utils-validation.d.mts +2 -124
  39. package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
  40. package/dist/array/impl/array-utils-validation.mjs +1 -7
  41. package/dist/array/impl/array-utils-validation.mjs.map +1 -1
  42. package/dist/array/impl/index.d.mts +2 -0
  43. package/dist/array/impl/index.d.mts.map +1 -1
  44. package/dist/array/impl/index.mjs +5 -3
  45. package/dist/array/impl/index.mjs.map +1 -1
  46. package/dist/collections/imap.d.mts +1 -1
  47. package/dist/collections/imap.mjs.map +1 -1
  48. package/dist/collections/iset-mapped.d.mts +2 -2
  49. package/dist/collections/iset-mapped.d.mts.map +1 -1
  50. package/dist/collections/iset-mapped.mjs.map +1 -1
  51. package/dist/collections/iset.d.mts +2 -2
  52. package/dist/collections/iset.d.mts.map +1 -1
  53. package/dist/collections/iset.mjs.map +1 -1
  54. package/dist/number/num-arithmetic-types.d.mts +2 -2
  55. package/dist/number/num-arithmetic-types.d.mts.map +1 -1
  56. package/dist/object/object.mjs +1 -1
  57. package/dist/string/str.d.mts +44 -44
  58. package/dist/string/str.d.mts.map +1 -1
  59. package/dist/string/str.mjs +17 -17
  60. package/dist/string/str.mjs.map +1 -1
  61. package/package.json +4 -5
  62. package/src/array/impl/array-utils-creation.mts +7 -3
  63. package/src/array/impl/array-utils-creation.test.mts +10 -8
  64. package/src/array/impl/array-utils-length-bounded-array-cast.mts +88 -50
  65. package/src/array/impl/array-utils-length-bounded-array-cast.test.mts +40 -13
  66. package/src/array/impl/array-utils-length-bounded-array-guard.mts +248 -39
  67. package/src/array/impl/array-utils-length-bounded-array-guard.test.mts +135 -24
  68. package/src/array/impl/array-utils-length-bounded-tuple-cast.mts +364 -0
  69. package/src/array/impl/array-utils-length-bounded-tuple-cast.test.mts +207 -0
  70. package/src/array/impl/array-utils-length-bounded-tuple-guard.mts +266 -0
  71. package/src/array/impl/array-utils-length-bounded-tuple-guard.test.mts +441 -0
  72. package/src/array/impl/array-utils-length-constraint-preservation.test.mts +287 -0
  73. package/src/array/impl/array-utils-modification.mts +84 -61
  74. package/src/array/impl/array-utils-modification.test.mts +145 -6
  75. package/src/array/impl/array-utils-reducing-value.mts +5 -5
  76. package/src/array/impl/array-utils-shape-invariants.test.mts +284 -0
  77. package/src/array/impl/array-utils-slice-clamped.mts +2 -1
  78. package/src/array/impl/array-utils-slicing.mts +65 -43
  79. package/src/array/impl/array-utils-slicing.test.mts +55 -0
  80. package/src/array/impl/array-utils-transformation.mts +91 -37
  81. package/src/array/impl/array-utils-transformation.test.mts +96 -23
  82. package/src/array/impl/array-utils-validation.mts +2 -162
  83. package/src/array/impl/array-utils-validation.test.mts +0 -371
  84. package/src/array/impl/index.mts +2 -0
  85. package/src/collections/imap.mts +1 -1
  86. package/src/collections/iset-mapped.mts +7 -3
  87. package/src/collections/iset.mts +7 -3
  88. package/src/collections/iset.test.mts +9 -6
  89. package/src/collections/queue.test.mts +1 -1
  90. package/src/number/num-arithmetic-types.mts +8 -4
  91. package/src/number/num.test.mts +2 -2
  92. package/src/string/str.mts +69 -69
  93. package/src/string/str.test.mts +40 -40
@@ -0,0 +1,266 @@
1
+ import {
2
+ type BoundedLengthTuple,
3
+ type FixedLengthTuple,
4
+ type MaxLengthTuple,
5
+ type MinLengthTuple,
6
+ } from 'ts-type-forge';
7
+ import { type SizeType } from '../../types.mjs';
8
+
9
+ /**
10
+ * Type guard that checks if an array is empty, narrowing it to the structural
11
+ * empty tuple.
12
+ *
13
+ * This is the structural counterpart of `isEmpty`: it narrows to `readonly []`
14
+ * rather than to the branded `FixedLengthArray<0, E>`. Prefer `isEmpty` unless
15
+ * you specifically need the structural type.
16
+ *
17
+ * @example
18
+ *
19
+ * ```ts
20
+ * const values: readonly number[] = [] as const;
21
+ *
22
+ * assert.isTrue(Arr.isEmptyTuple(values));
23
+ *
24
+ * assert.isFalse(Arr.isEmptyTuple([1] as const));
25
+ * ```
26
+ *
27
+ * @template E - The element type.
28
+ * @param array - The array to check.
29
+ * @returns `true` if `array.length === 0`. When `true`, TypeScript narrows
30
+ * `array` to `readonly []`.
31
+ */
32
+ export const isEmptyTuple = <E,>(array: readonly E[]): array is readonly [] =>
33
+ array.length === 0;
34
+
35
+ /**
36
+ * Type guard that checks if an array is non-empty, narrowing it to the
37
+ * structural non-empty tuple.
38
+ *
39
+ * This is the structural counterpart of `isNonEmpty`: it narrows to
40
+ * `MinLengthTuple<1, E>` rather than to the branded `MinLengthArray<1, E>`.
41
+ * Prefer `isNonEmpty` unless you specifically need the structural type.
42
+ *
43
+ * @example
44
+ *
45
+ * ```ts
46
+ * const values: readonly number[] = [1, 2] as const;
47
+ *
48
+ * assert.isTrue(Arr.isNonEmptyTuple(values));
49
+ *
50
+ * assert.isFalse(Arr.isNonEmptyTuple([] as const));
51
+ * ```
52
+ *
53
+ * @template E - The element type.
54
+ * @param array - The array to check.
55
+ * @returns `true` if `array.length >= 1`. When `true`, TypeScript narrows
56
+ * `array` to `MinLengthTuple<1, E>`.
57
+ */
58
+ export const isNonEmptyTuple = <E,>(
59
+ array: readonly E[],
60
+ ): array is MinLengthTuple<1, E> => array.length >= 1;
61
+
62
+ /**
63
+ * Checks if an array has a specific length.
64
+ *
65
+ * @example
66
+ *
67
+ * ```ts
68
+ * const pair: readonly number[] = [1, 2] as const;
69
+ *
70
+ * const triple: readonly number[] = [1, 2, 3] as const;
71
+ *
72
+ * assert.isTrue(Arr.isFixedLengthTuple(2, pair));
73
+ *
74
+ * assert.isFalse(Arr.isFixedLengthTuple(2, triple));
75
+ *
76
+ * if (Arr.isFixedLengthTuple(2, pair)) {
77
+ * assert.deepStrictEqual(pair, [1, 2]);
78
+ * }
79
+ * ```
80
+ */
81
+ export function isFixedLengthTuple<N extends SizeType.ArgArr, E>(
82
+ len: N,
83
+ array: readonly E[],
84
+ ): array is FixedLengthTuple<N, E>;
85
+
86
+ // Curried version
87
+ export function isFixedLengthTuple<N extends SizeType.ArgArr>(
88
+ len: N,
89
+ ): <E>(array: readonly E[]) => array is FixedLengthTuple<N, E>;
90
+
91
+ export function isFixedLengthTuple(
92
+ ...args:
93
+ | readonly [len: SizeType.ArgArr, array: readonly unknown[]]
94
+ | readonly [len: SizeType.ArgArr]
95
+ ): boolean | ((array: readonly unknown[]) => boolean) {
96
+ switch (args.length) {
97
+ case 2:
98
+ return hasFixedLength(args[0], args[1]);
99
+
100
+ case 1:
101
+ return (array) => hasFixedLength(args[0], array);
102
+ }
103
+ }
104
+
105
+ const hasFixedLength = (
106
+ len: SizeType.ArgArr,
107
+ array: readonly unknown[],
108
+ ): boolean => array.length === len;
109
+
110
+ /**
111
+ * Checks if an array has at least a specific length.
112
+ *
113
+ * @example
114
+ *
115
+ * ```ts
116
+ * const queue: readonly string[] = ['task-1', 'task-2'] as const;
117
+ *
118
+ * const emptyQueue: readonly string[] = [] as const;
119
+ *
120
+ * assert.isTrue(Arr.isMinLengthTuple(1, queue));
121
+ *
122
+ * assert.isFalse(Arr.isMinLengthTuple(1, emptyQueue));
123
+ *
124
+ * if (Arr.isMinLengthTuple(1, queue)) {
125
+ * assert.isTrue(queue[0] === 'task-1');
126
+ * }
127
+ * ```
128
+ */
129
+ export function isMinLengthTuple<N extends SizeType.ArgArr, E>(
130
+ len: N,
131
+ array: readonly E[],
132
+ ): array is MinLengthTuple<N, E>;
133
+
134
+ // Curried version
135
+ export function isMinLengthTuple<N extends SizeType.ArgArr>(
136
+ len: N,
137
+ ): <E>(array: readonly E[]) => array is MinLengthTuple<N, E>;
138
+
139
+ export function isMinLengthTuple(
140
+ ...args:
141
+ | readonly [len: SizeType.ArgArr, array: readonly unknown[]]
142
+ | readonly [len: SizeType.ArgArr]
143
+ ): boolean | ((array: readonly unknown[]) => boolean) {
144
+ switch (args.length) {
145
+ case 2:
146
+ return hasMinLength(args[0], args[1]);
147
+
148
+ case 1:
149
+ return (array) => hasMinLength(args[0], array);
150
+ }
151
+ }
152
+
153
+ const hasMinLength = (
154
+ len: SizeType.ArgArr,
155
+ array: readonly unknown[],
156
+ ): boolean => array.length >= len;
157
+
158
+ /**
159
+ * Checks if an array has at most a specific length.
160
+ *
161
+ * @example
162
+ *
163
+ * ```ts
164
+ * const pair: readonly number[] = [1, 2] as const;
165
+ *
166
+ * const triple: readonly number[] = [1, 2, 3] as const;
167
+ *
168
+ * assert.isTrue(Arr.isMaxLengthTuple(2, pair));
169
+ *
170
+ * assert.isFalse(Arr.isMaxLengthTuple(2, triple));
171
+ *
172
+ * if (Arr.isMaxLengthTuple(2, pair)) {
173
+ * assert.isTrue(pair.length <= 2);
174
+ * }
175
+ * ```
176
+ */
177
+ export function isMaxLengthTuple<N extends SizeType.ArgArr, E>(
178
+ len: N,
179
+ array: readonly E[],
180
+ ): array is MaxLengthTuple<N, E>;
181
+
182
+ // Curried version
183
+ export function isMaxLengthTuple<N extends SizeType.ArgArr>(
184
+ len: N,
185
+ ): <E>(array: readonly E[]) => array is MaxLengthTuple<N, E>;
186
+
187
+ export function isMaxLengthTuple(
188
+ ...args:
189
+ | readonly [len: SizeType.ArgArr, array: readonly unknown[]]
190
+ | readonly [len: SizeType.ArgArr]
191
+ ): boolean | ((array: readonly unknown[]) => boolean) {
192
+ switch (args.length) {
193
+ case 2:
194
+ return hasMaxLength(args[0], args[1]);
195
+
196
+ case 1:
197
+ return (array) => hasMaxLength(args[0], array);
198
+ }
199
+ }
200
+
201
+ const hasMaxLength = (
202
+ len: SizeType.ArgArr,
203
+ array: readonly unknown[],
204
+ ): boolean => array.length <= len;
205
+
206
+ /**
207
+ * Checks if an array's length is within a specific inclusive range.
208
+ *
209
+ * @example
210
+ *
211
+ * ```ts
212
+ * const pair: readonly number[] = [1, 2] as const;
213
+ *
214
+ * const quad: readonly number[] = [1, 2, 3, 4] as const;
215
+ *
216
+ * assert.isTrue(Arr.isBoundedLengthTuple(1, 3, pair));
217
+ *
218
+ * assert.isFalse(Arr.isBoundedLengthTuple(1, 3, quad));
219
+ *
220
+ * if (Arr.isBoundedLengthTuple(1, 3, pair)) {
221
+ * assert.isTrue(pair.length >= 1 && pair.length <= 3);
222
+ * }
223
+ * ```
224
+ */
225
+ export function isBoundedLengthTuple<
226
+ Min extends SizeType.ArgArr,
227
+ Max extends SizeType.ArgArr,
228
+ E,
229
+ >(
230
+ min: Min,
231
+ max: Max,
232
+ array: readonly E[],
233
+ ): array is BoundedLengthTuple<Min, Max, E>;
234
+
235
+ // Curried version
236
+ export function isBoundedLengthTuple<
237
+ Min extends SizeType.ArgArr,
238
+ Max extends SizeType.ArgArr,
239
+ >(
240
+ min: Min,
241
+ max: Max,
242
+ ): <E>(array: readonly E[]) => array is BoundedLengthTuple<Min, Max, E>;
243
+
244
+ export function isBoundedLengthTuple(
245
+ ...args:
246
+ | readonly [
247
+ min: SizeType.ArgArr,
248
+ max: SizeType.ArgArr,
249
+ array: readonly unknown[],
250
+ ]
251
+ | readonly [min: SizeType.ArgArr, max: SizeType.ArgArr]
252
+ ): boolean | ((array: readonly unknown[]) => boolean) {
253
+ switch (args.length) {
254
+ case 3:
255
+ return isWithinBounds(args[0], args[1], args[2]);
256
+
257
+ case 2:
258
+ return (array) => isWithinBounds(args[0], args[1], array);
259
+ }
260
+ }
261
+
262
+ const isWithinBounds = (
263
+ min: SizeType.ArgArr,
264
+ max: SizeType.ArgArr,
265
+ array: readonly unknown[],
266
+ ): boolean => array.length >= min && array.length <= max;
@@ -0,0 +1,441 @@
1
+ import {
2
+ type BoundedLengthTuple,
3
+ type FixedLengthTuple,
4
+ type MaxLengthTuple,
5
+ type MinLengthTuple,
6
+ } from 'ts-type-forge';
7
+ import { expectType } from '../../expect-type.mjs';
8
+ import {
9
+ isBoundedLengthTuple,
10
+ isEmptyTuple,
11
+ isFixedLengthTuple,
12
+ isMaxLengthTuple,
13
+ isMinLengthTuple,
14
+ isNonEmptyTuple,
15
+ } from './array-utils-length-bounded-tuple-guard.mjs';
16
+
17
+ describe('Arr structural tuple length guards', () => {
18
+ describe(isFixedLengthTuple, () => {
19
+ test('should return true if array has specified length', () => {
20
+ const arr = [1, 2, 3] as const;
21
+
22
+ assert.isTrue(isFixedLengthTuple(3, arr));
23
+
24
+ if (isFixedLengthTuple(3, arr)) {
25
+ expectType<typeof arr, readonly [1, 2, 3]>('=');
26
+ }
27
+ });
28
+
29
+ test('should return false if array does not have specified length', () => {
30
+ const arr = [1, 2, 3] as const;
31
+
32
+ assert.isFalse(isFixedLengthTuple(2, arr));
33
+ });
34
+
35
+ test('should return true for empty array and length 0', () => {
36
+ const arr = [] as const;
37
+
38
+ assert.isTrue(isFixedLengthTuple(0, arr));
39
+
40
+ if (isFixedLengthTuple(0, arr)) {
41
+ expectType<typeof arr, readonly []>('=');
42
+ }
43
+ });
44
+
45
+ test('should return false for non-empty array and length 0', () => {
46
+ const arr = [1] as const;
47
+
48
+ assert.isFalse(isFixedLengthTuple(0, arr));
49
+ });
50
+
51
+ test('should work with unknown array type', () => {
52
+ const mut_arr: number[] = [1, 2];
53
+
54
+ assert.isTrue(isFixedLengthTuple(2, mut_arr));
55
+
56
+ if (isFixedLengthTuple(2, mut_arr)) {
57
+ // transformer-ignore-next-line
58
+ expectType<typeof mut_arr, number[] & FixedLengthTuple<2, number>>('=');
59
+ }
60
+
61
+ assert.isFalse(isFixedLengthTuple(3, mut_arr));
62
+ });
63
+
64
+ test('should work with unknown readonly array type', () => {
65
+ const arr: readonly number[] = [1, 2] as const;
66
+
67
+ assert.isTrue(isFixedLengthTuple(2, arr));
68
+
69
+ if (isFixedLengthTuple(2, arr)) {
70
+ expectType<typeof arr, FixedLengthTuple<2, number>>('=');
71
+ }
72
+
73
+ assert.isFalse(isFixedLengthTuple(3, arr));
74
+ });
75
+
76
+ test('should return true for arrays of exact length (additional)', () => {
77
+ assert.isTrue(isFixedLengthTuple(3, [1, 2, 3]));
78
+
79
+ assert.isTrue(isFixedLengthTuple(0, []));
80
+
81
+ assert.isTrue(isFixedLengthTuple(1, ['a']));
82
+ });
83
+
84
+ test('should return false for arrays of different length (additional)', () => {
85
+ assert.isFalse(isFixedLengthTuple(2, [1, 2, 3]));
86
+
87
+ assert.isFalse(isFixedLengthTuple(4, [1, 2, 3]));
88
+
89
+ assert.isFalse(isFixedLengthTuple(1, []));
90
+ });
91
+
92
+ test('should work as type guard with exact length (additional)', () => {
93
+ const array: readonly number[] = [1, 2, 3] as const;
94
+
95
+ if (isFixedLengthTuple(3, array)) {
96
+ expectType<typeof array, FixedLengthTuple<3, number>>('=');
97
+
98
+ expect(array).toHaveLength(3);
99
+ }
100
+ });
101
+ });
102
+
103
+ describe(isMinLengthTuple, () => {
104
+ test('should return true if array length is greater than or equal to specified length', () => {
105
+ const arr = [1, 2, 3] as const;
106
+
107
+ assert.isTrue(isMinLengthTuple(3, arr));
108
+
109
+ if (isMinLengthTuple(3, arr)) {
110
+ expectType<typeof arr, readonly [1, 2, 3]>('=');
111
+ }
112
+
113
+ assert.isTrue(isMinLengthTuple(2, arr));
114
+
115
+ if (isMinLengthTuple(2, arr)) {
116
+ expectType<typeof arr, readonly [1, 2, 3]>('=');
117
+ }
118
+ });
119
+
120
+ test('should return false if array length is less than specified length', () => {
121
+ const arr = [1, 2, 3] as const;
122
+
123
+ assert.isFalse(isMinLengthTuple(4, arr));
124
+ });
125
+
126
+ test('should return true for empty array and length 0', () => {
127
+ const arr = [] as const;
128
+
129
+ assert.isTrue(isMinLengthTuple(0, arr));
130
+
131
+ if (isMinLengthTuple(0, arr)) {
132
+ expectType<typeof arr, readonly []>('=');
133
+ }
134
+ });
135
+
136
+ test('should return false for empty array and positive length', () => {
137
+ const arr = [] as const;
138
+
139
+ assert.isFalse(isMinLengthTuple(1, arr));
140
+ });
141
+
142
+ test('should work with unknown array type', () => {
143
+ const mut_arr: number[] = [1, 2];
144
+
145
+ assert.isTrue(isMinLengthTuple(2, mut_arr));
146
+
147
+ // transformer-ignore-next-line
148
+ expectType<typeof mut_arr, number[] & MinLengthTuple<2, number>>('=');
149
+
150
+ assert.isFalse(isMinLengthTuple(3, mut_arr));
151
+ });
152
+
153
+ test('should work with unknown array type 2', () => {
154
+ const mut_arr: number[] = [1, 2];
155
+
156
+ assert.isTrue(isMinLengthTuple(1, mut_arr));
157
+
158
+ // transformer-ignore-next-line
159
+ expectType<typeof mut_arr, number[] & MinLengthTuple<1, number>>('=');
160
+
161
+ assert.isFalse(isMinLengthTuple(3, mut_arr));
162
+ });
163
+
164
+ test('should return true for arrays of at least specified length (additional)', () => {
165
+ assert.isTrue(isMinLengthTuple(3, [1, 2, 3]));
166
+
167
+ assert.isTrue(isMinLengthTuple(2, [1, 2, 3]));
168
+
169
+ assert.isTrue(isMinLengthTuple(1, [1, 2, 3]));
170
+
171
+ assert.isTrue(isMinLengthTuple(0, [1, 2, 3]));
172
+ });
173
+
174
+ test('should return false for arrays shorter than specified length (additional)', () => {
175
+ assert.isFalse(isMinLengthTuple(4, [1, 2, 3]));
176
+
177
+ assert.isFalse(isMinLengthTuple(1, []));
178
+ });
179
+
180
+ test('should work as type guard for at least length (additional)', () => {
181
+ const array: readonly number[] = [1, 2, 3] as const;
182
+
183
+ if (isMinLengthTuple(2, array)) {
184
+ expectType<typeof array, MinLengthTuple<2, number>>('=');
185
+
186
+ expect(array.length).toBeGreaterThanOrEqual(2);
187
+ }
188
+ });
189
+ });
190
+
191
+ describe(isMaxLengthTuple, () => {
192
+ test('should return true if array length is less than or equal to specified length', () => {
193
+ const arr = [1, 2, 3] as const;
194
+
195
+ assert.isTrue(isMaxLengthTuple(3, arr));
196
+
197
+ if (isMaxLengthTuple(3, arr)) {
198
+ expectType<typeof arr, readonly [1, 2, 3]>('=');
199
+ }
200
+
201
+ assert.isTrue(isMaxLengthTuple(4, arr));
202
+
203
+ if (isMaxLengthTuple(4, arr)) {
204
+ expectType<typeof arr, readonly [1, 2, 3]>('=');
205
+ }
206
+ });
207
+
208
+ test('should return false if array length is greater than specified length', () => {
209
+ const arr = [1, 2, 3] as const;
210
+
211
+ assert.isFalse(isMaxLengthTuple(2, arr));
212
+ });
213
+
214
+ test('should return true for empty array and length 0', () => {
215
+ const arr = [] as const;
216
+
217
+ assert.isTrue(isMaxLengthTuple(0, arr));
218
+
219
+ if (isMaxLengthTuple(0, arr)) {
220
+ expectType<typeof arr, readonly []>('=');
221
+ }
222
+ });
223
+
224
+ test('should return false for non-empty array and length 0', () => {
225
+ const arr = [1] as const;
226
+
227
+ assert.isFalse(isMaxLengthTuple(0, arr));
228
+ });
229
+
230
+ test('should work with unknown array type', () => {
231
+ const mut_arr: number[] = [1, 2];
232
+
233
+ assert.isTrue(isMaxLengthTuple(2, mut_arr));
234
+
235
+ // transformer-ignore-next-line
236
+ expectType<typeof mut_arr, number[] & MaxLengthTuple<2, number>>('=');
237
+
238
+ assert.isFalse(isMaxLengthTuple(1, mut_arr));
239
+ });
240
+
241
+ test('should return true for arrays of at most specified length (additional)', () => {
242
+ assert.isTrue(isMaxLengthTuple(3, [1, 2, 3]));
243
+
244
+ assert.isTrue(isMaxLengthTuple(4, [1, 2, 3]));
245
+
246
+ assert.isTrue(isMaxLengthTuple(0, []));
247
+ });
248
+
249
+ test('should return false for arrays longer than specified length (additional)', () => {
250
+ assert.isFalse(isMaxLengthTuple(2, [1, 2, 3]));
251
+
252
+ assert.isFalse(isMaxLengthTuple(0, [1, 2, 3]));
253
+ });
254
+
255
+ test('should work as type guard for at most length (additional)', () => {
256
+ const array: readonly number[] = [1, 2] as const;
257
+
258
+ if (isMaxLengthTuple(3, array)) {
259
+ expectType<typeof array, MaxLengthTuple<3, number>>('=');
260
+
261
+ expect(array.length).toBeLessThanOrEqual(3);
262
+ }
263
+ });
264
+ });
265
+
266
+ describe(isBoundedLengthTuple, () => {
267
+ test('should return true if array length is within the specified range', () => {
268
+ const arr = [1, 2, 3] as const;
269
+
270
+ assert.isTrue(isBoundedLengthTuple(1, 3, arr));
271
+
272
+ if (isBoundedLengthTuple(1, 3, arr)) {
273
+ expectType<typeof arr, readonly [1, 2, 3]>('=');
274
+ }
275
+
276
+ assert.isTrue(isBoundedLengthTuple(3, 5, arr));
277
+
278
+ if (isBoundedLengthTuple(3, 5, arr)) {
279
+ expectType<typeof arr, readonly [1, 2, 3]>('=');
280
+ }
281
+ });
282
+
283
+ test('should return false if array length is below the specified range', () => {
284
+ const arr = [1, 2, 3] as const;
285
+
286
+ assert.isFalse(isBoundedLengthTuple(4, 5, arr));
287
+ });
288
+
289
+ test('should return false if array length is above the specified range', () => {
290
+ const arr = [1, 2, 3] as const;
291
+
292
+ assert.isFalse(isBoundedLengthTuple(0, 2, arr));
293
+ });
294
+
295
+ test('should return true for empty array and range including 0', () => {
296
+ const arr = [] as const;
297
+
298
+ assert.isTrue(isBoundedLengthTuple(0, 2, arr));
299
+
300
+ if (isBoundedLengthTuple(0, 2, arr)) {
301
+ expectType<typeof arr, readonly []>('=');
302
+ }
303
+ });
304
+
305
+ test('should work with unknown array type', () => {
306
+ const mut_arr: number[] = [1, 2];
307
+
308
+ assert.isTrue(isBoundedLengthTuple(1, 3, mut_arr));
309
+
310
+ // transformer-ignore-next-line
311
+ expectType<typeof mut_arr, number[] & BoundedLengthTuple<1, 3, number>>(
312
+ '=',
313
+ );
314
+
315
+ assert.isFalse(isBoundedLengthTuple(3, 5, mut_arr));
316
+ });
317
+
318
+ test('should return true for arrays within specified range (additional)', () => {
319
+ assert.isTrue(isBoundedLengthTuple(1, 3, [1, 2, 3]));
320
+
321
+ assert.isTrue(isBoundedLengthTuple(3, 3, [1, 2, 3]));
322
+
323
+ assert.isTrue(isBoundedLengthTuple(0, 0, []));
324
+ });
325
+
326
+ test('should return false for arrays outside specified range (additional)', () => {
327
+ assert.isFalse(isBoundedLengthTuple(4, 5, [1, 2, 3]));
328
+
329
+ assert.isFalse(isBoundedLengthTuple(0, 2, [1, 2, 3]));
330
+ });
331
+
332
+ test('should work as type guard for bounded length (additional)', () => {
333
+ const array: readonly number[] = [1, 2] as const;
334
+
335
+ if (isBoundedLengthTuple(1, 3, array)) {
336
+ expectType<typeof array, BoundedLengthTuple<1, 3, number>>('=');
337
+
338
+ expect(array.length).toBeGreaterThanOrEqual(1);
339
+
340
+ expect(array.length).toBeLessThanOrEqual(3);
341
+ }
342
+ });
343
+ });
344
+
345
+ describe(isEmptyTuple, () => {
346
+ test('narrows an empty array to the empty tuple', () => {
347
+ const values: readonly number[] = [] as const;
348
+
349
+ assert.isTrue(isEmptyTuple(values));
350
+
351
+ if (isEmptyTuple(values)) {
352
+ expectType<typeof values, readonly []>('=');
353
+ }
354
+ });
355
+
356
+ test('rejects a non-empty array', () => {
357
+ assert.isFalse(isEmptyTuple([1] as const));
358
+ });
359
+ });
360
+
361
+ describe(isNonEmptyTuple, () => {
362
+ test('narrows to the structural non-empty tuple', () => {
363
+ const values: readonly number[] = [1, 2] as const;
364
+
365
+ assert.isTrue(isNonEmptyTuple(values));
366
+
367
+ if (isNonEmptyTuple(values)) {
368
+ expectType<typeof values, MinLengthTuple<1, number>>('=');
369
+
370
+ // The structural prefix makes the first element defined.
371
+ const first: number = values[0];
372
+
373
+ assert.deepStrictEqual(first, 1);
374
+ }
375
+ });
376
+
377
+ test('rejects an empty array', () => {
378
+ assert.isFalse(isNonEmptyTuple([] as const));
379
+ });
380
+ });
381
+ });
382
+
383
+ describe('curried tuple length guards', () => {
384
+ test('isFixedLengthTuple narrows when applied later', () => {
385
+ const isPair = isFixedLengthTuple(2);
386
+
387
+ assert.isTrue(isPair([1, 2]));
388
+
389
+ assert.isFalse(isPair([1, 2, 3]));
390
+
391
+ const value: readonly number[] = [1, 2] as const;
392
+
393
+ if (isPair(value)) {
394
+ expectType<typeof value, FixedLengthTuple<2, number>>('=');
395
+ }
396
+ });
397
+
398
+ test('isMinLengthTuple narrows when applied later', () => {
399
+ const hasOne = isMinLengthTuple(1);
400
+
401
+ assert.isTrue(hasOne(['task-1']));
402
+
403
+ assert.isFalse(hasOne([]));
404
+
405
+ const value: readonly string[] = ['task-1'] as const;
406
+
407
+ if (hasOne(value)) {
408
+ expectType<typeof value, MinLengthTuple<1, string>>('=');
409
+ }
410
+ });
411
+
412
+ test('isMaxLengthTuple narrows when applied later', () => {
413
+ const fitsInTwo = isMaxLengthTuple(2);
414
+
415
+ assert.isTrue(fitsInTwo([1, 2]));
416
+
417
+ assert.isFalse(fitsInTwo([1, 2, 3]));
418
+
419
+ const value: readonly number[] = [1, 2] as const;
420
+
421
+ if (fitsInTwo(value)) {
422
+ expectType<typeof value, MaxLengthTuple<2, number>>('=');
423
+ }
424
+ });
425
+
426
+ test('isBoundedLengthTuple narrows when applied later', () => {
427
+ const isSmall = isBoundedLengthTuple(1, 3);
428
+
429
+ assert.isTrue(isSmall([1, 2]));
430
+
431
+ assert.isFalse(isSmall([]));
432
+
433
+ assert.isFalse(isSmall([1, 2, 3, 4]));
434
+
435
+ const value: readonly number[] = [1, 2] as const;
436
+
437
+ if (isSmall(value)) {
438
+ expectType<typeof value, BoundedLengthTuple<1, 3, number>>('=');
439
+ }
440
+ });
441
+ });