style: format code using Prettier

This commit is contained in:
2026-08-08 17:23:19 +02:00
parent 1932eaf154
commit ea5e238ad0
59 changed files with 9649 additions and 8795 deletions
@@ -24,11 +24,11 @@ const { t } = useI18n();
const props = defineProps({
setting: {
required: true
required: true,
},
path: {
required: true
}
required: true,
},
});
const store = useSettingsStore();
@@ -41,22 +41,22 @@ const doneButtonInitial = ref(true);
const inputId = useId();
const inputLabelId = useId();
const inputType = computed(() => ({
number: 'number',
string: 'text'
}[props.setting.type]
));
const hasChanged = computed(() =>
inputModel.value !== store.get(props.path)
const inputType = computed(
() =>
({
number: 'number',
string: 'text',
})[props.setting.type],
);
const hasChanged = computed(() => inputModel.value !== store.get(props.path));
watch(store, (newValue) => {
const newInputValue = newValue.get(props.path);
if (inputModel.value === oldInputValue) {
inputModel.value = newInputValue;
oldInputValue = newInputValue;
};
}
});
watch(inputModel, () => {
@@ -129,7 +129,10 @@ const apply = function apply() {
}
.input-container.done-button-shown .input {
width: calc(100% - 1.5 * var(--input-padding) - 26px - 0.75 * var(--input-height) + 1.5px);
width: calc(
100% - 1.5 * var(--input-padding) - 26px - 0.75 * var(--input-height) +
1.5px
);
}
.done-button {
@@ -25,11 +25,11 @@ const { t } = useI18n();
const props = defineProps({
setting: {
required: true
required: true,
},
path: {
required: true
}
required: true,
},
});
</script>
@@ -78,15 +78,15 @@ const props = defineProps({
margin-bottom: calc(var(--size) - 0.2em);
}
.section-heading[aria-level="2"] {
.section-heading[aria-level='2'] {
--size: 1.8em;
}
.section-heading[aria-level="3"] {
.section-heading[aria-level='3'] {
--size: 1.6em;
}
.section-heading[aria-level="4"] {
.section-heading[aria-level='4'] {
--size: 1.4em;
}
</style>
@@ -25,12 +25,12 @@ const { goToSettingsPage } = useSettingsPage();
const props = defineProps({
target: {
required: true,
type: String
type: String,
},
displayName: {
required: true,
type: String
}
type: String,
},
});
const followLink = function followLink() {
@@ -42,7 +42,9 @@ const followLink = function followLink() {
<button
class="settings-button button settings-page-link"
:title="props.displayName"
:aria-label="t('settings.pages.link.ariaLabel', { page: props.displayName })"
:aria-label="
t('settings.pages.link.ariaLabel', { page: props.displayName })
"
@click="followLink"
>
{{ props.displayName }}
@@ -44,19 +44,22 @@ const normalizeSelectedValue = function normalizeSelectedValue(value) {
const props = defineProps({
setting: {
required: true
required: true,
},
path: {
required: true
}
required: true,
},
});
const store = useSettingsStore();
const optionType = computed(() => props.setting.allowMultiple ? 'checkbox' : 'radio');
const optionType = computed(() =>
props.setting.allowMultiple ? 'checkbox' : 'radio',
);
const selected = computed({
get: () => normalizeSelectedValue(store.get(props.path) ?? props.setting.default),
set: (value) => store.set(props.path, value)
get: () =>
normalizeSelectedValue(store.get(props.path) ?? props.setting.default),
set: (value) => store.set(props.path, value),
});
const labelId = useId();
@@ -72,7 +75,11 @@ const labelId = useId();
<label
class="settings-button"
:title="t(option.i18n)"
:aria-label="t('settings.selection.option.ariaLabel', { option: t(option.i18n) })"
:aria-label="
t('settings.selection.option.ariaLabel', {
option: t(option.i18n),
})
"
>
<input
:type="optionType"
@@ -80,9 +87,13 @@ const labelId = useId();
:value="option.name"
class="option-input"
v-model="selected"
>
/>
<div class="option-check-mark-wrapper">
<UIIcon class="option-check-mark" name="check-mark" size="1em" />
<UIIcon
class="option-check-mark"
name="check-mark"
size="1em"
/>
</div>
<div class="input-label">
{{ t(option.i18n) }}
@@ -23,11 +23,11 @@ const { t } = useI18n();
const props = defineProps({
setting: {
required: true
required: true,
},
path: {
required: true
}
required: true,
},
});
const labelId = useId();
@@ -20,18 +20,29 @@ import { mountComponent } from '@/test-utils/mountComponent';
import { describe, expect, test } from 'vitest';
import { nextTick } from 'vue';
const getWrapper = function getWrapper({ type = 'string', i18n = 'input', defaultValue = undefined, piniaOptions = {} } = {}) {
return mountComponent(SettingsInput, {
attrs: {
setting: {
name: 'input',
default: defaultValue,
i18n,
type
const getWrapper = function getWrapper({
type = 'string',
i18n = 'input',
defaultValue = undefined,
piniaOptions = {},
} = {}) {
return mountComponent(
SettingsInput,
{
attrs: {
setting: {
name: 'input',
default: defaultValue,
i18n,
type,
},
path: 'input',
},
path: 'input'
}
}, {}, [], piniaOptions);
},
{},
[],
piniaOptions,
);
};
describe('SettingsInput', () => {
@@ -63,8 +74,8 @@ describe('SettingsInput', () => {
setupStores: () => {
const store = useSettingsStore();
store.settings.input = value;
}
}
},
},
});
const inputElement = stringSettingWrapper.find('input');
@@ -76,7 +87,7 @@ describe('SettingsInput', () => {
const wrapper = getWrapper({
type: 'string',
i18n: 'input',
defaultValue: value
defaultValue: value,
});
const inputElement = wrapper.find('input');
@@ -94,8 +105,8 @@ describe('SettingsInput', () => {
setupStores: () => {
const store = useSettingsStore();
store.settings.input = storeValue;
}
}
},
},
});
const inputElement = wrapper.find('input');
@@ -191,18 +202,22 @@ describe('SettingsInput', () => {
test('shows correct translation', () => {
const translation = 'Text input';
const wrapper = mountComponent(SettingsInput, {
attrs: {
setting: {
name: 'input',
i18n: 'input',
type: 'string',
const wrapper = mountComponent(
SettingsInput,
{
attrs: {
setting: {
name: 'input',
i18n: 'input',
type: 'string',
},
path: 'input',
},
path: 'input'
}
}, {
input: translation
});
},
{
input: translation,
},
);
const label = wrapper.find('label');
expect(label.text()).toBe(translation);
@@ -22,36 +22,45 @@ import SettingsSwitch from '../SettingsSwitch.vue';
import { mountComponent } from '@/test-utils/mountComponent.js';
import { describe, expect, test } from 'vitest';
const getWrapper = function getWrapper({ content = [], translations = {}, piniaOptions = {} }) {
return mountComponent(SettingsPage, {
attrs: {
setting: {
name: 'section',
type: 'section',
i18n: 'section',
content
const getWrapper = function getWrapper({
content = [],
translations = {},
piniaOptions = {},
}) {
return mountComponent(
SettingsPage,
{
attrs: {
setting: {
name: 'section',
type: 'section',
i18n: 'section',
content,
},
path: 'section',
},
path: 'section'
}
}, translations, [], piniaOptions);
},
translations,
[],
piniaOptions,
);
};
const exampleSettings = [
{
type: 'bool',
name: 'b0',
i18n: 'settings.section.b0'
i18n: 'settings.section.b0',
},
{
type: 'number',
name: 'n0',
i18n: 'settings.section.n0'
i18n: 'settings.section.n0',
},
{
type: 'string',
name: 'st0',
i18n: 'settings.section1.st0'
i18n: 'settings.section1.st0',
},
{
type: 'selection',
@@ -61,14 +70,14 @@ const exampleSettings = [
{ name: 'o0', i18n: 'settings.section.se0.o0' },
{ name: 'o1', i18n: 'settings.section.se0.o1' },
{ name: 'o2', i18n: 'settings.section.se0.o2' },
{ name: 'o3', i18n: 'settings.section.se0.o3' }
]
{ name: 'o3', i18n: 'settings.section.se0.o3' },
],
},
{
type: 'section',
name: 's0',
i18n: 'settings.section.s0',
content: []
content: [],
},
{
type: 'section',
@@ -79,17 +88,17 @@ const exampleSettings = [
type: 'bool',
name: 'b0',
i18n: 'settings.section.s1.b0',
default: true
}
]
}
default: true,
},
],
},
];
describe('SettingsPage', () => {
test('shows correct heading', () => {
const translation = 'Example section';
const wrapper = getWrapper({
translations: { section: translation }
translations: { section: translation },
});
const heading = wrapper.find('.section-heading');
@@ -98,10 +107,9 @@ describe('SettingsPage', () => {
test('shows settings correctly', () => {
const wrapper = getWrapper({
content: exampleSettings
content: exampleSettings,
});
const settingsWrappers = wrapper.findAll('.setting');
expect(settingsWrappers.length).toBe(exampleSettings.length);
@@ -110,8 +118,10 @@ describe('SettingsPage', () => {
const switchSetting = settingWrapper.findComponent(SettingsSwitch);
const inputSetting = settingWrapper.findComponent(SettingsInput);
const selectionSetting = settingWrapper.findComponent(SettingsSelection);
const settingsPageLink = settingWrapper.findComponent(SettingsPageLink);
const selectionSetting =
settingWrapper.findComponent(SettingsSelection);
const settingsPageLink =
settingWrapper.findComponent(SettingsPageLink);
let setting;
if (expectedSetting.type === 'bool') {
@@ -121,7 +131,10 @@ describe('SettingsPage', () => {
expect(settingsPageLink.exists()).toBe(false);
setting = switchSetting;
} else if (expectedSetting.type === 'string' || expectedSetting.type === 'number') {
} else if (
expectedSetting.type === 'string' ||
expectedSetting.type === 'number'
) {
expect(switchSetting.exists()).toBe(false);
expect(inputSetting.exists()).toBe(true);
expect(selectionSetting.exists()).toBe(false);
@@ -150,10 +163,14 @@ describe('SettingsPage', () => {
expectedSetting.type === 'selection'
) {
expect(setting.props('setting')).toMatchObject(expectedSetting);
expect(setting.props('path')).toBe(`section.${expectedSetting.name}`);
expect(setting.props('path')).toBe(
`section.${expectedSetting.name}`,
);
} else {
expect(setting.props('displayName')).toBe(expectedSetting.i18n);
expect(setting.props('target')).toBe(`section.${expectedSetting.name}`);
expect(setting.props('target')).toBe(
`section.${expectedSetting.name}`,
);
}
});
});
@@ -22,21 +22,19 @@ import { createI18n } from 'vue-i18n';
const goToSettingsPage = vi.fn();
vi.mock('../../composables/useSettingsPage.js', () => ({
useSettingsPage: () => ({
goToSettingsPage
})
goToSettingsPage,
}),
}));
const getWrapper = function getWrapper({ target, displayName }) {
return mount(SettingsPageLink, {
attrs: {
target,
displayName
displayName,
},
global: {
plugins: [
createI18n()
]
}
plugins: [createI18n()],
},
});
};
@@ -26,21 +26,27 @@ const getWrapper = function getWrapper({
allowMultiple = false,
defaultValue = undefined,
translations = {},
piniaOptions = {}
piniaOptions = {},
} = {}) {
return mountComponent(SettingsSelection, {
attrs: {
setting: {
name: 'selection',
type: 'selection',
i18n,
options,
allowMultiple,
default: defaultValue
return mountComponent(
SettingsSelection,
{
attrs: {
setting: {
name: 'selection',
type: 'selection',
i18n,
options,
allowMultiple,
default: defaultValue,
},
path: 'selection',
},
path: 'selection'
}
}, translations, [], piniaOptions);
},
translations,
[],
piniaOptions,
);
};
const getChecked = function getChecked(inputs) {
@@ -50,7 +56,7 @@ const getChecked = function getChecked(inputs) {
const exampleOptions = [
{ name: 'o0', i18n: 'o0' },
{ name: 'o1', i18n: 'o1' },
{ name: 'o2', i18n: 'o2' }
{ name: 'o2', i18n: 'o2' },
];
describe('SettingsSelection', () => {
@@ -81,8 +87,8 @@ describe('SettingsSelection', () => {
setupStores: () => {
const store = useSettingsStore();
store.set('selection', 'o1');
}
}
},
},
});
const inputs = wrapper.findAll('input');
@@ -97,8 +103,8 @@ describe('SettingsSelection', () => {
setupStores: () => {
const store = useSettingsStore();
store.set('selection', 'o1');
}
}
},
},
});
const settings = useSettingsStore();
@@ -123,9 +129,9 @@ describe('SettingsSelection', () => {
setupStores: () => {
const store = useSettingsStore();
store.set('selection', ['o1', 'o2']);
}
},
},
allowMultiple: true
allowMultiple: true,
});
const inputs = wrapper.findAll('input');
@@ -136,7 +142,7 @@ describe('SettingsSelection', () => {
test('shows default value', () => {
const wrapper = getWrapper({
options: exampleOptions,
defaultValue: 'o2'
defaultValue: 'o2',
});
const inputs = wrapper.findAll('input');
@@ -148,7 +154,7 @@ describe('SettingsSelection', () => {
const wrapper = getWrapper({
options: exampleOptions,
defaultValue: ['o0', 'o1'],
allowMultiple: true
allowMultiple: true,
});
const inputs = wrapper.findAll('input');
@@ -164,8 +170,8 @@ describe('SettingsSelection', () => {
setupStores: () => {
const store = useSettingsStore();
store.set('selection', 'o1');
}
}
},
},
});
const inputs = wrapper.findAll('input');
@@ -212,20 +218,23 @@ describe('SettingsSelection', () => {
return setValue(input, false);
};
const expectSelectionValue = function expectSelectionValue(value, options = exampleOptions) {
const expectSelectionValue = function expectSelectionValue(
value,
options = exampleOptions,
) {
value.sort();
expect(store.get('selection').sort()).toStrictEqual(value);
const checkedInputs = getChecked(inputs);
expect(
options
.map((option) => option.name)
.filter((_, i) => checkedInputs[i])
.map((option) => option.name)
.filter((_, i) => checkedInputs[i]),
).toStrictEqual(value);
};
const wrapper = getWrapper({
options: exampleOptions,
allowMultiple: true
allowMultiple: true,
});
const store = useSettingsStore();
@@ -283,8 +292,8 @@ describe('SettingsSelection', () => {
const translation = 'Selection setting';
const wrapper = getWrapper({
translations: {
selection: translation
}
selection: translation,
},
});
const label = wrapper.find('.label');
@@ -24,19 +24,25 @@ const getWrapper = function getWrapper({
i18n = 'switch1',
defaultValue = undefined,
translations = {},
piniaOptions = {}
piniaOptions = {},
} = {}) {
return mountComponent(SettingsSwitch, {
attrs: {
setting: {
type: 'bool',
name: 'switch',
i18n,
default: defaultValue
return mountComponent(
SettingsSwitch,
{
attrs: {
setting: {
type: 'bool',
name: 'switch',
i18n,
default: defaultValue,
},
path: 'switch',
},
path: 'switch'
}
}, translations, [], piniaOptions);
},
translations,
[],
piniaOptions,
);
};
describe('SettingsSwitch', () => {
@@ -61,7 +67,7 @@ describe('SettingsSwitch', () => {
test('shows default value', () => {
const wrapper = getWrapper({
defaultValue: true
defaultValue: true,
});
const switchElement = wrapper.find('.switch-wrapper');
@@ -75,8 +81,8 @@ describe('SettingsSwitch', () => {
setupStores: () => {
const store = useSettingsStore();
store.set('switch', false);
}
}
},
},
});
const switchElement = wrapper.find('.switch-wrapper');
@@ -116,7 +122,7 @@ describe('SettingsSwitch', () => {
const translation = 'Switch';
const wrapper = getWrapper({
translations: { switch1: translation }
translations: { switch1: translation },
});
const label = wrapper.find('label');
@@ -33,17 +33,17 @@ vi.mock('../../config.js', () => ({
type: 'number',
i18n: '',
name: 'number',
default: settingDefaultValue
default: settingDefaultValue,
},
{
type: 'bool',
i18n: '',
name: 'bool'
}
]
}
]
}
name: 'bool',
},
],
},
],
},
}));
describe('useSettings', () => {
@@ -85,7 +85,7 @@ beforeEach(() => {
setActivePinia(
createTestingPinia({
createSpy: vi.fn,
stubActions: false
})
stubActions: false,
}),
);
});
@@ -20,8 +20,8 @@ import { describe, expect, test, vi } from 'vitest';
const pushRoute = vi.fn();
vi.mock('vue-router', () => ({
useRouter: () => ({
push: pushRoute
})
push: pushRoute,
}),
}));
describe('useSettingsPage', () => {
@@ -32,8 +32,8 @@ describe('useSettingsPage', () => {
expect(pushRoute).toHaveBeenCalledWith({
name: 'settings',
params: {
rest: ['a', 'b', 'c', 'd', 'e']
}
rest: ['a', 'b', 'c', 'd', 'e'],
},
});
});
});
@@ -32,9 +32,7 @@ export const useSettings = function useSettings() {
return (
settingsStore.get(key.join('.')) ??
getSettingRecursively(
key, settings.contents ?? []
)?.default
getSettingRecursively(key, settings.contents ?? [])?.default
);
};
@@ -24,8 +24,8 @@ export const useSettingsPage = function useSettingsPage() {
return router.push({
name: 'settings',
params: {
rest: getSettingsPagePathSegments(pagePath)
}
rest: getSettingsPagePathSegments(pagePath),
},
});
};
+13 -13
View File
@@ -40,10 +40,10 @@ export default {
default: defaultLanguage,
options: SUPPORTED_LANGUAGES.map((language) => ({
name: language,
i18n: `preferences.locale.languages.${language}`
}))
}
]
i18n: `preferences.locale.languages.${language}`,
})),
},
],
},
{
name: 'appearance',
@@ -57,19 +57,19 @@ export default {
options: [
{
name: 'auto',
i18n: 'settings.settings.appearance.contents.colorScheme.options.auto'
i18n: 'settings.settings.appearance.contents.colorScheme.options.auto',
},
{
name: 'light',
i18n: 'settings.settings.appearance.contents.colorScheme.options.light'
i18n: 'settings.settings.appearance.contents.colorScheme.options.light',
},
{
name: 'dark',
i18n: 'settings.settings.appearance.contents.colorScheme.options.dark'
}
]
}
]
}
]
i18n: 'settings.settings.appearance.contents.colorScheme.options.dark',
},
],
},
],
},
],
};
+22 -18
View File
@@ -17,26 +17,30 @@ limitations under the License.
import { defineStore } from 'pinia';
import { reactive } from 'vue';
export const useSettingsStore = defineStore('settings', () => {
const settings = reactive({});
export const useSettingsStore = defineStore(
'settings',
() => {
const settings = reactive({});
const set = function set(key, value) {
settings[key] = value;
};
const set = function set(key, value) {
settings[key] = value;
};
const get = function get(key) {
return settings[key];
};
const get = function get(key) {
return settings[key];
};
const remove = function remove(key) {
delete settings[key];
};
const remove = function remove(key) {
delete settings[key];
};
const clear = function clear() {
Object.keys(settings).forEach(key => delete settings[key]);
};
const clear = function clear() {
Object.keys(settings).forEach((key) => delete settings[key]);
};
return { set, get, remove, clear, settings };
}, {
persist: true
});
return { set, get, remove, clear, settings };
},
{
persist: true,
},
);
@@ -19,19 +19,23 @@ import { describe, test, expect } from 'vitest';
describe('getSetting', () => {
test.for([
({ settings: [], name: '', expected: undefined }),
({ settings: [], name: 'a', expected: undefined }),
({ settings: [
{
{ settings: [], name: '', expected: undefined },
{ settings: [], name: 'a', expected: undefined },
{
settings: [
{
type: 'bool',
name: 'a',
i18n: 'a',
},
],
name: 'a',
expected: {
type: 'bool',
name: 'a',
i18n: 'a'
}
], name: 'a', expected: {
type: 'bool',
name: 'a',
i18n: 'a'
} })
i18n: 'a',
},
},
])('returns $expected', ({ settings, name, expected }) => {
expect(getSetting(name, settings)).toStrictEqual(expected);
});
@@ -40,7 +44,7 @@ describe('getSetting', () => {
{
type: 'bool',
name: '1a',
i18n: '1a'
i18n: '1a',
},
{
type: 'section',
@@ -50,7 +54,7 @@ describe('getSetting', () => {
{
type: 'bool',
name: '2a',
i18n: '2a'
i18n: '2a',
},
{
type: 'section',
@@ -60,7 +64,7 @@ describe('getSetting', () => {
{
type: 'bool',
name: '3a',
i18n: '3a'
i18n: '3a',
},
{
type: 'section',
@@ -70,7 +74,7 @@ describe('getSetting', () => {
{
type: 'bool',
name: '4a',
i18n: '4a'
i18n: '4a',
},
{
type: 'section',
@@ -80,7 +84,7 @@ describe('getSetting', () => {
{
type: 'bool',
name: '5a',
i18n: '5a'
i18n: '5a',
},
{
type: 'section',
@@ -90,7 +94,7 @@ describe('getSetting', () => {
{
type: 'bool',
name: '6a',
i18n: '6a'
i18n: '6a',
},
{
type: 'section',
@@ -100,428 +104,488 @@ describe('getSetting', () => {
{
type: 'bool',
name: '7a',
i18n: '7a'
}
]
}
]
}
]
}
]
}
]
}
]
}
i18n: '7a',
},
],
},
],
},
],
},
],
},
],
},
],
},
];
test.for([
({ settings: [], path: ['a'], expected: undefined }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
}
], path: ['1a'], expected: {
type: 'bool',
name: '1a',
i18n: '1a'
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
}
]
}
], path: ['1a'], expected: {
type: 'bool',
name: '1a',
i18n: '1a'
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
}
]
}
], path: ['1b', '2a'], expected: {
type: 'bool',
name: '2a',
i18n: '2a'
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'number',
name: '2b',
i18n: '2b',
default: 42
}
]
}
], path: ['1b', '2b'], expected: {
type: 'number',
name: '2b',
i18n: '2b',
default: 42
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1b', '2b', '3a'], expected: {
type: 'string',
name: '3a',
i18n: '3a'
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1b'], expected: {
type: 'section',
name: '1b',
i18n: '1b',
content: [
{ settings: [], path: ['a'], expected: undefined },
{
settings: [
{
type: 'bool',
name: '2a',
i18n: '2a'
name: '1a',
i18n: '1a',
},
],
path: ['1a'],
expected: {
type: 'bool',
name: '1a',
i18n: '1a',
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1b', '2b'], expected: {
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1b', '2b', '3b'], expected: undefined }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1b', '2a', '3a'], expected: undefined }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1b', '2c', '3a'], expected: undefined }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1a', '2b', '3a'], expected: undefined }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1c', '2b', '3a'], expected: undefined }),
({ settings: veryNestedSettingsList, path: ['1b', '2b', '3b', '4b', '5b', '6b', '7a'], expected: {
type: 'bool',
name: '7a',
i18n: '7a'
} }),
({ settings: veryNestedSettingsList, path: ['1b', '2b', '3b', '4b', '5b'], expected: {
type: 'section',
name: '5b',
i18n: '5b',
content: [
{
type: 'bool',
name: '6a',
i18n: '6a'
},
{
type: 'section',
name: '6b',
i18n: '6b',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '7a',
i18n: '7a'
}
]
}
]
} }),
({ settings: veryNestedSettingsList, path: ['1b', '2b', '3b', '4ba', '5b', '6b', '7a'], expected: undefined })
name: '2a',
i18n: '2a',
},
],
},
],
path: ['1a'],
expected: {
type: 'bool',
name: '1a',
i18n: '1a',
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
],
},
],
path: ['1b', '2a'],
expected: {
type: 'bool',
name: '2a',
i18n: '2a',
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'number',
name: '2b',
i18n: '2b',
default: 42,
},
],
},
],
path: ['1b', '2b'],
expected: {
type: 'number',
name: '2b',
i18n: '2b',
default: 42,
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1b', '2b', '3a'],
expected: {
type: 'string',
name: '3a',
i18n: '3a',
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1b'],
expected: {
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1b', '2b'],
expected: {
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1b', '2b', '3b'],
expected: undefined,
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1b', '2a', '3a'],
expected: undefined,
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1b', '2c', '3a'],
expected: undefined,
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1a', '2b', '3a'],
expected: undefined,
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1c', '2b', '3a'],
expected: undefined,
},
{
settings: veryNestedSettingsList,
path: ['1b', '2b', '3b', '4b', '5b', '6b', '7a'],
expected: {
type: 'bool',
name: '7a',
i18n: '7a',
},
},
{
settings: veryNestedSettingsList,
path: ['1b', '2b', '3b', '4b', '5b'],
expected: {
type: 'section',
name: '5b',
i18n: '5b',
content: [
{
type: 'bool',
name: '6a',
i18n: '6a',
},
{
type: 'section',
name: '6b',
i18n: '6b',
content: [
{
type: 'bool',
name: '7a',
i18n: '7a',
},
],
},
],
},
},
{
settings: veryNestedSettingsList,
path: ['1b', '2b', '3b', '4ba', '5b', '6b', '7a'],
expected: undefined,
},
])('returns $expected', ({ settings, path, expected }) => {
expect(getSettingRecursively(path, settings)).toStrictEqual(expected);
});
@@ -47,15 +47,24 @@ describe('settingsPage', () => {
{ path: 'a1.#b.c/', expected: ['a1', 'b', 'c'] },
{ path: 'a-1.#b.c/', expected: ['a-1', 'b', 'c'] },
{ path: 'a-1.b@.c', expected: ['a-1', 'b', 'c'] },
{ path: '....@a/#...)!&§[b.#§c..d....', expected: ['a', 'b', 'c', 'd'] },
{ path: '....@a/#...)!&§[b.#§c..dä....', expected: ['a', 'b', 'c', 'd'] },
{ path: '..,...~..@a/#.+..)!&§[b.#§c..dä..)..', expected: ['a', 'b', 'c', 'd'] },
{
path: '....@a/#...)!&§[b.#§c..d....',
expected: ['a', 'b', 'c', 'd'],
},
{
path: '....@a/#...)!&§[b.#§c..dä....',
expected: ['a', 'b', 'c', 'd'],
},
{
path: '..,...~..@a/#.+..)!&§[b.#§c..dä..)..',
expected: ['a', 'b', 'c', 'd'],
},
{ path: 'a.@.b', expected: ['a', 'b'] },
{ path: 1, expected: [] },
{ path: false, expected: [] },
{ path: true, expected: [] },
{ path: null, expected: [] },
{ path: undefined, expected: [] }
{ path: undefined, expected: [] },
])('returns $expected', ({ path, expected }) => {
expect(getSettingsPagePathSegments(path)).toStrictEqual(expected);
});
File diff suppressed because it is too large Load Diff
+8 -2
View File
@@ -18,7 +18,10 @@ export const getSetting = function getSetting(name, settings) {
return settings.filter((setting) => setting.name === name)[0];
};
export const getSettingRecursively = function getSettingRecursively(sectionPath, settings) {
export const getSettingRecursively = function getSettingRecursively(
sectionPath,
settings,
) {
const oneSettingsLevel = function oneSettingsLevel(cursor, acc) {
const setting = getSetting(cursor[0], acc.content);
if (!setting) return;
@@ -28,7 +31,10 @@ export const getSettingRecursively = function getSettingRecursively(sectionPath,
const newCursor = cursor.slice(1, cursor.length);
if (newCursor.length !== 0) {
return oneSettingsLevel(cursor.slice(1, cursor.length), setting);
return oneSettingsLevel(
cursor.slice(1, cursor.length),
setting,
);
} else {
return setting;
}
+4 -4
View File
@@ -19,13 +19,13 @@ limitations under the License.
* @param {string} path - The settings page path.
* @returns {Array} The settings page path segments.
*/
export const getSettingsPagePathSegments = function getSettingsPagePathSegments(path) {
export const getSettingsPagePathSegments = function getSettingsPagePathSegments(
path,
) {
if (typeof path !== 'string') return [];
return path
.replace(/^\.+|\.+$/g, '')
.split('.')
.map(
(segment) => segment.replace(/[^a-zA-Z0-9-]/g, '')
)
.map((segment) => segment.replace(/[^a-zA-Z0-9-]/g, ''))
.filter(Boolean);
};
@@ -25,14 +25,19 @@ export const assertString = function assertString(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(', ')}`
`[settings] "${path}" has invalid type "${value}". Must be one of: ${VALID_TYPES.join(', ')}`,
);
}
};
export const validateSelectionOptions = 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`);
throw new Error(
`[settings] "${path}.options" must be a non-empty array`,
);
}
options.forEach((opt, i) => {
assertString(opt.name, `${path}.options[${i}].name`);
@@ -51,7 +56,7 @@ export const validateEntry = function validateEntry(entry, path) {
throw new Error(`[settings] "${path}.content" must be an array`);
}
entry.content.forEach((child, i) =>
validateEntry(child, `${path}.content[${i}]`)
validateEntry(child, `${path}.content[${i}]`),
);
return;
}
@@ -59,7 +64,9 @@ export const validateEntry = function validateEntry(entry, path) {
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`);
throw new Error(
`[settings] "${path}.allowMultiple" must be a boolean`,
);
}
}
if (entry.default !== undefined) {
@@ -75,30 +82,47 @@ export const validateEntry = function validateEntry(entry, path) {
if (entry.type === 'selection') {
if (entry.allowMultiple) {
if (!Array.isArray(entry.default)) {
throw new Error(`[settings] "${path}.default" must be an array`);
throw new Error(
`[settings] "${path}.default" must be an array`,
);
}
const allOptions = entry.options.map((option) => option.name);
entry.default.forEach((defaultValue, index) => {
if (typeof defaultValue !== 'string') {
throw new Error(`[settings] "${path}.default[${index}]" must be a string`);
throw new Error(
`[settings] "${path}.default[${index}]" must be a string`,
);
}
if (!allOptions.includes(defaultValue)) {
throw new Error(`[settings] option "${path}.default[${index}]" does not exist`);
throw new Error(
`[settings] option "${path}.default[${index}]" does not exist`,
);
}
});
} else {
if (typeof entry.default !== 'string') {
throw new Error(`[settings] "${path}.default" must be a 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`);
if (
!entry.options
.map((option) => option.name)
.includes(entry.default)
) {
throw new Error(
`[settings] option "${path}.default" does not exist`,
);
}
}
}
}
};
export const validateFirstLevelSection = function validateFirstLevelSection(section, path) {
export const validateFirstLevelSection = function validateFirstLevelSection(
section,
path,
) {
assertString(section.name);
assertString(section.i18n);
@@ -107,7 +131,7 @@ export const validateFirstLevelSection = function validateFirstLevelSection(sect
}
section.content.forEach((entry, i) =>
validateEntry(entry, `${path}.content[${i}]`)
validateEntry(entry, `${path}.content[${i}]`),
);
};
@@ -124,6 +148,6 @@ export const validateSettingsConfig = function validateSettingsConfig(raw) {
throw new Error('[settings] "contents" must be an array');
}
raw.contents.forEach((entry, i) =>
validateFirstLevelSection(entry, `contents[${i}]`)
validateFirstLevelSection(entry, `contents[${i}]`),
);
};
+9 -6
View File
@@ -15,7 +15,6 @@ limitations under the License.
-->
<script setup>
import SettingsPage from '../components/SettingsPage.vue';
import settingsConfiguration from '../config.js';
import { getSettingRecursively } from '../utils/getSetting.js';
@@ -31,9 +30,7 @@ const router = useRouter();
const settings = settingsConfiguration.contents;
const activePath = computed(() => {
const segments = route.path
.split('/')
.filter(Boolean);
const segments = route.path.split('/').filter(Boolean);
return segments.slice(1, segments.length);
});
@@ -50,7 +47,9 @@ const updateSettings = async function updateSettings() {
watch(route, (newRoute) => updateSettings(newRoute));
onMounted(updateSettings);
const toggleSidebarIfSmallScreen = function toggleSidebarIfSmallScreen(toggleSidebarFunction) {
const toggleSidebarIfSmallScreen = function toggleSidebarIfSmallScreen(
toggleSidebarFunction,
) {
if (matchMedia('(max-width: 48rem)').matches) toggleSidebarFunction();
};
</script>
@@ -69,7 +68,11 @@ const toggleSidebarIfSmallScreen = function toggleSidebarIfSmallScreen(toggleSid
<RouterLink
:to="`/settings/${section.name}`"
class="button button-link link sidebar-section"
:class="{ active: activeSection && activeSection.name === section.name }"
:class="{
active:
activeSection &&
activeSection.name === section.name,
}"
@click="toggleSidebarIfSmallScreen(toggleSidebar)"
>
{{ t(section.i18n) }}
@@ -23,7 +23,7 @@ const settingsHoisted = vi.hoisted(() => [
{
name: 's0',
i18n: 'settings.s0',
content: []
content: [],
},
{
name: 's1',
@@ -32,17 +32,17 @@ const settingsHoisted = vi.hoisted(() => [
{
type: 'bool',
name: 'b0',
i18n: 'settings.s1.b0'
i18n: 'settings.s1.b0',
},
{
type: 'number',
name: 'n0',
i18n: 'settings.s1.n0'
i18n: 'settings.s1.n0',
},
{
type: 'string',
name: 'st0',
i18n: 'settings.s1.st0'
i18n: 'settings.s1.st0',
},
{
type: 'selection',
@@ -52,14 +52,14 @@ const settingsHoisted = vi.hoisted(() => [
{ name: 'o0', i18n: 'settings.s1.se0.o0' },
{ name: 'o1', i18n: 'settings.s1.se0.o1' },
{ name: 'o2', i18n: 'settings.s1.se0.o2' },
{ name: 'o3', i18n: 'settings.s1.se0.o3' }
]
{ name: 'o3', i18n: 'settings.s1.se0.o3' },
],
},
{
type: 'section',
name: 's0',
i18n: 'settings.s1.s0',
content: []
content: [],
},
{
type: 'section',
@@ -70,11 +70,11 @@ const settingsHoisted = vi.hoisted(() => [
type: 'bool',
name: 'b0',
i18n: 'settings.s1.s1.b0',
default: true
}
]
}
]
default: true,
},
],
},
],
},
{
name: 's2',
@@ -89,11 +89,11 @@ const settingsHoisted = vi.hoisted(() => [
type: 'bool',
name: 'b0',
i18n: 'settings.s2.s0.b0',
default: true
}
]
}
]
default: true,
},
],
},
],
},
{
name: 's3',
@@ -103,9 +103,9 @@ const settingsHoisted = vi.hoisted(() => [
type: 'section',
name: 's0',
i18n: 'settings.s3.s0',
content: []
}
]
content: [],
},
],
},
{
name: 's4',
@@ -119,10 +119,10 @@ const settingsHoisted = vi.hoisted(() => [
{ name: 'o0', i18n: 'settings.s4.se0.o0' },
{ name: 'o1', i18n: 'settings.s4.se0.o1' },
{ name: 'o2', i18n: 'settings.s4.se0.o2' },
{ name: 'o3', i18n: 'settings.s4.se0.o3' }
]
}
]
{ name: 'o3', i18n: 'settings.s4.se0.o3' },
],
},
],
},
{
name: 's5',
@@ -131,9 +131,9 @@ const settingsHoisted = vi.hoisted(() => [
{
type: 'string',
name: 'st0',
i18n: 'settings.s5.st0'
}
]
i18n: 'settings.s5.st0',
},
],
},
{
name: 's6',
@@ -142,9 +142,9 @@ const settingsHoisted = vi.hoisted(() => [
{
type: 'number',
name: 'n0',
i18n: 'settings.s6.n0'
}
]
i18n: 'settings.s6.n0',
},
],
},
{
name: 's7',
@@ -153,9 +153,9 @@ const settingsHoisted = vi.hoisted(() => [
{
type: 'bool',
name: 'b0',
i18n: 'settings.s7.b0'
}
]
i18n: 'settings.s7.b0',
},
],
},
{
name: 's8',
@@ -181,16 +181,16 @@ const settingsHoisted = vi.hoisted(() => [
name: 'se0',
i18n: 'settings.s8.s0.s0.s0.se0',
allowMultiple: true,
options: []
}
]
}
]
}
]
}
]
}
options: [],
},
],
},
],
},
],
},
],
},
]);
const settings = settingsHoisted.values();
@@ -198,7 +198,7 @@ const originalCurrentSection = 's0';
let currentSection = originalCurrentSection;
vi.mock('../../config.js', () => ({
default: { contents: settingsHoisted }
default: { contents: settingsHoisted },
}));
vi.mock('vue-router', async (importOriginal) => {
@@ -206,8 +206,8 @@ vi.mock('vue-router', async (importOriginal) => {
return {
...actual,
useRoute: () => ({
path: `/settings/${currentSection}`
})
path: `/settings/${currentSection}`,
}),
};
});
@@ -221,8 +221,8 @@ describe('SettingsView', () => {
const translation = 'Settings';
const wrapper = getWrapper({
translations: {
'preferences.settings': translation
}
'preferences.settings': translation,
},
});
const header = wrapper.find('header');
@@ -242,8 +242,12 @@ describe('SettingsView', () => {
expect(sidebarItem.text()).toBe(setting.i18n);
const link = sidebarItem.find('*');
expect(link.attributes('href')).toBe(`/settings/${setting.name}`);
expect(link.classes('active')).toBe(setting.name === currentSection);
expect(link.attributes('href')).toBe(
`/settings/${setting.name}`,
);
expect(link.classes('active')).toBe(
setting.name === currentSection,
);
});
});
});
@@ -257,7 +261,9 @@ describe('SettingsView', () => {
await nextTick();
const mainContent = wrapper.find('.settings-main-content');
const mainContentNoSection = wrapper.find('.main-content--no-section');
const mainContentNoSection = wrapper.find(
'.main-content--no-section',
);
expect(mainContent.exists()).toBe(false);
expect(mainContentNoSection.exists()).toBe(true);
@@ -268,7 +274,9 @@ describe('SettingsView', () => {
await nextTick();
const mainContent = wrapper.find('.settings-main-content');
const mainContentNoSection = wrapper.find('.main-content--no-section');
const mainContentNoSection = wrapper.find(
'.main-content--no-section',
);
expect(mainContent.exists()).toBe(true);
expect(mainContentNoSection.exists()).toBe(false);