From 39e6c94d090b084669cc985790608766b3e768e2 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 12:55:23 +0200 Subject: [PATCH 01/20] test(settings): Add settings validator utility test boilerplate --- .../utils/__tests__/settingsValidator.test.js | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/features/settings/utils/__tests__/settingsValidator.test.js diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js new file mode 100644 index 0000000..923f4d9 --- /dev/null +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -0,0 +1,37 @@ +/* +Copyright 2026 Seekra + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { describe } from 'vitest'; + +describe('validateSettingsConfig', () => { + +}); + +describe('validateEntry', () => { + +}); + +describe('validateSelectionOptions', () => { + +}); + +describe('assertType', () => { + +}); + +describe('assertString', () => { + +}); \ No newline at end of file -- 2.39.5 From 156b3b552c3d3366e9fd695ad88269565602b2e4 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 13:53:47 +0200 Subject: [PATCH 02/20] test(settings): add unit test for the settings validator assertString function for test cases that should throw an error --- .../utils/__tests__/settingsValidator.test.js | 16 +++++++++++++++- src/features/settings/utils/settingsValidator.js | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js index 923f4d9..2d54b20 100644 --- a/src/features/settings/utils/__tests__/settingsValidator.test.js +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { describe } from 'vitest'; +import { describe, test, expect } from 'vitest'; +import { assertString } from '../settingsValidator'; describe('validateSettingsConfig', () => { @@ -33,5 +34,18 @@ describe('assertType', () => { }); describe('assertString', () => { + test.for([ + [0], + [1], + [42], + [-1], + [-42], + [''], + [' '], + [' '], + [' '] + ])('throws error for the value %s', ([ value ]) => { + expect(() => assertString(value)).throws(Error); + }); }); \ No newline at end of file diff --git a/src/features/settings/utils/settingsValidator.js b/src/features/settings/utils/settingsValidator.js index ec30713..74372be 100644 --- a/src/features/settings/utils/settingsValidator.js +++ b/src/features/settings/utils/settingsValidator.js @@ -16,7 +16,7 @@ limitations under the License. const VALID_TYPES = ['bool', 'number', 'string', 'selection', 'section']; -function assertString(value, path) { +export const assertString = function assertString (value, path) { if (typeof value !== 'string' || value.trim() === '') { throw new Error(`[settings] "${path}" must be a non-empty string`); } -- 2.39.5 From e0268b6e6ce28513a261c362738c940c76912b8b Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 13:56:16 +0200 Subject: [PATCH 03/20] test(settings): add unit test for the settings validator assertString function for test cases that should not throw an error --- .../utils/__tests__/settingsValidator.test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js index 2d54b20..4154e5f 100644 --- a/src/features/settings/utils/__tests__/settingsValidator.test.js +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -34,6 +34,22 @@ describe('assertType', () => { }); describe('assertString', () => { + test.for([ + ['a'], + ['b'], + ['ab'], + + ['0'], + ['42'], + + ['null'], + ['undefined'], + + ['()&%())'] + ])('throws no error for the value %s', ([ value ]) => { + expect(() => assertString(value)).not.throw(Error); + }); + test.for([ [0], [1], -- 2.39.5 From 867b3a41f83f42af5d824dda3180d0e1cae0ae41 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 13:59:14 +0200 Subject: [PATCH 04/20] test(settings): add unit test for the settings validator assertType function for test cases that should not throw an error --- .../utils/__tests__/settingsValidator.test.js | 12 ++++++++++-- src/features/settings/utils/settingsValidator.js | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js index 4154e5f..36c3398 100644 --- a/src/features/settings/utils/__tests__/settingsValidator.test.js +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -15,7 +15,7 @@ limitations under the License. */ import { describe, test, expect } from 'vitest'; -import { assertString } from '../settingsValidator'; +import { assertType, assertString } from '../settingsValidator'; describe('validateSettingsConfig', () => { @@ -30,7 +30,15 @@ describe('validateSelectionOptions', () => { }); describe('assertType', () => { - + test.for([ + ['bool'], + ['number'], + ['string'], + ['selection'], + ['section'] + ])('throws no error for the value %s', ([ value ]) => { + expect(() => assertType(value)).not.throw(Error); + }); }); describe('assertString', () => { diff --git a/src/features/settings/utils/settingsValidator.js b/src/features/settings/utils/settingsValidator.js index 74372be..7400c4c 100644 --- a/src/features/settings/utils/settingsValidator.js +++ b/src/features/settings/utils/settingsValidator.js @@ -22,7 +22,7 @@ export const assertString = function assertString (value, path) { } } -function assertType(value, path) { +export const assertType = function assertType (value, path) { if (!VALID_TYPES.includes(value)) { throw new Error( `[settings] "${path}" has invalid type "${value}". Must be one of: ${VALID_TYPES.join(', ')}` -- 2.39.5 From 948d6d41e8baad5f2a877cf049a28a48e4ce782f Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 14:02:09 +0200 Subject: [PATCH 05/20] test(settings): add unit test for the settings validator assertType function for test cases that should throw an error --- .../utils/__tests__/settingsValidator.test.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js index 36c3398..b4afc06 100644 --- a/src/features/settings/utils/__tests__/settingsValidator.test.js +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -39,6 +39,29 @@ describe('assertType', () => { ])('throws no error for the value %s', ([ value ]) => { expect(() => assertType(value)).not.throw(Error); }); + + test.for([ + [''], + [' '], + [' '], + [' '], + + ['42'], + ['0'], + ['-42'], + ['-42.0'], + ['-0.0'], + + ['a'], + ['ab'], + ['SeekraIsGreat!'], + ['Seekra is great!'], + + [undefined], + [null] + ])('throws an error for the value %s', ([ value ]) => { + expect(() => assertType(value)).throw(Error); + }); }); describe('assertString', () => { -- 2.39.5 From bf317eea35eb571f8f5af090cae85e36bb4e683c Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 14:02:48 +0200 Subject: [PATCH 06/20] test(settings): update test description for the unit test for the settings validator assertString function for test cases that should throw an error --- src/features/settings/utils/__tests__/settingsValidator.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js index b4afc06..ff40170 100644 --- a/src/features/settings/utils/__tests__/settingsValidator.test.js +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -92,7 +92,7 @@ describe('assertString', () => { [' '], [' '], [' '] - ])('throws error for the value %s', ([ value ]) => { + ])('throws an error for the value %s', ([ value ]) => { expect(() => assertString(value)).throws(Error); }); }); \ No newline at end of file -- 2.39.5 From f4121bf419b68b41780edb7d5777893132e9652d Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 14:38:05 +0200 Subject: [PATCH 07/20] test(settings): add unit test for the settings validator validateSelectionOptions function for test cases that should not throw an error --- .../settings/utils/__tests__/settingsValidator.test.js | 10 ++++++++-- src/features/settings/utils/settingsValidator.js | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js index ff40170..ced00fd 100644 --- a/src/features/settings/utils/__tests__/settingsValidator.test.js +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -15,7 +15,7 @@ limitations under the License. */ import { describe, test, expect } from 'vitest'; -import { assertType, assertString } from '../settingsValidator'; +import { validateSelectionOptions, assertType, assertString } from '../settingsValidator'; describe('validateSettingsConfig', () => { @@ -26,7 +26,13 @@ describe('validateEntry', () => { }); describe('validateSelectionOptions', () => { - + test.for([ + [[{ name: 'test', i18n: 'test.label' }]], + [[{ name: 'test', i18n: 'test.label' }, { name: 'test2', i18n: 'test2.label' }]], + [[{ name: 'test', i18n: 'test.label' }, { name: 'test', i18n: 'test2.label' }, { name: 'test3', i18n: 'test.label' }]] + ])('throws no error for the options %s', ([ options ]) => { + expect(() => validateSelectionOptions(options)).not.throws(Error); + }); }); describe('assertType', () => { diff --git a/src/features/settings/utils/settingsValidator.js b/src/features/settings/utils/settingsValidator.js index 7400c4c..a663a3a 100644 --- a/src/features/settings/utils/settingsValidator.js +++ b/src/features/settings/utils/settingsValidator.js @@ -30,10 +30,10 @@ export const assertType = function assertType (value, path) { } } -function validateSelectionOptions(options, path) { +export const validateSelectionOptions = function validateSelectionOptions (options, path) { if (!Array.isArray(options) || options.length === 0) { throw new Error(`[settings] "${path}.options" must be a non-empty array`); - } + }; options.forEach((opt, i) => { assertString(opt.name, `${path}.options[${i}].name`); assertString(opt.i18n, `${path}.options[${i}].i18n`); -- 2.39.5 From ea87e0832a830126a7c518915316c9c8b5264314 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 14:40:34 +0200 Subject: [PATCH 08/20] test(settings): add unit test for the settings validator validateSelectionOptions function for test cases that should throw an error --- .../utils/__tests__/settingsValidator.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js index ced00fd..bb251af 100644 --- a/src/features/settings/utils/__tests__/settingsValidator.test.js +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -33,6 +33,17 @@ describe('validateSelectionOptions', () => { ])('throws no error for the options %s', ([ options ]) => { expect(() => validateSelectionOptions(options)).not.throws(Error); }); + + test.for([ + [[{ name: '', i18n: 'test.label' }]], + [[{ name: 'test', i18n: '' }]], + [[{ name: '', i18n: '' }]], + [[{ name: '', i18n: 'test.label' }, { name: 'test2', i18n: '' }]], + [[{ name: 'test', i18n: '' }, { name: '', i18n: 'test2.label' }, { name: '', i18n: ' ' }, { name: '42', i18n: '42.i18n' }]], + [[]] + ])('throws an error for the options %s', ([ options ]) => { + expect(() => validateSelectionOptions(options)).throws(Error); + }); }); describe('assertType', () => { -- 2.39.5 From ecf9074522722a19826025f149390b0866b5f671 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 14:53:47 +0200 Subject: [PATCH 09/20] test(settings): add unit test for the settings validator validateEntry function for test cases that should not throw an error --- .../utils/__tests__/settingsValidator.test.js | 45 ++++++++++++++++++- .../settings/utils/settingsValidator.js | 2 +- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js index bb251af..3444b3a 100644 --- a/src/features/settings/utils/__tests__/settingsValidator.test.js +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -15,14 +15,57 @@ limitations under the License. */ import { describe, test, expect } from 'vitest'; -import { validateSelectionOptions, assertType, assertString } from '../settingsValidator'; +import { validateEntry, validateSelectionOptions, assertType, assertString } from '../settingsValidator'; describe('validateSettingsConfig', () => { }); describe('validateEntry', () => { + test.for([ + [{ name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable' }], + [{ name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: true }], + [{ name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: false }], + + [{ name: 'aNumber', type: 'number', i18n: 'aNumber.label' }], + [{ name: 'aNumber', type: 'number', i18n: 'aNumber.label', default: 42 }], + [{ name: 'aNumber', type: 'number', i18n: 'aNumber.label', default: 0 }], + [{ name: 'aNumber', type: 'number', i18n: 'aNumber.label', default: -42.7 }], + + [{ name: 'aString', type: 'string', i18n: 'aString.label' }], + [{ name: 'aString', type: 'string', i18n: 'aString.label', default: '' }], + [{ name: 'aString', type: 'string', i18n: 'aString.label', default: 'Seekra is great!' }], + [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', options: [ + { name: 'yes', i18n: 'selectSomething.options.yes' }, + { name: 'no', i18n: 'selectSomething.options.no' }, + { name: 'maybe', i18n: 'selectSomething.options.maybe' } + ] }], + + [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', default: 'no', options: [ + { name: 'yes', i18n: 'selectSomething.options.yes' }, + { name: 'no', i18n: 'selectSomething.options.no' }, + { name: 'maybe', i18n: 'selectSomething.options.maybe' } + ] }], + + [{ name: 'aSection', type: 'section', i18n: 'sections.aSection.title', content: [] }], + [{ name: 'aSection', type: 'section', i18n: 'sections.aSection.title', content: [ + { name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: true } + ] }], + [{ name: 'aSection', type: 'section', i18n: 'sections.aSection.title', content: [ + { name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: true }, + { name: 'enableFeature43', type: 'bool', i18n: 'feature.43.enable', default: true } + ] }], + [{ name: 'aSection', type: 'section', i18n: 'sections.aSection.title', content: [ + { name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: true }, + { name: 'enableFeature43', type: 'bool', i18n: 'feature.43.enable', default: true }, + { name: 'aSecondSection', type: 'section', i18n: 'sections.aSecondSection.title', content: [ + { name: 'enableFeature44', type: 'bool', i18n: 'feature.44.enable', default: true }, + ] } + ] }] + ])('throws no error for the entry %s', ([ entry ]) => { + expect(() => validateEntry(entry)).not.throws(Error); + }); }); describe('validateSelectionOptions', () => { diff --git a/src/features/settings/utils/settingsValidator.js b/src/features/settings/utils/settingsValidator.js index a663a3a..fe693d6 100644 --- a/src/features/settings/utils/settingsValidator.js +++ b/src/features/settings/utils/settingsValidator.js @@ -40,7 +40,7 @@ export const validateSelectionOptions = function validateSelectionOptions (optio }); } -function validateEntry(entry, path) { +export const validateEntry = function validateEntry (entry, path) { assertType(entry.type, `${path}.type`); if (entry.type === 'section') { -- 2.39.5 From 970f4a74d1f0d52cc7daff2673a4c656646d84c1 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 15:08:33 +0200 Subject: [PATCH 10/20] fix(settings): do not enforce allowMultiple set for selection settings --- src/features/settings/utils/settingsValidator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/settings/utils/settingsValidator.js b/src/features/settings/utils/settingsValidator.js index fe693d6..8cb9652 100644 --- a/src/features/settings/utils/settingsValidator.js +++ b/src/features/settings/utils/settingsValidator.js @@ -69,7 +69,7 @@ export const validateEntry = function validateEntry (entry, path) { } if (entry.type === 'selection') { validateSelectionOptions(entry.options, path); - if (typeof entry.allowMultiple !== 'boolean') { + if (typeof entry.allowMultiple !== 'boolean' && entry.allowMultiple) { throw new Error(`[settings] "${path}.allowMultiple" must be a boolean`); } } -- 2.39.5 From 5c8073d26459ac6b3ab7ba97415f0ac90cb08cdc Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 15:12:49 +0200 Subject: [PATCH 11/20] test(settings): add unit test for the settings validator validateEntry function for test cases that should throw an error --- .../utils/__tests__/settingsValidator.test.js | 77 ++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js index 3444b3a..79c5e62 100644 --- a/src/features/settings/utils/__tests__/settingsValidator.test.js +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -17,9 +17,9 @@ limitations under the License. import { describe, test, expect } from 'vitest'; import { validateEntry, validateSelectionOptions, assertType, assertString } from '../settingsValidator'; -describe('validateSettingsConfig', () => { +// describe('validateSettingsConfig', () => { -}); +// }); describe('validateEntry', () => { test.for([ @@ -66,6 +66,79 @@ describe('validateEntry', () => { ])('throws no error for the entry %s', ([ entry ]) => { expect(() => validateEntry(entry)).not.throws(Error); }); + + test.for([ + [{ name: 'enableFeature42', type: 'bool', i18n: '' }], + [{ name: 'enableFeature42', type: 'thisTypeDoesNotExistAndHasAVeryLongName', i18n: 'feature.42.enable' }], + [{ name: '', type: 'thisTypeDoesNotExistAndHasAVeryLongName', i18n: 'feature.42.enable' }], + [{ name: '', type: 'thisTypeDoesNotExistAndHasAVeryLongName', i18n: '' }], + [{ name: '', type: 'bool', i18n: '' }], + [{ name: '', type: 'bool', i18n: '' }], + [{ name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: 42 }], + [{ name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: '42' }], + [{ name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: 'Seekra' }], + [{ name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: 'true' }], + [{ name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: 'false' }], + [{ name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: 'undefined' }], + [{ name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: 'null' }], + [{ name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: [] }], + [{ name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: {} }], + [{ name: 'enableFeature42', type: '', i18n: 'feature.42.enable', default: {} }], + + [{ name: '', type: 'number', i18n: 'aNumber.label' }], + [{ name: 'aNumber', type: 'number', i18n: 'aNumber.label', default: '42' }], + [{ name: 'aNumber', type: 'number', i18n: 'aNumber.label', default: 'zero' }], + [{ name: 'aNumber', type: 'number', i18n: 'aNumber.label', default: 'undefined' }], + [{ name: 'aNumber', type: 'number', i18n: 'aNumber.label', default: 'false' }], + [{ name: 'aNumber', type: 'number', i18n: 'aNumber.label', default: true }], + [{ name: 'aNumber', type: 'number', i18n: 'aNumber.label', default: false }], + [{ name: 'aNumber', type: 'number', i18n: 'aNumber.label', default: [] }], + [{ name: 'aNumber', type: 'number', i18n: 'aNumber.label', default: {} }], + [{ name: 'aNumber', type: '', i18n: 'aNumber.label', default: {} }], + [{ name: 'aNumber', type: '', i18n: 'aNumber.label', default: 42 }], + + [{ name: 'aString', type: 'string', i18n: '' }], + [{ name: 'aString', type: 'string', i18n: 'aString.label', default: 42 }], + [{ name: 'aString', type: 'string', i18n: 'aString.label', default: true }], + [{ name: 'aString', type: 'string', i18n: 'aString.label', default: false }], + [{ name: 'aString', type: 'string', i18n: 'aString.label', default: [] }], + [{ name: 'aString', type: 'string', i18n: 'aString.label', default: {} }], + [{ name: 'aString', type: 'string', i18n: '', default: {} }], + + [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', default: '1', options: true }], + [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', options: [] }], + + [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', default: 'no', options: [ + { name: 'yes', i18n: '' }, + { name: '', i18n: 'selectSomething.options.no' }, + { name: 'maybe', i18n: 'selectSomething.options.maybe' }, + { name: '', i18n: '' }, + { name: 42, i18n: 43 }, + { name: '' }, + { i18n: '' }, + {} + ] }], + + [{ name: 'aSection', type: 'section', i18n: '', content: [] }], + [{ name: 'aSection', type: 'section', i18n: 'sections.aSection.title', content: '' }], + [{ name: 'aSection', type: 'section', i18n: 'sections.aSection.title', content: '[]' }], + [{ name: 'aSection', type: 'section', i18n: 'sections.aSection.title', content: 'a' }], + [{ name: 'aSection', type: 'section', i18n: 'sections.aSection.title', content: [ + { name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: true }, + { name: 'enableFeature43', type: 'bool', i18n: 'feature.42.enable', default: 0 }, + { name: 'enableFeature44', type: 'bool' } + ] }], + [{ name: 'aSection', type: 'section', i18n: 'sections.aSection.title', content: [ + { name: 'enableFeature42', type: 'bool', i18n: 'feature.42.enable', default: true }, + { name: 'enableFeature43', type: 'bool', i18n: 'feature.43.enable', default: true }, + { name: 'aSecondSection', type: 'section', i18n: 'sections.aSecondSection.title', content: 'Hello' }, + { name: 'aSecondSection', type: 'section', i18n: '', content: [] }, + { name: 'aSecondSection', type: 'section', i18n: 'i18n', content: [] }, + { name: 'aSecondSection', type: 'section', content: [] } + ] }] + ])('throws an error for the entry %s', ([ entry ]) => { + expect(() => validateEntry(entry)).throws(Error); + }); }); describe('validateSelectionOptions', () => { -- 2.39.5 From 122663e2e36380b209efcfadb0d2a8cf1dc9980c Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 15:15:37 +0200 Subject: [PATCH 12/20] fix(settings): check the name and i18n before the checks for a section in validateEntry in the settings validator --- src/features/settings/utils/settingsValidator.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/features/settings/utils/settingsValidator.js b/src/features/settings/utils/settingsValidator.js index 8cb9652..b792c61 100644 --- a/src/features/settings/utils/settingsValidator.js +++ b/src/features/settings/utils/settingsValidator.js @@ -42,9 +42,11 @@ export const validateSelectionOptions = function validateSelectionOptions (optio export const validateEntry = function validateEntry (entry, path) { assertType(entry.type, `${path}.type`); + + assertString(entry.name, `${path}.name`); + assertString(entry.i18n, `${path}.i18n`); if (entry.type === 'section') { - assertString(entry.name, `${path}.name`); if (!Array.isArray(entry.content)) { throw new Error(`[settings] "${path}.content" must be an array`); } @@ -54,9 +56,6 @@ export const validateEntry = function validateEntry (entry, path) { return; } - assertString(entry.name, `${path}.name`); - assertString(entry.i18n, `${path}.i18n`); - if (entry.default !== undefined) { if (entry.type === 'bool' && typeof entry.default !== 'boolean') { throw new Error(`[settings] "${path}.default" must be a boolean`); -- 2.39.5 From 5f07e66915811ada883202a9ae45bcfe1bf103a6 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 15:17:32 +0200 Subject: [PATCH 13/20] fix(settings): check the selection options and allowMultiple after and not in the default check in the settings validator --- src/features/settings/utils/settingsValidator.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/features/settings/utils/settingsValidator.js b/src/features/settings/utils/settingsValidator.js index b792c61..e90558d 100644 --- a/src/features/settings/utils/settingsValidator.js +++ b/src/features/settings/utils/settingsValidator.js @@ -66,12 +66,12 @@ export const validateEntry = function validateEntry (entry, path) { if (entry.type === 'string' && typeof entry.default !== 'string') { throw new Error(`[settings] "${path}.default" must be a string`); } - if (entry.type === 'selection') { - validateSelectionOptions(entry.options, path); - if (typeof entry.allowMultiple !== 'boolean' && entry.allowMultiple) { - throw new Error(`[settings] "${path}.allowMultiple" must be a boolean`); - } - } + } + if (entry.type === 'selection') { + validateSelectionOptions(entry.options, path); + if (typeof entry.allowMultiple !== 'boolean' && entry.allowMultiple) { + throw new Error(`[settings] "${path}.allowMultiple" must be a boolean`); + } } } -- 2.39.5 From deaf3935c917856ba6c8fdeb3108b4ea0a447a43 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 15:18:11 +0200 Subject: [PATCH 14/20] fix(settings): fix indentation in validateEntry in the settings validator --- src/features/settings/utils/settingsValidator.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/features/settings/utils/settingsValidator.js b/src/features/settings/utils/settingsValidator.js index e90558d..1f670a7 100644 --- a/src/features/settings/utils/settingsValidator.js +++ b/src/features/settings/utils/settingsValidator.js @@ -58,15 +58,15 @@ export const validateEntry = function validateEntry (entry, path) { if (entry.default !== undefined) { if (entry.type === 'bool' && typeof entry.default !== 'boolean') { - throw new Error(`[settings] "${path}.default" must be a boolean`); - } - if (entry.type === 'number' && typeof entry.default !== 'number') { - throw new Error(`[settings] "${path}.default" must be a number`); - } - if (entry.type === 'string' && typeof entry.default !== 'string') { - throw new Error(`[settings] "${path}.default" must be a string`); - } + throw new Error(`[settings] "${path}.default" must be a boolean`); } + if (entry.type === 'number' && typeof entry.default !== 'number') { + throw new Error(`[settings] "${path}.default" must be a number`); + } + if (entry.type === 'string' && typeof entry.default !== 'string') { + throw new Error(`[settings] "${path}.default" must be a string`); + } + } if (entry.type === 'selection') { validateSelectionOptions(entry.options, path); if (typeof entry.allowMultiple !== 'boolean' && entry.allowMultiple) { -- 2.39.5 From 7407366f454b855bd2f75bb92b2c962485d37527 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 15:19:12 +0200 Subject: [PATCH 15/20] fix(settings): check the selection options and allowMultiple before and not after the default check in the settings validator --- src/features/settings/utils/settingsValidator.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/features/settings/utils/settingsValidator.js b/src/features/settings/utils/settingsValidator.js index 1f670a7..8afc467 100644 --- a/src/features/settings/utils/settingsValidator.js +++ b/src/features/settings/utils/settingsValidator.js @@ -55,7 +55,13 @@ export const validateEntry = function validateEntry (entry, path) { ); return; } - + + if (entry.type === 'selection') { + validateSelectionOptions(entry.options, path); + if (typeof entry.allowMultiple !== 'boolean' && entry.allowMultiple) { + throw new Error(`[settings] "${path}.allowMultiple" must be a boolean`); + } + }; if (entry.default !== undefined) { if (entry.type === 'bool' && typeof entry.default !== 'boolean') { throw new Error(`[settings] "${path}.default" must be a boolean`); @@ -67,12 +73,6 @@ export const validateEntry = function validateEntry (entry, path) { throw new Error(`[settings] "${path}.default" must be a string`); } } - if (entry.type === 'selection') { - validateSelectionOptions(entry.options, path); - if (typeof entry.allowMultiple !== 'boolean' && entry.allowMultiple) { - throw new Error(`[settings] "${path}.allowMultiple" must be a boolean`); - } - } } /** -- 2.39.5 From 775e2063227765b3020124f7beb2bbb15b0bc650 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 15:24:30 +0200 Subject: [PATCH 16/20] fix(settings): check selection default value in the settings validator --- src/features/settings/utils/settingsValidator.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/features/settings/utils/settingsValidator.js b/src/features/settings/utils/settingsValidator.js index 8afc467..1cf5b09 100644 --- a/src/features/settings/utils/settingsValidator.js +++ b/src/features/settings/utils/settingsValidator.js @@ -72,6 +72,14 @@ export const validateEntry = function validateEntry (entry, path) { if (entry.type === 'string' && typeof entry.default !== 'string') { throw new Error(`[settings] "${path}.default" must be a string`); } + if (entry.type === 'selection') { + if (typeof entry.default !== 'string') { + throw new Error(`[settings] "${path}.default" must be a string`); + }; + if (!entry.options.map((option) => option.name).includes(entry.default)) { + throw new Error(`[settings] option "${path}.default" does not exist`); + }; + }; } } -- 2.39.5 From 98940eeec439034887bc6e930d10628296528c6c Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 15:26:55 +0200 Subject: [PATCH 17/20] test(settings): add test cases for the settings validator validateEntry function for test cases that should throw an error --- .../utils/__tests__/settingsValidator.test.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js index 79c5e62..9fbe840 100644 --- a/src/features/settings/utils/__tests__/settingsValidator.test.js +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -105,8 +105,17 @@ describe('validateEntry', () => { [{ name: 'aString', type: 'string', i18n: 'aString.label', default: {} }], [{ name: 'aString', type: 'string', i18n: '', default: {} }], - [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', default: '1', options: true }], - [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', options: [] }], + [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', default: 'a', options: true }], + [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', default: 'a', options: [] }], + [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', default: 'c', options: [ + { name: 'a', i18n: 'a' }, + { name: 'b', i18n: 'b' } + ] }], + [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', default: 'a', options: [ + { name: 'a', i18n: 'a' }, + { name: 'b', i18n: '' }, + { name: 'c' } + ] }], [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', default: 'no', options: [ { name: 'yes', i18n: '' }, -- 2.39.5 From 88819f56845b2663822c6cf4a62dad3476fe8c22 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 15:27:55 +0200 Subject: [PATCH 18/20] test(settings): add test cases for the settings validator validateSelectionOptions function for test cases that should throw an error --- .../settings/utils/__tests__/settingsValidator.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js index 9fbe840..5f0a98a 100644 --- a/src/features/settings/utils/__tests__/settingsValidator.test.js +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -160,6 +160,11 @@ describe('validateSelectionOptions', () => { }); test.for([ + [[{}]], + [[{ i18n: '' }]], + [[{ i18n: 'a' }]], + [[{ name: 'a' }]], + [[{ name: '' }]], [[{ name: '', i18n: 'test.label' }]], [[{ name: 'test', i18n: '' }]], [[{ name: '', i18n: '' }]], -- 2.39.5 From 87026486248217539a282c3c126a5917ca9ec376 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 16:22:34 +0200 Subject: [PATCH 19/20] test(settings): add unit test for the settings validator validateSettingsConfig function --- .../utils/__tests__/settingsValidator.test.js | 327 +++++++++++++++++- 1 file changed, 323 insertions(+), 4 deletions(-) diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js index 5f0a98a..efa1d12 100644 --- a/src/features/settings/utils/__tests__/settingsValidator.test.js +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -15,11 +15,330 @@ limitations under the License. */ import { describe, test, expect } from 'vitest'; -import { validateEntry, validateSelectionOptions, assertType, assertString } from '../settingsValidator'; +import { validateSettingsConfig, validateEntry, validateSelectionOptions, assertType, assertString } from '../settingsValidator'; -// describe('validateSettingsConfig', () => { - -// }); +describe('validateSettingsConfig', () => { + test.for([ + { raw: false, expected: false }, + { raw: true, expected: false }, + { raw: 0, expected: false }, + { raw: 42, expected: false }, + { raw: '', expected: false }, + { raw: ' ', expected: false }, + { raw: 'a', expected: false }, + { raw: {}, expected: false }, + { raw: { + contents: [] + }, expected: true }, + { raw: { + contents: [ + { + type: 'section', + name: 'general', + i18n: 'settings.settings.general', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + i18n: 'settings.settings.general.enableFeature42' + } + ] + } + ] + }, expected: true }, + { raw: { + contents: [ + { + type: 'section', + name: 'general', + i18n: 'settings.settings.general', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + { + type: 'section', + name: 'copyOfGeneral', + i18n: 'settings.settings.copyOfGeneral', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + ] + }, expected: true }, + { raw: { + contents: [ + { + type: 'section', + name: 'general', + i18n: 'settings.settings.general', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + { + type: 'section', + name: 'copyOfGeneral', + i18n: 'settings.settings.copyOfGeneral', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + default: false, + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + { + type: 'section', + name: 'aSection', + i18n: 'settings.settings.aSection', + content: [ + { + type: 'bool', + name: 'Enable feature 43', + i18n: 'settings.settings.aSection.enableFeature43' + }, + { + type: 'selection', + name: 'language', + i18n: 'settings.settings.aSection.language.label', + default: 'en', + options: [ + { name: 'en', i18n: 'settings.settings.aSection.language.options.en' }, + { name: 'de', i18n: 'settings.settings.aSection.language.options.de' }, + ] + }, + { + type: 'section', + name: 'section2', + i18n: 'settings.settings.aSection.section2.label', + content: [ + { + type: 'string', + name: 'string', + i18n: 'settings.settings.aSection.sections.string', + default: 'str' + } + ] + }, + ] + }, + ] + }, expected: true }, + { raw: { + contents: [ + { + type: 'section', + name: 'general', + i18n: 'settings.settings.general', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + { + type: 'section', + name: 'copyOfGeneral', + i18n: 'settings.settings.copyOfGeneral', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + default: false, + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + { + type: 'section', + name: 'aSection', + i18n: 'settings.settings.aSection', + content: [ + { + type: 'bool', + name: 'Enable feature 43', + i18n: 'settings.settings.aSection.enableFeature43', + default: 'true' + }, + { + type: 'selection', + name: 'language', + i18n: 'settings.settings.aSection.language.label', + default: 'en', + options: [ + { name: 'en', i18n: 'settings.settings.aSection.language.options.en' }, + { name: 'de', i18n: 'settings.settings.aSection.language.options.de' }, + ] + }, + { + type: 'section', + name: 'section2', + i18n: 'settings.settings.aSection.section2.label', + content: [ + { + type: 'string', + name: 'string', + i18n: 'settings.settings.aSection.sections.string', + default: 42 + } + ] + }, + ] + }, + ] + }, expected: false }, + { raw: { + content: [ + { + type: 'section', + name: 'general', + i18n: 'settings.settings.general', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + { + type: 'section', + name: 'copyOfGeneral', + i18n: 'settings.settings.copyOfGeneral', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + default: false, + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + { + type: 'section', + name: 'aSection', + i18n: 'settings.settings.aSection', + content: [ + { + type: 'bool', + name: 'Enable feature 43', + i18n: 'settings.settings.aSection.enableFeature43' + }, + { + type: 'selection', + name: 'language', + i18n: 'settings.settings.aSection.language.label', + default: 'en', + options: [ + { name: 'en', i18n: 'settings.settings.aSection.language.options.en' }, + { name: 'de', i18n: 'settings.settings.aSection.language.options.de' }, + ] + }, + { + type: 'section', + name: 'section2', + i18n: 'settings.settings.aSection.section2.label', + content: [ + { + type: 'string', + name: 'string', + i18n: 'settings.settings.aSection.sections.string', + default: 'str' + } + ] + }, + ] + }, + ] + }, expected: false }, + { raw: { + content: [ + { + type: 'sectio', + name: 'general', + i18n: 'settings.settings.general', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + { + type: 'section', + name: 'copyOfGeneral', + i18n: 'settings.settings.copyOfGeneral', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + default: false, + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + { + type: 'section', + name: 'aSection', + i18n: 'settings.settings.aSection', + content: [ + { + type: 'bool', + name: 'Enable feature 43', + i18n: 'settings.settings.aSection.enableFeature43' + }, + { + type: 'selection', + name: 'language', + i18n: 'settings.settings.aSection.language.label', + default: 'en', + options: [ + { name: 'en', i18n: 'settings.settings.aSection.language.options.en' }, + { name: 'de', i18n: 'settings.settings.aSection.language.options.de' }, + ] + }, + { + type: 'section', + name: 'section2', + i18n: 'settings.settings.aSection.section2.label', + content: [ + { + type: 'string', + name: 'string', + i18n: 'settings.settings.aSection.sections.string', + default: 'str' + } + ] + }, + ] + }, + ] + }, expected: false }, + ])('returns valid: $expected', ({ raw, expected }) => { + const result = validateSettingsConfig(raw); + if (!result.valid) { + console.error('Error message:', result.error); + }; + expect(result.valid).toBe(expected); + }); +}); describe('validateEntry', () => { test.for([ -- 2.39.5 From 1473dcf06049fafe3ee6ed372e6b5a5dbaf04210 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Thu, 4 Jun 2026 16:38:05 +0200 Subject: [PATCH 20/20] test(settings): add test cases for the selection setting allowMultiple property to the settings validator unit test --- .../utils/__tests__/settingsValidator.test.js | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/src/features/settings/utils/__tests__/settingsValidator.test.js b/src/features/settings/utils/__tests__/settingsValidator.test.js index efa1d12..5f30729 100644 --- a/src/features/settings/utils/__tests__/settingsValidator.test.js +++ b/src/features/settings/utils/__tests__/settingsValidator.test.js @@ -138,6 +138,71 @@ describe('validateSettingsConfig', () => { }, ] }, expected: true }, + { raw: { + contents: [ + { + type: 'section', + name: 'general', + i18n: 'settings.settings.general', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + { + type: 'section', + name: 'copyOfGeneral', + i18n: 'settings.settings.copyOfGeneral', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + default: false, + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + { + type: 'section', + name: 'aSection', + i18n: 'settings.settings.aSection', + content: [ + { + type: 'bool', + name: 'Enable feature 43', + i18n: 'settings.settings.aSection.enableFeature43' + }, + { + type: 'selection', + name: 'language', + i18n: 'settings.settings.aSection.language.label', + default: 'en', + allowMultiple: false, + options: [ + { name: 'en', i18n: 'settings.settings.aSection.language.options.en' }, + { name: 'de', i18n: 'settings.settings.aSection.language.options.de' }, + ] + }, + { + type: 'section', + name: 'section2', + i18n: 'settings.settings.aSection.section2.label', + content: [ + { + type: 'string', + name: 'string', + i18n: 'settings.settings.aSection.sections.string', + default: 'str' + } + ] + }, + ] + }, + ] + }, expected: true }, { raw: { contents: [ { @@ -331,6 +396,71 @@ describe('validateSettingsConfig', () => { }, ] }, expected: false }, + { raw: { + content: [ + { + type: 'sectio', + name: 'general', + i18n: 'settings.settings.general', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + { + type: 'section', + name: 'copyOfGeneral', + i18n: 'settings.settings.copyOfGeneral', + content: [ + { + type: 'bool', + name: 'Enable feature 42', + default: false, + i18n: 'settings.settings.general.enableFeature42' + } + ] + }, + { + type: 'section', + name: 'aSection', + i18n: 'settings.settings.aSection', + content: [ + { + type: 'bool', + name: 'Enable feature 43', + i18n: 'settings.settings.aSection.enableFeature43' + }, + { + type: 'selection', + name: 'language', + i18n: 'settings.settings.aSection.language.label', + default: 'en', + allowMultiple: 'false', + options: [ + { name: 'en', i18n: 'settings.settings.aSection.language.options.en' }, + { name: 'de', i18n: 'settings.settings.aSection.language.options.de' }, + ] + }, + { + type: 'section', + name: 'section2', + i18n: 'settings.settings.aSection.section2.label', + content: [ + { + type: 'string', + name: 'string', + i18n: 'settings.settings.aSection.sections.string', + default: 'str' + } + ] + }, + ] + }, + ] + }, expected: false } ])('returns valid: $expected', ({ raw, expected }) => { const result = validateSettingsConfig(raw); if (!result.valid) { @@ -435,6 +565,11 @@ describe('validateEntry', () => { { name: 'b', i18n: '' }, { name: 'c' } ] }], + [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', default: 'a', allowMultiple: 'false', options: [ + { name: 'a', i18n: 'a' }, + { name: 'b', i18n: '' }, + { name: 'c' } + ] }], [{ name: 'selectSomething', type: 'selection', i18n: 'selectSomething.title', default: 'no', options: [ { name: 'yes', i18n: '' }, -- 2.39.5