mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-09 16:19:25 -07:00
* fix: harden preload argv parsing for popup windows * fix: align youtube playback with shared overlay startup * fix: unwrap mpv youtube streams for anki media mining * docs: update docs for youtube subtitle and mining flow * refactor: unify cli and runtime wiring for startup and youtube flow * feat: update subtitle sidebar overlay behavior * chore: add shared log-file source for diagnostics * fix(ci): add changelog fragment for immersion changes * fix: address CodeRabbit review feedback * fix: persist canonical title from youtube metadata * style: format stats library tab * fix: address latest review feedback * style: format stats library files * test: stub launcher youtube deps in CI * test: isolate launcher youtube flow deps * test: stub launcher youtube deps in failing case * test: force x11 backend in launcher ci harness * test: address latest review feedback * fix(launcher): preserve user YouTube ytdl raw options * docs(backlog): update task tracking notes * fix(immersion): special-case youtube media paths in runtime and tracking * feat(stats): improve YouTube media metadata and picker key handling * fix(ci): format stats media library hook * fix: address latest CodeRabbit review items * docs: update youtube release notes and docs * feat: auto-load youtube subtitles before manual picker * fix: restore app-owned youtube subtitle flow * docs: update youtube playback docs and config copy * refactor: remove legacy youtube launcher mode plumbing * fix: refine youtube subtitle startup binding * docs: clarify youtube subtitle startup behavior * fix: address PR #31 latest review follow-ups * fix: address PR #31 follow-up review comments * test: harden youtube picker test harness * udpate backlog * fix: add timeout to youtube metadata probe * docs: refresh youtube and stats docs * update backlog * update backlog * chore: release v0.9.0
121 lines
4.8 KiB
TypeScript
121 lines
4.8 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { handleMpvCommandFromIpc } from './ipc-command';
|
|
|
|
function createOptions(overrides: Partial<Parameters<typeof handleMpvCommandFromIpc>[1]> = {}) {
|
|
const calls: string[] = [];
|
|
const sentCommands: (string | number)[][] = [];
|
|
const osd: string[] = [];
|
|
const options: Parameters<typeof handleMpvCommandFromIpc>[1] = {
|
|
specialCommands: {
|
|
SUBSYNC_TRIGGER: '__subsync-trigger',
|
|
RUNTIME_OPTIONS_OPEN: '__runtime-options-open',
|
|
RUNTIME_OPTION_CYCLE_PREFIX: '__runtime-option-cycle:',
|
|
REPLAY_SUBTITLE: '__replay-subtitle',
|
|
PLAY_NEXT_SUBTITLE: '__play-next-subtitle',
|
|
SHIFT_SUB_DELAY_TO_NEXT_SUBTITLE_START: '__sub-delay-next-line',
|
|
SHIFT_SUB_DELAY_TO_PREVIOUS_SUBTITLE_START: '__sub-delay-prev-line',
|
|
YOUTUBE_PICKER_OPEN: '__youtube-picker-open',
|
|
},
|
|
triggerSubsyncFromConfig: () => {
|
|
calls.push('subsync');
|
|
},
|
|
openRuntimeOptionsPalette: () => {
|
|
calls.push('runtime-options');
|
|
},
|
|
openYoutubeTrackPicker: () => {
|
|
calls.push('youtube-picker');
|
|
},
|
|
runtimeOptionsCycle: () => ({ ok: true }),
|
|
showMpvOsd: (text) => {
|
|
osd.push(text);
|
|
},
|
|
mpvReplaySubtitle: () => {
|
|
calls.push('replay');
|
|
},
|
|
mpvPlayNextSubtitle: () => {
|
|
calls.push('next');
|
|
},
|
|
shiftSubDelayToAdjacentSubtitle: async (direction) => {
|
|
calls.push(`shift:${direction}`);
|
|
},
|
|
mpvSendCommand: (command) => {
|
|
sentCommands.push(command);
|
|
},
|
|
resolveProxyCommandOsd: async () => null,
|
|
isMpvConnected: () => true,
|
|
hasRuntimeOptionsManager: () => true,
|
|
...overrides,
|
|
};
|
|
return { options, calls, sentCommands, osd };
|
|
}
|
|
|
|
test('handleMpvCommandFromIpc forwards regular mpv commands', () => {
|
|
const { options, sentCommands, osd } = createOptions();
|
|
handleMpvCommandFromIpc(['cycle', 'pause'], options);
|
|
assert.deepEqual(sentCommands, [['cycle', 'pause']]);
|
|
assert.deepEqual(osd, []);
|
|
});
|
|
|
|
test('handleMpvCommandFromIpc emits osd for subtitle position keybinding proxies', async () => {
|
|
const { options, sentCommands, osd } = createOptions();
|
|
handleMpvCommandFromIpc(['add', 'sub-pos', 1], options);
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.deepEqual(sentCommands, [['add', 'sub-pos', 1]]);
|
|
assert.deepEqual(osd, ['Subtitle position: ${sub-pos}']);
|
|
});
|
|
|
|
test('handleMpvCommandFromIpc emits resolved osd for primary subtitle track keybinding proxies', async () => {
|
|
const { options, sentCommands, osd } = createOptions({
|
|
resolveProxyCommandOsd: async () => 'Subtitle track: Internal #3 - Japanese (active)',
|
|
});
|
|
handleMpvCommandFromIpc(['cycle', 'sid'], options);
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.deepEqual(sentCommands, [['cycle', 'sid']]);
|
|
assert.deepEqual(osd, ['Subtitle track: Internal #3 - Japanese (active)']);
|
|
});
|
|
|
|
test('handleMpvCommandFromIpc emits resolved osd for secondary subtitle track keybinding proxies', async () => {
|
|
const { options, sentCommands, osd } = createOptions({
|
|
resolveProxyCommandOsd: async () =>
|
|
'Secondary subtitle track: External #8 - English Commentary',
|
|
});
|
|
handleMpvCommandFromIpc(['set_property', 'secondary-sid', 'auto'], options);
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.deepEqual(sentCommands, [['set_property', 'secondary-sid', 'auto']]);
|
|
assert.deepEqual(osd, ['Secondary subtitle track: External #8 - English Commentary']);
|
|
});
|
|
|
|
test('handleMpvCommandFromIpc emits osd for subtitle delay keybinding proxies', async () => {
|
|
const { options, sentCommands, osd } = createOptions();
|
|
handleMpvCommandFromIpc(['add', 'sub-delay', 0.1], options);
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.deepEqual(sentCommands, [['add', 'sub-delay', 0.1]]);
|
|
assert.deepEqual(osd, ['Subtitle delay: ${sub-delay}']);
|
|
});
|
|
|
|
test('handleMpvCommandFromIpc dispatches special subtitle-delay shift command', () => {
|
|
const { options, calls, sentCommands, osd } = createOptions();
|
|
handleMpvCommandFromIpc(['__sub-delay-next-line'], options);
|
|
assert.deepEqual(calls, ['shift:next']);
|
|
assert.deepEqual(sentCommands, []);
|
|
assert.deepEqual(osd, []);
|
|
});
|
|
|
|
test('handleMpvCommandFromIpc dispatches special youtube picker open command', () => {
|
|
const { options, calls, sentCommands, osd } = createOptions();
|
|
handleMpvCommandFromIpc(['__youtube-picker-open'], options);
|
|
assert.deepEqual(calls, ['youtube-picker']);
|
|
assert.deepEqual(sentCommands, []);
|
|
assert.deepEqual(osd, []);
|
|
});
|
|
|
|
test('handleMpvCommandFromIpc does not forward commands while disconnected', () => {
|
|
const { options, sentCommands, osd } = createOptions({
|
|
isMpvConnected: () => false,
|
|
});
|
|
handleMpvCommandFromIpc(['add', 'sub-pos', 1], options);
|
|
assert.deepEqual(sentCommands, []);
|
|
assert.deepEqual(osd, []);
|
|
});
|