@sanity/validation 2.30.1-purple-unicorn.964 → 3.0.0-dev-preview.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.
- package/lib/dts/src/Rule.js +317 -312
- package/lib/dts/src/ValidationError.js +16 -16
- package/lib/dts/src/index.js +7 -7
- package/lib/dts/src/inferFromSchema.js +11 -11
- package/lib/dts/src/inferFromSchemaType.js +24 -24
- package/lib/dts/src/util/convertToValidationMarker.js +44 -53
- package/lib/dts/src/util/deepEquals.js +50 -49
- package/lib/dts/src/util/escapeRegex.js +4 -4
- package/lib/dts/src/util/normalizeValidationRules.js +87 -78
- package/lib/dts/src/util/normalizeValidationRules.test.d.ts +2 -2
- package/lib/dts/src/util/normalizeValidationRules.test.js +149 -149
- package/lib/dts/src/util/pathToString.js +16 -16
- package/lib/dts/src/util/typeString.js +17 -14
- package/lib/dts/src/util/typeString.test.d.ts +2 -2
- package/lib/dts/src/util/typeString.test.js +23 -24
- package/lib/dts/src/validateDocument.js +132 -157
- package/lib/dts/src/validateDocument.test.d.ts +2 -2
- package/lib/dts/src/validateDocument.test.js +664 -678
- package/lib/dts/src/validators/arrayValidator.js +75 -75
- package/lib/dts/src/validators/booleanValidator.js +11 -11
- package/lib/dts/src/validators/dateValidator.js +74 -68
- package/lib/dts/src/validators/genericValidator.js +89 -87
- package/lib/dts/src/validators/numberValidator.js +46 -48
- package/lib/dts/src/validators/objectValidator.js +47 -47
- package/lib/dts/src/validators/slugValidator.js +68 -74
- package/lib/dts/src/validators/stringValidator.js +93 -93
- package/lib/dts/test/array.test.d.ts +2 -2
- package/lib/dts/test/array.test.js +54 -78
- package/lib/dts/test/children.test.d.ts +2 -2
- package/lib/dts/test/children.test.js +69 -87
- package/lib/dts/test/createSchema.d.ts +6 -3
- package/lib/dts/test/createSchema.js +17 -17
- package/lib/dts/test/generics.test.d.ts +2 -2
- package/lib/dts/test/generics.test.js +59 -59
- package/lib/dts/test/infer.test.d.ts +2 -2
- package/lib/dts/test/infer.test.js +236 -285
- package/lib/dts/test/mocks/mockSanityClient.d.ts +4 -5
- package/lib/dts/test/mocks/mockSanityClient.d.ts.map +1 -1
- package/lib/dts/test/mocks/mockSanityClient.js +6 -6
- package/lib/dts/test/nullExport.d.ts +3 -3
- package/lib/dts/test/nullExport.js +2 -2
- package/lib/dts/test/numbers.test.d.ts +2 -2
- package/lib/dts/test/numbers.test.js +56 -62
- package/lib/dts/test/strings.test.d.ts +2 -2
- package/lib/dts/test/strings.test.js +99 -150
- package/lib/dts/tsconfig.lib.tsbuildinfo +1 -1
- package/lib/dts/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -1,717 +1,703 @@
|
|
|
1
|
-
import {createSchema} from '../test/createSchema'
|
|
2
|
-
import {createMockSanityClient} from '../test/mocks/mockSanityClient'
|
|
3
|
-
import validateDocument, {resolveTypeForArrayItem, validateItem} from './validateDocument'
|
|
4
|
-
import {convertToValidationMarker} from './util/convertToValidationMarker'
|
|
1
|
+
import { createSchema } from '../test/createSchema';
|
|
2
|
+
import { createMockSanityClient } from '../test/mocks/mockSanityClient';
|
|
3
|
+
import validateDocument, { resolveTypeForArrayItem, validateItem } from './validateDocument';
|
|
4
|
+
import { convertToValidationMarker } from './util/convertToValidationMarker';
|
|
5
5
|
jest.mock('./util/convertToValidationMarker', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
})
|
|
6
|
+
return {
|
|
7
|
+
convertToValidationMarker: jest.fn(jest.requireActual('./util/convertToValidationMarker').convertToValidationMarker),
|
|
8
|
+
};
|
|
9
|
+
});
|
|
12
10
|
beforeEach(() => {
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
;
|
|
12
|
+
convertToValidationMarker.mockClear();
|
|
13
|
+
});
|
|
15
14
|
// mock client
|
|
16
|
-
const client = createMockSanityClient()
|
|
15
|
+
const client = createMockSanityClient();
|
|
17
16
|
describe('resolveTypeForArrayItem', () => {
|
|
18
|
-
const schema = createSchema({
|
|
19
|
-
types: [
|
|
20
|
-
{
|
|
21
|
-
name: 'foo',
|
|
22
|
-
type: 'object',
|
|
23
|
-
fields: [{name: 'title', type: 'number'}],
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
name: 'bar',
|
|
27
|
-
type: 'object',
|
|
28
|
-
fields: [{name: 'title', type: 'string'}],
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
})
|
|
32
|
-
const fooType = schema.get('foo')
|
|
33
|
-
const barType = schema.get('bar')
|
|
34
|
-
it('finds a matching schema type for an array item value given a list of candidate types', () => {
|
|
35
|
-
const resolved = resolveTypeForArrayItem(
|
|
36
|
-
{
|
|
37
|
-
_type: 'bar',
|
|
38
|
-
_key: 'exampleKey',
|
|
39
|
-
title: 5,
|
|
40
|
-
},
|
|
41
|
-
[fooType, barType]
|
|
42
|
-
)
|
|
43
|
-
expect(resolved).toBe(barType)
|
|
44
|
-
})
|
|
45
|
-
it('assumes the type if there is only one possible candidate', () => {
|
|
46
|
-
const resolved = resolveTypeForArrayItem(
|
|
47
|
-
{
|
|
48
|
-
// notice no _type
|
|
49
|
-
_key: 'exampleKey',
|
|
50
|
-
title: 5,
|
|
51
|
-
},
|
|
52
|
-
[fooType]
|
|
53
|
-
)
|
|
54
|
-
expect(resolved).toBe(fooType)
|
|
55
|
-
})
|
|
56
|
-
})
|
|
57
|
-
describe('validateDocument', () => {
|
|
58
|
-
it('takes in a document + a compiled schema and returns a list of validation markers', async () => {
|
|
59
17
|
const schema = createSchema({
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
18
|
+
types: [
|
|
19
|
+
{
|
|
20
|
+
name: 'foo',
|
|
21
|
+
type: 'object',
|
|
22
|
+
fields: [{ name: 'title', type: 'number' }],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'bar',
|
|
26
|
+
type: 'object',
|
|
27
|
+
fields: [{ name: 'title', type: 'string' }],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
const fooType = schema.get('foo');
|
|
32
|
+
const barType = schema.get('bar');
|
|
33
|
+
it('finds a matching schema type for an array item value given a list of candidate types', () => {
|
|
34
|
+
const resolved = resolveTypeForArrayItem({
|
|
35
|
+
_type: 'bar',
|
|
36
|
+
_key: 'exampleKey',
|
|
37
|
+
title: 5,
|
|
38
|
+
}, [fooType, barType]);
|
|
39
|
+
expect(resolved).toBe(barType);
|
|
40
|
+
});
|
|
41
|
+
it('assumes the type if there is only one possible candidate', () => {
|
|
42
|
+
const resolved = resolveTypeForArrayItem({
|
|
43
|
+
// notice no _type
|
|
44
|
+
_key: 'exampleKey',
|
|
45
|
+
title: 5,
|
|
46
|
+
}, [fooType]);
|
|
47
|
+
expect(resolved).toBe(fooType);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe('validateDocument', () => {
|
|
51
|
+
it('takes in a document + a compiled schema and returns a list of validation markers', async () => {
|
|
52
|
+
const schema = createSchema({
|
|
53
|
+
types: [
|
|
54
|
+
{
|
|
55
|
+
name: 'simpleDoc',
|
|
56
|
+
type: 'document',
|
|
57
|
+
title: 'Simple Document',
|
|
58
|
+
fields: [
|
|
59
|
+
{
|
|
60
|
+
name: 'title',
|
|
61
|
+
type: 'string',
|
|
62
|
+
validation: (rule) => rule.required(),
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
const document = {
|
|
69
|
+
_id: 'testId',
|
|
70
|
+
_createdAt: '2021-08-27T14:48:51.650Z',
|
|
71
|
+
_rev: 'exampleRev',
|
|
72
|
+
_type: 'simpleDoc',
|
|
73
|
+
_updatedAt: '2021-08-27T14:48:51.650Z',
|
|
74
|
+
title: null,
|
|
75
|
+
};
|
|
76
|
+
const result = await validateDocument(client, document, schema);
|
|
77
|
+
expect(result).toMatchObject([
|
|
78
|
+
{
|
|
79
|
+
level: 'error',
|
|
80
|
+
item: {
|
|
81
|
+
message: 'Expected type "String", got "null"',
|
|
82
|
+
paths: [],
|
|
83
|
+
},
|
|
84
|
+
path: ['title'],
|
|
85
|
+
},
|
|
66
86
|
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
level: 'error',
|
|
88
|
+
item: {
|
|
89
|
+
message: 'Required',
|
|
90
|
+
paths: [],
|
|
91
|
+
},
|
|
92
|
+
path: ['title'],
|
|
70
93
|
},
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
})
|
|
75
|
-
const document = {
|
|
76
|
-
_id: 'testId',
|
|
77
|
-
_createdAt: '2021-08-27T14:48:51.650Z',
|
|
78
|
-
_rev: 'exampleRev',
|
|
79
|
-
_type: 'simpleDoc',
|
|
80
|
-
_updatedAt: '2021-08-27T14:48:51.650Z',
|
|
81
|
-
title: null,
|
|
82
|
-
}
|
|
83
|
-
const result = await validateDocument(client, document, schema)
|
|
84
|
-
expect(result).toMatchObject([
|
|
85
|
-
{
|
|
86
|
-
level: 'error',
|
|
87
|
-
item: {
|
|
88
|
-
message: 'Expected type "String", got "null"',
|
|
89
|
-
paths: [],
|
|
90
|
-
},
|
|
91
|
-
path: ['title'],
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
level: 'error',
|
|
95
|
-
item: {
|
|
96
|
-
message: 'Required',
|
|
97
|
-
paths: [],
|
|
98
|
-
},
|
|
99
|
-
path: ['title'],
|
|
100
|
-
},
|
|
101
|
-
])
|
|
102
|
-
})
|
|
103
|
-
})
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
104
97
|
describe('validateItem', () => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
98
|
+
it("runs nested validation on an undefined value for object types if it's required", async () => {
|
|
99
|
+
const validation = (rule) => [
|
|
100
|
+
rule.required().error('This is required!'),
|
|
101
|
+
rule.max(160).warning('Too long!'),
|
|
102
|
+
];
|
|
103
|
+
const schema = createSchema({
|
|
104
|
+
types: [
|
|
105
|
+
{
|
|
106
|
+
name: 'testObj',
|
|
107
|
+
type: 'object',
|
|
108
|
+
title: 'Test Object',
|
|
109
|
+
fields: [
|
|
110
|
+
{ name: 'registeredString', type: 'registeredString' },
|
|
111
|
+
{ name: 'inlineString', type: 'string', validation },
|
|
112
|
+
{
|
|
113
|
+
name: 'registeredObject',
|
|
114
|
+
type: 'registeredObjectField',
|
|
115
|
+
validation: (rule) => rule.required(),
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'inlineObject',
|
|
119
|
+
type: 'object',
|
|
120
|
+
fields: [{ name: 'foo', type: 'string', validation }],
|
|
121
|
+
validation: (rule) => rule.required(),
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'notRequiredRegisteredObject',
|
|
125
|
+
type: 'registeredObjectField',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'notRequiredInlineObject',
|
|
129
|
+
type: 'object',
|
|
130
|
+
fields: [{ name: 'foo', type: 'string', validation }],
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
{ name: 'registeredString', title: 'Registered String', type: 'string', validation },
|
|
135
|
+
{
|
|
136
|
+
title: 'Registered Object Field',
|
|
137
|
+
name: 'registeredObjectField',
|
|
138
|
+
type: 'object',
|
|
139
|
+
fields: [{ name: 'foo', type: 'string', validation }],
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
// ensures there are no schema formatting issues
|
|
144
|
+
expect(schema._validation).toHaveLength(0);
|
|
145
|
+
await expect(validateItem({
|
|
146
|
+
client: client,
|
|
147
|
+
value: {},
|
|
148
|
+
document: undefined,
|
|
149
|
+
path: [],
|
|
150
|
+
parent: undefined,
|
|
151
|
+
type: schema.get('testObj'),
|
|
152
|
+
getDocumentExists: undefined,
|
|
153
|
+
})).resolves.toMatchObject([
|
|
119
154
|
{
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
155
|
+
level: 'error',
|
|
156
|
+
item: { message: 'This is required!' },
|
|
157
|
+
path: ['registeredString'],
|
|
123
158
|
},
|
|
124
159
|
{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
validation: (rule) => rule.required(),
|
|
160
|
+
level: 'error',
|
|
161
|
+
item: { message: 'This is required!' },
|
|
162
|
+
path: ['inlineString'],
|
|
129
163
|
},
|
|
130
164
|
{
|
|
131
|
-
|
|
132
|
-
|
|
165
|
+
level: 'error',
|
|
166
|
+
item: { message: 'Required' },
|
|
167
|
+
path: ['registeredObject'],
|
|
133
168
|
},
|
|
134
169
|
{
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
170
|
+
level: 'error',
|
|
171
|
+
item: { message: 'This is required!' },
|
|
172
|
+
path: ['registeredObject', 'foo'],
|
|
138
173
|
},
|
|
139
|
-
],
|
|
140
|
-
},
|
|
141
|
-
{name: 'registeredString', title: 'Registered String', type: 'string', validation},
|
|
142
|
-
{
|
|
143
|
-
title: 'Registered Object Field',
|
|
144
|
-
name: 'registeredObjectField',
|
|
145
|
-
type: 'object',
|
|
146
|
-
fields: [{name: 'foo', type: 'string', validation}],
|
|
147
|
-
},
|
|
148
|
-
],
|
|
149
|
-
})
|
|
150
|
-
// ensures there are no schema formatting issues
|
|
151
|
-
expect(schema._validation).toHaveLength(0)
|
|
152
|
-
await expect(
|
|
153
|
-
validateItem({
|
|
154
|
-
client: client,
|
|
155
|
-
value: {},
|
|
156
|
-
document: undefined,
|
|
157
|
-
path: [],
|
|
158
|
-
parent: undefined,
|
|
159
|
-
type: schema.get('testObj'),
|
|
160
|
-
getDocumentExists: undefined,
|
|
161
|
-
})
|
|
162
|
-
).resolves.toMatchObject([
|
|
163
|
-
{
|
|
164
|
-
level: 'error',
|
|
165
|
-
item: {message: 'This is required!'},
|
|
166
|
-
path: ['registeredString'],
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
level: 'error',
|
|
170
|
-
item: {message: 'This is required!'},
|
|
171
|
-
path: ['inlineString'],
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
level: 'error',
|
|
175
|
-
item: {message: 'Required'},
|
|
176
|
-
path: ['registeredObject'],
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
level: 'error',
|
|
180
|
-
item: {message: 'This is required!'},
|
|
181
|
-
path: ['registeredObject', 'foo'],
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
level: 'error',
|
|
185
|
-
item: {message: 'Required'},
|
|
186
|
-
path: ['inlineObject'],
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
level: 'error',
|
|
190
|
-
item: {message: 'This is required!'},
|
|
191
|
-
path: ['inlineObject', 'foo'],
|
|
192
|
-
},
|
|
193
|
-
])
|
|
194
|
-
})
|
|
195
|
-
it('runs nested validation for object-level rules set via Rule.fields()', async () => {
|
|
196
|
-
const schema = createSchema({
|
|
197
|
-
types: [
|
|
198
|
-
{
|
|
199
|
-
name: 'testObj',
|
|
200
|
-
type: 'object',
|
|
201
|
-
title: 'Test Object',
|
|
202
|
-
fields: [
|
|
203
|
-
{name: 'foo', type: 'string'},
|
|
204
|
-
{name: 'bar', type: 'string'},
|
|
205
|
-
],
|
|
206
|
-
validation: (rule) => [
|
|
207
|
-
rule.required(),
|
|
208
|
-
rule.fields({
|
|
209
|
-
foo: (r) => r.required(),
|
|
210
|
-
bar: (r) => r.required(),
|
|
211
|
-
}),
|
|
212
|
-
],
|
|
213
|
-
},
|
|
214
|
-
],
|
|
215
|
-
})
|
|
216
|
-
// ensures there are no schema formatting issues
|
|
217
|
-
expect(schema._validation).toHaveLength(0)
|
|
218
|
-
await expect(
|
|
219
|
-
validateItem({
|
|
220
|
-
client: client,
|
|
221
|
-
document: undefined,
|
|
222
|
-
parent: undefined,
|
|
223
|
-
path: undefined,
|
|
224
|
-
type: schema.get('testObj'),
|
|
225
|
-
value: {foo: 5},
|
|
226
|
-
getDocumentExists: undefined,
|
|
227
|
-
})
|
|
228
|
-
).resolves.toMatchObject([
|
|
229
|
-
{
|
|
230
|
-
item: {message: 'Expected type "String", got "Number"'},
|
|
231
|
-
level: 'error',
|
|
232
|
-
path: ['foo'],
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
item: {message: 'Required'},
|
|
236
|
-
level: 'error',
|
|
237
|
-
path: ['bar'],
|
|
238
|
-
},
|
|
239
|
-
])
|
|
240
|
-
})
|
|
241
|
-
it('runs nested validation for markDefs', async () => {
|
|
242
|
-
const linkValidationSpy = jest.fn(() => true)
|
|
243
|
-
const internalLinkSpy = jest.fn(() => 'mock invalid response')
|
|
244
|
-
const schema = createSchema({
|
|
245
|
-
types: [
|
|
246
|
-
{
|
|
247
|
-
name: 'post',
|
|
248
|
-
title: 'Post',
|
|
249
|
-
type: 'document',
|
|
250
|
-
fields: [
|
|
251
|
-
{name: 'title', type: 'string'},
|
|
252
|
-
{name: 'body', type: 'string'},
|
|
253
|
-
],
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
name: 'registeredEditor',
|
|
257
|
-
type: 'object',
|
|
258
|
-
title: 'Registered Editor',
|
|
259
|
-
fields: [
|
|
260
174
|
{
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
175
|
+
level: 'error',
|
|
176
|
+
item: { message: 'Required' },
|
|
177
|
+
path: ['inlineObject'],
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
level: 'error',
|
|
181
|
+
item: { message: 'This is required!' },
|
|
182
|
+
path: ['inlineObject', 'foo'],
|
|
183
|
+
},
|
|
184
|
+
]);
|
|
185
|
+
});
|
|
186
|
+
it('runs nested validation for object-level rules set via Rule.fields()', async () => {
|
|
187
|
+
const schema = createSchema({
|
|
188
|
+
types: [
|
|
264
189
|
{
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
name: '
|
|
270
|
-
type: '
|
|
271
|
-
|
|
272
|
-
|
|
190
|
+
name: 'testObj',
|
|
191
|
+
type: 'object',
|
|
192
|
+
title: 'Test Object',
|
|
193
|
+
fields: [
|
|
194
|
+
{ name: 'foo', type: 'string' },
|
|
195
|
+
{ name: 'bar', type: 'string' },
|
|
196
|
+
],
|
|
197
|
+
validation: (rule) => [
|
|
198
|
+
rule.required(),
|
|
199
|
+
rule.fields({
|
|
200
|
+
foo: (r) => r.required(),
|
|
201
|
+
bar: (r) => r.required(),
|
|
202
|
+
}),
|
|
273
203
|
],
|
|
274
|
-
},
|
|
275
204
|
},
|
|
276
|
-
|
|
205
|
+
],
|
|
206
|
+
});
|
|
207
|
+
// ensures there are no schema formatting issues
|
|
208
|
+
expect(schema._validation).toHaveLength(0);
|
|
209
|
+
await expect(validateItem({
|
|
210
|
+
client: client,
|
|
211
|
+
document: undefined,
|
|
212
|
+
parent: undefined,
|
|
213
|
+
path: undefined,
|
|
214
|
+
type: schema.get('testObj'),
|
|
215
|
+
value: { foo: 5 },
|
|
216
|
+
getDocumentExists: undefined,
|
|
217
|
+
})).resolves.toMatchObject([
|
|
218
|
+
{
|
|
219
|
+
item: { message: 'Expected type "String", got "Number"' },
|
|
220
|
+
level: 'error',
|
|
221
|
+
path: ['foo'],
|
|
277
222
|
},
|
|
278
|
-
],
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
name: 'blockTest',
|
|
282
|
-
type: 'document',
|
|
283
|
-
title: 'blockTest',
|
|
284
|
-
fields: [
|
|
285
223
|
{
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
224
|
+
item: { message: 'Required' },
|
|
225
|
+
level: 'error',
|
|
226
|
+
path: ['bar'],
|
|
227
|
+
},
|
|
228
|
+
]);
|
|
229
|
+
});
|
|
230
|
+
it('runs nested validation for markDefs', async () => {
|
|
231
|
+
const linkValidationSpy = jest.fn(() => true);
|
|
232
|
+
const internalLinkSpy = jest.fn(() => 'mock invalid response');
|
|
233
|
+
const schema = createSchema({
|
|
234
|
+
types: [
|
|
290
235
|
{
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
name: '
|
|
296
|
-
type: '
|
|
297
|
-
title: 'link',
|
|
298
|
-
fields: [{name: 'url', type: 'url'}],
|
|
299
|
-
validation: (rule) => rule.custom(linkValidationSpy),
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
name: 'internalLink',
|
|
303
|
-
type: 'object',
|
|
304
|
-
title: 'Internal link',
|
|
305
|
-
fields: [{name: 'reference', type: 'reference', to: [{type: 'post'}]}],
|
|
306
|
-
validation: (rule) => rule.custom(internalLinkSpy),
|
|
307
|
-
},
|
|
308
|
-
{name: 'nestedEditor', type: 'registeredEditor'},
|
|
236
|
+
name: 'post',
|
|
237
|
+
title: 'Post',
|
|
238
|
+
type: 'document',
|
|
239
|
+
fields: [
|
|
240
|
+
{ name: 'title', type: 'string' },
|
|
241
|
+
{ name: 'body', type: 'string' },
|
|
309
242
|
],
|
|
310
|
-
},
|
|
311
243
|
},
|
|
312
|
-
],
|
|
313
|
-
},
|
|
314
|
-
],
|
|
315
|
-
},
|
|
316
|
-
],
|
|
317
|
-
})
|
|
318
|
-
expect(schema._validation).toHaveLength(0)
|
|
319
|
-
const nestedBlock = {
|
|
320
|
-
_type: 'block',
|
|
321
|
-
_key: 'nested-block-key',
|
|
322
|
-
children: [
|
|
323
|
-
{
|
|
324
|
-
_key: 'some-key',
|
|
325
|
-
_type: 'span',
|
|
326
|
-
text: 'hey',
|
|
327
|
-
marks: ['example-annotation-key'],
|
|
328
|
-
},
|
|
329
|
-
],
|
|
330
|
-
markDefs: [
|
|
331
|
-
{
|
|
332
|
-
_type: 'exampleAnnotation',
|
|
333
|
-
_key: 'example-annotation-key',
|
|
334
|
-
value: 5,
|
|
335
|
-
},
|
|
336
|
-
],
|
|
337
|
-
style: 'normal',
|
|
338
|
-
}
|
|
339
|
-
const block = {
|
|
340
|
-
_key: 'block-key',
|
|
341
|
-
_type: 'block',
|
|
342
|
-
children: [
|
|
343
|
-
{
|
|
344
|
-
_key: 'child-0',
|
|
345
|
-
_type: 'span',
|
|
346
|
-
marks: ['0', '1', '2'],
|
|
347
|
-
text: 'hey',
|
|
348
|
-
},
|
|
349
|
-
],
|
|
350
|
-
markDefs: [
|
|
351
|
-
{
|
|
352
|
-
_key: '0',
|
|
353
|
-
_type: 'link',
|
|
354
|
-
url: 'https://example.com',
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
_key: '1',
|
|
358
|
-
_type: 'internalLink',
|
|
359
|
-
_ref: 'post-id',
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
_key: '2',
|
|
363
|
-
_type: 'nestedEditor',
|
|
364
|
-
editor: [nestedBlock],
|
|
365
|
-
},
|
|
366
|
-
],
|
|
367
|
-
style: 'normal',
|
|
368
|
-
}
|
|
369
|
-
const document = {
|
|
370
|
-
_id: 'mock-id',
|
|
371
|
-
_type: 'blockTest',
|
|
372
|
-
_createdAt: '2021-11-15T21:06:41.812Z',
|
|
373
|
-
_rev: 'example-ref',
|
|
374
|
-
_updatedAt: '2021-11-15T21:06:41.812Z',
|
|
375
|
-
content: [block],
|
|
376
|
-
}
|
|
377
|
-
const result = await validateItem({
|
|
378
|
-
client: client,
|
|
379
|
-
document: document,
|
|
380
|
-
parent: undefined,
|
|
381
|
-
path: [],
|
|
382
|
-
type: schema.get('blockTest'),
|
|
383
|
-
value: document,
|
|
384
|
-
getDocumentExists: undefined,
|
|
385
|
-
})
|
|
386
|
-
expect(result).toMatchObject([
|
|
387
|
-
{
|
|
388
|
-
item: {message: 'mock invalid response', paths: []},
|
|
389
|
-
level: 'error',
|
|
390
|
-
path: ['content', {_key: 'block-key'}, 'markDefs', {_key: '1'}],
|
|
391
|
-
},
|
|
392
|
-
// this tests for nested markDef validation
|
|
393
|
-
{
|
|
394
|
-
item: {
|
|
395
|
-
message: 'Expected type "String", got "Number"',
|
|
396
|
-
paths: [],
|
|
397
|
-
},
|
|
398
|
-
level: 'error',
|
|
399
|
-
path: [
|
|
400
|
-
'content',
|
|
401
|
-
{_key: 'block-key'},
|
|
402
|
-
'markDefs',
|
|
403
|
-
{_key: '2'},
|
|
404
|
-
'editor',
|
|
405
|
-
{_key: 'nested-block-key'},
|
|
406
|
-
'markDefs',
|
|
407
|
-
{_key: 'example-annotation-key'},
|
|
408
|
-
'value',
|
|
409
|
-
],
|
|
410
|
-
},
|
|
411
|
-
])
|
|
412
|
-
expect(linkValidationSpy.mock.calls).toMatchObject([
|
|
413
|
-
[
|
|
414
|
-
{
|
|
415
|
-
_key: '0',
|
|
416
|
-
_type: 'link',
|
|
417
|
-
url: 'https://example.com',
|
|
418
|
-
},
|
|
419
|
-
{
|
|
420
|
-
document: {_id: 'mock-id'},
|
|
421
|
-
parent: {_key: 'block-key'},
|
|
422
|
-
path: ['content', {_key: 'block-key'}, 'markDefs', {_key: '0'}],
|
|
423
|
-
type: {name: 'link'},
|
|
424
|
-
},
|
|
425
|
-
],
|
|
426
|
-
])
|
|
427
|
-
expect(internalLinkSpy.mock.calls).toMatchObject([
|
|
428
|
-
[
|
|
429
|
-
{
|
|
430
|
-
_key: '1',
|
|
431
|
-
_ref: 'post-id',
|
|
432
|
-
_type: 'internalLink',
|
|
433
|
-
},
|
|
434
|
-
{
|
|
435
|
-
document: {_id: 'mock-id'},
|
|
436
|
-
parent: {_key: 'block-key'},
|
|
437
|
-
path: ['content', {_key: 'block-key'}, 'markDefs', {_key: '1'}],
|
|
438
|
-
type: {name: 'internalLink'},
|
|
439
|
-
},
|
|
440
|
-
],
|
|
441
|
-
])
|
|
442
|
-
})
|
|
443
|
-
it('resolves an array item type if there is just one type', async () => {
|
|
444
|
-
const schema = createSchema({
|
|
445
|
-
types: [
|
|
446
|
-
{
|
|
447
|
-
name: 'values',
|
|
448
|
-
title: 'Values',
|
|
449
|
-
type: 'array',
|
|
450
|
-
// note that there is only one type available
|
|
451
|
-
of: [{type: 'arrayItem'}],
|
|
452
|
-
validation: (rule) => rule.required(),
|
|
453
|
-
},
|
|
454
|
-
{
|
|
455
|
-
title: 'Array Item',
|
|
456
|
-
name: 'arrayItem',
|
|
457
|
-
type: 'object',
|
|
458
|
-
fields: [{name: 'title', type: 'string'}],
|
|
459
|
-
},
|
|
460
|
-
],
|
|
461
|
-
})
|
|
462
|
-
// ensures there are no schema formatting issues
|
|
463
|
-
expect(schema._validation).toHaveLength(0)
|
|
464
|
-
const values = [
|
|
465
|
-
{
|
|
466
|
-
// note how this doesn't have a _type
|
|
467
|
-
title: 5,
|
|
468
|
-
_key: 'exampleKey',
|
|
469
|
-
},
|
|
470
|
-
]
|
|
471
|
-
await expect(
|
|
472
|
-
validateItem({
|
|
473
|
-
client: client,
|
|
474
|
-
document: undefined,
|
|
475
|
-
parent: undefined,
|
|
476
|
-
path: [],
|
|
477
|
-
type: schema.get('values'),
|
|
478
|
-
value: values,
|
|
479
|
-
getDocumentExists: undefined,
|
|
480
|
-
})
|
|
481
|
-
).resolves.toEqual([
|
|
482
|
-
{
|
|
483
|
-
level: 'error',
|
|
484
|
-
item: {
|
|
485
|
-
message: 'Expected type "String", got "Number"',
|
|
486
|
-
paths: [],
|
|
487
|
-
},
|
|
488
|
-
path: [{_key: 'exampleKey'}, 'title'],
|
|
489
|
-
},
|
|
490
|
-
])
|
|
491
|
-
})
|
|
492
|
-
it('properly passes the nested value, type, and path to rule.validate', async () => {
|
|
493
|
-
const schema = createSchema({
|
|
494
|
-
types: [
|
|
495
|
-
{
|
|
496
|
-
name: 'root',
|
|
497
|
-
type: 'object',
|
|
498
|
-
title: 'Root',
|
|
499
|
-
fields: [
|
|
500
|
-
{
|
|
501
|
-
name: 'level1Object',
|
|
502
|
-
type: 'object',
|
|
503
|
-
validation: (rule) => rule.custom(() => 'from level 1 object'),
|
|
504
|
-
fields: [
|
|
505
244
|
{
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
245
|
+
name: 'registeredEditor',
|
|
246
|
+
type: 'object',
|
|
247
|
+
title: 'Registered Editor',
|
|
248
|
+
fields: [
|
|
249
|
+
{
|
|
250
|
+
name: 'editor',
|
|
251
|
+
type: 'array',
|
|
252
|
+
of: [
|
|
253
|
+
{
|
|
254
|
+
type: 'block',
|
|
255
|
+
marks: {
|
|
256
|
+
annotations: [
|
|
257
|
+
{
|
|
258
|
+
name: 'exampleAnnotation',
|
|
259
|
+
type: 'object',
|
|
260
|
+
fields: [{ name: 'value', type: 'string' }],
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: 'blockTest',
|
|
271
|
+
type: 'document',
|
|
272
|
+
title: 'blockTest',
|
|
273
|
+
fields: [
|
|
274
|
+
{
|
|
275
|
+
name: 'content',
|
|
276
|
+
title: 'Content',
|
|
277
|
+
type: 'array',
|
|
278
|
+
of: [
|
|
279
|
+
{
|
|
280
|
+
type: 'block',
|
|
281
|
+
marks: {
|
|
282
|
+
annotations: [
|
|
283
|
+
{
|
|
284
|
+
name: 'link',
|
|
285
|
+
type: 'object',
|
|
286
|
+
title: 'link',
|
|
287
|
+
fields: [{ name: 'url', type: 'url' }],
|
|
288
|
+
validation: (rule) => rule.custom(linkValidationSpy),
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: 'internalLink',
|
|
292
|
+
type: 'object',
|
|
293
|
+
title: 'Internal link',
|
|
294
|
+
fields: [{ name: 'reference', type: 'reference', to: [{ type: 'post' }] }],
|
|
295
|
+
validation: (rule) => rule.custom(internalLinkSpy),
|
|
296
|
+
},
|
|
297
|
+
{ name: 'nestedEditor', type: 'registeredEditor' },
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
});
|
|
307
|
+
expect(schema._validation).toHaveLength(0);
|
|
308
|
+
const nestedBlock = {
|
|
309
|
+
_type: 'block',
|
|
310
|
+
_key: 'nested-block-key',
|
|
311
|
+
children: [
|
|
312
|
+
{
|
|
313
|
+
_key: 'some-key',
|
|
314
|
+
_type: 'span',
|
|
315
|
+
text: 'hey',
|
|
316
|
+
marks: ['example-annotation-key'],
|
|
509
317
|
},
|
|
510
|
-
|
|
318
|
+
],
|
|
319
|
+
markDefs: [
|
|
320
|
+
{
|
|
321
|
+
_type: 'exampleAnnotation',
|
|
322
|
+
_key: 'example-annotation-key',
|
|
323
|
+
value: 5,
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
style: 'normal',
|
|
327
|
+
};
|
|
328
|
+
const block = {
|
|
329
|
+
_key: 'block-key',
|
|
330
|
+
_type: 'block',
|
|
331
|
+
children: [
|
|
332
|
+
{
|
|
333
|
+
_key: 'child-0',
|
|
334
|
+
_type: 'span',
|
|
335
|
+
marks: ['0', '1', '2'],
|
|
336
|
+
text: 'hey',
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
markDefs: [
|
|
340
|
+
{
|
|
341
|
+
_key: '0',
|
|
342
|
+
_type: 'link',
|
|
343
|
+
url: 'https://example.com',
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
_key: '1',
|
|
347
|
+
_type: 'internalLink',
|
|
348
|
+
_ref: 'post-id',
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
_key: '2',
|
|
352
|
+
_type: 'nestedEditor',
|
|
353
|
+
editor: [nestedBlock],
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
style: 'normal',
|
|
357
|
+
};
|
|
358
|
+
const document = {
|
|
359
|
+
_id: 'mock-id',
|
|
360
|
+
_type: 'blockTest',
|
|
361
|
+
_createdAt: '2021-11-15T21:06:41.812Z',
|
|
362
|
+
_rev: 'example-ref',
|
|
363
|
+
_updatedAt: '2021-11-15T21:06:41.812Z',
|
|
364
|
+
content: [block],
|
|
365
|
+
};
|
|
366
|
+
const result = await validateItem({
|
|
367
|
+
client: client,
|
|
368
|
+
document: document,
|
|
369
|
+
parent: undefined,
|
|
370
|
+
path: [],
|
|
371
|
+
type: schema.get('blockTest'),
|
|
372
|
+
value: document,
|
|
373
|
+
getDocumentExists: undefined,
|
|
374
|
+
});
|
|
375
|
+
expect(result).toMatchObject([
|
|
376
|
+
{
|
|
377
|
+
item: { message: 'mock invalid response', paths: [] },
|
|
378
|
+
level: 'error',
|
|
379
|
+
path: ['content', { _key: 'block-key' }, 'markDefs', { _key: '1' }],
|
|
511
380
|
},
|
|
381
|
+
// this tests for nested markDef validation
|
|
512
382
|
{
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
383
|
+
item: {
|
|
384
|
+
message: 'Expected type "String", got "Number"',
|
|
385
|
+
paths: [],
|
|
386
|
+
},
|
|
387
|
+
level: 'error',
|
|
388
|
+
path: [
|
|
389
|
+
'content',
|
|
390
|
+
{ _key: 'block-key' },
|
|
391
|
+
'markDefs',
|
|
392
|
+
{ _key: '2' },
|
|
393
|
+
'editor',
|
|
394
|
+
{ _key: 'nested-block-key' },
|
|
395
|
+
'markDefs',
|
|
396
|
+
{ _key: 'example-annotation-key' },
|
|
397
|
+
'value',
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
]);
|
|
401
|
+
expect(linkValidationSpy.mock.calls).toMatchObject([
|
|
402
|
+
[
|
|
403
|
+
{
|
|
404
|
+
_key: '0',
|
|
405
|
+
_type: 'link',
|
|
406
|
+
url: 'https://example.com',
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
document: { _id: 'mock-id' },
|
|
410
|
+
parent: { _key: 'block-key' },
|
|
411
|
+
path: ['content', { _key: 'block-key' }, 'markDefs', { _key: '0' }],
|
|
412
|
+
type: { name: 'link' },
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
]);
|
|
416
|
+
expect(internalLinkSpy.mock.calls).toMatchObject([
|
|
417
|
+
[
|
|
418
|
+
{
|
|
419
|
+
_key: '1',
|
|
420
|
+
_ref: 'post-id',
|
|
421
|
+
_type: 'internalLink',
|
|
422
|
+
},
|
|
517
423
|
{
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
{
|
|
521
|
-
|
|
522
|
-
type: 'number',
|
|
523
|
-
validation: (rule) => rule.custom(() => 'from level 2 via array'),
|
|
524
|
-
},
|
|
525
|
-
],
|
|
424
|
+
document: { _id: 'mock-id' },
|
|
425
|
+
parent: { _key: 'block-key' },
|
|
426
|
+
path: ['content', { _key: 'block-key' }, 'markDefs', { _key: '1' }],
|
|
427
|
+
type: { name: 'internalLink' },
|
|
526
428
|
},
|
|
527
|
-
|
|
429
|
+
],
|
|
430
|
+
]);
|
|
431
|
+
});
|
|
432
|
+
it('resolves an array item type if there is just one type', async () => {
|
|
433
|
+
const schema = createSchema({
|
|
434
|
+
types: [
|
|
435
|
+
{
|
|
436
|
+
name: 'values',
|
|
437
|
+
title: 'Values',
|
|
438
|
+
type: 'array',
|
|
439
|
+
// note that there is only one type available
|
|
440
|
+
of: [{ type: 'arrayItem' }],
|
|
441
|
+
validation: (rule) => rule.required(),
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
title: 'Array Item',
|
|
445
|
+
name: 'arrayItem',
|
|
446
|
+
type: 'object',
|
|
447
|
+
fields: [{ name: 'title', type: 'string' }],
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
});
|
|
451
|
+
// ensures there are no schema formatting issues
|
|
452
|
+
expect(schema._validation).toHaveLength(0);
|
|
453
|
+
const values = [
|
|
454
|
+
{
|
|
455
|
+
// note how this doesn't have a _type
|
|
456
|
+
title: 5,
|
|
457
|
+
_key: 'exampleKey',
|
|
528
458
|
},
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
level1Array: [{level2Number: 5}],
|
|
540
|
-
}
|
|
541
|
-
const document = {
|
|
542
|
-
value,
|
|
543
|
-
_type: 'something',
|
|
544
|
-
_createdAt: '2021-09-05T19:28:30.882Z',
|
|
545
|
-
_id: 'something.id',
|
|
546
|
-
_rev: 'exampleRev',
|
|
547
|
-
_updatedAt: '2021-09-05T19:28:30.882Z',
|
|
548
|
-
}
|
|
549
|
-
const getField = ({in: type, name}) => {
|
|
550
|
-
const result = 'fields' in type && type.fields.find((f) => f.name === name)?.type
|
|
551
|
-
if (!result) throw new Error(`Could not find field \`${name}\` in \`${type.name}\``)
|
|
552
|
-
return result
|
|
553
|
-
}
|
|
554
|
-
const rootType = schema.get('root')
|
|
555
|
-
const level1ObjectType = getField({in: rootType, name: 'level1Object'})
|
|
556
|
-
const level2StringType = getField({in: level1ObjectType, name: 'level2String'})
|
|
557
|
-
const level1ArrayType = getField({in: rootType, name: 'level1Array'})
|
|
558
|
-
const level2NumberType = getField({
|
|
559
|
-
in: level1ArrayType.of[0],
|
|
560
|
-
name: 'level2Number',
|
|
561
|
-
})
|
|
562
|
-
await expect(
|
|
563
|
-
validateItem({
|
|
564
|
-
client: client,
|
|
565
|
-
value,
|
|
566
|
-
type: rootType,
|
|
567
|
-
document,
|
|
568
|
-
parent: document,
|
|
569
|
-
path: undefined,
|
|
570
|
-
getDocumentExists: undefined,
|
|
571
|
-
})
|
|
572
|
-
).resolves.toMatchObject([
|
|
573
|
-
{
|
|
574
|
-
item: {message: 'from root'},
|
|
575
|
-
path: [],
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
item: {message: 'from level 1 object'},
|
|
579
|
-
path: ['level1Object'],
|
|
580
|
-
},
|
|
581
|
-
{
|
|
582
|
-
item: {message: 'from level 2 via object'},
|
|
583
|
-
path: ['level1Object', 'level2String'],
|
|
584
|
-
},
|
|
585
|
-
{
|
|
586
|
-
item: {message: 'from level 1 array'},
|
|
587
|
-
path: ['level1Array'],
|
|
588
|
-
},
|
|
589
|
-
{
|
|
590
|
-
item: {message: 'from level 2 via array'},
|
|
591
|
-
path: ['level1Array', 0, 'level2Number'],
|
|
592
|
-
},
|
|
593
|
-
])
|
|
594
|
-
const calls = convertToValidationMarker.mock.calls
|
|
595
|
-
expect(calls.find((call) => call[0] === 'from root')).toMatchObject([
|
|
596
|
-
'from root',
|
|
597
|
-
'error',
|
|
598
|
-
{
|
|
599
|
-
parent: document,
|
|
600
|
-
document: document,
|
|
601
|
-
path: [],
|
|
602
|
-
type: rootType,
|
|
603
|
-
},
|
|
604
|
-
])
|
|
605
|
-
expect(calls.find((call) => call[0] === 'from level 1 object')).toMatchObject([
|
|
606
|
-
'from level 1 object',
|
|
607
|
-
'error',
|
|
608
|
-
{
|
|
609
|
-
parent: value,
|
|
610
|
-
document: document,
|
|
611
|
-
path: ['level1Object'],
|
|
612
|
-
type: level1ObjectType,
|
|
613
|
-
},
|
|
614
|
-
])
|
|
615
|
-
expect(calls.find((call) => call[0] === 'from level 2 via object')).toMatchObject([
|
|
616
|
-
'from level 2 via object',
|
|
617
|
-
'error',
|
|
618
|
-
{
|
|
619
|
-
parent: value.level1Object,
|
|
620
|
-
document: document,
|
|
621
|
-
path: ['level1Object', 'level2String'],
|
|
622
|
-
type: level2StringType,
|
|
623
|
-
},
|
|
624
|
-
])
|
|
625
|
-
expect(calls.find((call) => call[0] === 'from level 1 array')).toMatchObject([
|
|
626
|
-
'from level 1 array',
|
|
627
|
-
'error',
|
|
628
|
-
{
|
|
629
|
-
parent: value,
|
|
630
|
-
document: document,
|
|
631
|
-
path: ['level1Array'],
|
|
632
|
-
type: level1ArrayType,
|
|
633
|
-
},
|
|
634
|
-
])
|
|
635
|
-
expect(calls.find((call) => call[0] === 'from level 2 via array')).toMatchObject([
|
|
636
|
-
'from level 2 via array',
|
|
637
|
-
'error',
|
|
638
|
-
{
|
|
639
|
-
parent: value.level1Array[0],
|
|
640
|
-
document: document,
|
|
641
|
-
path: ['level1Array', 0, 'level2Number'],
|
|
642
|
-
type: level2NumberType,
|
|
643
|
-
},
|
|
644
|
-
])
|
|
645
|
-
})
|
|
646
|
-
it('runs all nested validation checks concurrently', async () => {
|
|
647
|
-
const resolvers = new Set()
|
|
648
|
-
const customValidationFn = () => new Promise((resolve) => resolvers.add(resolve))
|
|
649
|
-
const schema = createSchema({
|
|
650
|
-
types: [
|
|
651
|
-
{
|
|
652
|
-
name: 'root',
|
|
653
|
-
type: 'object',
|
|
654
|
-
fields: [
|
|
459
|
+
];
|
|
460
|
+
await expect(validateItem({
|
|
461
|
+
client: client,
|
|
462
|
+
document: undefined,
|
|
463
|
+
parent: undefined,
|
|
464
|
+
path: [],
|
|
465
|
+
type: schema.get('values'),
|
|
466
|
+
value: values,
|
|
467
|
+
getDocumentExists: undefined,
|
|
468
|
+
})).resolves.toEqual([
|
|
655
469
|
{
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
470
|
+
level: 'error',
|
|
471
|
+
item: {
|
|
472
|
+
message: 'Expected type "String", got "Number"',
|
|
473
|
+
paths: [],
|
|
474
|
+
},
|
|
475
|
+
path: [{ _key: 'exampleKey' }, 'title'],
|
|
476
|
+
},
|
|
477
|
+
]);
|
|
478
|
+
});
|
|
479
|
+
it('properly passes the nested value, type, and path to rule.validate', async () => {
|
|
480
|
+
const schema = createSchema({
|
|
481
|
+
types: [
|
|
665
482
|
{
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
483
|
+
name: 'root',
|
|
484
|
+
type: 'object',
|
|
485
|
+
title: 'Root',
|
|
486
|
+
fields: [
|
|
487
|
+
{
|
|
488
|
+
name: 'level1Object',
|
|
489
|
+
type: 'object',
|
|
490
|
+
validation: (rule) => rule.custom(() => 'from level 1 object'),
|
|
491
|
+
fields: [
|
|
492
|
+
{
|
|
493
|
+
name: 'level2String',
|
|
494
|
+
type: 'string',
|
|
495
|
+
validation: (rule) => rule.custom(() => 'from level 2 via object'),
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
name: 'level1Array',
|
|
501
|
+
type: 'array',
|
|
502
|
+
validation: (rule) => rule.custom(() => 'from level 1 array'),
|
|
503
|
+
of: [
|
|
504
|
+
{
|
|
505
|
+
type: 'object',
|
|
506
|
+
fields: [
|
|
507
|
+
{
|
|
508
|
+
name: 'level2Number',
|
|
509
|
+
type: 'number',
|
|
510
|
+
validation: (rule) => rule.custom(() => 'from level 2 via array'),
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
},
|
|
514
|
+
],
|
|
515
|
+
},
|
|
516
|
+
],
|
|
517
|
+
validation: (rule) => rule.custom(() => 'from root'),
|
|
669
518
|
},
|
|
670
|
-
|
|
519
|
+
],
|
|
520
|
+
});
|
|
521
|
+
// ensures there are no schema formatting issues
|
|
522
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
523
|
+
expect(schema._validation).toHaveLength(0);
|
|
524
|
+
const value = {
|
|
525
|
+
level1Object: { level3String: 'a string' },
|
|
526
|
+
level1Array: [{ level2Number: 5 }],
|
|
527
|
+
};
|
|
528
|
+
const document = {
|
|
529
|
+
value,
|
|
530
|
+
_type: 'something',
|
|
531
|
+
_createdAt: '2021-09-05T19:28:30.882Z',
|
|
532
|
+
_id: 'something.id',
|
|
533
|
+
_rev: 'exampleRev',
|
|
534
|
+
_updatedAt: '2021-09-05T19:28:30.882Z',
|
|
535
|
+
};
|
|
536
|
+
const getField = ({ in: type, name }) => {
|
|
537
|
+
const result = 'fields' in type && type.fields.find((f) => f.name === name)?.type;
|
|
538
|
+
if (!result)
|
|
539
|
+
throw new Error(`Could not find field \`${name}\` in \`${type.name}\``);
|
|
540
|
+
return result;
|
|
541
|
+
};
|
|
542
|
+
const rootType = schema.get('root');
|
|
543
|
+
const level1ObjectType = getField({ in: rootType, name: 'level1Object' });
|
|
544
|
+
const level2StringType = getField({ in: level1ObjectType, name: 'level2String' });
|
|
545
|
+
const level1ArrayType = getField({ in: rootType, name: 'level1Array' });
|
|
546
|
+
const level2NumberType = getField({
|
|
547
|
+
in: level1ArrayType.of[0],
|
|
548
|
+
name: 'level2Number',
|
|
549
|
+
});
|
|
550
|
+
await expect(validateItem({
|
|
551
|
+
client: client,
|
|
552
|
+
value,
|
|
553
|
+
type: rootType,
|
|
554
|
+
document,
|
|
555
|
+
parent: document,
|
|
556
|
+
path: undefined,
|
|
557
|
+
getDocumentExists: undefined,
|
|
558
|
+
})).resolves.toMatchObject([
|
|
559
|
+
{
|
|
560
|
+
item: { message: 'from root' },
|
|
561
|
+
path: [],
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
item: { message: 'from level 1 object' },
|
|
565
|
+
path: ['level1Object'],
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
item: { message: 'from level 2 via object' },
|
|
569
|
+
path: ['level1Object', 'level2String'],
|
|
671
570
|
},
|
|
672
571
|
{
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
572
|
+
item: { message: 'from level 1 array' },
|
|
573
|
+
path: ['level1Array'],
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
item: { message: 'from level 2 via array' },
|
|
577
|
+
path: ['level1Array', 0, 'level2Number'],
|
|
578
|
+
},
|
|
579
|
+
]);
|
|
580
|
+
const calls = convertToValidationMarker.mock.calls;
|
|
581
|
+
expect(calls.find((call) => call[0] === 'from root')).toMatchObject([
|
|
582
|
+
'from root',
|
|
583
|
+
'error',
|
|
584
|
+
{
|
|
585
|
+
parent: document,
|
|
586
|
+
document: document,
|
|
587
|
+
path: [],
|
|
588
|
+
type: rootType,
|
|
589
|
+
},
|
|
590
|
+
]);
|
|
591
|
+
expect(calls.find((call) => call[0] === 'from level 1 object')).toMatchObject([
|
|
592
|
+
'from level 1 object',
|
|
593
|
+
'error',
|
|
594
|
+
{
|
|
595
|
+
parent: value,
|
|
596
|
+
document: document,
|
|
597
|
+
path: ['level1Object'],
|
|
598
|
+
type: level1ObjectType,
|
|
599
|
+
},
|
|
600
|
+
]);
|
|
601
|
+
expect(calls.find((call) => call[0] === 'from level 2 via object')).toMatchObject([
|
|
602
|
+
'from level 2 via object',
|
|
603
|
+
'error',
|
|
604
|
+
{
|
|
605
|
+
parent: value.level1Object,
|
|
606
|
+
document: document,
|
|
607
|
+
path: ['level1Object', 'level2String'],
|
|
608
|
+
type: level2StringType,
|
|
609
|
+
},
|
|
610
|
+
]);
|
|
611
|
+
expect(calls.find((call) => call[0] === 'from level 1 array')).toMatchObject([
|
|
612
|
+
'from level 1 array',
|
|
613
|
+
'error',
|
|
614
|
+
{
|
|
615
|
+
parent: value,
|
|
616
|
+
document: document,
|
|
617
|
+
path: ['level1Array'],
|
|
618
|
+
type: level1ArrayType,
|
|
619
|
+
},
|
|
620
|
+
]);
|
|
621
|
+
expect(calls.find((call) => call[0] === 'from level 2 via array')).toMatchObject([
|
|
622
|
+
'from level 2 via array',
|
|
623
|
+
'error',
|
|
624
|
+
{
|
|
625
|
+
parent: value.level1Array[0],
|
|
626
|
+
document: document,
|
|
627
|
+
path: ['level1Array', 0, 'level2Number'],
|
|
628
|
+
type: level2NumberType,
|
|
629
|
+
},
|
|
630
|
+
]);
|
|
631
|
+
});
|
|
632
|
+
it('runs all nested validation checks concurrently', async () => {
|
|
633
|
+
const resolvers = new Set();
|
|
634
|
+
const customValidationFn = () => new Promise((resolve) => resolvers.add(resolve));
|
|
635
|
+
const schema = createSchema({
|
|
636
|
+
types: [
|
|
677
637
|
{
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
638
|
+
name: 'root',
|
|
639
|
+
type: 'object',
|
|
640
|
+
fields: [
|
|
641
|
+
{
|
|
642
|
+
name: 'level1Object',
|
|
643
|
+
type: 'object',
|
|
644
|
+
validation: (rule) => [
|
|
645
|
+
rule.custom(customValidationFn),
|
|
646
|
+
rule.fields({
|
|
647
|
+
level2String: (r) => r.custom(customValidationFn),
|
|
648
|
+
}),
|
|
649
|
+
],
|
|
650
|
+
fields: [
|
|
651
|
+
{
|
|
652
|
+
name: 'level2String',
|
|
653
|
+
type: 'string',
|
|
654
|
+
validation: (rule) => rule.custom(customValidationFn),
|
|
655
|
+
},
|
|
656
|
+
],
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
name: 'level1Array',
|
|
660
|
+
type: 'array',
|
|
661
|
+
validation: (rule) => rule.custom(customValidationFn),
|
|
662
|
+
of: [
|
|
663
|
+
{
|
|
664
|
+
type: 'object',
|
|
665
|
+
fields: [
|
|
666
|
+
{
|
|
667
|
+
name: 'level2Number',
|
|
668
|
+
type: 'number',
|
|
669
|
+
validation: (rule) => rule.custom(customValidationFn),
|
|
670
|
+
},
|
|
671
|
+
],
|
|
672
|
+
},
|
|
673
|
+
],
|
|
674
|
+
},
|
|
675
|
+
],
|
|
676
|
+
validation: (rule) => rule.custom(customValidationFn),
|
|
686
677
|
},
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
await resultPromise
|
|
715
|
-
})
|
|
716
|
-
})
|
|
717
|
-
//# sourceMappingURL=validateDocument.test.js.map
|
|
678
|
+
],
|
|
679
|
+
});
|
|
680
|
+
const value = {
|
|
681
|
+
level1Object: { level3String: 'a string' },
|
|
682
|
+
level1Array: [{ level2Number: 5 }],
|
|
683
|
+
};
|
|
684
|
+
const resultPromise = validateItem({
|
|
685
|
+
client: client,
|
|
686
|
+
value,
|
|
687
|
+
document: undefined,
|
|
688
|
+
parent: undefined,
|
|
689
|
+
path: undefined,
|
|
690
|
+
type: schema.get('root'),
|
|
691
|
+
getDocumentExists: undefined,
|
|
692
|
+
});
|
|
693
|
+
// after `validateItem(...)` has been initiated, all of the custom
|
|
694
|
+
// validation calls should have fired so the resolver size should equal the
|
|
695
|
+
// amount of total fields
|
|
696
|
+
expect(resolvers.size).toBe(6);
|
|
697
|
+
for (const resolver of resolvers) {
|
|
698
|
+
resolver(true);
|
|
699
|
+
}
|
|
700
|
+
await resultPromise;
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
//# sourceMappingURL=validateDocument.test.js.map
|