mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-29 01:01:34 -07:00
236f22662c
- Add `youtube.mediaCache.mode` config option (`direct` | `background`) - Resolve EDL stream URLs to single audio/video URLs before ffmpeg extraction - Pass reconnect, user-agent, and safe headers to ffmpeg for remote streams - Add background yt-dlp media cache with fallback to direct extraction - Introduce `MediaInput` structured type replacing bare path strings
125 lines
3.5 KiB
TypeScript
125 lines
3.5 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import { renderControl } from './settings-controls';
|
|
import type { ConfigSettingsField } from '../types/settings';
|
|
|
|
class FakeOption {
|
|
value = '';
|
|
textContent: string | null = null;
|
|
selected = false;
|
|
className = '';
|
|
}
|
|
|
|
class FakeSelect {
|
|
value = '';
|
|
className = '';
|
|
options: FakeOption[] = [];
|
|
private readonly listeners = new Map<string, Array<() => void>>();
|
|
|
|
append(...children: FakeOption[]): void {
|
|
for (const child of children) {
|
|
this.options.push(child);
|
|
if (child.selected) {
|
|
this.value = child.value;
|
|
}
|
|
}
|
|
}
|
|
|
|
addEventListener(type: string, listener: () => void): void {
|
|
const listeners = this.listeners.get(type) ?? [];
|
|
listeners.push(listener);
|
|
this.listeners.set(type, listeners);
|
|
}
|
|
|
|
dispatchEvent(event: Event): boolean {
|
|
for (const listener of this.listeners.get(event.type) ?? []) {
|
|
listener();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function installDocumentStub(): () => void {
|
|
const previousDocument = globalThis.document;
|
|
globalThis.document = {
|
|
createElement(tagName: string) {
|
|
return tagName === 'select' ? new FakeSelect() : new FakeOption();
|
|
},
|
|
} as unknown as Document;
|
|
return () => {
|
|
globalThis.document = previousDocument;
|
|
};
|
|
}
|
|
|
|
function createSelectField(): ConfigSettingsField {
|
|
return {
|
|
id: 'updates.notificationType',
|
|
label: 'Notification Type',
|
|
description: 'How SubMiner announces available updates.',
|
|
configPath: 'updates.notificationType',
|
|
category: 'tracking-app',
|
|
section: 'Updates',
|
|
control: 'select',
|
|
defaultValue: 'system',
|
|
enumValues: ['overlay', 'system', 'both', 'none'],
|
|
restartBehavior: 'restart',
|
|
};
|
|
}
|
|
|
|
test('select controls show config-only current values without offering them otherwise', () => {
|
|
const restoreDocument = installDocumentStub();
|
|
const updates: Array<{ path: string; value: unknown }> = [];
|
|
try {
|
|
const control = renderControl(createSelectField(), {
|
|
valueForField: () => 'osd-system',
|
|
valueForPath: () => undefined,
|
|
updateDraft: (path, value) => updates.push({ path, value }),
|
|
resetDraftPath: () => {},
|
|
setFieldError: () => {},
|
|
}) as HTMLSelectElement;
|
|
|
|
assert.equal(control.value, 'osd-system');
|
|
assert.deepEqual(
|
|
Array.from(control.options).map((option) => option.value),
|
|
['osd-system', 'overlay', 'system', 'both', 'none'],
|
|
);
|
|
|
|
control.value = 'overlay';
|
|
control.dispatchEvent(new Event('change'));
|
|
|
|
assert.deepEqual(updates, [{ path: 'updates.notificationType', value: 'overlay' }]);
|
|
} finally {
|
|
restoreDocument();
|
|
}
|
|
});
|
|
|
|
test('select controls use configured option labels', () => {
|
|
const restoreDocument = installDocumentStub();
|
|
try {
|
|
const field = {
|
|
...createSelectField(),
|
|
enumValues: ['direct', 'background'],
|
|
enumLabels: {
|
|
direct: 'Direct stream extraction',
|
|
background: 'Background media cache',
|
|
},
|
|
} as ConfigSettingsField & { enumLabels: Record<string, string> };
|
|
|
|
const control = renderControl(field, {
|
|
valueForField: () => 'direct',
|
|
valueForPath: () => undefined,
|
|
updateDraft: () => {},
|
|
resetDraftPath: () => {},
|
|
setFieldError: () => {},
|
|
}) as HTMLSelectElement;
|
|
|
|
assert.deepEqual(
|
|
Array.from(control.options).map((option) => option.textContent),
|
|
['Direct stream extraction', 'Background media cache'],
|
|
);
|
|
} finally {
|
|
restoreDocument();
|
|
}
|
|
});
|