@json-editor/json-editor 2.5.2 → 2.6.1
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/.eslintrc +5 -2
- package/.github/PULL_REQUEST_TEMPLATE.md +6 -6
- package/.github/workflows/build.yml +58 -0
- package/CHANGELOG.md +41 -1
- package/CONTRIBUTING.md +1 -1
- package/README.md +39 -4
- package/README_ADDON.md +65 -0
- package/config/codeceptjs_helpers.js +4 -0
- package/dist/jsoneditor.js +2 -2
- package/dist/nonmin/jsoneditor.js +3711 -3324
- package/dist/nonmin/jsoneditor.js.map +1 -1
- package/docs/cleave.html +1 -1
- package/docs/datetime.html +1 -1
- package/docs/describedby.html +1 -1
- package/docs/index.html +4 -2
- package/docs/materialize_css.html +1 -1
- package/docs/meta_schema.json +0 -1
- package/docs/radio.html +1 -1
- package/docs/select2.html +1 -1
- package/docs/selectize.html +1 -1
- package/docs/starrating.html +1 -1
- package/docs/wysiwyg.html +1 -1
- package/package.json +27 -26
- package/release-notes.md +9 -9
- package/src/core.js +1 -0
- package/src/defaults.js +182 -94
- package/src/editor.js +28 -9
- package/src/editors/array.js +20 -16
- package/src/editors/autocomplete.js +1 -0
- package/src/editors/base64.js +5 -4
- package/src/editors/button.js +2 -2
- package/src/editors/checkbox.js +3 -3
- package/src/editors/datetime.js +2 -2
- package/src/editors/info.js +1 -1
- package/src/editors/multiple.js +8 -2
- package/src/editors/multiselect.js +5 -3
- package/src/editors/object.js +35 -21
- package/src/editors/radio.js +9 -4
- package/src/editors/select.js +6 -6
- package/src/editors/signature.js +3 -2
- package/src/editors/starrating.js +5 -5
- package/src/editors/string.js +6 -4
- package/src/editors/table.js +24 -14
- package/src/editors/upload.js +4 -3
- package/src/editors/uuid.js +1 -1
- package/src/iconlibs/index.js +2 -0
- package/src/iconlibs/openiconic.js +28 -0
- package/src/schemaloader.js +112 -28
- package/src/theme.js +6 -3
- package/src/themes/bootstrap3.js +4 -4
- package/src/themes/bootstrap4.js +11 -3
- package/src/themes/html.js +1 -2
- package/src/themes/materialize.js +1 -1
- package/src/themes/spectre.js +11 -8
- package/src/themes/tailwind.js +1 -1
- package/src/validator.js +128 -16
- package/tests/codeceptjs/core_test.js +125 -1
- package/tests/codeceptjs/editors/array_test.js +13 -11
- package/tests/codeceptjs/editors/button_test.js +6 -1
- package/tests/codeceptjs/editors/issues/issue-gh-812_test.js +32 -0
- package/tests/codeceptjs/editors/number_test.js +1 -1
- package/tests/codeceptjs/editors/object_test.js +216 -100
- package/tests/codeceptjs/editors/programmatic-changes_test.js +3 -1
- package/tests/codeceptjs/editors/radio_test.js +10 -0
- package/tests/codeceptjs/editors/rating_test.js +10 -11
- package/tests/codeceptjs/editors/select_test.js +17 -15
- package/tests/codeceptjs/editors/stepper_test.js +13 -1
- package/tests/codeceptjs/editors/string_test.js +81 -80
- package/tests/codeceptjs/editors/table-confirm-delete_test.js +58 -56
- package/tests/codeceptjs/editors/tabs_test.js +12 -10
- package/tests/codeceptjs/editors/validation_test.js +10 -8
- package/tests/codeceptjs/meta-schema_test.js +13 -14
- package/tests/codeceptjs/schemaloader_test.js +13 -0
- package/tests/codeceptjs/steps_file.js +4 -3
- package/tests/codeceptjs/themes_test.js +31 -0
- package/tests/docker-compose.yml +4 -3
- package/tests/fixtures/validation.json +382 -1
- package/tests/pages/_demo.html +2 -0
- package/tests/pages/anyof.html +80 -0
- package/tests/pages/issues/issue-gh-812.html +110 -0
- package/tests/pages/issues/issue-gh-848.html +81 -0
- package/tests/pages/meta_schema.json +0 -1
- package/tests/pages/object-no-additional-properties.html +27 -12
- package/tests/pages/object-required-properties.html +43 -9
- package/tests/pages/object-show-opt-in.html +110 -0
- package/tests/pages/object-with-dependencies-array.html +56 -0
- package/tests/pages/oneof.html +103 -0
- package/tests/pages/read-only.html +19 -4
- package/tests/pages/stepper-manual.html +57 -0
- package/tests/pages/themes.html +2 -0
- package/tests/pages/translate-property.html +247 -0
- package/tests/pages/urn.html +93 -0
- package/tests/unit/core.spec.js +2 -0
- package/tests/unit/defaults.spec.js +4 -2
- package/tests/unit/editor.spec.js +2 -0
- package/tests/unit/editors/array.spec.js +86 -0
- package/tests/unit/editors/table.spec.js +91 -0
- package/tests/unit/schemaloader.spec.js +362 -3
- package/tests/unit/validator.spec.js +14 -2
package/src/themes/bootstrap4.js
CHANGED
|
@@ -135,6 +135,12 @@ export class bootstrap4Theme extends AbstractTheme {
|
|
|
135
135
|
const min = input.getAttribute('min')
|
|
136
136
|
const max = input.getAttribute('max')
|
|
137
137
|
|
|
138
|
+
input.addEventListener('change', () => {
|
|
139
|
+
if (!input.getAttribute('initialized')) {
|
|
140
|
+
input.setAttribute('initialized', '1')
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
138
144
|
minusBtn.addEventListener('click', () => {
|
|
139
145
|
if (!input.getAttribute('initialized')) {
|
|
140
146
|
initialize(input, min)
|
|
@@ -246,6 +252,7 @@ export class bootstrap4Theme extends AbstractTheme {
|
|
|
246
252
|
if (window.jQuery && window.jQuery().tooltip) {
|
|
247
253
|
window.jQuery(button).tooltip()
|
|
248
254
|
} else {
|
|
255
|
+
// eslint-disable-next-line no-console
|
|
249
256
|
console.warn('Could not find popper jQuery plugin of Bootstrap.')
|
|
250
257
|
}
|
|
251
258
|
} else if (this.options.tooltip === 'css') {
|
|
@@ -398,12 +405,13 @@ export class bootstrap4Theme extends AbstractTheme {
|
|
|
398
405
|
return el
|
|
399
406
|
}
|
|
400
407
|
|
|
401
|
-
getHeader (text) {
|
|
408
|
+
getHeader (text, pathDepth) {
|
|
402
409
|
/* var cardHeader = document.createElement('div') */
|
|
403
410
|
/* cardHeader.classList.add('card-header') */
|
|
404
411
|
|
|
405
412
|
const el = document.createElement('h3')
|
|
406
413
|
el.classList.add('card-title')
|
|
414
|
+
el.classList.add('level-' + pathDepth)
|
|
407
415
|
|
|
408
416
|
if (typeof text === 'string') {
|
|
409
417
|
el.textContent = text
|
|
@@ -475,7 +483,7 @@ export class bootstrap4Theme extends AbstractTheme {
|
|
|
475
483
|
addInputError (input, text) {
|
|
476
484
|
if (!input.controlgroup) return
|
|
477
485
|
|
|
478
|
-
input.classList.add('is-invalid')
|
|
486
|
+
input.controlgroup.classList.add('is-invalid')
|
|
479
487
|
|
|
480
488
|
if (!input.errmsg) {
|
|
481
489
|
input.errmsg = document.createElement('p')
|
|
@@ -491,7 +499,7 @@ export class bootstrap4Theme extends AbstractTheme {
|
|
|
491
499
|
removeInputError (input) {
|
|
492
500
|
if (!input.errmsg) return
|
|
493
501
|
input.errmsg.style.display = 'none'
|
|
494
|
-
input.classList.remove('is-invalid')
|
|
502
|
+
input.controlgroup.classList.remove('is-invalid')
|
|
495
503
|
}
|
|
496
504
|
|
|
497
505
|
getTabHolder (propertyName) {
|
package/src/themes/html.js
CHANGED
|
@@ -43,10 +43,9 @@ export class htmlTheme extends AbstractTheme {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
addInputError (input, text) {
|
|
46
|
-
|
|
46
|
+
const group = this.closest(input, '.form-control') || input.controlgroup
|
|
47
47
|
|
|
48
48
|
if (!input.errmsg) {
|
|
49
|
-
const group = this.closest(input, '.form-control')
|
|
50
49
|
input.errmsg = document.createElement('div')
|
|
51
50
|
input.errmsg.setAttribute('class', 'errmsg')
|
|
52
51
|
input.errmsg.style = input.errmsg.style || {}
|
|
@@ -140,7 +140,7 @@ export class materializeTheme extends AbstractTheme {
|
|
|
140
140
|
* @param {string|HTMLElement} text The header text or element.
|
|
141
141
|
* @returns {HTMLElement} The header element.
|
|
142
142
|
*/
|
|
143
|
-
getHeader (text) {
|
|
143
|
+
getHeader (text, pathDepth) {
|
|
144
144
|
const el = document.createElement('h5')
|
|
145
145
|
|
|
146
146
|
if (typeof text === 'string') {
|
package/src/themes/spectre.js
CHANGED
|
@@ -98,7 +98,7 @@ export class spectreTheme extends AbstractTheme {
|
|
|
98
98
|
return el
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
getHeader (text) {
|
|
101
|
+
getHeader (text, pathDepth) {
|
|
102
102
|
const el = document.createElement('h4')
|
|
103
103
|
if (typeof text === 'string') {
|
|
104
104
|
el.textContent = text
|
|
@@ -146,7 +146,6 @@ export class spectreTheme extends AbstractTheme {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
getMultiCheckboxHolder (controls, label, description, infoText) {
|
|
149
|
-
console.log('mul')
|
|
150
149
|
return super.getMultiCheckboxHolder(controls, label, description, infoText)
|
|
151
150
|
}
|
|
152
151
|
|
|
@@ -218,13 +217,17 @@ export class spectreTheme extends AbstractTheme {
|
|
|
218
217
|
const group = document.createElement('div')
|
|
219
218
|
group.classList.add('form-group')
|
|
220
219
|
|
|
221
|
-
if (label) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
label.classList.add('form-label')
|
|
220
|
+
if (label && (input.type === 'checkbox' || input.type === 'radio')) {
|
|
221
|
+
group.classList.add(input.type)
|
|
222
|
+
label.insertBefore(input, label.firstChild)
|
|
226
223
|
group.appendChild(label)
|
|
227
|
-
|
|
224
|
+
} else {
|
|
225
|
+
if (label) {
|
|
226
|
+
label.classList.add('form-label')
|
|
227
|
+
group.appendChild(label)
|
|
228
|
+
if (infoText) label.appendChild(infoText)
|
|
229
|
+
}
|
|
230
|
+
group.appendChild(input)
|
|
228
231
|
}
|
|
229
232
|
|
|
230
233
|
if (this.options.input_size === 'small') input.classList.add('input-sm', 'select-sm')
|
package/src/themes/tailwind.js
CHANGED
package/src/validator.js
CHANGED
|
@@ -7,9 +7,21 @@ export class Validator {
|
|
|
7
7
|
this.schema = schema || this.jsoneditor.schema
|
|
8
8
|
this.options = options || {}
|
|
9
9
|
this.translate = this.jsoneditor.translate || defaults.translate
|
|
10
|
+
this.translateProperty = this.jsoneditor.translateProperty || defaults.translateProperty
|
|
10
11
|
this.defaults = defaults
|
|
11
12
|
|
|
12
13
|
this._validateSubSchema = {
|
|
14
|
+
const (schema, value, path) {
|
|
15
|
+
const valid = JSON.stringify(schema.const) === JSON.stringify(value) && !(Array.isArray(value) || typeof value === 'object')
|
|
16
|
+
if (!valid) {
|
|
17
|
+
return [{
|
|
18
|
+
path,
|
|
19
|
+
property: 'const',
|
|
20
|
+
message: this.translate('error_const')
|
|
21
|
+
}]
|
|
22
|
+
}
|
|
23
|
+
return []
|
|
24
|
+
},
|
|
13
25
|
enum (schema, value, path) {
|
|
14
26
|
const stringified = JSON.stringify(value)
|
|
15
27
|
const valid = schema.enum.some(e => stringified === JSON.stringify(e))
|
|
@@ -244,7 +256,6 @@ export class Validator {
|
|
|
244
256
|
/* If this item has a specific schema tied to it */
|
|
245
257
|
/* Validate against it */
|
|
246
258
|
if (schema.items[i]) {
|
|
247
|
-
console.log('-->')
|
|
248
259
|
errors.push(...this._validateSchema(schema.items[i], value[i], `${path}.${i}`))
|
|
249
260
|
/* If all additional items are allowed */
|
|
250
261
|
} else if (schema.additionalItems === true) {
|
|
@@ -339,12 +350,13 @@ export class Validator {
|
|
|
339
350
|
schema.required.forEach(e => {
|
|
340
351
|
if (typeof value[e] !== 'undefined') return
|
|
341
352
|
const editor = this.jsoneditor.getEditor(`${path}.${e}`)
|
|
353
|
+
if (editor && editor.dependenciesFulfilled === false) return
|
|
342
354
|
/* Ignore required error if editor is of type "button" or "info" */
|
|
343
355
|
if (editor && ['button', 'info'].includes(editor.schema.format || editor.schema.type)) return
|
|
344
356
|
errors.push({
|
|
345
357
|
path,
|
|
346
358
|
property: 'required',
|
|
347
|
-
message: this.translate('error_required', [e])
|
|
359
|
+
message: this.translate('error_required', [schema && schema.properties && schema.properties[e] && schema.properties[e].title ? schema.properties[e].title : e])
|
|
348
360
|
})
|
|
349
361
|
})
|
|
350
362
|
}
|
|
@@ -375,6 +387,91 @@ export class Validator {
|
|
|
375
387
|
}
|
|
376
388
|
|
|
377
389
|
this._validateObjectSubSchema2 = {
|
|
390
|
+
propertyNames (schema, value, path, validatedProperties) {
|
|
391
|
+
const errors = []
|
|
392
|
+
const keys = Object.keys(value)
|
|
393
|
+
let k = null
|
|
394
|
+
for (let i = 0; i < keys.length; i++) {
|
|
395
|
+
let msg = ''
|
|
396
|
+
let truthy = false
|
|
397
|
+
k = keys[i]
|
|
398
|
+
/* Check property names that don't match */
|
|
399
|
+
if (typeof schema.propertyNames === 'boolean') {
|
|
400
|
+
if (schema.propertyNames === true) {
|
|
401
|
+
continue
|
|
402
|
+
}
|
|
403
|
+
errors.push({
|
|
404
|
+
path,
|
|
405
|
+
property: 'propertyNames',
|
|
406
|
+
message: this.translate('error_property_names_false', [k])
|
|
407
|
+
})
|
|
408
|
+
break
|
|
409
|
+
}
|
|
410
|
+
truthy = Object.entries(schema.propertyNames).every(([j, prop]) => {
|
|
411
|
+
let match = false
|
|
412
|
+
let regex = null
|
|
413
|
+
switch (j) {
|
|
414
|
+
case 'maxLength':
|
|
415
|
+
if (typeof prop !== 'number') {
|
|
416
|
+
msg = 'error_property_names_maxlength'
|
|
417
|
+
break
|
|
418
|
+
}
|
|
419
|
+
if (k.length > prop) {
|
|
420
|
+
msg = 'error_property_names_exceeds_maxlength'
|
|
421
|
+
break
|
|
422
|
+
}
|
|
423
|
+
return true
|
|
424
|
+
case 'const':
|
|
425
|
+
if (prop !== k) {
|
|
426
|
+
msg = 'error_property_names_const_mismatch'
|
|
427
|
+
break
|
|
428
|
+
}
|
|
429
|
+
return true
|
|
430
|
+
case 'enum':
|
|
431
|
+
if (!Array.isArray(prop)) {
|
|
432
|
+
msg = 'error_property_names_enum'
|
|
433
|
+
break
|
|
434
|
+
}
|
|
435
|
+
prop.forEach(p => {
|
|
436
|
+
if (p === k) {
|
|
437
|
+
match = true
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
if (!match) {
|
|
441
|
+
msg = 'error_property_names_enum_mismatch'
|
|
442
|
+
break
|
|
443
|
+
}
|
|
444
|
+
return true
|
|
445
|
+
case 'pattern':
|
|
446
|
+
if (typeof prop !== 'string') {
|
|
447
|
+
msg = 'error_property_names_pattern'
|
|
448
|
+
break
|
|
449
|
+
}
|
|
450
|
+
regex = new RegExp(prop)
|
|
451
|
+
if (!regex.test(k)) {
|
|
452
|
+
msg = 'error_property_names_pattern_mismatch'
|
|
453
|
+
break
|
|
454
|
+
}
|
|
455
|
+
return true
|
|
456
|
+
default:
|
|
457
|
+
errors.push({
|
|
458
|
+
path,
|
|
459
|
+
property: 'propertyNames',
|
|
460
|
+
message: this.translate('error_property_names_unsupported', [j])
|
|
461
|
+
})
|
|
462
|
+
return false
|
|
463
|
+
}
|
|
464
|
+
errors.push({
|
|
465
|
+
path,
|
|
466
|
+
property: 'propertyNames',
|
|
467
|
+
message: this.translate(msg, [k])
|
|
468
|
+
})
|
|
469
|
+
return false
|
|
470
|
+
})
|
|
471
|
+
if (!truthy) break
|
|
472
|
+
}
|
|
473
|
+
return errors
|
|
474
|
+
},
|
|
378
475
|
additionalProperties (schema, value, path, validatedProperties) {
|
|
379
476
|
const errors = []
|
|
380
477
|
const keys = Object.keys(value)
|
|
@@ -431,20 +528,35 @@ export class Validator {
|
|
|
431
528
|
const fit = { match: 0, extra: 0 }
|
|
432
529
|
if (typeof value === 'object' && value !== null) {
|
|
433
530
|
/* Work on a copy of the schema */
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
531
|
+
const schema = this._getSchema(givenSchema)
|
|
532
|
+
/* If the schema is an anyOf declaration, do use the properties of the allowed sub schemata instead.
|
|
533
|
+
Of these sub schemata, the best fit is selected */
|
|
534
|
+
if (schema.anyOf) {
|
|
535
|
+
let bestFit = { ...fit }
|
|
536
|
+
for (const subSchema of schema.anyOf) {
|
|
537
|
+
const subFit = this.fitTest(value, subSchema, weight)
|
|
538
|
+
/* The best fit is the one with the best value for match. If there are multiple results
|
|
539
|
+
with the same match value, use the one with the least number of extra properties */
|
|
540
|
+
if ((subFit.match > bestFit.match) || (subFit.match === bestFit.match && subFit.extra < bestFit.extra)) {
|
|
541
|
+
bestFit = subFit
|
|
542
|
+
}
|
|
445
543
|
}
|
|
446
|
-
|
|
447
|
-
|
|
544
|
+
return bestFit
|
|
545
|
+
} else {
|
|
546
|
+
const properties = this._getSchema(givenSchema).properties
|
|
547
|
+
for (const i in properties) {
|
|
548
|
+
if (!hasOwnProperty(properties, i)) {
|
|
549
|
+
fit.extra += weight
|
|
550
|
+
continue
|
|
551
|
+
}
|
|
552
|
+
if (typeof value[i] === 'object' && typeof properties[i] === 'object' && typeof properties[i].properties === 'object') {
|
|
553
|
+
const result = this.fitTest(value[i], properties[i], weight / 100)
|
|
554
|
+
fit.match += result.match
|
|
555
|
+
fit.extra += result.extra
|
|
556
|
+
}
|
|
557
|
+
if (typeof value[i] !== 'undefined') {
|
|
558
|
+
fit.match += weight
|
|
559
|
+
}
|
|
448
560
|
}
|
|
449
561
|
}
|
|
450
562
|
}
|
|
@@ -522,7 +634,7 @@ export class Validator {
|
|
|
522
634
|
}
|
|
523
635
|
|
|
524
636
|
_validateV3Required (schema, value, path) {
|
|
525
|
-
if ((typeof schema.required !== 'undefined' && schema.required === true) || (typeof schema.required === 'undefined' && this.jsoneditor.options.required_by_default === true)) {
|
|
637
|
+
if (((typeof schema.required !== 'undefined' && schema.required === true) || (typeof schema.required === 'undefined' && this.jsoneditor.options.required_by_default === true)) && (schema.type !== 'info')) {
|
|
526
638
|
return [{
|
|
527
639
|
path,
|
|
528
640
|
property: 'required',
|
|
@@ -57,7 +57,7 @@ Scenario('should watch a specific field for changes', async (I) => {
|
|
|
57
57
|
I.seeElement('.name-changed')
|
|
58
58
|
})
|
|
59
59
|
|
|
60
|
-
Scenario('should watch form for changes
|
|
60
|
+
Scenario('should watch form for changes', async (I) => {
|
|
61
61
|
I.amOnPage('core.html')
|
|
62
62
|
I.dontSeeElement('.form-changed')
|
|
63
63
|
I.click('.set-value')
|
|
@@ -91,3 +91,127 @@ Scenario('should change the form if form_name_root option is set @core', async (
|
|
|
91
91
|
I.click('#get-value-form-2')
|
|
92
92
|
assert.equal(await I.grabValueFrom('#value-form-2'), '"no"')
|
|
93
93
|
})
|
|
94
|
+
|
|
95
|
+
Scenario('should validate against oneOf schemas and display single oneOf and editors error messages @core @oneof', async (I) => {
|
|
96
|
+
I.amOnPage('oneof.html')
|
|
97
|
+
I.waitForText('Object is missing the required property \'p4\'', '.alert-danger')
|
|
98
|
+
I.waitForText('Value must validate against exactly one of the provided schemas. It currently validates against 0 of the schemas.', '.alert-danger')
|
|
99
|
+
I.waitForText('Object is missing the required property \'p1\'', '.alert-danger')
|
|
100
|
+
I.waitForText('Object is missing the required property \'p2\'', '.alert-danger')
|
|
101
|
+
I.waitForText('Property must be set.', '[data-schemapath="root.p4"] .invalid-feedback')
|
|
102
|
+
I.waitForText('Property must be set.', '[data-schemapath="root.p5.p1"] .invalid-feedback')
|
|
103
|
+
I.waitForText('Property must be set.', '[data-schemapath="root.p5.p2"] .invalid-feedback')
|
|
104
|
+
I.fillField('root[p4]', 'to')
|
|
105
|
+
I.fillField('root[p5][p1]', 'to')
|
|
106
|
+
I.fillField('root[p5][p2]', 'to')
|
|
107
|
+
I.click('Get Value')
|
|
108
|
+
I.wait(3)
|
|
109
|
+
I.dontSee('Object is missing the required property \'p4\'', '.alert-danger')
|
|
110
|
+
I.dontSee('Object is missing the required property \'p1\'', '.alert-danger')
|
|
111
|
+
I.dontSee('Object is missing the required property \'p2\'', '.alert-danger')
|
|
112
|
+
I.waitForText('Value must be at least 4 characters long.', '[data-schemapath="root.p4"] .invalid-feedback')
|
|
113
|
+
I.waitForText('Value must be at least 4 characters long.', '[data-schemapath="root.p5.p1"] .invalid-feedback')
|
|
114
|
+
I.waitForText('Value must be at least 4 characters long.', '[data-schemapath="root.p5.p2"] .invalid-feedback')
|
|
115
|
+
I.fillField('root[p4]', 'todo')
|
|
116
|
+
I.fillField('root[p5][p1]', 'todo')
|
|
117
|
+
I.fillField('root[p5][p2]', 'todo')
|
|
118
|
+
I.click('Get Value')
|
|
119
|
+
I.wait(3)
|
|
120
|
+
I.dontSee('Value must be at least 4 characters long.', '[data-schemapath="root.p4"] .invalid-feedback')
|
|
121
|
+
I.dontSee('Value must be at least 4 characters long.', '[data-schemapath="root.p5.p1"] .invalid-feedback')
|
|
122
|
+
I.dontSee('Value must be at least 4 characters long.', '[data-schemapath="root.p5.p2"] .invalid-feedback')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
Scenario('should validate against anyOf schemas and display single anyOf and editors error messages @core @anyof', async (I) => {
|
|
126
|
+
I.amOnPage('anyof.html')
|
|
127
|
+
I.dontSeeElement('.alert-danger')
|
|
128
|
+
I.selectOption('.je-switcher', 'Value, number')
|
|
129
|
+
I.dontSeeElement('.alert-danger')
|
|
130
|
+
I.selectOption('.je-switcher', 'Value, null')
|
|
131
|
+
I.dontSeeElement('.alert-danger')
|
|
132
|
+
I.selectOption('.je-switcher', 'Value, string')
|
|
133
|
+
I.waitForText('Object is missing the required property \'age\'', '.alert-danger')
|
|
134
|
+
I.waitForText('Property must be set.', '[data-schemapath="root.age"] .invalid-feedback')
|
|
135
|
+
I.fillField('root[age]', 'to')
|
|
136
|
+
I.click('Get Value')
|
|
137
|
+
I.wait(3)
|
|
138
|
+
I.dontSee('Object is missing the required property \'age\'', '.alert-danger')
|
|
139
|
+
I.dontSee('Property must be set.', '[data-schemapath="root.age"] .invalid-feedback')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
Scenario('should display anyOf and oneOf error messages in the correct places @848', async (I) => {
|
|
143
|
+
I.amOnPage('issues/issue-gh-848.html')
|
|
144
|
+
I.selectOption('.je-switcher', 'Value, string')
|
|
145
|
+
I.waitForElement('[data-schemapath="root.list"] .invalid-feedback', 5)
|
|
146
|
+
I.dontSeeElement('[data-schemapath="root.list_group"] .invalid-feedback', 5)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
Scenario('should validate against oneOf schemas and display single oneOf and editors error messages @core @translate-property', async (I) => {
|
|
150
|
+
I.amOnPage('translate-property.html?lang=en')
|
|
151
|
+
I.waitForText('Object Title')
|
|
152
|
+
I.waitForText('Object Description')
|
|
153
|
+
I.waitForText('Boolean Title')
|
|
154
|
+
I.waitForText('Boolean Description')
|
|
155
|
+
I.seeInSource('Boolean Info Text')
|
|
156
|
+
I.waitForText('String Title')
|
|
157
|
+
I.waitForText('String Description')
|
|
158
|
+
I.seeInSource('String Info Text')
|
|
159
|
+
I.waitForText('String Radio Title')
|
|
160
|
+
I.waitForText('String Radio Description')
|
|
161
|
+
I.seeInSource('String Radio Info Text')
|
|
162
|
+
I.waitForText('Integer Title')
|
|
163
|
+
I.waitForText('Integer Description')
|
|
164
|
+
I.seeInSource('Integer Info Text')
|
|
165
|
+
I.waitForText('Number Title')
|
|
166
|
+
I.waitForText('Number Description')
|
|
167
|
+
I.seeInSource('Number Info Text')
|
|
168
|
+
I.waitForText('Array Title')
|
|
169
|
+
I.waitForText('Array Description')
|
|
170
|
+
I.seeInSource('Array Info Text')
|
|
171
|
+
I.waitForText('Array Tabs Title')
|
|
172
|
+
I.waitForText('Array Tabs Description')
|
|
173
|
+
I.seeInSource('Array Tabs Info Text')
|
|
174
|
+
I.waitForText('Array Table Title')
|
|
175
|
+
I.waitForText('Array Table Description')
|
|
176
|
+
I.seeInSource('Array Table Info Text')
|
|
177
|
+
I.waitForText('Signature Title')
|
|
178
|
+
I.waitForText('Signature Description')
|
|
179
|
+
I.seeInSource('Signature Info Text')
|
|
180
|
+
I.waitForText('Rating Title')
|
|
181
|
+
I.waitForText('Rating Description')
|
|
182
|
+
I.seeInSource('Rating Info Text')
|
|
183
|
+
|
|
184
|
+
I.amOnPage('translate-property.html?lang=de')
|
|
185
|
+
I.waitForText('Object Title (but in german)')
|
|
186
|
+
I.waitForText('Object Description (but in german)')
|
|
187
|
+
I.waitForText('Boolean Title (but in german)')
|
|
188
|
+
I.waitForText('Boolean Description (but in german)')
|
|
189
|
+
I.seeInSource('Boolean Info Text (but in german)')
|
|
190
|
+
I.waitForText('String Title (but in german)')
|
|
191
|
+
I.waitForText('String Description (but in german)')
|
|
192
|
+
I.seeInSource('String Info Text (but in german)')
|
|
193
|
+
I.waitForText('String Radio Title (but in german)')
|
|
194
|
+
I.waitForText('String Radio Description (but in german)')
|
|
195
|
+
I.seeInSource('String Radio Info Text (but in german)')
|
|
196
|
+
I.waitForText('Integer Title (but in german)')
|
|
197
|
+
I.waitForText('Integer Description (but in german)')
|
|
198
|
+
I.seeInSource('Integer Info Text (but in german)')
|
|
199
|
+
I.waitForText('Number Title (but in german)')
|
|
200
|
+
I.waitForText('Number Description (but in german)')
|
|
201
|
+
I.seeInSource('Number Info Text (but in german)')
|
|
202
|
+
I.waitForText('Array Title (but in german)')
|
|
203
|
+
I.waitForText('Array Description (but in german)')
|
|
204
|
+
I.seeInSource('Array Info Text (but in german)')
|
|
205
|
+
I.waitForText('Array Tabs Title (but in german)')
|
|
206
|
+
I.waitForText('Array Tabs Description (but in german)')
|
|
207
|
+
I.seeInSource('Array Tabs Info Text (but in german)')
|
|
208
|
+
I.waitForText('Array Table Title (but in german)')
|
|
209
|
+
I.waitForText('Array Table Description (but in german)')
|
|
210
|
+
I.seeInSource('Array Table Info Text (but in german)')
|
|
211
|
+
I.waitForText('Signature Title (but in german)')
|
|
212
|
+
I.waitForText('Signature Description (but in german)')
|
|
213
|
+
I.seeInSource('Signature Info Text (but in german)')
|
|
214
|
+
I.waitForText('Rating Title (but in german)')
|
|
215
|
+
I.waitForText('Rating Description (but in german)')
|
|
216
|
+
I.seeInSource('Rating Info Text (but in german)')
|
|
217
|
+
})
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* global Feature Scenario */
|
|
2
|
+
|
|
1
3
|
var assert = require('assert');
|
|
2
4
|
|
|
3
5
|
Feature('array');
|
|
@@ -8,7 +10,7 @@ Scenario('should have correct initial value', async (I) => {
|
|
|
8
10
|
assert.equal(await I.grabValueFrom('.debug'), '[]');
|
|
9
11
|
});
|
|
10
12
|
|
|
11
|
-
Scenario('should trigger array (table) editing triggers', async (I) => {
|
|
13
|
+
Scenario('should trigger array (table) editing triggers @retry', async (I) => {
|
|
12
14
|
I.amOnPage('table-move-events.html');
|
|
13
15
|
I.seeElement('[data-schemapath="root.0"]');
|
|
14
16
|
I.seeElement('[data-schemapath="root.1"]');
|
|
@@ -21,7 +23,7 @@ Scenario('should trigger array (table) editing triggers', async (I) => {
|
|
|
21
23
|
|
|
22
24
|
I.amAcceptingPopups();
|
|
23
25
|
I.click('//button[contains(@class, "json-editor-btn-moveup") and @data-i="1"]');
|
|
24
|
-
I.seeInPopup('moveRow');
|
|
26
|
+
I.retry({ retries: 5, minTimeout: 500 }).seeInPopup('moveRow');
|
|
25
27
|
I.acceptPopup();
|
|
26
28
|
I.click('.get-value');
|
|
27
29
|
value = await I.grabValueFrom('.debug');
|
|
@@ -29,7 +31,7 @@ Scenario('should trigger array (table) editing triggers', async (I) => {
|
|
|
29
31
|
|
|
30
32
|
I.amAcceptingPopups();
|
|
31
33
|
I.click('//button[contains(@class, "json-editor-btn-movedown") and @data-i="1"]');
|
|
32
|
-
I.seeInPopup('moveRow');
|
|
34
|
+
I.retry({ retries: 5, minTimeout: 500 }).seeInPopup('moveRow');
|
|
33
35
|
I.acceptPopup();
|
|
34
36
|
I.click('.get-value');
|
|
35
37
|
value = await I.grabValueFrom('.debug');
|
|
@@ -37,7 +39,7 @@ Scenario('should trigger array (table) editing triggers', async (I) => {
|
|
|
37
39
|
|
|
38
40
|
I.amAcceptingPopups();
|
|
39
41
|
I.click('//button[contains(@class, "json-editor-btn-copy") and @data-i="2"]');
|
|
40
|
-
I.seeInPopup('copyRow');
|
|
42
|
+
I.retry({ retries: 5, minTimeout: 500 }).seeInPopup('copyRow');
|
|
41
43
|
I.acceptPopup();
|
|
42
44
|
I.click('.get-value');
|
|
43
45
|
value = await I.grabValueFrom('.debug');
|
|
@@ -45,7 +47,7 @@ Scenario('should trigger array (table) editing triggers', async (I) => {
|
|
|
45
47
|
|
|
46
48
|
I.amAcceptingPopups();
|
|
47
49
|
I.click('.json-editor-btntype-add');
|
|
48
|
-
I.seeInPopup('addRow');
|
|
50
|
+
I.retry({ retries: 5, minTimeout: 500 }).seeInPopup('addRow');
|
|
49
51
|
I.acceptPopup();
|
|
50
52
|
I.click('.get-value');
|
|
51
53
|
value = await I.grabValueFrom('.debug');
|
|
@@ -58,10 +60,10 @@ Scenario('should trigger array (table) editing triggers', async (I) => {
|
|
|
58
60
|
// form field. Similar to the '.debug' field.
|
|
59
61
|
I.amAcceptingPopups();
|
|
60
62
|
I.click('.json-editor-btntype-deletelast');
|
|
61
|
-
I.seeInPopup('Are you sure you want to remove this node?');
|
|
63
|
+
I.retry({ retries: 5, minTimeout: 500 }).seeInPopup('Are you sure you want to remove this node?');
|
|
62
64
|
I.acceptPopup();
|
|
63
65
|
I.amAcceptingPopups();
|
|
64
|
-
I.seeInPopup('deleteRow');
|
|
66
|
+
I.retry({ retries: 5, minTimeout: 500 }).seeInPopup('deleteRow');
|
|
65
67
|
I.acceptPopup();
|
|
66
68
|
I.click('.get-value');
|
|
67
69
|
value = await I.grabValueFrom('.debug');
|
|
@@ -70,10 +72,10 @@ Scenario('should trigger array (table) editing triggers', async (I) => {
|
|
|
70
72
|
// This test will fail when using Puppeteer due to the way Puppeteer handles popups.
|
|
71
73
|
I.amAcceptingPopups();
|
|
72
74
|
I.click('.json-editor-btntype-deleteall');
|
|
73
|
-
I.seeInPopup('Are you sure you want to remove this node?');
|
|
75
|
+
I.retry({ retries: 5, minTimeout: 500 }).seeInPopup('Are you sure you want to remove this node?');
|
|
74
76
|
I.acceptPopup();
|
|
75
77
|
I.amAcceptingPopups();
|
|
76
|
-
I.seeInPopup('deleteAllRows');
|
|
78
|
+
I.retry({ retries: 5, minTimeout: 500 }).seeInPopup('deleteAllRows');
|
|
77
79
|
I.acceptPopup();
|
|
78
80
|
I.click('.get-value');
|
|
79
81
|
value = await I.grabValueFrom('.debug');
|
|
@@ -873,9 +875,9 @@ Scenario('should work well with nested array editors', async (I) => {
|
|
|
873
875
|
Scenario('should work well with selectize multiselect editors', async (I) => {
|
|
874
876
|
I.amOnPage('array-selectize.html');
|
|
875
877
|
I.click('Add item');
|
|
878
|
+
await I.seeElement('[data-schemapath="root.0"]');
|
|
876
879
|
I.click('Add item');
|
|
877
|
-
I.seeElement('[data-schemapath="root.
|
|
878
|
-
I.seeElement('[data-schemapath="root.1"]');
|
|
880
|
+
await I.seeElement('[data-schemapath="root.1"]');
|
|
879
881
|
I.click('.get-value');
|
|
880
882
|
value = await I.grabValueFrom('.debug');
|
|
881
883
|
// ensure defaults
|
|
@@ -12,7 +12,7 @@ Scenario('should work with button editor callbacks', async (I) => {
|
|
|
12
12
|
Scenario('should work with option "validated"', async (I) => {
|
|
13
13
|
I.amOnPage('button-callbacks.html');
|
|
14
14
|
I.seeElement('[data-schemapath="root.button1"] button');
|
|
15
|
-
I.seeDisabledAttribute('[data-schemapath="root.button2"] button');
|
|
15
|
+
I.retry({ retries: 3, minTimeout: 500 }).seeDisabledAttribute('[data-schemapath="root.button2"] button');
|
|
16
16
|
|
|
17
17
|
await I.fillField('[name="root[textinput]"]', 'Hello World');
|
|
18
18
|
|
|
@@ -28,3 +28,8 @@ Scenario('should not leave any footprints in result', async (I) => {
|
|
|
28
28
|
assert.equal(await I.grabValueFrom('.value'), JSON.stringify({"textinput":""}));
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
Scenario('should be disabled if "readonly" is specified', async (I) => {
|
|
32
|
+
I.amOnPage('read-only.html');
|
|
33
|
+
|
|
34
|
+
I.seeDisabledAttribute('[data-schemapath="root.button"] button');
|
|
35
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* global Feature Scenario */
|
|
2
|
+
|
|
3
|
+
const assert = require('assert')
|
|
4
|
+
let value
|
|
5
|
+
|
|
6
|
+
Feature('issues')
|
|
7
|
+
|
|
8
|
+
Scenario('GitHub issue 812 should remain fixed @issue-812', async (I) => {
|
|
9
|
+
I.amOnPage('issues/issue-gh-812.html')
|
|
10
|
+
|
|
11
|
+
I.click('.get-value')
|
|
12
|
+
value = await I.grabValueFrom('.debug')
|
|
13
|
+
assert.equal(value, '{"students":[{"name":"AAA","sessions":[{"student_name":"AAA","minutes":15},{"student_name":"AAA","minutes":15}]},{"name":"BBB","sessions":[{"student_name":"BBB","minutes":20}]},{"name":"CCC","sessions":[{"student_name":"CCC","minutes":10}]}]}')
|
|
14
|
+
|
|
15
|
+
I.amAcceptingPopups()
|
|
16
|
+
I.click('//*[@id="root.students.0"]/span[2]/button[contains(@class, "json-editor-btn-delete") and @data-i="0"]')
|
|
17
|
+
I.retry({ retries: 5, minTimeout: 500 }).seeInPopup('Are you sure you want to remove this node?')
|
|
18
|
+
I.acceptPopup()
|
|
19
|
+
|
|
20
|
+
I.click('.get-value')
|
|
21
|
+
value = await I.grabValueFrom('.debug')
|
|
22
|
+
assert.equal(value, '{"students":[{"name":"BBB","sessions":[{"student_name":"BBB","minutes":20}]},{"name":"CCC","sessions":[{"student_name":"CCC","minutes":10}]}]}')
|
|
23
|
+
|
|
24
|
+
I.amAcceptingPopups()
|
|
25
|
+
I.click('//*[@id="root.students.0"]/span[2]/button[contains(@class, "json-editor-btn-delete") and @data-i="0"]')
|
|
26
|
+
I.retry({ retries: 5, minTimeout: 500 }).seeInPopup('Are you sure you want to remove this node?')
|
|
27
|
+
I.acceptPopup()
|
|
28
|
+
|
|
29
|
+
I.click('.get-value')
|
|
30
|
+
value = await I.grabValueFrom('.debug')
|
|
31
|
+
assert.equal(value, '{"students":[{"name":"CCC","sessions":[{"student_name":"CCC","minutes":10}]}]}')
|
|
32
|
+
})
|
|
@@ -12,7 +12,7 @@ Scenario('should validate value', async (I) => {
|
|
|
12
12
|
I.amOnPage('number.html');
|
|
13
13
|
await I.fillField('[name="root[number]"]', '12-12');
|
|
14
14
|
I.click('.get-value');
|
|
15
|
-
I.
|
|
15
|
+
I.waitForText('Value must be of type number.', 5, '[data-schemapath="root.number"] .invalid-feedback');
|
|
16
16
|
assert.equal(await I.grabValueFrom('.value'), '{"number":"12-12","number_number":5.75,"number_range":5.75}');
|
|
17
17
|
});
|
|
18
18
|
|