@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.
Files changed (99) hide show
  1. package/.eslintrc +5 -2
  2. package/.github/PULL_REQUEST_TEMPLATE.md +6 -6
  3. package/.github/workflows/build.yml +58 -0
  4. package/CHANGELOG.md +41 -1
  5. package/CONTRIBUTING.md +1 -1
  6. package/README.md +39 -4
  7. package/README_ADDON.md +65 -0
  8. package/config/codeceptjs_helpers.js +4 -0
  9. package/dist/jsoneditor.js +2 -2
  10. package/dist/nonmin/jsoneditor.js +3711 -3324
  11. package/dist/nonmin/jsoneditor.js.map +1 -1
  12. package/docs/cleave.html +1 -1
  13. package/docs/datetime.html +1 -1
  14. package/docs/describedby.html +1 -1
  15. package/docs/index.html +4 -2
  16. package/docs/materialize_css.html +1 -1
  17. package/docs/meta_schema.json +0 -1
  18. package/docs/radio.html +1 -1
  19. package/docs/select2.html +1 -1
  20. package/docs/selectize.html +1 -1
  21. package/docs/starrating.html +1 -1
  22. package/docs/wysiwyg.html +1 -1
  23. package/package.json +27 -26
  24. package/release-notes.md +9 -9
  25. package/src/core.js +1 -0
  26. package/src/defaults.js +182 -94
  27. package/src/editor.js +28 -9
  28. package/src/editors/array.js +20 -16
  29. package/src/editors/autocomplete.js +1 -0
  30. package/src/editors/base64.js +5 -4
  31. package/src/editors/button.js +2 -2
  32. package/src/editors/checkbox.js +3 -3
  33. package/src/editors/datetime.js +2 -2
  34. package/src/editors/info.js +1 -1
  35. package/src/editors/multiple.js +8 -2
  36. package/src/editors/multiselect.js +5 -3
  37. package/src/editors/object.js +35 -21
  38. package/src/editors/radio.js +9 -4
  39. package/src/editors/select.js +6 -6
  40. package/src/editors/signature.js +3 -2
  41. package/src/editors/starrating.js +5 -5
  42. package/src/editors/string.js +6 -4
  43. package/src/editors/table.js +24 -14
  44. package/src/editors/upload.js +4 -3
  45. package/src/editors/uuid.js +1 -1
  46. package/src/iconlibs/index.js +2 -0
  47. package/src/iconlibs/openiconic.js +28 -0
  48. package/src/schemaloader.js +112 -28
  49. package/src/theme.js +6 -3
  50. package/src/themes/bootstrap3.js +4 -4
  51. package/src/themes/bootstrap4.js +11 -3
  52. package/src/themes/html.js +1 -2
  53. package/src/themes/materialize.js +1 -1
  54. package/src/themes/spectre.js +11 -8
  55. package/src/themes/tailwind.js +1 -1
  56. package/src/validator.js +128 -16
  57. package/tests/codeceptjs/core_test.js +125 -1
  58. package/tests/codeceptjs/editors/array_test.js +13 -11
  59. package/tests/codeceptjs/editors/button_test.js +6 -1
  60. package/tests/codeceptjs/editors/issues/issue-gh-812_test.js +32 -0
  61. package/tests/codeceptjs/editors/number_test.js +1 -1
  62. package/tests/codeceptjs/editors/object_test.js +216 -100
  63. package/tests/codeceptjs/editors/programmatic-changes_test.js +3 -1
  64. package/tests/codeceptjs/editors/radio_test.js +10 -0
  65. package/tests/codeceptjs/editors/rating_test.js +10 -11
  66. package/tests/codeceptjs/editors/select_test.js +17 -15
  67. package/tests/codeceptjs/editors/stepper_test.js +13 -1
  68. package/tests/codeceptjs/editors/string_test.js +81 -80
  69. package/tests/codeceptjs/editors/table-confirm-delete_test.js +58 -56
  70. package/tests/codeceptjs/editors/tabs_test.js +12 -10
  71. package/tests/codeceptjs/editors/validation_test.js +10 -8
  72. package/tests/codeceptjs/meta-schema_test.js +13 -14
  73. package/tests/codeceptjs/schemaloader_test.js +13 -0
  74. package/tests/codeceptjs/steps_file.js +4 -3
  75. package/tests/codeceptjs/themes_test.js +31 -0
  76. package/tests/docker-compose.yml +4 -3
  77. package/tests/fixtures/validation.json +382 -1
  78. package/tests/pages/_demo.html +2 -0
  79. package/tests/pages/anyof.html +80 -0
  80. package/tests/pages/issues/issue-gh-812.html +110 -0
  81. package/tests/pages/issues/issue-gh-848.html +81 -0
  82. package/tests/pages/meta_schema.json +0 -1
  83. package/tests/pages/object-no-additional-properties.html +27 -12
  84. package/tests/pages/object-required-properties.html +43 -9
  85. package/tests/pages/object-show-opt-in.html +110 -0
  86. package/tests/pages/object-with-dependencies-array.html +56 -0
  87. package/tests/pages/oneof.html +103 -0
  88. package/tests/pages/read-only.html +19 -4
  89. package/tests/pages/stepper-manual.html +57 -0
  90. package/tests/pages/themes.html +2 -0
  91. package/tests/pages/translate-property.html +247 -0
  92. package/tests/pages/urn.html +93 -0
  93. package/tests/unit/core.spec.js +2 -0
  94. package/tests/unit/defaults.spec.js +4 -2
  95. package/tests/unit/editor.spec.js +2 -0
  96. package/tests/unit/editors/array.spec.js +86 -0
  97. package/tests/unit/editors/table.spec.js +91 -0
  98. package/tests/unit/schemaloader.spec.js +362 -3
  99. package/tests/unit/validator.spec.js +14 -2
@@ -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) {
@@ -43,10 +43,9 @@ export class htmlTheme extends AbstractTheme {
43
43
  }
44
44
 
45
45
  addInputError (input, text) {
46
- input.style.borderColor = 'red'
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') {
@@ -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
- if (input.type === 'checkbox') {
223
- label = this.getFormCheckboxControl(label, input, false)
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
- if (infoText) group.insertBefore(infoText, group.firstChild)
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')
@@ -60,7 +60,7 @@ export class tailwindTheme extends AbstractTheme {
60
60
  }
61
61
 
62
62
  getTitle () {
63
- return this.schema.title
63
+ return this.translateProperty(this.schema.title)
64
64
  }
65
65
 
66
66
  getSelectInput (options, multiple) {
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 properties = this._getSchema(givenSchema).properties
435
-
436
- for (const i in properties) {
437
- if (!hasOwnProperty(properties, i)) {
438
- fit.extra += weight
439
- continue
440
- }
441
- if (typeof value[i] === 'object' && typeof properties[i] === 'object' && typeof properties[i].properties === 'object') {
442
- const result = this.fitTest(value[i], properties[i], weight / 100)
443
- fit.match += result.match
444
- fit.extra += result.extra
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
- if (typeof value[i] !== 'undefined') {
447
- fit.match += weight
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 @optional', async (I) => {
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.0"]');
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.see('Value must be of type number.', '[data-schemapath="root.number"] div');
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