mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-23 05:16:23 -07:00
139 lines
5.3 KiB
TypeScript
139 lines
5.3 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
import type { CompiledSessionBinding, ResolvedConfig } from '../../types';
|
|
import { createSessionBindingsRuntime } from './session-bindings-runtime';
|
|
|
|
test('persistSessionBindings logs and does not publish bindings when artifact write fails', () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-session-bindings-runtime-'));
|
|
const configDir = path.join(root, 'config-file');
|
|
fs.writeFileSync(configDir, 'not a directory');
|
|
const calls: string[] = [];
|
|
const runtime = createSessionBindingsRuntime({
|
|
configDir,
|
|
getKeybindings: () => [],
|
|
getConfiguredShortcuts: () => ({ multiCopyTimeoutMs: 1500 }) as never,
|
|
getResolvedConfig: () =>
|
|
({
|
|
stats: { toggleKey: 's', markWatchedKey: 'w' },
|
|
}) as ResolvedConfig,
|
|
getMpvClient: () => null,
|
|
setSessionBindings: () => calls.push('setSessionBindings'),
|
|
setSessionBindingsInitialized: () => calls.push('setSessionBindingsInitialized'),
|
|
logWarn: (message) => calls.push(`warn:${message}`),
|
|
});
|
|
|
|
try {
|
|
assert.throws(
|
|
() => runtime.persistSessionBindings([] as CompiledSessionBinding[]),
|
|
/ENOTDIR|EEXIST/,
|
|
);
|
|
assert.deepEqual(calls, ['warn:[session-bindings] Failed to write session bindings artifact']);
|
|
} finally {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('persistSessionBindings keeps saved bindings when mpv reload notification fails', () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-session-bindings-runtime-'));
|
|
const calls: string[] = [];
|
|
const runtime = createSessionBindingsRuntime({
|
|
configDir: root,
|
|
getKeybindings: () => [],
|
|
getConfiguredShortcuts: () => ({ multiCopyTimeoutMs: 1500 }) as never,
|
|
getResolvedConfig: () =>
|
|
({
|
|
stats: { toggleKey: 's', markWatchedKey: 'w' },
|
|
}) as ResolvedConfig,
|
|
getMpvClient: () =>
|
|
({
|
|
connected: true,
|
|
send: () => {
|
|
throw new Error('mpv unavailable');
|
|
},
|
|
}) as never,
|
|
setSessionBindings: () => calls.push('setSessionBindings'),
|
|
setSessionBindingsInitialized: () => calls.push('setSessionBindingsInitialized'),
|
|
logWarn: (message) => calls.push(`warn:${message}`),
|
|
});
|
|
|
|
try {
|
|
assert.doesNotThrow(() => runtime.persistSessionBindings([] as CompiledSessionBinding[]));
|
|
assert.deepEqual(calls, [
|
|
'setSessionBindings',
|
|
'setSessionBindingsInitialized',
|
|
'warn:[session-bindings] Failed to notify mpv to reload session bindings',
|
|
]);
|
|
} finally {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('native prefix conflicts publish the same effective bindings to the overlay and plugin and recover', async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-session-conflict-'));
|
|
const sequence: CompiledSessionBinding = {
|
|
sourcePath: 'shortcuts.openSubtitleSelection',
|
|
originalKey: 'g-s',
|
|
key: { code: 'KeyG-KeyS', modifiers: [] },
|
|
actionType: 'session-action',
|
|
actionId: 'openSubtitleSelection',
|
|
};
|
|
let nativeKeys: unknown = [];
|
|
let failDiscovery = false;
|
|
let published: CompiledSessionBinding[] = [];
|
|
const events: CompiledSessionBinding[][] = [];
|
|
const warnings: string[] = [];
|
|
const client = {
|
|
connected: true,
|
|
send: () => {},
|
|
requestProperty: async () => {
|
|
if (failDiscovery) throw new Error('temporarily unavailable');
|
|
return nativeKeys;
|
|
},
|
|
};
|
|
const runtime = createSessionBindingsRuntime({
|
|
configDir: root,
|
|
getKeybindings: () => [],
|
|
getConfiguredShortcuts: () => ({ multiCopyTimeoutMs: 1500 }) as never,
|
|
getResolvedConfig: () => ({ stats: { toggleKey: 's', markWatchedKey: 'w' } }) as ResolvedConfig,
|
|
getMpvClient: () => client,
|
|
setSessionBindings: (bindings) => {
|
|
published = bindings;
|
|
},
|
|
setSessionBindingsInitialized: () => {},
|
|
logWarn: () => {},
|
|
onBindingsChanged: (bindings) => events.push(bindings),
|
|
onWarning: (warning) => warnings.push(warning.message),
|
|
});
|
|
const readArtifact = () =>
|
|
JSON.parse(fs.readFileSync(path.join(root, 'session-bindings.json'), 'utf8'));
|
|
try {
|
|
runtime.persistSessionBindings([sequence]);
|
|
nativeKeys = [{ key: 'g', cmd: 'show-text single', priority: 1 }];
|
|
await runtime.refreshMpvSessionBindings();
|
|
assert.deepEqual(published, []);
|
|
assert.deepEqual(events.at(-1), readArtifact().bindings);
|
|
assert.equal(warnings.length, 1);
|
|
assert.match(warnings[0]!, /mpv input binding "g"/);
|
|
await runtime.refreshMpvSessionBindings();
|
|
assert.equal(events.length, 2, 'unchanged discovery must not create a reload loop');
|
|
assert.equal(warnings.length, 1);
|
|
failDiscovery = true;
|
|
await runtime.refreshMpvSessionBindings();
|
|
assert.deepEqual(published, [], 'failed discovery retains the known conflict');
|
|
failDiscovery = false;
|
|
nativeKeys = [{ key: 'Shift+g', cmd: 'show-text shifted', priority: 1 }];
|
|
await runtime.refreshMpvSessionBindings();
|
|
assert.deepEqual(published, [sequence]);
|
|
assert.equal(readArtifact().bindings[0].key.code, 'KeyG-KeyS');
|
|
assert.deepEqual(readArtifact().warnings, []);
|
|
client.connected = false;
|
|
await runtime.refreshMpvSessionBindings();
|
|
assert.deepEqual(published, [sequence]);
|
|
} finally {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|