ts-fortress 2.2.0 → 2.2.2
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.
- package/README.md +135 -0
- package/dist/array/array-of-length.d.mts.map +1 -1
- package/dist/array/array-of-length.mjs +8 -6
- package/dist/array/array-of-length.mjs.map +1 -1
- package/dist/array/array.d.mts.map +1 -1
- package/dist/array/array.mjs +8 -6
- package/dist/array/array.mjs.map +1 -1
- package/dist/array/non-empty-array.d.mts.map +1 -1
- package/dist/array/non-empty-array.mjs +8 -6
- package/dist/array/non-empty-array.mjs.map +1 -1
- package/dist/array/tuple.d.mts.map +1 -1
- package/dist/array/tuple.mjs +8 -6
- package/dist/array/tuple.mjs.map +1 -1
- package/dist/compose/intersection.d.mts.map +1 -1
- package/dist/compose/intersection.mjs +10 -10
- package/dist/compose/intersection.mjs.map +1 -1
- package/dist/compose/merge-records.d.mts.map +1 -1
- package/dist/compose/merge-records.mjs +10 -10
- package/dist/compose/merge-records.mjs.map +1 -1
- package/dist/compose/union.d.mts.map +1 -1
- package/dist/compose/union.mjs.map +1 -1
- package/dist/enum/enum.d.mts.map +1 -1
- package/dist/enum/enum.mjs.map +1 -1
- package/dist/enum/uint-range.d.mts.map +1 -1
- package/dist/enum/uint-range.mjs.map +1 -1
- package/dist/index.d.mts +0 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/record/key-value-record.d.mts.map +1 -1
- package/dist/record/key-value-record.mjs +21 -24
- package/dist/record/key-value-record.mjs.map +1 -1
- package/dist/record/omit.d.mts.map +1 -1
- package/dist/record/omit.mjs +3 -2
- package/dist/record/omit.mjs.map +1 -1
- package/dist/record/partial.d.mts.map +1 -1
- package/dist/record/partial.mjs +2 -2
- package/dist/record/partial.mjs.map +1 -1
- package/dist/record/pick.d.mts.map +1 -1
- package/dist/record/pick.mjs +2 -2
- package/dist/record/pick.mjs.map +1 -1
- package/dist/record/record.d.mts +6 -2
- package/dist/record/record.d.mts.map +1 -1
- package/dist/record/record.mjs +23 -21
- package/dist/record/record.mjs.map +1 -1
- package/dist/type.d.mts +1 -1
- package/dist/type.d.mts.map +1 -1
- package/dist/utils/create-assert-fn.mjs +1 -1
- package/dist/utils/create-cast-fn.mjs +1 -1
- package/dist/utils/create-primitive-type.mjs +1 -1
- package/dist/utils/index.d.mts +1 -0
- package/dist/utils/index.d.mts.map +1 -1
- package/dist/utils/index.mjs +1 -0
- package/dist/utils/index.mjs.map +1 -1
- package/dist/utils/validation-error.d.mts.map +1 -0
- package/dist/utils/validation-error.mjs.map +1 -0
- package/package.json +4 -2
- package/src/array/array-of-length.mts +12 -7
- package/src/array/array-of-length.test.mts +10 -1
- package/src/array/array.mts +12 -7
- package/src/array/array.test.mts +10 -1
- package/src/array/non-empty-array.mts +11 -7
- package/src/array/non-empty-array.test.mts +10 -1
- package/src/array/tuple.mts +11 -9
- package/src/array/tuple.test.mts +14 -1
- package/src/branded/brand.mts +2 -2
- package/src/branded/number/finite-number.test.mts +10 -1
- package/src/branded/number/int.test.mts +10 -1
- package/src/branded/number/int16.test.mts +10 -1
- package/src/branded/number/int32.test.mts +10 -1
- package/src/branded/number/non-negative-finite-number.test.mts +10 -1
- package/src/branded/number/non-zero-finite-number.test.mts +10 -1
- package/src/branded/number/non-zero-int.test.mts +10 -1
- package/src/branded/number/non-zero-safe-int.test.mts +10 -1
- package/src/branded/number/positive-finite-number.test.mts +10 -1
- package/src/branded/number/positive-int.test.mts +10 -1
- package/src/branded/number/positive-safe-int.test.mts +10 -1
- package/src/branded/number/safe-int.test.mts +10 -1
- package/src/branded/number/safe-uint.test.mts +10 -1
- package/src/branded/number/uint.test.mts +10 -1
- package/src/branded/number/uint16.test.mts +10 -1
- package/src/branded/number/uint32.test.mts +10 -1
- package/src/branded/simple-branded-number.test.mts +19 -1
- package/src/branded/simple-branded-string.test.mts +19 -1
- package/src/compose/intersection.mts +23 -19
- package/src/compose/intersection.test.mts +28 -1
- package/src/compose/merge-records.mts +22 -19
- package/src/compose/merge-records.test.mts +10 -1
- package/src/compose/union.mts +6 -2
- package/src/compose/union.test.mts +10 -1
- package/src/enum/enum.mts +6 -2
- package/src/enum/enum.test.mts +10 -1
- package/src/enum/uint-range.mts +6 -2
- package/src/enum/uint-range.test.mts +10 -1
- package/src/index.mts +0 -1
- package/src/primitives/bigint.test.mts +19 -1
- package/src/primitives/boolean.test.mts +28 -1
- package/src/primitives/null.test.mts +9 -0
- package/src/primitives/number.test.mts +18 -0
- package/src/primitives/string.test.mts +18 -0
- package/src/primitives/symbol.test.mts +10 -1
- package/src/primitives/undefined.test.mts +9 -0
- package/src/record/key-value-record.mts +34 -36
- package/src/record/key-value-record.test.mts +14 -1
- package/src/record/keyof.test.mts +10 -1
- package/src/record/omit.mts +8 -5
- package/src/record/omit.test.mts +46 -1
- package/src/record/partial.mts +7 -5
- package/src/record/partial.test.mts +71 -1
- package/src/record/pick.mts +7 -5
- package/src/record/pick.test.mts +46 -1
- package/src/record/record-allow-excess-properties.test.mts +28 -1
- package/src/record/record-strict-composition-simple.test.mts +38 -0
- package/src/record/record-strict-composition.test.mts +71 -0
- package/src/record/record.mts +50 -36
- package/src/record/record.test.mts +26 -1
- package/src/record/strict-record.test.mts +10 -1
- package/src/type.mts +1 -1
- package/src/utils/create-assert-fn.mts +1 -1
- package/src/utils/create-cast-fn.mts +1 -1
- package/src/utils/create-primitive-type.mts +1 -1
- package/src/utils/index.mts +1 -0
- package/dist/validation-error.d.mts.map +0 -1
- package/dist/validation-error.mjs.map +0 -1
- package/src/combination.test.mts +0 -544
- /package/dist/{validation-error.d.mts → utils/validation-error.d.mts} +0 -0
- /package/dist/{validation-error.mjs → utils/validation-error.mjs} +0 -0
- /package/src/{validation-error.mts → utils/validation-error.mts} +0 -0
- /package/src/{validation-error.test.mts → utils/validation-error.test.mts} +0 -0
package/src/combination.test.mts
DELETED
|
@@ -1,544 +0,0 @@
|
|
|
1
|
-
import { asInt, expectType, Result } from 'ts-data-forge';
|
|
2
|
-
import { array } from './array/index.mjs';
|
|
3
|
-
import { int } from './branded/index.mjs';
|
|
4
|
-
import { union } from './compose/index.mjs';
|
|
5
|
-
import { uintRange } from './enum/index.mjs';
|
|
6
|
-
import { nullType, number, undefinedType } from './primitives/index.mjs';
|
|
7
|
-
import { keyof, optional, partial, pick, record } from './record/index.mjs';
|
|
8
|
-
import { type TypeOf } from './type.mjs';
|
|
9
|
-
import { unknown } from './unknown.mjs';
|
|
10
|
-
import { validationErrorsToMessages } from './validation-error.mjs';
|
|
11
|
-
|
|
12
|
-
describe('nested record', () => {
|
|
13
|
-
const nestedRecord = record({
|
|
14
|
-
xs: array(int(asInt(2))),
|
|
15
|
-
rec: pick(
|
|
16
|
-
record({
|
|
17
|
-
a: uintRange({ start: 0, end: 11, defaultValue: 0 }),
|
|
18
|
-
b: uintRange({ start: 0, end: 11, defaultValue: 0 }),
|
|
19
|
-
c: optional(uintRange({ start: 3, end: 6, defaultValue: 3 })),
|
|
20
|
-
d: unknown(),
|
|
21
|
-
}),
|
|
22
|
-
['a', 'b', 'c'],
|
|
23
|
-
),
|
|
24
|
-
meta: number(100),
|
|
25
|
-
u: unknown(),
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
type NestedRecord = TypeOf<typeof nestedRecord>;
|
|
29
|
-
|
|
30
|
-
expectType<
|
|
31
|
-
NestedRecord,
|
|
32
|
-
Readonly<{
|
|
33
|
-
xs: readonly Int[];
|
|
34
|
-
rec: Readonly<{
|
|
35
|
-
a: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
|
|
36
|
-
b: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
|
|
37
|
-
c?: 3 | 4 | 5;
|
|
38
|
-
}>;
|
|
39
|
-
meta: number;
|
|
40
|
-
u: unknown;
|
|
41
|
-
}>
|
|
42
|
-
>('=');
|
|
43
|
-
|
|
44
|
-
expectType<typeof nestedRecord.defaultValue, NestedRecord>('=');
|
|
45
|
-
|
|
46
|
-
describe('is', () => {
|
|
47
|
-
test('truthy case', () => {
|
|
48
|
-
const x: UnknownRecord = {
|
|
49
|
-
xs: [1, 2, 3],
|
|
50
|
-
rec: {
|
|
51
|
-
a: 1,
|
|
52
|
-
b: 2,
|
|
53
|
-
c: 3,
|
|
54
|
-
},
|
|
55
|
-
meta: 3,
|
|
56
|
-
u: undefined,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
if (nestedRecord.is(x)) {
|
|
60
|
-
expectType<typeof x, NestedRecord>('=');
|
|
61
|
-
} else {
|
|
62
|
-
expectType<typeof x, UnknownRecord>('=');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
expect(nestedRecord.is(x)).toBe(true);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('falsy case', () => {
|
|
69
|
-
const x: UnknownRecord = {
|
|
70
|
-
xs: [-1, 2.2, 3.3],
|
|
71
|
-
rec: {
|
|
72
|
-
a: 123,
|
|
73
|
-
b: 234,
|
|
74
|
-
c: 3,
|
|
75
|
-
},
|
|
76
|
-
meta: 345,
|
|
77
|
-
u: undefined,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
if (nestedRecord.is(x)) {
|
|
81
|
-
expectType<typeof x, NestedRecord>('=');
|
|
82
|
-
} else {
|
|
83
|
-
expectType<typeof x, UnknownRecord>('=');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
expect(nestedRecord.is(x)).toBe(false);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('validate', () => {
|
|
91
|
-
test('falsy case', () => {
|
|
92
|
-
const x: UnknownRecord = {
|
|
93
|
-
xs: [-1, 2.2, 3.3],
|
|
94
|
-
rec: {
|
|
95
|
-
a: 123,
|
|
96
|
-
b: 234,
|
|
97
|
-
c: 3,
|
|
98
|
-
},
|
|
99
|
-
meta: 345,
|
|
100
|
-
u: undefined,
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const result = nestedRecord.validate(x);
|
|
104
|
-
expect(Result.isErr(result)).toBe(true);
|
|
105
|
-
|
|
106
|
-
if (Result.isErr(result)) {
|
|
107
|
-
expect(result.value).toStrictEqual([
|
|
108
|
-
{
|
|
109
|
-
path: ['xs', '1'],
|
|
110
|
-
actualValue: 2.2,
|
|
111
|
-
expectedType: 'Finite & Int & not(NaNValue)',
|
|
112
|
-
typeName: 'Finite & Int & not(NaNValue)',
|
|
113
|
-
message:
|
|
114
|
-
'The value must satisfy the constraint corresponding to the brand keys: <Finite & Int & not(NaNValue)>',
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
path: ['xs', '2'],
|
|
118
|
-
actualValue: 3.3,
|
|
119
|
-
expectedType: 'Finite & Int & not(NaNValue)',
|
|
120
|
-
typeName: 'Finite & Int & not(NaNValue)',
|
|
121
|
-
message:
|
|
122
|
-
'The value must satisfy the constraint corresponding to the brand keys: <Finite & Int & not(NaNValue)>',
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
path: ['rec', 'a'],
|
|
126
|
-
actualValue: 123,
|
|
127
|
-
expectedType: 'uintRange(0, 11)',
|
|
128
|
-
typeName: 'uintRange(0, 11)',
|
|
129
|
-
message: 'The value is expected to be an integer between 0 and 10',
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
path: ['rec', 'b'],
|
|
133
|
-
actualValue: 234,
|
|
134
|
-
expectedType: 'uintRange(0, 11)',
|
|
135
|
-
typeName: 'uintRange(0, 11)',
|
|
136
|
-
message: 'The value is expected to be an integer between 0 and 10',
|
|
137
|
-
},
|
|
138
|
-
]);
|
|
139
|
-
expect(validationErrorsToMessages(result.value)).toStrictEqual([
|
|
140
|
-
'The value must satisfy the constraint corresponding to the brand keys: <Finite & Int & not(NaNValue)> at xs.1',
|
|
141
|
-
'The value must satisfy the constraint corresponding to the brand keys: <Finite & Int & not(NaNValue)> at xs.2',
|
|
142
|
-
'The value is expected to be an integer between 0 and 10 at rec.a',
|
|
143
|
-
'The value is expected to be an integer between 0 and 10 at rec.b',
|
|
144
|
-
]);
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe('fill', () => {
|
|
150
|
-
test('from an empty record', () => {
|
|
151
|
-
const x: UnknownRecord = {};
|
|
152
|
-
|
|
153
|
-
expect(nestedRecord.fill(x)).toStrictEqual({
|
|
154
|
-
xs: [],
|
|
155
|
-
rec: {
|
|
156
|
-
a: 0,
|
|
157
|
-
b: 0,
|
|
158
|
-
c: 3,
|
|
159
|
-
},
|
|
160
|
-
meta: 100,
|
|
161
|
-
u: undefined,
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test('from a filled record', () => {
|
|
166
|
-
const x: UnknownRecord = {
|
|
167
|
-
xs: [-1, 2.2, 3.3],
|
|
168
|
-
rec: {
|
|
169
|
-
a: 123,
|
|
170
|
-
b: 234,
|
|
171
|
-
c: 3,
|
|
172
|
-
},
|
|
173
|
-
meta: 345,
|
|
174
|
-
u: undefined,
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
expect(nestedRecord.fill(x)).toStrictEqual({
|
|
178
|
-
xs: [-1, 2, 2],
|
|
179
|
-
rec: {
|
|
180
|
-
a: 0,
|
|
181
|
-
b: 0,
|
|
182
|
-
c: 3,
|
|
183
|
-
},
|
|
184
|
-
meta: 345,
|
|
185
|
-
u: undefined,
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test('from a partial record', () => {
|
|
190
|
-
const x: UnknownRecord = {
|
|
191
|
-
xs: [11, 22],
|
|
192
|
-
rec: {
|
|
193
|
-
a: 3,
|
|
194
|
-
},
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
expect(nestedRecord.fill(x)).toStrictEqual({
|
|
198
|
-
xs: [11, 22],
|
|
199
|
-
rec: {
|
|
200
|
-
a: 3,
|
|
201
|
-
b: 0,
|
|
202
|
-
c: 3,
|
|
203
|
-
},
|
|
204
|
-
meta: 100,
|
|
205
|
-
u: undefined,
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test('from a partial record with excess property', () => {
|
|
210
|
-
const x: UnknownRecord = {
|
|
211
|
-
xs: [11, 22],
|
|
212
|
-
rec: {
|
|
213
|
-
a: 3,
|
|
214
|
-
d: 9988,
|
|
215
|
-
},
|
|
216
|
-
u: undefined,
|
|
217
|
-
aaaaa: [9999],
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
expect(nestedRecord.fill(x)).toStrictEqual({
|
|
221
|
-
xs: [11, 22],
|
|
222
|
-
rec: {
|
|
223
|
-
a: 3,
|
|
224
|
-
b: 0,
|
|
225
|
-
c: 3,
|
|
226
|
-
},
|
|
227
|
-
meta: 100,
|
|
228
|
-
u: undefined,
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
describe('ymd', () => {
|
|
235
|
-
const ymd = record({
|
|
236
|
-
year: number(1900),
|
|
237
|
-
month: number(1),
|
|
238
|
-
date: number(1),
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
test('is', () => {
|
|
242
|
-
const x = {
|
|
243
|
-
year: 2000,
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
expect(ymd.is(x)).toBe(false);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
test('fill', () => {
|
|
250
|
-
const x = {
|
|
251
|
-
year: 2000,
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
expect(ymd.fill(x)).toStrictEqual({
|
|
255
|
-
year: 2000,
|
|
256
|
-
month: 1,
|
|
257
|
-
date: 1,
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test('assertion function', () => {
|
|
262
|
-
type YMD = TypeOf<typeof ymd>;
|
|
263
|
-
|
|
264
|
-
const assertIsYmd: (a: unknown) => asserts a is YMD = ymd.assertIs;
|
|
265
|
-
|
|
266
|
-
const x = {
|
|
267
|
-
year: 2000,
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
const f = (): void => {
|
|
271
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
272
|
-
// @ts-expect-error
|
|
273
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
274
|
-
x.month;
|
|
275
|
-
|
|
276
|
-
assertIsYmd(x); // throws an error
|
|
277
|
-
|
|
278
|
-
expectType<typeof x.month, number>('='); // no type error
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
expect(f).toThrow(
|
|
282
|
-
'\nMissing required key "month" at month,\nMissing required key "date" at date',
|
|
283
|
-
);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test('validation function', () => {
|
|
287
|
-
const x = {
|
|
288
|
-
year: 2000,
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
const result = ymd.validate(x);
|
|
292
|
-
|
|
293
|
-
expect(Result.isErr(result)).toBe(true);
|
|
294
|
-
|
|
295
|
-
if (Result.isErr(result)) {
|
|
296
|
-
expect(result.value).toStrictEqual([
|
|
297
|
-
{
|
|
298
|
-
path: ['month'],
|
|
299
|
-
actualValue: x,
|
|
300
|
-
expectedType: '{ year: number, month: number, date: number }',
|
|
301
|
-
typeName: '{ year: number, month: number, date: number }',
|
|
302
|
-
message: 'Missing required key "month"',
|
|
303
|
-
},
|
|
304
|
-
{
|
|
305
|
-
path: ['date'],
|
|
306
|
-
actualValue: x,
|
|
307
|
-
expectedType: '{ year: number, month: number, date: number }',
|
|
308
|
-
typeName: '{ year: number, month: number, date: number }',
|
|
309
|
-
message: 'Missing required key "date"',
|
|
310
|
-
},
|
|
311
|
-
]);
|
|
312
|
-
expect(validationErrorsToMessages(result.value)).toStrictEqual([
|
|
313
|
-
'Missing required key "month" at month',
|
|
314
|
-
'Missing required key "date" at date',
|
|
315
|
-
]);
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
describe('ymd2', () => {
|
|
321
|
-
const ymd2 = record({
|
|
322
|
-
year: int(asInt(1900)),
|
|
323
|
-
month: uintRange({
|
|
324
|
-
defaultValue: 1,
|
|
325
|
-
start: 1,
|
|
326
|
-
end: 13,
|
|
327
|
-
}),
|
|
328
|
-
date: uintRange({
|
|
329
|
-
defaultValue: 1,
|
|
330
|
-
start: 1,
|
|
331
|
-
end: 32,
|
|
332
|
-
}),
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
type YMD2 = TypeOf<typeof ymd2>;
|
|
336
|
-
|
|
337
|
-
expectType<YMD2['month'], 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12>(
|
|
338
|
-
'=',
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
test('is', () => {
|
|
342
|
-
const x = {
|
|
343
|
-
year: 2000,
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
expect(ymd2.is(x)).toBe(false);
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
// Tests demonstrating ts-fortress correct behavior for io-ts bugs mentioned in README
|
|
351
|
-
describe('io-ts bug examples - correct behavior in ts-fortress', () => {
|
|
352
|
-
describe('keyof type consistency (io-ts issue #697)', () => {
|
|
353
|
-
test('keyof with numeric keys produces correct types and runtime behavior', () => {
|
|
354
|
-
// ts-fortress: Runtime and types always match
|
|
355
|
-
const T = keyof(
|
|
356
|
-
record({
|
|
357
|
-
0: undefinedType,
|
|
358
|
-
1: undefinedType,
|
|
359
|
-
2: undefinedType,
|
|
360
|
-
3: undefinedType,
|
|
361
|
-
4: undefinedType,
|
|
362
|
-
}),
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
type T = TypeOf<typeof T>;
|
|
366
|
-
// TypeScript correctly infers: "0" | "1" | "2" | "3" | "4" (string literals)
|
|
367
|
-
expectType<T, '0' | '1' | '2' | '3' | '4'>('=');
|
|
368
|
-
|
|
369
|
-
// Runtime behavior matches TypeScript types exactly
|
|
370
|
-
expect(Result.isErr(T.validate(0))).toBe(true); // number 0 is rejected
|
|
371
|
-
expect(Result.isOk(T.validate('0'))).toBe(true); // string "0" is accepted
|
|
372
|
-
|
|
373
|
-
expect(Result.isOk(T.validate('1'))).toBe(true);
|
|
374
|
-
expect(Result.isOk(T.validate('2'))).toBe(true);
|
|
375
|
-
expect(Result.isOk(T.validate('3'))).toBe(true);
|
|
376
|
-
expect(Result.isOk(T.validate('4'))).toBe(true);
|
|
377
|
-
|
|
378
|
-
expect(Result.isErr(T.validate('5'))).toBe(true); // invalid key
|
|
379
|
-
expect(Result.isErr(T.validate(1))).toBe(true); // number literal rejected
|
|
380
|
-
expect(Result.isErr(T.validate('invalid'))).toBe(true); // invalid string
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
describe('union validation consistency (io-ts issue #677)', () => {
|
|
385
|
-
test('union validation produces predictable results without unexpected fields', () => {
|
|
386
|
-
const A = record({
|
|
387
|
-
A: union([number(0), undefinedType, nullType]),
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
const B = record({
|
|
391
|
-
B: union([number(0), undefinedType, nullType]),
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
const C = partial(
|
|
395
|
-
record({
|
|
396
|
-
C: union([number(0), nullType]),
|
|
397
|
-
}),
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
// Case 1: Union validation is predictable and correct
|
|
401
|
-
{
|
|
402
|
-
const UnionBA = union([B, A]);
|
|
403
|
-
const result = UnionBA.validate({ A: 1 });
|
|
404
|
-
|
|
405
|
-
expect(Result.isOk(result)).toBe(true);
|
|
406
|
-
|
|
407
|
-
if (Result.isOk(result)) {
|
|
408
|
-
// No unexpected fields added
|
|
409
|
-
expect(result.value).toStrictEqual({ A: 1 });
|
|
410
|
-
|
|
411
|
-
// Type guards behave correctly
|
|
412
|
-
expect(A.is(result.value)).toBe(true); // Correct
|
|
413
|
-
expect(B.is(result.value)).toBe(false); // Correct! B requires field B
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Case 2: Consistent validation behavior
|
|
418
|
-
{
|
|
419
|
-
const UnionCA = union([C, A]);
|
|
420
|
-
const result = UnionCA.validate({ A: 1 });
|
|
421
|
-
|
|
422
|
-
expect(Result.isOk(result)).toBe(true);
|
|
423
|
-
|
|
424
|
-
if (Result.isOk(result)) {
|
|
425
|
-
// Correct and consistent result
|
|
426
|
-
expect(result.value).toStrictEqual({ A: 1 });
|
|
427
|
-
|
|
428
|
-
// Type guards work as expected
|
|
429
|
-
expect(A.is(result.value)).toBe(true); // Correct
|
|
430
|
-
// Note: In ts-fortress, partial types are more permissive and can accept
|
|
431
|
-
// objects with additional fields, so C.is() returns true here.
|
|
432
|
-
// This is different from io-ts behavior and shows ts-fortress's
|
|
433
|
-
// consistent approach to partial type validation.
|
|
434
|
-
expect(C.is(result.value)).toBe(true); // ts-fortress allows extra fields in partial
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
test('union validation handles partial types correctly', () => {
|
|
440
|
-
const FullRecord = record({
|
|
441
|
-
name: number(0),
|
|
442
|
-
value: number(0),
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
const PartialRecord = partial(
|
|
446
|
-
record({
|
|
447
|
-
name: number(0),
|
|
448
|
-
optional: number(0),
|
|
449
|
-
}),
|
|
450
|
-
);
|
|
451
|
-
|
|
452
|
-
const UnionType = union([PartialRecord, FullRecord]);
|
|
453
|
-
|
|
454
|
-
// Test with data that matches FullRecord
|
|
455
|
-
{
|
|
456
|
-
const result = UnionType.validate({ name: 42, value: 100 });
|
|
457
|
-
expect(Result.isOk(result)).toBe(true);
|
|
458
|
-
|
|
459
|
-
if (Result.isOk(result)) {
|
|
460
|
-
expect(result.value).toStrictEqual({ name: 42, value: 100 });
|
|
461
|
-
expect(FullRecord.is(result.value)).toBe(true);
|
|
462
|
-
// Note: In ts-fortress, partial types accept objects with extra fields
|
|
463
|
-
// so PartialRecord.is() returns true even with the 'value' field
|
|
464
|
-
expect(PartialRecord.is(result.value)).toBe(true); // ts-fortress partial allows extra fields
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Test with data that matches PartialRecord
|
|
469
|
-
{
|
|
470
|
-
const result = UnionType.validate({ name: 42 });
|
|
471
|
-
expect(Result.isOk(result)).toBe(true);
|
|
472
|
-
|
|
473
|
-
if (Result.isOk(result)) {
|
|
474
|
-
expect(result.value).toStrictEqual({ name: 42 });
|
|
475
|
-
expect(FullRecord.is(result.value)).toBe(false); // Missing 'value' field
|
|
476
|
-
expect(PartialRecord.is(result.value)).toBe(true);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
test('union validation with complex nested structures', () => {
|
|
482
|
-
const TypeA = record({
|
|
483
|
-
type: union([undefinedType]), // Only accepts undefined
|
|
484
|
-
data: record({
|
|
485
|
-
valueA: number(0),
|
|
486
|
-
}),
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
const TypeB = record({
|
|
490
|
-
type: nullType, // Only accepts null
|
|
491
|
-
data: record({
|
|
492
|
-
valueB: number(0),
|
|
493
|
-
}),
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
const UnionAB = union([TypeA, TypeB]);
|
|
497
|
-
|
|
498
|
-
// Test TypeA validation
|
|
499
|
-
{
|
|
500
|
-
const input = {
|
|
501
|
-
type: undefined,
|
|
502
|
-
data: { valueA: 42 },
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
const result = UnionAB.validate(input);
|
|
506
|
-
expect(Result.isOk(result)).toBe(true);
|
|
507
|
-
|
|
508
|
-
if (Result.isOk(result)) {
|
|
509
|
-
expect(result.value).toStrictEqual(input);
|
|
510
|
-
expect(TypeA.is(result.value)).toBe(true);
|
|
511
|
-
expect(TypeB.is(result.value)).toBe(false);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// Test TypeB validation
|
|
516
|
-
{
|
|
517
|
-
const input = {
|
|
518
|
-
type: null,
|
|
519
|
-
data: { valueB: 99 },
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
const result = UnionAB.validate(input);
|
|
523
|
-
expect(Result.isOk(result)).toBe(true);
|
|
524
|
-
|
|
525
|
-
if (Result.isOk(result)) {
|
|
526
|
-
expect(result.value).toStrictEqual(input);
|
|
527
|
-
expect(TypeA.is(result.value)).toBe(false);
|
|
528
|
-
expect(TypeB.is(result.value)).toBe(true);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// Test invalid data
|
|
533
|
-
{
|
|
534
|
-
const input = {
|
|
535
|
-
type: 'invalid',
|
|
536
|
-
data: { someField: 123 },
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
const result = UnionAB.validate(input);
|
|
540
|
-
expect(Result.isErr(result)).toBe(true);
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
});
|
|
544
|
-
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|