ts-fortress 2.0.4 → 2.2.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 (33) hide show
  1. package/README.md +80 -1
  2. package/dist/index.mjs +1 -1
  3. package/dist/record/index.mjs +1 -1
  4. package/dist/record/key-value-record.d.mts +1 -1
  5. package/dist/record/key-value-record.d.mts.map +1 -1
  6. package/dist/record/record.d.mts +31 -5
  7. package/dist/record/record.d.mts.map +1 -1
  8. package/dist/record/record.mjs +42 -4
  9. package/dist/record/record.mjs.map +1 -1
  10. package/package.json +1 -1
  11. package/src/array/array-of-length.test.mts +16 -1
  12. package/src/array/array.test.mts +16 -1
  13. package/src/array/non-empty-array.test.mts +16 -1
  14. package/src/array/tuple.test.mts +18 -1
  15. package/src/compose/intersection.test.mts +43 -1
  16. package/src/compose/merge-records.test.mts +16 -1
  17. package/src/compose/union.test.mts +16 -1
  18. package/src/enum/enum.test.mts +16 -1
  19. package/src/enum/uint-range.test.mts +14 -1
  20. package/src/primitives/null.test.mts +170 -0
  21. package/src/primitives/number.test.mts +284 -0
  22. package/src/primitives/string.test.mts +341 -0
  23. package/src/primitives/undefined.test.mts +179 -0
  24. package/src/record/key-value-record.mts +4 -4
  25. package/src/record/key-value-record.test.mts +28 -2
  26. package/src/record/keyof.test.mts +12 -0
  27. package/src/record/partial.test.mts +18 -0
  28. package/src/record/record-allow-excess-properties.test.mts +180 -0
  29. package/src/record/record-strict-composition-simple.test.mts +134 -0
  30. package/src/record/record-strict-composition.test.mts +403 -0
  31. package/src/record/record.mts +92 -31
  32. package/src/record/record.test.mts +71 -1
  33. package/src/record/strict-record.test.mts +192 -0
@@ -0,0 +1,170 @@
1
+ import { expectType, Result } from 'ts-data-forge';
2
+ import { type TypeOf } from '../type.mjs';
3
+ import { nullType } from './null.mjs';
4
+
5
+ describe('nullType', () => {
6
+ type Null = TypeOf<typeof nullType>;
7
+
8
+ expectType<Null, null>('=');
9
+ expectType<typeof nullType.defaultValue, Null>('=');
10
+
11
+ describe('default value', () => {
12
+ test('is null', () => {
13
+ expect(nullType.defaultValue).toBeNull();
14
+ });
15
+ });
16
+
17
+ describe('is', () => {
18
+ test('null returns true', () => {
19
+ const value: unknown = null;
20
+
21
+ if (nullType.is(value)) {
22
+ expectType<typeof value, Null>('=');
23
+ } else {
24
+ expectType<typeof value, {} | undefined>('=');
25
+ }
26
+
27
+ expect(nullType.is(value)).toBe(true);
28
+ });
29
+
30
+ test('undefined returns false', () => {
31
+ const value: unknown = undefined;
32
+
33
+ if (nullType.is(value)) {
34
+ expectType<typeof value, Null>('=');
35
+ } else {
36
+ expectType<typeof value, {} | undefined>('=');
37
+ }
38
+
39
+ expect(nullType.is(value)).toBe(false);
40
+ });
41
+
42
+ test('other values return false', () => {
43
+ const values = [0, '', false, Number.NaN, {}, [], 'null', 42, true];
44
+
45
+ for (const value of values) {
46
+ const v: unknown = value;
47
+
48
+ if (nullType.is(v)) {
49
+ expectType<typeof v, Null>('=');
50
+ } else {
51
+ expectType<typeof v, {} | undefined>('=');
52
+ }
53
+
54
+ expect(nullType.is(v)).toBe(false);
55
+ }
56
+ });
57
+ });
58
+
59
+ describe('assertIs', () => {
60
+ test('null does not throw', () => {
61
+ const value: unknown = null;
62
+ expect(() => {
63
+ nullType.assertIs(value);
64
+ }).not.toThrow();
65
+ });
66
+
67
+ test('non-null throws', () => {
68
+ const value: unknown = undefined;
69
+ expect(() => {
70
+ nullType.assertIs(value);
71
+ }).toThrow('Expected null, got undefined');
72
+ });
73
+ });
74
+
75
+ describe('cast', () => {
76
+ test('null returns null', () => {
77
+ const value: unknown = null;
78
+ const result = nullType.cast(value);
79
+ expect(result).toBeNull();
80
+ });
81
+
82
+ test('non-null throws error', () => {
83
+ const value: unknown = undefined;
84
+ expect(() => nullType.cast(value)).toThrow(
85
+ 'Expected null, got undefined',
86
+ );
87
+ });
88
+
89
+ test('any non-null value throws error', () => {
90
+ const values = [0, '', false, undefined, 42, 'hello'];
91
+
92
+ for (const value of values) {
93
+ expect(() => nullType.cast(value)).toThrow(
94
+ /Expected null, got (number|string|boolean|undefined)/u,
95
+ );
96
+ }
97
+ });
98
+ });
99
+
100
+ describe('fill', () => {
101
+ test('null returns null', () => {
102
+ const value: unknown = null;
103
+ const result = nullType.fill(value);
104
+ expect(result).toBeNull();
105
+ });
106
+
107
+ test('undefined returns default (null)', () => {
108
+ const value: unknown = undefined;
109
+ const result = nullType.fill(value);
110
+ expect(result).toBeNull();
111
+ });
112
+
113
+ test('other values return default (null)', () => {
114
+ const values = [0, '', false, 42, 'hello', {}];
115
+
116
+ for (const value of values) {
117
+ const result = nullType.fill(value);
118
+ expect(result).toBeNull();
119
+ }
120
+ });
121
+ });
122
+
123
+ describe('validate', () => {
124
+ test('null is valid', () => {
125
+ const value: unknown = null;
126
+ const result = nullType.validate(value);
127
+
128
+ expect(Result.isOk(result)).toBe(true);
129
+ if (Result.isOk(result)) {
130
+ expect(result.value).toBeNull();
131
+ }
132
+ });
133
+
134
+ test('undefined is invalid', () => {
135
+ const value: unknown = undefined;
136
+ const result = nullType.validate(value);
137
+
138
+ expect(Result.isErr(result)).toBe(true);
139
+ if (Result.isErr(result)) {
140
+ expect(result.value).toStrictEqual([
141
+ {
142
+ path: [],
143
+ actualValue: undefined,
144
+ expectedType: 'null',
145
+ typeName: 'null',
146
+ message: undefined,
147
+ },
148
+ ]);
149
+ }
150
+ });
151
+
152
+ test('other values are invalid', () => {
153
+ const value: unknown = 0;
154
+ const result = nullType.validate(value);
155
+
156
+ expect(Result.isErr(result)).toBe(true);
157
+ if (Result.isErr(result)) {
158
+ expect(result.value).toStrictEqual([
159
+ {
160
+ path: [],
161
+ actualValue: 0,
162
+ expectedType: 'null',
163
+ typeName: 'null',
164
+ message: undefined,
165
+ },
166
+ ]);
167
+ }
168
+ });
169
+ });
170
+ });
@@ -0,0 +1,284 @@
1
+ import { expectType, Result } from 'ts-data-forge';
2
+ import { type TypeOf } from '../type.mjs';
3
+ import { number, numberLiteral } from './number.mjs';
4
+
5
+ describe('number', () => {
6
+ describe('default value', () => {
7
+ test('without explicit default value', () => {
8
+ const numDefault = number();
9
+ expect(numDefault.defaultValue).toBe(0);
10
+ });
11
+
12
+ test('with explicit default value', () => {
13
+ const numCustom = number(42);
14
+ expect(numCustom.defaultValue).toBe(42);
15
+ });
16
+ });
17
+
18
+ const num = number(0);
19
+ type Num = TypeOf<typeof num>;
20
+
21
+ expectType<Num, number>('=');
22
+ expectType<typeof num.defaultValue, Num>('=');
23
+
24
+ describe('is', () => {
25
+ test('truthy cases', () => {
26
+ const values = [
27
+ 0,
28
+ 42,
29
+ -42,
30
+ 3.14,
31
+ -3.14,
32
+ Number.POSITIVE_INFINITY,
33
+ Number.NEGATIVE_INFINITY,
34
+ Number.NaN,
35
+ ];
36
+
37
+ for (const value of values) {
38
+ const v: unknown = value;
39
+
40
+ if (num.is(v)) {
41
+ expectType<typeof v, Num>('=');
42
+ } else {
43
+ expectType<typeof v, unknown>('=');
44
+ }
45
+
46
+ expect(num.is(v)).toBe(true);
47
+ }
48
+ });
49
+
50
+ test('falsy cases', () => {
51
+ const values = [
52
+ '42',
53
+ '0',
54
+ true,
55
+ false,
56
+ null,
57
+ undefined,
58
+ {},
59
+ [],
60
+ Symbol('test'),
61
+ ];
62
+
63
+ for (const value of values) {
64
+ const v: unknown = value;
65
+
66
+ if (num.is(v)) {
67
+ expectType<typeof v, Num>('=');
68
+ } else {
69
+ expectType<typeof v, unknown>('=');
70
+ }
71
+
72
+ expect(num.is(v)).toBe(false);
73
+ }
74
+ });
75
+ });
76
+
77
+ describe('assertIs', () => {
78
+ const assertIs: (a: unknown) => asserts a is number = num.assertIs;
79
+
80
+ test('valid number', () => {
81
+ const value: unknown = 42;
82
+ expect(() => {
83
+ assertIs(value);
84
+ }).not.toThrow();
85
+ });
86
+
87
+ test('invalid value throws', () => {
88
+ const value: unknown = '42';
89
+ expect(() => {
90
+ assertIs(value);
91
+ }).toThrow(/Expected number/u);
92
+ });
93
+ });
94
+
95
+ describe('cast', () => {
96
+ test('valid number returns as is', () => {
97
+ const value: unknown = 42;
98
+ const result = num.cast(value);
99
+ expect(result).toBe(42);
100
+ });
101
+
102
+ test('invalid value throws error', () => {
103
+ const value: unknown = 'not a number';
104
+ expect(() => num.cast(value)).toThrow('Expected number, got string');
105
+ });
106
+
107
+ test('throws error with type mismatch', () => {
108
+ const numWithDefault = number(100);
109
+ const value: unknown = 'not a number';
110
+ expect(() => numWithDefault.cast(value)).toThrow(
111
+ 'Expected number, got string',
112
+ );
113
+ });
114
+ });
115
+
116
+ describe('fill', () => {
117
+ test('valid number returns as is', () => {
118
+ const value: unknown = 42;
119
+ const result = num.fill(value);
120
+ expect(result).toBe(42);
121
+ });
122
+
123
+ test('undefined returns default', () => {
124
+ const value: unknown = undefined;
125
+ const result = num.fill(value);
126
+ expect(result).toBe(0);
127
+ });
128
+
129
+ test('null returns default', () => {
130
+ const value: unknown = null;
131
+ const result = num.fill(value);
132
+ expect(result).toBe(0);
133
+ });
134
+
135
+ test('invalid value returns default', () => {
136
+ const value: unknown = 'not a number';
137
+ const result = num.fill(value);
138
+ expect(result).toBe(0);
139
+ });
140
+
141
+ test('uses custom default value for invalid', () => {
142
+ const numWithDefault = number(100);
143
+ const value: unknown = 'not a number';
144
+ const result = numWithDefault.fill(value);
145
+ expect(result).toBe(100);
146
+ });
147
+ });
148
+
149
+ describe('validate', () => {
150
+ test('valid number', () => {
151
+ const value: unknown = 42;
152
+ const result = num.validate(value);
153
+
154
+ expect(Result.isOk(result)).toBe(true);
155
+ if (Result.isOk(result)) {
156
+ expect(result.value).toBe(42);
157
+ }
158
+ });
159
+
160
+ test('invalid value', () => {
161
+ const value: unknown = 'not a number';
162
+ const result = num.validate(value);
163
+
164
+ expect(Result.isErr(result)).toBe(true);
165
+ if (Result.isErr(result)) {
166
+ expect(result.value).toStrictEqual([
167
+ {
168
+ path: [],
169
+ actualValue: 'not a number',
170
+ expectedType: 'number',
171
+ typeName: 'number',
172
+ message: undefined,
173
+ },
174
+ ]);
175
+ }
176
+ });
177
+ });
178
+ });
179
+
180
+ describe('numberLiteral', () => {
181
+ const literal42 = numberLiteral(42);
182
+ type Literal42 = TypeOf<typeof literal42>;
183
+
184
+ expectType<Literal42, 42>('=');
185
+ expectType<typeof literal42.defaultValue, Literal42>('=');
186
+
187
+ describe('default value', () => {
188
+ test('uses literal as default', () => {
189
+ expect(literal42.defaultValue).toBe(42);
190
+ });
191
+ });
192
+
193
+ describe('is', () => {
194
+ test('exact match returns true', () => {
195
+ const value: unknown = 42;
196
+
197
+ if (literal42.is(value)) {
198
+ expectType<typeof value, Literal42>('=');
199
+ } else {
200
+ expectType<typeof value, unknown>('=');
201
+ }
202
+
203
+ expect(literal42.is(value)).toBe(true);
204
+ });
205
+
206
+ test('different number returns false', () => {
207
+ const value: unknown = 43;
208
+
209
+ if (literal42.is(value)) {
210
+ expectType<typeof value, Literal42>('=');
211
+ } else {
212
+ expectType<typeof value, unknown>('=');
213
+ }
214
+
215
+ expect(literal42.is(value)).toBe(false);
216
+ });
217
+
218
+ test('non-number returns false', () => {
219
+ const value: unknown = '42';
220
+
221
+ expect(literal42.is(value)).toBe(false);
222
+ });
223
+ });
224
+
225
+ describe('special number literals', () => {
226
+ test('zero literal', () => {
227
+ const zero = numberLiteral(0);
228
+ type Zero = TypeOf<typeof zero>;
229
+ expectType<Zero, 0>('=');
230
+
231
+ expect(zero.is(0)).toBe(true);
232
+ expect(zero.is(-0)).toBe(true); // JavaScript treats 0 and -0 as equal
233
+ expect(zero.is(1)).toBe(false);
234
+ });
235
+
236
+ test('negative literal', () => {
237
+ const negative = numberLiteral(-100);
238
+ type Negative = TypeOf<typeof negative>;
239
+ expectType<Negative, -100>('=');
240
+
241
+ expect(negative.is(-100)).toBe(true);
242
+ expect(negative.is(100)).toBe(false);
243
+ });
244
+
245
+ test('decimal literal', () => {
246
+ const pi = numberLiteral(3.14);
247
+ type Pi = TypeOf<typeof pi>;
248
+ expectType<Pi, 3.14>('=');
249
+
250
+ expect(pi.is(3.14)).toBe(true);
251
+ expect(pi.is(3.141)).toBe(false);
252
+ });
253
+ });
254
+
255
+ describe('validate', () => {
256
+ test('exact match', () => {
257
+ const value: unknown = 42;
258
+ const result = literal42.validate(value);
259
+
260
+ expect(Result.isOk(result)).toBe(true);
261
+ if (Result.isOk(result)) {
262
+ expect(result.value).toBe(42);
263
+ }
264
+ });
265
+
266
+ test('different value', () => {
267
+ const value: unknown = 43;
268
+ const result = literal42.validate(value);
269
+
270
+ expect(Result.isErr(result)).toBe(true);
271
+ if (Result.isErr(result)) {
272
+ expect(result.value).toStrictEqual([
273
+ {
274
+ path: [],
275
+ actualValue: 43,
276
+ expectedType: 'numberLiteral(42)',
277
+ typeName: 'numberLiteral(42)',
278
+ message: undefined,
279
+ },
280
+ ]);
281
+ }
282
+ });
283
+ });
284
+ });