From 294fdab550b996f38979fc7907a98bfc053d2a34 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Wed, 5 Aug 2026 00:27:20 +0200 Subject: [PATCH 1/4] feat(settings): add composable for settings values Added the composable useSettings that provides the function getSetting. This function returns the set value of the setting, or otherwise the default value. If there is no default value, it returns undefined. --- .../settings/composables/useSettings.js | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/features/settings/composables/useSettings.js diff --git a/src/features/settings/composables/useSettings.js b/src/features/settings/composables/useSettings.js new file mode 100644 index 0000000..f37102c --- /dev/null +++ b/src/features/settings/composables/useSettings.js @@ -0,0 +1,43 @@ +/* +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 { useSettingsStore } from '../stores/settingsStore'; +import { getSettingRecursively } from '../utils/getSetting'; +import { loadSettingsConfig } from '../utils/settingsParser'; + +/** + * Provides access to the stored settings. + */ +export const useSettings = function useSettings () { + /** + * Returns the value of a specific setting. + * @param {string[]} key - The settings key. + * @return The value of the setting. + */ + const getSetting = async function getSetting (key) { + const settingsStore = useSettingsStore(); + + const settings = await loadSettingsConfig(); + return ( + settingsStore.get(key) ?? + getSettingRecursively( + key, settings.contents ?? [] + )?.default + ); + }; + + return { getSetting }; +}; -- 2.39.5 From ff6ab7a78c98bfe4cd7c1a0565f0b2695612d953 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Wed, 5 Aug 2026 01:05:12 +0200 Subject: [PATCH 2/4] test(settings): add tests for useSettings composable Added some tests for the getSetting function from the useSettings composable. --- .../composables/__tests__/useSettings.test.js | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/features/settings/composables/__tests__/useSettings.test.js diff --git a/src/features/settings/composables/__tests__/useSettings.test.js b/src/features/settings/composables/__tests__/useSettings.test.js new file mode 100644 index 0000000..b6637fe --- /dev/null +++ b/src/features/settings/composables/__tests__/useSettings.test.js @@ -0,0 +1,85 @@ +/* +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 { useSettingsStore } from '../../stores/settingsStore'; +import { useSettings } from '../useSettings'; +import { createTestingPinia } from '@pinia/testing'; +import { setActivePinia } from 'pinia'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; + +const settingDefaultValue = vi.hoisted(() => 42); +const settingPath = vi.hoisted(() => ['test', 'number']); + +vi.mock('../../utils/settingsParser', () => ({ + loadSettingsConfig: vi.fn().mockResolvedValue({ + contents: [ + { + name: 'test', + i18n: '', + content: [ + { + type: 'number', + i18n: '', + name: 'number', + default: settingDefaultValue + } + ] + } + ] + }) +})); + +describe('useSettings', () => { + describe('getSetting', () => { + test('returns stored value', async () => { + const { getSetting } = useSettings(); + const settings = useSettingsStore(); + + const key = 'setting'; + const value = 42; + + settings.set(key, value); + + expect(await getSetting(key)).toBe(value); + }); + + test('returns default value', async () => { + const { getSetting } = useSettings(); + expect(await getSetting(settingPath)).toBe(settingDefaultValue); + }); + + test('returns stored value instead of default value', async () => { + const { getSetting } = useSettings(); + const settings = useSettingsStore(); + + const value = 43; + + expect(await getSetting(settingPath)).toBe(settingDefaultValue); + + settings.set(settingPath, value); + expect(await getSetting(settingPath)).toBe(value); + }); + }); +}); + +beforeEach(() => { + setActivePinia( + createTestingPinia({ + createSpy: vi.fn, + stubActions: false + }) + ); +}); -- 2.39.5 From 04c148a3b1bcfac9d62c3c09454eb3af4a67a08a Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Wed, 5 Aug 2026 01:29:04 +0200 Subject: [PATCH 3/4] feat(settings): make setting value function synchronous Added a store to cache the settings configuration. The getSetting function from useSettings is now synchronous and uses this cache. The settings cache is set when loading the settings initially (asnychronously). --- .../composables/__tests__/useSettings.test.js | 91 +++++++++---------- .../settings/composables/useSettings.js | 5 +- .../stores/settingsConfigCacheStore.js | 31 +++++++ src/features/settings/utils/settingsParser.js | 3 + 4 files changed, 82 insertions(+), 48 deletions(-) create mode 100644 src/features/settings/stores/settingsConfigCacheStore.js diff --git a/src/features/settings/composables/__tests__/useSettings.test.js b/src/features/settings/composables/__tests__/useSettings.test.js index b6637fe..772467c 100644 --- a/src/features/settings/composables/__tests__/useSettings.test.js +++ b/src/features/settings/composables/__tests__/useSettings.test.js @@ -14,17 +14,58 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { useSettingsConfigCacheStore } from '../../stores/settingsConfigCacheStore'; import { useSettingsStore } from '../../stores/settingsStore'; import { useSettings } from '../useSettings'; import { createTestingPinia } from '@pinia/testing'; import { setActivePinia } from 'pinia'; import { beforeEach, describe, expect, test, vi } from 'vitest'; -const settingDefaultValue = vi.hoisted(() => 42); -const settingPath = vi.hoisted(() => ['test', 'number']); +const settingDefaultValue = 42; +const settingPath = ['test', 'number']; -vi.mock('../../utils/settingsParser', () => ({ - loadSettingsConfig: vi.fn().mockResolvedValue({ +describe('useSettings', () => { + describe('getSetting', () => { + test('returns stored value', () => { + const { getSetting } = useSettings(); + const settings = useSettingsStore(); + + const key = 'setting'; + const value = 42; + + settings.set(key, value); + + expect(getSetting(key)).toBe(value); + }); + + test('returns default value', () => { + const { getSetting } = useSettings(); + expect(getSetting(settingPath)).toBe(settingDefaultValue); + }); + + test('returns stored value instead of default value', () => { + const { getSetting } = useSettings(); + const settings = useSettingsStore(); + + const value = 43; + + expect(getSetting(settingPath)).toBe(settingDefaultValue); + + settings.set(settingPath, value); + expect(getSetting(settingPath)).toBe(value); + }); + }); +}); + +beforeEach(() => { + setActivePinia( + createTestingPinia({ + createSpy: vi.fn, + stubActions: false + }) + ); + const settingsConfigCache = useSettingsConfigCacheStore(); + settingsConfigCache.set({ contents: [ { name: 'test', @@ -39,47 +80,5 @@ vi.mock('../../utils/settingsParser', () => ({ ] } ] - }) -})); - -describe('useSettings', () => { - describe('getSetting', () => { - test('returns stored value', async () => { - const { getSetting } = useSettings(); - const settings = useSettingsStore(); - - const key = 'setting'; - const value = 42; - - settings.set(key, value); - - expect(await getSetting(key)).toBe(value); - }); - - test('returns default value', async () => { - const { getSetting } = useSettings(); - expect(await getSetting(settingPath)).toBe(settingDefaultValue); - }); - - test('returns stored value instead of default value', async () => { - const { getSetting } = useSettings(); - const settings = useSettingsStore(); - - const value = 43; - - expect(await getSetting(settingPath)).toBe(settingDefaultValue); - - settings.set(settingPath, value); - expect(await getSetting(settingPath)).toBe(value); - }); }); }); - -beforeEach(() => { - setActivePinia( - createTestingPinia({ - createSpy: vi.fn, - stubActions: false - }) - ); -}); diff --git a/src/features/settings/composables/useSettings.js b/src/features/settings/composables/useSettings.js index f37102c..cd8cc29 100644 --- a/src/features/settings/composables/useSettings.js +++ b/src/features/settings/composables/useSettings.js @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { useSettingsConfigCacheStore } from '../stores/settingsConfigCacheStore'; import { useSettingsStore } from '../stores/settingsStore'; import { getSettingRecursively } from '../utils/getSetting'; import { loadSettingsConfig } from '../utils/settingsParser'; @@ -27,10 +28,10 @@ export const useSettings = function useSettings () { * @param {string[]} key - The settings key. * @return The value of the setting. */ - const getSetting = async function getSetting (key) { + const getSetting = function getSetting (key) { const settingsStore = useSettingsStore(); - const settings = await loadSettingsConfig(); + const settings = useSettingsConfigCacheStore().get(); return ( settingsStore.get(key) ?? getSettingRecursively( diff --git a/src/features/settings/stores/settingsConfigCacheStore.js b/src/features/settings/stores/settingsConfigCacheStore.js new file mode 100644 index 0000000..9ed923f --- /dev/null +++ b/src/features/settings/stores/settingsConfigCacheStore.js @@ -0,0 +1,31 @@ +/* +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 { defineStore } from 'pinia'; + +export const useSettingsConfigCacheStore = defineStore('settingsConfigCache', () => { + let settingsConfig = undefined; + + const set = function set (value) { + settingsConfig = value; + }; + + const get = function get () { + return settingsConfig; + }; + + return { set, get }; +}); diff --git a/src/features/settings/utils/settingsParser.js b/src/features/settings/utils/settingsParser.js index 2b7e658..1dd9626 100644 --- a/src/features/settings/utils/settingsParser.js +++ b/src/features/settings/utils/settingsParser.js @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { useSettingsConfigCacheStore } from '../stores/settingsConfigCacheStore'; import { validateSettingsConfig } from './settingsValidator.js'; /** @@ -21,10 +22,12 @@ import { validateSettingsConfig } from './settingsValidator.js'; * @returns {Promise} */ export async function loadSettingsConfig() { + const settingsConfigCache = useSettingsConfigCacheStore(); let raw; try { raw = (await import('../settings.json')).default; + settingsConfigCache.set(raw); } catch (e) { throw new Error(`[settings] Failed to load settings.json: ${e.message}`); } -- 2.39.5 From fab7d7e901b500c02063e6080e5ebf9a15bb647a Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Wed, 5 Aug 2026 01:44:03 +0200 Subject: [PATCH 4/4] test(settings): add test cases for empty setting value return value --- .../settings/composables/__tests__/useSettings.test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/features/settings/composables/__tests__/useSettings.test.js b/src/features/settings/composables/__tests__/useSettings.test.js index 772467c..dc62d69 100644 --- a/src/features/settings/composables/__tests__/useSettings.test.js +++ b/src/features/settings/composables/__tests__/useSettings.test.js @@ -54,6 +54,11 @@ describe('useSettings', () => { settings.set(settingPath, value); expect(getSetting(settingPath)).toBe(value); }); + + test('returns undefined if there is no value', () => { + const { getSetting } = useSettings(); + expect(getSetting(['test', 'bool'])).toBeUndefined(); + }); }); }); @@ -76,6 +81,11 @@ beforeEach(() => { i18n: '', name: 'number', default: settingDefaultValue + }, + { + type: 'bool', + i18n: '', + name: 'bool' } ] } -- 2.39.5