@tldraw/validate 5.1.0 → 5.2.0-canary.05c017c18b15

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.
@@ -1,230 +0,0 @@
1
- import * as T from '../lib/validation'
2
- import { ValidationError } from '../lib/validation'
3
-
4
- describe('validations', () => {
5
- it('Returns referentially identical objects', () => {
6
- const validator = T.object({
7
- name: T.string,
8
- items: T.arrayOf(T.string.nullable()),
9
- })
10
-
11
- const value = {
12
- name: 'toad',
13
- items: ['toad', 'berd', null, 'bot'],
14
- }
15
-
16
- expect(validator.validate(value)).toStrictEqual(value)
17
- })
18
- it('Rejects unknown object keys', () => {
19
- expect(() =>
20
- T.object({ moo: T.literal('cow') }).validate({ moo: 'cow', cow: 'moo' })
21
- ).toThrowErrorMatchingInlineSnapshot(`[ValidationError: At cow: Unexpected property]`)
22
- })
23
- it('Produces nice error messages', () => {
24
- expect(() =>
25
- T.object({
26
- toad: T.object({
27
- name: T.number,
28
- friends: T.arrayOf(
29
- T.object({
30
- name: T.string,
31
- })
32
- ),
33
- }),
34
- }).validate({
35
- toad: {
36
- name: 'toad',
37
- friends: [
38
- {
39
- name: 'bird',
40
- },
41
-
42
- {
43
- name: 1235,
44
- },
45
- ],
46
- },
47
- })
48
- ).toThrowErrorMatchingInlineSnapshot(
49
- `[ValidationError: At toad.name: Expected number, got a string]`
50
- )
51
-
52
- expect(() =>
53
- T.model(
54
- 'shape',
55
- T.object({
56
- id: T.string,
57
- x: T.number,
58
- y: T.number,
59
- })
60
- ).validate({
61
- id: 'abc123',
62
- x: 132,
63
- y: NaN,
64
- })
65
- ).toThrowErrorMatchingInlineSnapshot(
66
- `[ValidationError: At shape.y: Expected a number, got NaN]`
67
- )
68
-
69
- expect(() =>
70
- T.model(
71
- 'shape',
72
- T.object({
73
- id: T.string,
74
- color: T.setEnum(new Set(['red', 'green', 'blue'])),
75
- })
76
- ).validate({ id: 'abc13', color: 'rubbish' })
77
- ).toThrowErrorMatchingInlineSnapshot(
78
- `[ValidationError: At shape.color: Expected "red" or "green" or "blue", got rubbish]`
79
- )
80
- })
81
-
82
- it('Understands unions & produces nice error messages', () => {
83
- const catSchema = T.object({
84
- type: T.literal('cat'),
85
- id: T.string,
86
- meow: T.boolean,
87
- })
88
- const dogSchema = T.object({
89
- type: T.literal('dog'),
90
- id: T.string,
91
- bark: T.boolean,
92
- })
93
- const animalSchema = T.union('type', {
94
- cat: catSchema,
95
- dog: dogSchema,
96
- })
97
-
98
- const nested = T.object({
99
- animal: animalSchema,
100
- })
101
-
102
- expect(() =>
103
- nested.validate({ animal: { type: 'cow', moo: true, id: 'abc123' } })
104
- ).toThrowErrorMatchingInlineSnapshot(
105
- `[ValidationError: At animal.type: Expected one of "cat" or "dog", got "cow"]`
106
- )
107
-
108
- expect(() =>
109
- nested.validate({ animal: { type: 'cat', meow: 'yes', id: 'abc123' } })
110
- ).toThrowErrorMatchingInlineSnapshot(
111
- `[ValidationError: At animal(type = cat).meow: Expected boolean, got a string]`
112
- )
113
-
114
- expect(() =>
115
- T.model('animal', animalSchema).validate({ type: 'cat', moo: true, id: 'abc123' })
116
- ).toThrowErrorMatchingInlineSnapshot(
117
- `[ValidationError: At animal(type = cat).meow: Expected boolean, got undefined]`
118
- )
119
- })
120
-
121
- it('Rejects Infinity and -Infinity in numberUnion discriminators', () => {
122
- const numberUnionSchema = T.numberUnion('version', {
123
- 1: T.object({ version: T.literal(1), data: T.string }),
124
- 2: T.object({ version: T.literal(2), data: T.string }),
125
- })
126
-
127
- // Valid cases
128
- expect(numberUnionSchema.validate({ version: 1, data: 'hello' })).toEqual({
129
- version: 1,
130
- data: 'hello',
131
- })
132
- expect(numberUnionSchema.validate({ version: 2, data: 'world' })).toEqual({
133
- version: 2,
134
- data: 'world',
135
- })
136
-
137
- // Should reject Infinity
138
- expect(() =>
139
- numberUnionSchema.validate({ version: Infinity, data: 'test' })
140
- ).toThrowErrorMatchingInlineSnapshot(
141
- `[ValidationError: At null: Expected a number for key "version", got "Infinity"]`
142
- )
143
-
144
- // Should reject -Infinity
145
- expect(() =>
146
- numberUnionSchema.validate({ version: -Infinity, data: 'test' })
147
- ).toThrowErrorMatchingInlineSnapshot(
148
- `[ValidationError: At null: Expected a number for key "version", got "-Infinity"]`
149
- )
150
-
151
- // Should reject NaN
152
- expect(() => numberUnionSchema.validate({ version: NaN, data: 'test' })).toThrowError(
153
- /Expected a number for key "version"/
154
- )
155
- })
156
- })
157
-
158
- describe('T.refine', () => {
159
- it('Refines a validator.', () => {
160
- // refine can transform values (e.g., string to number)
161
- const stringToNumber = T.string.refine((str) => parseInt(str, 10))
162
- expect(stringToNumber.validate('42')).toBe(42)
163
-
164
- // refine can also modify values of the same type
165
- const prefixedString = T.string.refine((str) =>
166
- str.startsWith('prefix:') ? str : `prefix:${str}`
167
- )
168
- expect(prefixedString.validate('test')).toBe('prefix:test')
169
- expect(prefixedString.validate('prefix:existing')).toBe('prefix:existing')
170
- })
171
-
172
- it('Produces a type error if the refinement is not of the correct type.', () => {
173
- const stringToNumber = T.string.refine((str) => {
174
- const num = parseInt(str, 10)
175
- if (isNaN(num)) {
176
- throw new ValidationError('Invalid number format')
177
- }
178
- return num
179
- })
180
-
181
- expect(() => stringToNumber.validate('not-a-number')).toThrowErrorMatchingInlineSnapshot(
182
- `[ValidationError: At null: Invalid number format]`
183
- )
184
- })
185
- })
186
-
187
- describe('T.check', () => {
188
- it('Adds a check to a validator.', () => {
189
- const evenNumber = T.number.check((value) => {
190
- if (value % 2 !== 0) {
191
- throw new ValidationError('Expected even number')
192
- }
193
- })
194
-
195
- expect(evenNumber.validate(4)).toBe(4)
196
- expect(evenNumber.validate(0)).toBe(0)
197
- expect(() => evenNumber.validate(3)).toThrowErrorMatchingInlineSnapshot(
198
- `[ValidationError: At null: Expected even number]`
199
- )
200
-
201
- const namedCheck = T.number.check('positive', (value) => {
202
- if (value <= 0) {
203
- throw new ValidationError('Must be positive')
204
- }
205
- })
206
-
207
- expect(namedCheck.validate(5)).toBe(5)
208
- expect(() => namedCheck.validate(-1)).toThrowErrorMatchingInlineSnapshot(
209
- `[ValidationError: At (check positive): Must be positive]`
210
- )
211
- })
212
- })
213
-
214
- describe('T.indexKey', () => {
215
- it('validates index keys', () => {
216
- expect(T.indexKey.validate('a0')).toBe('a0')
217
- expect(T.indexKey.validate('a1J')).toBe('a1J')
218
- })
219
- it('rejects invalid index keys', () => {
220
- expect(() => T.indexKey.validate('a')).toThrowErrorMatchingInlineSnapshot(
221
- `[ValidationError: At null: Expected an index key, got "a"]`
222
- )
223
- expect(() => T.indexKey.validate('a00')).toThrowErrorMatchingInlineSnapshot(
224
- `[ValidationError: At null: Expected an index key, got "a00"]`
225
- )
226
- expect(() => T.indexKey.validate('')).toThrowErrorMatchingInlineSnapshot(
227
- `[ValidationError: At null: Expected an index key, got ""]`
228
- )
229
- })
230
- })