mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-14 13:55:55 -07:00
feat(config): add configuration window (#70)
This commit is contained in:
@@ -4,7 +4,10 @@ import {
|
||||
createSettingsDraft,
|
||||
filterSettingsFields,
|
||||
setDraftValue,
|
||||
resetDraftPath,
|
||||
getDirtyOperations,
|
||||
toConfigDraftValue,
|
||||
toSettingsDisplayValue,
|
||||
} from './settings-model';
|
||||
import type { ConfigSettingsField } from '../types/settings';
|
||||
|
||||
@@ -14,8 +17,8 @@ const fields: ConfigSettingsField[] = [
|
||||
label: 'Pause on subtitle hover',
|
||||
description: 'Pause while hovering subtitles.',
|
||||
configPath: 'subtitleStyle.autoPauseVideoOnHover',
|
||||
category: 'viewing',
|
||||
section: 'Playback pause behavior',
|
||||
category: 'behavior',
|
||||
section: 'Playback Behavior',
|
||||
control: 'boolean',
|
||||
defaultValue: true,
|
||||
restartBehavior: 'hot-reload',
|
||||
@@ -31,16 +34,80 @@ const fields: ConfigSettingsField[] = [
|
||||
defaultValue: true,
|
||||
restartBehavior: 'restart',
|
||||
},
|
||||
{
|
||||
id: 'subtitleStyle.fontSize',
|
||||
label: 'Font Size',
|
||||
description: 'Hidden behind CSS editor.',
|
||||
configPath: 'subtitleStyle.fontSize',
|
||||
category: 'appearance',
|
||||
section: 'Primary Subtitle Appearance',
|
||||
control: 'number',
|
||||
defaultValue: 35,
|
||||
restartBehavior: 'hot-reload',
|
||||
settingsHidden: true,
|
||||
},
|
||||
];
|
||||
|
||||
test('filterSettingsFields searches label, section, and config path', () => {
|
||||
assert.deepEqual(
|
||||
filterSettingsFields(fields, { category: 'viewing', query: 'hover' }).map(
|
||||
filterSettingsFields(fields, { category: 'behavior', query: 'hover' }).map(
|
||||
(field) => field.configPath,
|
||||
),
|
||||
['subtitleStyle.autoPauseVideoOnHover'],
|
||||
);
|
||||
assert.deepEqual(filterSettingsFields(fields, { category: 'viewing', query: 'anki' }), []);
|
||||
assert.deepEqual(filterSettingsFields(fields, { category: 'behavior', query: 'anki' }), []);
|
||||
assert.deepEqual(
|
||||
filterSettingsFields(fields, { query: 'anki' }).map((field) => field.configPath),
|
||||
['ankiConnect.enabled'],
|
||||
);
|
||||
assert.deepEqual(
|
||||
filterSettingsFields(fields, { query: 'pause hover' }).map((field) => field.configPath),
|
||||
['subtitleStyle.autoPauseVideoOnHover'],
|
||||
);
|
||||
assert.deepEqual(filterSettingsFields(fields, { query: 'pause anki' }), []);
|
||||
assert.deepEqual(filterSettingsFields(fields, { category: 'appearance', query: '' }), []);
|
||||
});
|
||||
|
||||
test('filterSettingsFields normalizes punctuation in query terms', () => {
|
||||
const nPlusOneFields: ConfigSettingsField[] = [
|
||||
{
|
||||
id: 'ankiConnect.nPlusOne.enabled',
|
||||
label: 'Enable N+1',
|
||||
description: 'Highlight N+1 cards.',
|
||||
configPath: 'ankiConnect.nPlusOne.enabled',
|
||||
category: 'mining-anki',
|
||||
section: 'N+1',
|
||||
control: 'boolean',
|
||||
defaultValue: true,
|
||||
restartBehavior: 'hot-reload',
|
||||
},
|
||||
];
|
||||
|
||||
assert.deepEqual(
|
||||
filterSettingsFields(nPlusOneFields, { query: 'n+1' }).map((field) => field.configPath),
|
||||
['ankiConnect.nPlusOne.enabled'],
|
||||
);
|
||||
});
|
||||
|
||||
test('filterSettingsFields preserves non-Latin query terms', () => {
|
||||
const japaneseFields: ConfigSettingsField[] = [
|
||||
{
|
||||
id: 'subtitleStyle.japaneseFontFamily',
|
||||
label: '日本語フォント',
|
||||
description: '字幕の表示に使う書体。',
|
||||
configPath: 'subtitleStyle.japaneseFontFamily',
|
||||
category: 'appearance',
|
||||
section: 'Primary Subtitle Appearance',
|
||||
control: 'text',
|
||||
defaultValue: '',
|
||||
restartBehavior: 'hot-reload',
|
||||
},
|
||||
];
|
||||
|
||||
assert.deepEqual(
|
||||
filterSettingsFields(japaneseFields, { query: '日本語' }).map((field) => field.configPath),
|
||||
['subtitleStyle.japaneseFontFamily'],
|
||||
);
|
||||
});
|
||||
|
||||
test('settings draft tracks dirty set and emits save operations', () => {
|
||||
@@ -60,3 +127,40 @@ test('settings draft tracks dirty set and emits save operations', () => {
|
||||
setDraftValue(draft, 'subtitleStyle.autoPauseVideoOnHover', true);
|
||||
assert.deepEqual(getDirtyOperations(draft), []);
|
||||
});
|
||||
|
||||
test('settings draft emits reset operations for css-editor-owned legacy style paths', () => {
|
||||
const draft = createSettingsDraft({
|
||||
'subtitleStyle.css': {},
|
||||
'subtitleStyle.fontSize': 35,
|
||||
});
|
||||
|
||||
setDraftValue(draft, 'subtitleStyle.css', { 'font-size': '42px' });
|
||||
resetDraftPath(draft, 'subtitleStyle.fontSize', undefined);
|
||||
|
||||
assert.deepEqual(getDirtyOperations(draft), [
|
||||
{
|
||||
op: 'set',
|
||||
path: 'subtitleStyle.css',
|
||||
value: { 'font-size': '42px' },
|
||||
},
|
||||
{
|
||||
op: 'reset',
|
||||
path: 'subtitleStyle.fontSize',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('discord presence update interval displays seconds while saving milliseconds', () => {
|
||||
const path = 'discordPresence.updateIntervalMs';
|
||||
|
||||
assert.equal(toSettingsDisplayValue(path, 3000), 3);
|
||||
assert.equal(toConfigDraftValue(path, 2.5), 2500);
|
||||
});
|
||||
|
||||
test('websocket enabled select values save booleans instead of strings', () => {
|
||||
assert.equal(toSettingsDisplayValue('websocket.enabled', true), 'true');
|
||||
assert.equal(toSettingsDisplayValue('websocket.enabled', false), 'false');
|
||||
assert.equal(toConfigDraftValue('websocket.enabled', 'true'), true);
|
||||
assert.equal(toConfigDraftValue('websocket.enabled', 'false'), false);
|
||||
assert.equal(toConfigDraftValue('websocket.enabled', 'auto'), 'auto');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user