@json-editor/json-editor 2.9.1 → 2.10.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/CHANGELOG.md +18 -0
- package/README.md +47 -1
- package/dist/jsoneditor.js +1 -1
- package/dist/jsoneditor.js.LICENSE.txt +1 -1
- package/dist/nonmin/jsoneditor.js +445 -124
- package/dist/nonmin/jsoneditor.js.map +1 -1
- package/docs/css_integration.html +17 -15
- package/docs/custom-editor.html +92 -0
- package/docs/index.html +4 -1
- package/docs/meta_schema.json +426 -398
- package/package.json +1 -1
- package/src/defaults.js +15 -1
- package/src/editor.js +23 -6
- package/src/editors/multiple.js +64 -7
- package/src/editors/object.js +0 -1
- package/src/iconlibs/bootstrap.js +28 -0
- package/src/iconlibs/index.js +2 -0
- package/src/resolvers.js +5 -2
- package/src/schemaloader.js +3 -1
- package/src/themes/bootstrap3.js +1 -1
- package/src/utilities.js +22 -0
- package/src/validator.js +93 -0
- package/tests/codeceptjs/constrains/contains_test.js +36 -0
- package/tests/codeceptjs/constrains/dependentSchemas_test.js +15 -0
- package/tests/codeceptjs/constrains/if-then-else_test.js +143 -0
- package/tests/codeceptjs/core_test.js +27 -27
- package/tests/codeceptjs/editors/advanced_test.js +11 -10
- package/tests/codeceptjs/editors/array_any_of_test.js +35 -35
- package/tests/codeceptjs/editors/array_test.js +757 -767
- package/tests/codeceptjs/editors/autocomplete_test.js +1 -3
- package/tests/codeceptjs/editors/button_test.js +25 -24
- package/tests/codeceptjs/editors/checkbox_test.js +17 -16
- package/tests/codeceptjs/editors/colorpicker_test.js +18 -16
- package/tests/codeceptjs/editors/datetime_test.js +7 -7
- package/tests/codeceptjs/editors/inheritance_test.js +7 -8
- package/tests/codeceptjs/editors/integer_test.js +71 -72
- package/tests/codeceptjs/editors/jodit_test.js +16 -17
- package/tests/codeceptjs/editors/multiselect_test.js +5 -5
- package/tests/codeceptjs/editors/number_test.js +64 -65
- package/tests/codeceptjs/editors/object_test.js +39 -13
- package/tests/codeceptjs/editors/option-no_default_values_test.js +4 -4
- package/tests/codeceptjs/editors/programmatic-changes_test.js +2 -3
- package/tests/codeceptjs/editors/range_test.js +1 -3
- package/tests/codeceptjs/editors/select_test.js +3 -5
- package/tests/codeceptjs/editors/stepper_test.js +5 -7
- package/tests/codeceptjs/editors/string_test.js +8 -8
- package/tests/codeceptjs/editors/table-confirm-delete_test.js +7 -9
- package/tests/codeceptjs/editors/uuid_test.js +10 -10
- package/tests/codeceptjs/editors/validation_test.js +1 -1
- package/tests/codeceptjs/{editors/issues → issues}/issue-gh-1133_test.js +1 -3
- package/tests/codeceptjs/issues/issue-gh-1158-2_test.js +10 -0
- package/tests/codeceptjs/{editors/issues → issues}/issue-gh-1158_test.js +0 -2
- package/tests/codeceptjs/issues/issue-gh-1164_test.js +10 -0
- package/tests/codeceptjs/issues/issue-gh-1211_test.js +17 -0
- package/tests/codeceptjs/{editors/issues → issues}/issue-gh-1257_test.js +2 -4
- package/tests/codeceptjs/issues/issue-gh-1338_test.js +16 -0
- package/tests/codeceptjs/issues/issue-gh-1347_test.js +8 -0
- package/tests/codeceptjs/issues/issue-gh-795_test.js +13 -0
- package/tests/codeceptjs/issues/issue-gh-810_test.js +52 -0
- package/tests/codeceptjs/issues/issue-gh-812_test.js +25 -0
- package/tests/codeceptjs/meta-schema_test.js +10 -10
- package/tests/codeceptjs/schemaloader_test.js +7 -7
- package/tests/codeceptjs/themes_test.js +31 -0
- package/tests/pages/autocomplete.html +1 -0
- package/tests/pages/contains.html +38 -0
- package/tests/pages/dependentSchemas.html +52 -0
- package/tests/pages/if-else.html +57 -0
- package/tests/pages/if-then-else-allOf.html +117 -0
- package/tests/pages/if-then-else.html +64 -0
- package/tests/pages/if-then.html +57 -0
- package/tests/pages/issues/issue-gh-1158-2.html +189 -0
- package/tests/pages/issues/issue-gh-1165.html +63 -0
- package/tests/pages/issues/issue-gh-1165.json +8 -0
- package/tests/pages/issues/issue-gh-1211.html +1022 -0
- package/tests/pages/issues/issue-gh-1338.html +74 -0
- package/tests/pages/issues/issue-gh-1347.html +142 -0
- package/tests/pages/issues/issue-gh-795.html +58 -0
- package/tests/pages/issues/issue-gh-810.html +149 -0
- package/tests/pages/maxContains.html +39 -0
- package/tests/pages/meta-schema.html +28 -0
- package/tests/pages/meta_schema.json +426 -398
- package/tests/pages/minContains.html +39 -0
- package/tests/pages/option-dependencies.html +106 -0
- package/tests/pages/themes.html +3 -1
- package/tests/unit/core.spec.js +2 -5
- package/tests/codeceptjs/editors/issues/issue-gh-1164_test.js +0 -12
- package/tests/codeceptjs/editors/issues/issue-gh-812_test.js +0 -32
package/package.json
CHANGED
package/src/defaults.js
CHANGED
|
@@ -101,6 +101,20 @@ languages.en = {
|
|
|
101
101
|
* @variables This key takes one variable: The maximum character count
|
|
102
102
|
*/
|
|
103
103
|
error_maxLength: 'Value must be at most {{0}} characters long',
|
|
104
|
+
/**
|
|
105
|
+
* When no array items validates the contains schema
|
|
106
|
+
*/
|
|
107
|
+
error_contains: 'No items match contains',
|
|
108
|
+
/**
|
|
109
|
+
* When an array have too few items that validate agaist contains schema
|
|
110
|
+
* @variables This key takes two variable: The valid items count and the minContains value
|
|
111
|
+
*/
|
|
112
|
+
error_minContains: 'Contains match count {{0}} is less than minimum contains count of {{1}}',
|
|
113
|
+
/**
|
|
114
|
+
* When an array have too many items that validate agaist contains schema
|
|
115
|
+
* @variables This key takes two variable: The valid items count and the maxContains value
|
|
116
|
+
*/
|
|
117
|
+
error_maxContains: 'Contains match count {{0}} exceeds maximum contains count of {{1}}',
|
|
104
118
|
/**
|
|
105
119
|
* When a value does not have enough characters
|
|
106
120
|
* @variables This key takes one variable: The minimum character count
|
|
@@ -345,7 +359,7 @@ languages.en = {
|
|
|
345
359
|
/**
|
|
346
360
|
* Warning when deleting a node
|
|
347
361
|
*/
|
|
348
|
-
button_delete_node_warning: 'Are you sure you want to remove this
|
|
362
|
+
button_delete_node_warning: 'Are you sure you want to remove this item?'
|
|
349
363
|
}
|
|
350
364
|
|
|
351
365
|
/* Default per-editor options */
|
package/src/editor.js
CHANGED
|
@@ -95,9 +95,17 @@ export class AbstractEditor {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
Object.keys(deps).forEach(dependency => {
|
|
98
|
-
let path
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
let path
|
|
99
|
+
const isFullPath = dependency.startsWith(this.jsoneditor.root.path)
|
|
100
|
+
|
|
101
|
+
if (isFullPath) {
|
|
102
|
+
path = dependency
|
|
103
|
+
} else {
|
|
104
|
+
path = this.path.split('.')
|
|
105
|
+
path[path.length - 1] = dependency
|
|
106
|
+
path = path.join('.')
|
|
107
|
+
}
|
|
108
|
+
|
|
101
109
|
this.jsoneditor.watch(path, () => {
|
|
102
110
|
this.evaluateDependencies()
|
|
103
111
|
})
|
|
@@ -114,14 +122,23 @@ export class AbstractEditor {
|
|
|
114
122
|
if (!deps) {
|
|
115
123
|
return
|
|
116
124
|
}
|
|
125
|
+
|
|
117
126
|
// Assume true and set to false if any unmet dependencies are found
|
|
118
127
|
const previousStatus = this.dependenciesFulfilled
|
|
119
128
|
this.dependenciesFulfilled = true
|
|
120
129
|
|
|
121
130
|
Object.keys(deps).forEach(dependency => {
|
|
122
|
-
let path
|
|
123
|
-
|
|
124
|
-
|
|
131
|
+
let path
|
|
132
|
+
const isFullPath = dependency.startsWith(this.jsoneditor.root.path)
|
|
133
|
+
|
|
134
|
+
if (isFullPath) {
|
|
135
|
+
path = dependency
|
|
136
|
+
} else {
|
|
137
|
+
path = this.path.split('.')
|
|
138
|
+
path[path.length - 1] = dependency
|
|
139
|
+
path = path.join('.')
|
|
140
|
+
}
|
|
141
|
+
|
|
125
142
|
const choices = deps[dependency]
|
|
126
143
|
this.checkDependency(path, choices)
|
|
127
144
|
})
|
package/src/editors/multiple.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Multiple Editor (for when `type` is an array, also when `oneOf` is present) */
|
|
2
2
|
import { AbstractEditor } from '../editor.js'
|
|
3
3
|
import { Validator } from '../validator.js'
|
|
4
|
-
import { extend } from '../utilities.js'
|
|
4
|
+
import { extend, mergeDeep } from '../utilities.js'
|
|
5
5
|
|
|
6
6
|
export class MultipleEditor extends AbstractEditor {
|
|
7
7
|
register () {
|
|
@@ -56,6 +56,8 @@ export class MultipleEditor extends AbstractEditor {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
switchEditor (i) {
|
|
59
|
+
this.lastType = this.type
|
|
60
|
+
|
|
59
61
|
if (!this.editors[i]) {
|
|
60
62
|
this.buildChildEditor(i)
|
|
61
63
|
}
|
|
@@ -68,11 +70,15 @@ export class MultipleEditor extends AbstractEditor {
|
|
|
68
70
|
|
|
69
71
|
this.editors.forEach((editor, type) => {
|
|
70
72
|
if (!editor) return
|
|
73
|
+
|
|
71
74
|
if (this.type === type) {
|
|
72
|
-
if (this.keep_values) editor.setValue(currentValue, true)
|
|
75
|
+
if (this.keep_values || this.if) editor.setValue(currentValue, true)
|
|
73
76
|
editor.container.style.display = ''
|
|
74
|
-
} else
|
|
77
|
+
} else {
|
|
78
|
+
editor.container.style.display = 'none'
|
|
79
|
+
}
|
|
75
80
|
})
|
|
81
|
+
|
|
76
82
|
this.refreshValue()
|
|
77
83
|
this.refreshHeaderText()
|
|
78
84
|
}
|
|
@@ -140,6 +146,31 @@ export class MultipleEditor extends AbstractEditor {
|
|
|
140
146
|
this.anyOf = true
|
|
141
147
|
this.types = this.schema.anyOf
|
|
142
148
|
delete this.schema.anyOf
|
|
149
|
+
} else if (this.schema.if) {
|
|
150
|
+
this.if = true
|
|
151
|
+
this.ifSchema = JSON.parse(JSON.stringify(this.schema.if))
|
|
152
|
+
this.thenSchema = { title: 'then' }
|
|
153
|
+
this.elseSchema = { title: 'else' }
|
|
154
|
+
this.types = []
|
|
155
|
+
|
|
156
|
+
if (this.schema.then) {
|
|
157
|
+
mergeDeep(this.thenSchema, this.schema, this.schema.then)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (this.schema.else) {
|
|
161
|
+
mergeDeep(this.elseSchema, this.schema, this.schema.else)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.types.push(this.thenSchema)
|
|
165
|
+
this.types.push(this.elseSchema)
|
|
166
|
+
|
|
167
|
+
this.types.forEach((schema) => {
|
|
168
|
+
delete schema.if
|
|
169
|
+
delete schema.then
|
|
170
|
+
delete schema.else
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
delete this.schema.if
|
|
143
174
|
} else {
|
|
144
175
|
if (!this.schema.type || this.schema.type === 'any') {
|
|
145
176
|
this.types = ['string', 'number', 'integer', 'boolean', 'object', 'array', 'null']
|
|
@@ -169,12 +200,14 @@ export class MultipleEditor extends AbstractEditor {
|
|
|
169
200
|
|
|
170
201
|
build () {
|
|
171
202
|
const { container } = this
|
|
172
|
-
|
|
173
203
|
this.header = this.label = this.theme.getFormInputLabel(this.getTitle(), this.isRequired())
|
|
174
|
-
this.container.appendChild(this.header)
|
|
175
|
-
|
|
176
204
|
this.switcher = this.theme.getSwitcher(this.display_text)
|
|
177
|
-
|
|
205
|
+
|
|
206
|
+
if (!this.if) {
|
|
207
|
+
this.container.appendChild(this.header)
|
|
208
|
+
container.appendChild(this.switcher)
|
|
209
|
+
}
|
|
210
|
+
|
|
178
211
|
this.switcher.addEventListener('change', e => {
|
|
179
212
|
e.preventDefault()
|
|
180
213
|
e.stopPropagation()
|
|
@@ -220,6 +253,7 @@ export class MultipleEditor extends AbstractEditor {
|
|
|
220
253
|
this.refreshHeaderText()
|
|
221
254
|
}
|
|
222
255
|
|
|
256
|
+
this.switchIf()
|
|
223
257
|
super.onChildEditorChange()
|
|
224
258
|
}
|
|
225
259
|
|
|
@@ -234,6 +268,24 @@ export class MultipleEditor extends AbstractEditor {
|
|
|
234
268
|
this.value = this.editors[this.type].getValue()
|
|
235
269
|
}
|
|
236
270
|
|
|
271
|
+
switchIf () {
|
|
272
|
+
if (this.ifSchema && this.value) {
|
|
273
|
+
const type = this.getIfType(this.value)
|
|
274
|
+
|
|
275
|
+
if (this.lastType !== type) {
|
|
276
|
+
this.switchEditor(type)
|
|
277
|
+
this.editors[this.type].setValue(this.value, true)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
this.switcher.value = this.display_text[this.type]
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
getIfType (value) {
|
|
285
|
+
const errors = this.jsoneditor.validator._validateSchema(this.ifSchema, value)
|
|
286
|
+
return errors.length === 0 ? 0 : 1
|
|
287
|
+
}
|
|
288
|
+
|
|
237
289
|
setValue (val, initial) {
|
|
238
290
|
/* Determine type by getting the first one that validates */
|
|
239
291
|
|
|
@@ -279,6 +331,9 @@ export class MultipleEditor extends AbstractEditor {
|
|
|
279
331
|
finalI = fitTestVal.i
|
|
280
332
|
}
|
|
281
333
|
}
|
|
334
|
+
if (this.if) {
|
|
335
|
+
finalI = this.getIfType(val)
|
|
336
|
+
}
|
|
282
337
|
if (finalI === null) {
|
|
283
338
|
finalI = this.type
|
|
284
339
|
}
|
|
@@ -286,8 +341,10 @@ export class MultipleEditor extends AbstractEditor {
|
|
|
286
341
|
this.switcher.value = this.display_text[finalI]
|
|
287
342
|
|
|
288
343
|
const typeChanged = this.type !== prevType
|
|
344
|
+
|
|
289
345
|
if (typeChanged) {
|
|
290
346
|
this.switchEditor(this.type)
|
|
347
|
+
this.editors[this.type].setValue(val, initial)
|
|
291
348
|
}
|
|
292
349
|
|
|
293
350
|
if (typeof val !== 'undefined') {
|
package/src/editors/object.js
CHANGED
|
@@ -893,7 +893,6 @@ export class ObjectEditor extends AbstractEditor {
|
|
|
893
893
|
let labelText
|
|
894
894
|
|
|
895
895
|
const checkbox = this.theme.getCheckbox()
|
|
896
|
-
checkbox.style.width = 'auto'
|
|
897
896
|
|
|
898
897
|
if (this.schema.properties[key] && this.schema.properties[key].title) { labelText = this.schema.properties[key].title } else { labelText = key }
|
|
899
898
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AbstractIconLib } from '../iconlib.js'
|
|
2
|
+
|
|
3
|
+
const iconPrefix = 'bi bi-'
|
|
4
|
+
const mapping = {
|
|
5
|
+
collapse: 'chevron-down',
|
|
6
|
+
expand: 'chevron-right',
|
|
7
|
+
delete: 'trash',
|
|
8
|
+
edit: 'pencil',
|
|
9
|
+
add: 'plus',
|
|
10
|
+
subtract: 'dash',
|
|
11
|
+
cancel: 'x-circle',
|
|
12
|
+
save: 'save',
|
|
13
|
+
moveup: 'arrow-up',
|
|
14
|
+
moveright: 'arrow-right',
|
|
15
|
+
movedown: 'arrow-down',
|
|
16
|
+
moveleft: 'arrow-left',
|
|
17
|
+
copy: 'clipboard',
|
|
18
|
+
clear: 'x-circle',
|
|
19
|
+
time: 'clock',
|
|
20
|
+
calendar: 'calendar',
|
|
21
|
+
edit_properties: 'list-ul'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class bootstrapIconlib extends AbstractIconLib {
|
|
25
|
+
constructor () {
|
|
26
|
+
super(iconPrefix, mapping)
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/iconlibs/index.js
CHANGED
|
@@ -9,9 +9,11 @@ import { jqueryuiIconlib } from './jqueryui.js'
|
|
|
9
9
|
// import { materialiconsIconlib } from './materialicons.js'
|
|
10
10
|
import { openiconicIconlib } from './openiconic.js'
|
|
11
11
|
import { spectreIconlib } from './spectre.js'
|
|
12
|
+
import { bootstrapIconlib } from './bootstrap'
|
|
12
13
|
|
|
13
14
|
export const iconlibs = {
|
|
14
15
|
// bootstrap2: bootstrap2Iconlib,
|
|
16
|
+
bootstrap: bootstrapIconlib,
|
|
15
17
|
bootstrap3: bootstrap3Iconlib,
|
|
16
18
|
fontawesome3: fontawesome3Iconlib,
|
|
17
19
|
fontawesome4: fontawesome4Iconlib,
|
package/src/resolvers.js
CHANGED
|
@@ -76,6 +76,9 @@ const arraysOfStrings = schema => {
|
|
|
76
76
|
/* Use the multiple editor for schemas with `oneOf` or `anyOf` set */
|
|
77
77
|
const oneOf = schema => (schema.oneOf || schema.anyOf) && 'multiple'
|
|
78
78
|
|
|
79
|
+
/* Use the multiple editor for schemas with `if` set */
|
|
80
|
+
const ifThenElse = schema => (schema.if) && 'multiple'
|
|
81
|
+
|
|
79
82
|
/* Specialized editor for date, time and datetime-local formats */
|
|
80
83
|
const date = schema => ['string', 'integer'].includes(schema.type) && ['date', 'time', 'datetime-local'].includes(schema.format) && 'datetime'
|
|
81
84
|
|
|
@@ -114,7 +117,7 @@ const markdown = schema => schema.type === 'string' && schema.format === 'markdo
|
|
|
114
117
|
const xhtml = schema => schema.type === 'string' && ['xhtml', 'bbcode'].includes(schema.format) && 'sceditor'
|
|
115
118
|
|
|
116
119
|
/* Use the ace editor for schemas with format equals any of ace editor modes */
|
|
117
|
-
const aceModes = ['actionscript', 'batchfile', 'c', 'c++', 'cpp', 'coffee', 'csharp', 'css', 'dart', 'django', 'ejs', 'erlang', 'golang', 'groovy', 'handlebars', 'haskell', 'haxe', 'html', 'ini', 'jade', 'java', 'javascript', 'json', 'less', 'lisp', 'lua', 'makefile', 'matlab', 'mysql', 'objectivec', 'pascal', 'perl', 'pgsql', 'php', 'python', 'r', 'ruby', 'sass', 'scala', 'scss', 'smarty', 'sql', 'sqlserver', 'stylus', 'svg', 'twig', 'vbscript', 'xml', 'yaml']
|
|
120
|
+
const aceModes = ['actionscript', 'batchfile', 'c', 'c++', 'cpp', 'coffee', 'csharp', 'css', 'dart', 'django', 'ejs', 'erlang', 'golang', 'groovy', 'handlebars', 'haskell', 'haxe', 'html', 'ini', 'jade', 'java', 'javascript', 'json', 'less', 'lisp', 'lua', 'makefile', 'matlab', 'mysql', 'objectivec', 'pascal', 'perl', 'pgsql', 'php', 'python', 'r', 'ruby', 'sass', 'scala', 'scss', 'sh', 'smarty', 'sql', 'sqlserver', 'stylus', 'svg', 'twig', 'vbscript', 'xml', 'yaml']
|
|
118
121
|
const ace = schema => schema.type === 'string' && aceModes.includes(schema.format) && 'ace'
|
|
119
122
|
|
|
120
123
|
const ip = schema => schema.type === 'string' && ['ip', 'ipv4', 'ipv6', 'hostname'].includes(schema.format) && 'ip'
|
|
@@ -122,4 +125,4 @@ const ip = schema => schema.type === 'string' && ['ip', 'ipv4', 'ipv6', 'hostnam
|
|
|
122
125
|
const colorPicker = schema => schema.type === 'string' && schema.format === 'color' && 'colorpicker'
|
|
123
126
|
|
|
124
127
|
/* Export resolvers in order of discovery, first to last */
|
|
125
|
-
export const resolvers = [colorPicker, ip, ace, xhtml, markdown, jodit, autoComplete, uuid, info, button, stepper, describeBy, starratings, date, oneOf, arraysOfStrings, enumeratedProperties, enumSource, table, upload, base64, any, boolean, signature, primitive, object, defaultResolver]
|
|
128
|
+
export const resolvers = [colorPicker, ip, ace, xhtml, markdown, jodit, autoComplete, uuid, info, button, stepper, describeBy, starratings, date, oneOf, ifThenElse, arraysOfStrings, enumeratedProperties, enumSource, table, upload, base64, any, boolean, signature, primitive, object, defaultResolver]
|
package/src/schemaloader.js
CHANGED
|
@@ -158,7 +158,9 @@ export class SchemaLoader {
|
|
|
158
158
|
// If local ref
|
|
159
159
|
if (refWithPointerSplit.length === 2 && !this.refs_with_info[_schema.$ref]) {
|
|
160
160
|
const sub = this.expandRecursivePointer(this.schema, refWithPointerSplit[1])
|
|
161
|
-
|
|
161
|
+
const expandedSchema = this.extendSchemas(_schema, this.expandSchema(sub))
|
|
162
|
+
delete expandedSchema.$ref
|
|
163
|
+
return expandedSchema
|
|
162
164
|
}
|
|
163
165
|
const refObj = (refWithPointerSplit.length > 2)
|
|
164
166
|
? this.refs_with_info['#' + refWithPointerSplit[1]]
|
package/src/themes/bootstrap3.js
CHANGED
package/src/utilities.js
CHANGED
|
@@ -104,3 +104,25 @@ export function generateUUID () {
|
|
|
104
104
|
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
|
|
105
105
|
})
|
|
106
106
|
}
|
|
107
|
+
|
|
108
|
+
export function isObject (item) {
|
|
109
|
+
return (item && typeof item === 'object' && !Array.isArray(item))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function mergeDeep (target, ...sources) {
|
|
113
|
+
if (!sources.length) return target
|
|
114
|
+
const source = sources.shift()
|
|
115
|
+
|
|
116
|
+
if (isObject(target) && isObject(source)) {
|
|
117
|
+
for (const key in source) {
|
|
118
|
+
if (isObject(source[key])) {
|
|
119
|
+
if (!target[key]) Object.assign(target, { [key]: {} })
|
|
120
|
+
mergeDeep(target[key], source[key])
|
|
121
|
+
} else {
|
|
122
|
+
Object.assign(target, { [key]: source[key] })
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return mergeDeep(target, ...sources)
|
|
128
|
+
}
|
package/src/validator.js
CHANGED
|
@@ -11,6 +11,99 @@ export class Validator {
|
|
|
11
11
|
this.defaults = defaults
|
|
12
12
|
|
|
13
13
|
this._validateSubSchema = {
|
|
14
|
+
dependentSchemas (schema, value, path) {
|
|
15
|
+
let errors = []
|
|
16
|
+
|
|
17
|
+
Object.keys(schema.dependentSchemas).forEach((key) => {
|
|
18
|
+
if (typeof value[key] !== 'undefined') {
|
|
19
|
+
const dependentSchema = schema.dependentSchemas[key]
|
|
20
|
+
const tmpErrors = this._validateSchema(dependentSchema, value, path)
|
|
21
|
+
errors = [...errors, ...tmpErrors]
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return errors
|
|
26
|
+
},
|
|
27
|
+
contains (schema, value, path) {
|
|
28
|
+
const errors = []
|
|
29
|
+
let counter = 0
|
|
30
|
+
|
|
31
|
+
value.forEach((item) => {
|
|
32
|
+
const containsErrors = this._validateSchema(schema.contains, item, path)
|
|
33
|
+
|
|
34
|
+
if (containsErrors.length === 0) {
|
|
35
|
+
counter++
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const containsInvalid = (counter === 0)
|
|
40
|
+
|
|
41
|
+
if (typeof schema.minContains !== 'undefined') {
|
|
42
|
+
const minContainsInvalid = (counter < schema.minContains)
|
|
43
|
+
|
|
44
|
+
if (minContainsInvalid) {
|
|
45
|
+
errors.push({
|
|
46
|
+
message: this.translate('error_minContains', [counter, schema.minContains], schema),
|
|
47
|
+
path: path
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
if (containsInvalid) {
|
|
52
|
+
errors.push({
|
|
53
|
+
message: this.translate('error_contains', null, schema),
|
|
54
|
+
path: path
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof schema.maxContains !== 'undefined') {
|
|
60
|
+
const maxContainsInvalid = (counter > schema.maxContains)
|
|
61
|
+
|
|
62
|
+
if (maxContainsInvalid) {
|
|
63
|
+
errors.push({
|
|
64
|
+
message: this.translate('error_maxContains', [counter, schema.maxContains], schema),
|
|
65
|
+
path: path
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return errors
|
|
71
|
+
},
|
|
72
|
+
if (schema, value, path) {
|
|
73
|
+
if (typeof schema.then === 'undefined' && typeof schema.else === 'undefined') {
|
|
74
|
+
return []
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const ifErrors = this._validateSchema(schema.if, value, path)
|
|
78
|
+
let thenErrors = []
|
|
79
|
+
let elseErrors = []
|
|
80
|
+
|
|
81
|
+
if (typeof schema.then !== 'undefined') {
|
|
82
|
+
thenErrors = this._validateSchema(schema.then, value, path)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (typeof schema.else !== 'undefined') {
|
|
86
|
+
elseErrors = this._validateSchema(schema.else, value, path)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (schema.if === true) {
|
|
90
|
+
return thenErrors
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (schema.if === false) {
|
|
94
|
+
return elseErrors
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (ifErrors.length === 0) {
|
|
98
|
+
return thenErrors
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (ifErrors.length > 0) {
|
|
102
|
+
return elseErrors
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return []
|
|
106
|
+
},
|
|
14
107
|
const (schema, value, path) {
|
|
15
108
|
const valid = JSON.stringify(schema.const) === JSON.stringify(value) && !(Array.isArray(value) || typeof value === 'object')
|
|
16
109
|
if (!valid) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* global Feature Scenario */
|
|
2
|
+
|
|
3
|
+
Feature('contains')
|
|
4
|
+
|
|
5
|
+
Scenario('should display contains validation errors @contains', ({ I }) => {
|
|
6
|
+
I.amOnPage('contains.html')
|
|
7
|
+
I.waitForElement('.je-ready')
|
|
8
|
+
I.waitForText('No items match contains')
|
|
9
|
+
I.click('.json-editor-btn-add')
|
|
10
|
+
I.dontSee('No items match contains')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
Scenario('should display @minContains validation errors @contains', ({ I }) => {
|
|
14
|
+
I.amOnPage('minContains.html')
|
|
15
|
+
I.waitForElement('.je-ready')
|
|
16
|
+
I.dontSee('No items match contains')
|
|
17
|
+
I.waitForText('Contains match count 0 is less than minimum contains count of 2')
|
|
18
|
+
I.click('.json-editor-btn-add')
|
|
19
|
+
I.dontSee('No items match contains')
|
|
20
|
+
I.waitForText('Contains match count 1 is less than minimum contains count of 2')
|
|
21
|
+
I.click('.json-editor-btn-add')
|
|
22
|
+
I.dontSee('No items match contains')
|
|
23
|
+
I.dontSee('minimum contains count of 2')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
Scenario('should display @maxContains validation errors @contains', ({ I }) => {
|
|
27
|
+
I.amOnPage('maxContains.html')
|
|
28
|
+
I.waitForElement('.je-ready')
|
|
29
|
+
I.waitForText('No items match contains')
|
|
30
|
+
I.click('.json-editor-btn-add')
|
|
31
|
+
I.dontSee('No items match contains')
|
|
32
|
+
I.click('.json-editor-btn-add')
|
|
33
|
+
I.dontSee('No items match contains')
|
|
34
|
+
I.click('.json-editor-btn-add')
|
|
35
|
+
I.waitForText('Contains match count 3 exceeds maximum contains count of 2')
|
|
36
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* global Feature Scenario */
|
|
2
|
+
|
|
3
|
+
Feature('dependentSchemas')
|
|
4
|
+
|
|
5
|
+
Scenario('@dependentSchemas should display validation errors', ({ I }) => {
|
|
6
|
+
I.amOnPage('dependentSchemas.html')
|
|
7
|
+
I.waitForElement('.je-ready')
|
|
8
|
+
I.dontSee('Object is missing the required property \'billing_address\'')
|
|
9
|
+
I.click('[data-schemapath="root.credit_card"] .json-editor-opt-in')
|
|
10
|
+
I.waitForText('Object is missing the required property \'billing_address\'')
|
|
11
|
+
I.click('[data-schemapath="root.billing_address"] .json-editor-opt-in')
|
|
12
|
+
I.dontSee('Object is missing the required property \'billing_address\'')
|
|
13
|
+
I.click('[data-schemapath="root.credit_card"] .json-editor-opt-in')
|
|
14
|
+
I.dontSee('Object is missing the required property \'billing_address\'')
|
|
15
|
+
})
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/* global Feature Scenario */
|
|
2
|
+
|
|
3
|
+
Feature('if-then-else')
|
|
4
|
+
|
|
5
|
+
Scenario('validate agaist allOf of if schemas @if-then-else', async ({ I }) => {
|
|
6
|
+
I.amOnPage('if-then-else-allOf.html')
|
|
7
|
+
I.waitForElement('.je-ready')
|
|
8
|
+
|
|
9
|
+
I.selectOption('[name="root[country]"]', 'United States of America')
|
|
10
|
+
I.waitForText('Value must match the pattern [0-9]{5}(-[0-9]{4})?.')
|
|
11
|
+
|
|
12
|
+
I.selectOption('[name="root[country]"]', 'Canada')
|
|
13
|
+
I.waitForText('Value must match the pattern [A-Z][0-9][A-Z] [0-9][A-Z][0-9].')
|
|
14
|
+
|
|
15
|
+
I.selectOption('[name="root[country]"]', 'Netherlands')
|
|
16
|
+
I.waitForText('Value must match the pattern [0-9]{4} [A-Z]{2}.')
|
|
17
|
+
|
|
18
|
+
I.fillField('#value', JSON.stringify({
|
|
19
|
+
street_address: '1600 Pennsylvania Avenue NW',
|
|
20
|
+
country: 'United States of America',
|
|
21
|
+
postal_code: '20500'
|
|
22
|
+
}))
|
|
23
|
+
I.click('#set-value')
|
|
24
|
+
I.dontSee('.invalid-feedback')
|
|
25
|
+
|
|
26
|
+
I.fillField('#value', JSON.stringify({
|
|
27
|
+
street_address: '1600 Pennsylvania Avenue NW',
|
|
28
|
+
postal_code: '20500'
|
|
29
|
+
}))
|
|
30
|
+
I.click('#set-value')
|
|
31
|
+
I.dontSee('.invalid-feedback')
|
|
32
|
+
|
|
33
|
+
I.fillField('#value', JSON.stringify({
|
|
34
|
+
street_address: '24 Sussex Drive',
|
|
35
|
+
country: 'Canada',
|
|
36
|
+
postal_code: 'K1M 1M4'
|
|
37
|
+
}))
|
|
38
|
+
I.click('#set-value')
|
|
39
|
+
I.dontSee('.invalid-feedback')
|
|
40
|
+
|
|
41
|
+
I.fillField('#value', JSON.stringify({
|
|
42
|
+
street_address: 'Adriaan Goekooplaan',
|
|
43
|
+
country: 'Netherlands',
|
|
44
|
+
postal_code: '2517 JX'
|
|
45
|
+
}))
|
|
46
|
+
I.click('#set-value')
|
|
47
|
+
I.dontSee('.invalid-feedback')
|
|
48
|
+
|
|
49
|
+
I.fillField('#value', JSON.stringify({
|
|
50
|
+
street_address: '24 Sussex Drive',
|
|
51
|
+
country: 'Canada',
|
|
52
|
+
postal_code: '10000'
|
|
53
|
+
}))
|
|
54
|
+
I.click('#set-value')
|
|
55
|
+
I.waitForText('Value must match the pattern [A-Z][0-9][A-Z] [0-9][A-Z][0-9].')
|
|
56
|
+
I.waitForElement('.invalid-feedback')
|
|
57
|
+
|
|
58
|
+
I.fillField('#value', JSON.stringify({
|
|
59
|
+
street_address: '1600 Pennsylvania Avenue NW',
|
|
60
|
+
postal_code: 'K1M 1M4'
|
|
61
|
+
}))
|
|
62
|
+
I.click('#set-value')
|
|
63
|
+
I.waitForElement('.invalid-feedback')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
Scenario('validate agaist if-then-else @if-then-else', async ({ I }) => {
|
|
67
|
+
I.amOnPage('if-then-else.html')
|
|
68
|
+
I.waitForElement('.je-ready')
|
|
69
|
+
|
|
70
|
+
I.selectOption('[name="root[country]"]', 'America')
|
|
71
|
+
I.fillField('[name="root[postal_code]"]', 'K1M 1M4')
|
|
72
|
+
I.pressKey('Tab')
|
|
73
|
+
I.waitForElement('.invalid-feedback')
|
|
74
|
+
I.waitForText('Value must match the pattern [0-9]{5}(-[0-9]{4})?.')
|
|
75
|
+
|
|
76
|
+
I.selectOption('[name="root[country]"]', 'Canada')
|
|
77
|
+
I.fillField('[name="root[postal_code]"]', '10000')
|
|
78
|
+
I.pressKey('Tab')
|
|
79
|
+
I.waitForElement('.invalid-feedback')
|
|
80
|
+
I.waitForText('Value must match the pattern [A-Z][0-9][A-Z] [0-9][A-Z][0-9].')
|
|
81
|
+
|
|
82
|
+
I.selectOption('[name="root[country]"]', 'America')
|
|
83
|
+
I.fillField('[name="root[postal_code]"]', '10000')
|
|
84
|
+
I.pressKey('Tab')
|
|
85
|
+
I.dontSee('.invalid-feedback')
|
|
86
|
+
|
|
87
|
+
I.selectOption('[name="root[country]"]', 'Canada')
|
|
88
|
+
I.fillField('[name="root[postal_code]"]', 'K1M 1M4')
|
|
89
|
+
I.pressKey('Tab')
|
|
90
|
+
I.dontSee('.invalid-feedback')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
Scenario('validate agaist if-then @if-then-else', async ({ I }) => {
|
|
94
|
+
I.amOnPage('if-then.html')
|
|
95
|
+
I.waitForElement('.je-ready')
|
|
96
|
+
|
|
97
|
+
I.selectOption('[name="root[country]"]', 'America')
|
|
98
|
+
I.fillField('[name="root[postal_code]"]', 'K1M 1M4')
|
|
99
|
+
I.pressKey('Tab')
|
|
100
|
+
I.waitForElement('.invalid-feedback')
|
|
101
|
+
I.waitForText('Value must match the pattern [0-9]{5}(-[0-9]{4})?.')
|
|
102
|
+
|
|
103
|
+
I.selectOption('[name="root[country]"]', 'Canada')
|
|
104
|
+
I.fillField('[name="root[postal_code]"]', '10000')
|
|
105
|
+
I.pressKey('Tab')
|
|
106
|
+
I.dontSee('.invalid-feedback')
|
|
107
|
+
|
|
108
|
+
I.selectOption('[name="root[country]"]', 'America')
|
|
109
|
+
I.fillField('[name="root[postal_code]"]', '10000')
|
|
110
|
+
I.pressKey('Tab')
|
|
111
|
+
I.dontSee('.invalid-feedback')
|
|
112
|
+
|
|
113
|
+
I.selectOption('[name="root[country]"]', 'Canada')
|
|
114
|
+
I.fillField('[name="root[postal_code]"]', 'K1M 1M4')
|
|
115
|
+
I.pressKey('Tab')
|
|
116
|
+
I.dontSee('.invalid-feedback')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
Scenario('validate agaist if-else @if-then-else', async ({ I }) => {
|
|
120
|
+
I.amOnPage('if-else.html')
|
|
121
|
+
I.waitForElement('.je-ready')
|
|
122
|
+
|
|
123
|
+
I.selectOption('[name="root[country]"]', 'America')
|
|
124
|
+
I.fillField('[name="root[postal_code]"]', 'K1M 1M4')
|
|
125
|
+
I.pressKey('Tab')
|
|
126
|
+
I.dontSee('.invalid-feedback')
|
|
127
|
+
|
|
128
|
+
I.selectOption('[name="root[country]"]', 'Canada')
|
|
129
|
+
I.fillField('[name="root[postal_code]"]', '10000')
|
|
130
|
+
I.pressKey('Tab')
|
|
131
|
+
I.waitForElement('.invalid-feedback')
|
|
132
|
+
I.waitForText('Value must match the pattern [A-Z][0-9][A-Z] [0-9][A-Z][0-9].')
|
|
133
|
+
|
|
134
|
+
I.selectOption('[name="root[country]"]', 'America')
|
|
135
|
+
I.fillField('[name="root[postal_code]"]', '10000')
|
|
136
|
+
I.pressKey('Tab')
|
|
137
|
+
I.dontSee('.invalid-feedback')
|
|
138
|
+
|
|
139
|
+
I.selectOption('[name="root[country]"]', 'Canada')
|
|
140
|
+
I.fillField('[name="root[postal_code]"]', 'K1M 1M4')
|
|
141
|
+
I.pressKey('Tab')
|
|
142
|
+
I.dontSee('.invalid-feedback')
|
|
143
|
+
})
|