mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-10-21 05:01:26 -07:00
# Conflicts: # launcher/main.test.ts # src/cli/help.test.ts # src/core/services/__tests__/stats-server.test.ts # src/core/services/cli-command.test.ts # src/core/services/tokenizer/yomitan-parser-runtime.test.ts # src/main/overlay-runtime.test.ts # src/main/runtime/character-dictionary-auto-sync.test.ts # src/main/runtime/cli-command-context-deps.test.ts # src/main/runtime/cli-command-context-factory.test.ts # src/main/runtime/cli-command-context-main-deps.test.ts # src/main/runtime/composers/cli-startup-composer.test.ts # src/main/runtime/stats-server-runtime.test.ts # src/main/runtime/tray-main-actions.test.ts # src/main/runtime/tray-main-deps.test.ts # src/main/runtime/tray-runtime-handlers.test.ts # src/main/runtime/tray-runtime.test.ts # src/main/runtime/yomitan-anki-server-sync.test.ts # src/preload.ts # src/renderer/handlers/mouse.test.ts # src/renderer/yomitan-popup.test.ts
778 lines
24 KiB
TypeScript
778 lines
24 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
buildFirstRunSetupHtml,
|
|
createHandleFirstRunSetupNavigationHandler,
|
|
createOpenFirstRunSetupWindowHandler,
|
|
parseFirstRunSetupSubmissionUrl,
|
|
} from './first-run-setup-window';
|
|
import type { CommandLineLauncherSnapshot } from './command-line-launcher';
|
|
|
|
function createCommandLineLauncherSnapshot(
|
|
overrides: Partial<CommandLineLauncherSnapshot> = {},
|
|
): CommandLineLauncherSnapshot {
|
|
return {
|
|
supported: true,
|
|
bun: {
|
|
status: 'missing',
|
|
commandPath: null,
|
|
version: null,
|
|
installMethod: 'official-script',
|
|
installCommand: ['bash', '-lc', 'curl -fsSL https://bun.com/install | bash'],
|
|
message: null,
|
|
},
|
|
launcher: {
|
|
status: 'not_installed',
|
|
commandPath: null,
|
|
installPath: '/home/tester/.local/bin/subminer',
|
|
pathDir: '/home/tester/.local/bin',
|
|
shadowedBy: null,
|
|
message: null,
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test('buildFirstRunSetupHtml shows legacy mpv plugin removal action with confirmation', () => {
|
|
const html = buildFirstRunSetupHtml({
|
|
configReady: true,
|
|
dictionaryCount: 1,
|
|
canFinish: true,
|
|
externalYomitanConfigured: false,
|
|
pluginStatus: 'installed',
|
|
pluginInstallPathSummary: '/tmp/mpv',
|
|
legacyMpvPluginPaths: ['/tmp/mpv/scripts/subminer', '/tmp/mpv/scripts/subminer.lua'],
|
|
mpvExecutablePath: '',
|
|
mpvExecutablePathStatus: 'blank',
|
|
windowsMpvShortcuts: {
|
|
supported: false,
|
|
startMenuEnabled: true,
|
|
desktopEnabled: true,
|
|
startMenuInstalled: false,
|
|
desktopInstalled: false,
|
|
status: 'optional',
|
|
},
|
|
commandLineLauncher: createCommandLineLauncherSnapshot(),
|
|
message: null,
|
|
});
|
|
|
|
assert.match(html, /Legacy mpv plugin/);
|
|
assert.doesNotMatch(html, /mpv runtime plugin/);
|
|
assert.match(html, /Found/);
|
|
assert.match(html, /\/tmp\/mpv\/scripts\/subminer/);
|
|
assert.match(html, /\/tmp\/mpv\/scripts\/subminer\.lua/);
|
|
assert.match(html, /Remove legacy mpv plugin/);
|
|
assert.match(html, /class="legacy-remove"/);
|
|
assert.match(html, /\.legacy-remove/);
|
|
assert.match(html, /Continue without removing/);
|
|
assert.match(
|
|
html,
|
|
/Remove these SubMiner mpv plugin files from mpv.s scripts directory\? This stops regular mpv from loading SubMiner\./,
|
|
);
|
|
assert.match(html, /action=remove-legacy-plugin/);
|
|
});
|
|
|
|
test('buildFirstRunSetupHtml marks an invalid configured mpv path as invalid', () => {
|
|
const html = buildFirstRunSetupHtml({
|
|
configReady: true,
|
|
dictionaryCount: 1,
|
|
canFinish: true,
|
|
externalYomitanConfigured: false,
|
|
pluginStatus: 'installed',
|
|
pluginInstallPathSummary: '/tmp/mpv',
|
|
mpvExecutablePath: 'C:\\Broken\\mpv.exe',
|
|
mpvExecutablePathStatus: 'invalid',
|
|
windowsMpvShortcuts: {
|
|
supported: true,
|
|
startMenuEnabled: true,
|
|
desktopEnabled: true,
|
|
startMenuInstalled: false,
|
|
desktopInstalled: false,
|
|
status: 'optional',
|
|
},
|
|
commandLineLauncher: createCommandLineLauncherSnapshot(),
|
|
message: null,
|
|
});
|
|
|
|
assert.match(html, />Invalid</);
|
|
assert.match(html, /Current: C:\\Broken\\mpv\.exe \(invalid; file not found\)/);
|
|
assert.match(html, /aria-label="Path to mpv\.exe"/);
|
|
});
|
|
|
|
test('buildFirstRunSetupHtml explains the config blocker when setup is missing config', () => {
|
|
const html = buildFirstRunSetupHtml({
|
|
configReady: false,
|
|
dictionaryCount: 0,
|
|
canFinish: false,
|
|
externalYomitanConfigured: false,
|
|
pluginStatus: 'required',
|
|
pluginInstallPathSummary: null,
|
|
mpvExecutablePath: '',
|
|
mpvExecutablePathStatus: 'blank',
|
|
windowsMpvShortcuts: {
|
|
supported: false,
|
|
startMenuEnabled: true,
|
|
desktopEnabled: true,
|
|
startMenuInstalled: false,
|
|
desktopInstalled: false,
|
|
status: 'optional',
|
|
},
|
|
commandLineLauncher: createCommandLineLauncherSnapshot(),
|
|
message: null,
|
|
});
|
|
|
|
assert.match(html, /Create or provide the config file before finishing setup\./);
|
|
assert.match(
|
|
html,
|
|
/<button class="primary" disabled [^>]*action=finish'">Finish setup<\/button>/,
|
|
);
|
|
});
|
|
|
|
test('buildFirstRunSetupHtml explains external yomitan mode and keeps finish enabled', () => {
|
|
const html = buildFirstRunSetupHtml({
|
|
configReady: true,
|
|
dictionaryCount: 0,
|
|
canFinish: true,
|
|
externalYomitanConfigured: true,
|
|
pluginStatus: 'installed',
|
|
pluginInstallPathSummary: null,
|
|
mpvExecutablePath: '',
|
|
mpvExecutablePathStatus: 'blank',
|
|
windowsMpvShortcuts: {
|
|
supported: false,
|
|
startMenuEnabled: true,
|
|
desktopEnabled: true,
|
|
startMenuInstalled: false,
|
|
desktopInstalled: false,
|
|
status: 'optional',
|
|
},
|
|
commandLineLauncher: createCommandLineLauncherSnapshot(),
|
|
message: null,
|
|
});
|
|
|
|
assert.match(html, /External profile configured/);
|
|
assert.match(
|
|
html,
|
|
/Finish stays unlocked while SubMiner is reusing an external Yomitan profile\./,
|
|
);
|
|
});
|
|
|
|
test('parseFirstRunSetupSubmissionUrl parses supported custom actions', () => {
|
|
assert.deepEqual(
|
|
parseFirstRunSetupSubmissionUrl(
|
|
'subminer://first-run-setup?action=configure-mpv-executable-path&mpvExecutablePath=C%3A%5CApps%5Cmpv%5Cmpv.exe',
|
|
),
|
|
{
|
|
action: 'configure-mpv-executable-path',
|
|
mpvExecutablePath: 'C:\\Apps\\mpv\\mpv.exe',
|
|
},
|
|
);
|
|
assert.deepEqual(parseFirstRunSetupSubmissionUrl('subminer://first-run-setup?action=refresh'), {
|
|
action: 'refresh',
|
|
});
|
|
assert.deepEqual(
|
|
parseFirstRunSetupSubmissionUrl('subminer://first-run-setup?action=install-bun'),
|
|
{
|
|
action: 'install-bun',
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
parseFirstRunSetupSubmissionUrl(
|
|
'subminer://first-run-setup?action=install-command-line-launcher',
|
|
),
|
|
{
|
|
action: 'install-command-line-launcher',
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
parseFirstRunSetupSubmissionUrl('subminer://first-run-setup?action=remove-legacy-plugin'),
|
|
{
|
|
action: 'remove-legacy-plugin',
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
parseFirstRunSetupSubmissionUrl('subminer://first-run-setup?action=open-config-settings'),
|
|
{
|
|
action: 'open-config-settings',
|
|
},
|
|
);
|
|
assert.equal(
|
|
parseFirstRunSetupSubmissionUrl('subminer://first-run-setup?action=skip-plugin'),
|
|
null,
|
|
);
|
|
assert.equal(parseFirstRunSetupSubmissionUrl('https://example.com'), null);
|
|
});
|
|
|
|
test('buildFirstRunSetupHtml reports a broken included runtime in the optional launcher controls', () => {
|
|
const html = buildFirstRunSetupHtml({
|
|
configReady: true,
|
|
dictionaryCount: 1,
|
|
canFinish: true,
|
|
externalYomitanConfigured: false,
|
|
pluginStatus: 'installed',
|
|
pluginInstallPathSummary: null,
|
|
mpvExecutablePath: '',
|
|
mpvExecutablePathStatus: 'blank',
|
|
windowsMpvShortcuts: {
|
|
supported: false,
|
|
startMenuEnabled: true,
|
|
desktopEnabled: true,
|
|
startMenuInstalled: false,
|
|
desktopInstalled: false,
|
|
status: 'optional',
|
|
},
|
|
commandLineLauncher: createCommandLineLauncherSnapshot({
|
|
bun: {
|
|
status: 'failed',
|
|
commandPath: null,
|
|
version: null,
|
|
installMethod: null,
|
|
installCommand: null,
|
|
message: 'Included Bun runtime is missing.',
|
|
},
|
|
launcher: {
|
|
status: 'installed_bun_missing',
|
|
commandPath: '/home/tester/.local/bin/subminer',
|
|
installPath: '/home/tester/.local/bin/subminer',
|
|
pathDir: '/home/tester/.local/bin',
|
|
shadowedBy: null,
|
|
message: 'Bun is missing.',
|
|
},
|
|
}),
|
|
message: null,
|
|
});
|
|
|
|
assert.match(html, /Command line launcher/);
|
|
assert.match(html, /Optional\. Install the launcher to use SubMiner from your terminal\./);
|
|
assert.match(html, /Failed/);
|
|
assert.match(html, /SubMiner launcher/);
|
|
assert.match(html, /Reinstall SubMiner to repair it/);
|
|
assert.match(html, /<button disabled[^>]+action=install-command-line-launcher/);
|
|
assert.match(html, /\/home\/tester\/\.local\/bin\/subminer/);
|
|
assert.match(html, /action=install-command-line-launcher/);
|
|
assert.match(
|
|
html,
|
|
/<button class="primary" onclick="window\.location\.href='subminer:\/\/first-run-setup\?action=finish'">Finish setup<\/button>/,
|
|
);
|
|
});
|
|
|
|
// The AppImage catalog test OCRs this fresh-install state and fails on "not installed".
|
|
test('buildFirstRunSetupHtml labels a missing launcher without error-message wording', () => {
|
|
const html = buildFirstRunSetupHtml({
|
|
configReady: true,
|
|
dictionaryCount: 0,
|
|
canFinish: false,
|
|
externalYomitanConfigured: false,
|
|
pluginStatus: 'required',
|
|
pluginInstallPathSummary: null,
|
|
mpvExecutablePath: '',
|
|
mpvExecutablePathStatus: 'blank',
|
|
windowsMpvShortcuts: {
|
|
supported: false,
|
|
startMenuEnabled: true,
|
|
desktopEnabled: true,
|
|
startMenuInstalled: false,
|
|
desktopInstalled: false,
|
|
status: 'optional',
|
|
},
|
|
commandLineLauncher: createCommandLineLauncherSnapshot({
|
|
bun: {
|
|
status: 'ready',
|
|
commandPath: '/tmp/.mount_SubMiner/resources/bun',
|
|
version: '1.3.0',
|
|
installMethod: null,
|
|
installCommand: null,
|
|
message: null,
|
|
},
|
|
}),
|
|
message: null,
|
|
});
|
|
|
|
assert.match(html, /<span class="badge warn">Not set up<\/span>/);
|
|
assert.doesNotMatch(html, /not installed/i);
|
|
});
|
|
|
|
test('buildFirstRunSetupHtml disables launcher install when no target is installable', () => {
|
|
const html = buildFirstRunSetupHtml({
|
|
configReady: true,
|
|
dictionaryCount: 1,
|
|
canFinish: true,
|
|
externalYomitanConfigured: false,
|
|
pluginStatus: 'installed',
|
|
pluginInstallPathSummary: null,
|
|
mpvExecutablePath: '',
|
|
mpvExecutablePathStatus: 'blank',
|
|
windowsMpvShortcuts: {
|
|
supported: false,
|
|
startMenuEnabled: true,
|
|
desktopEnabled: true,
|
|
startMenuInstalled: false,
|
|
desktopInstalled: false,
|
|
status: 'optional',
|
|
},
|
|
commandLineLauncher: createCommandLineLauncherSnapshot({
|
|
launcher: {
|
|
status: 'not_installable',
|
|
commandPath: null,
|
|
installPath: null,
|
|
pathDir: null,
|
|
shadowedBy: null,
|
|
message: 'No writable PATH directory found.',
|
|
},
|
|
}),
|
|
message: null,
|
|
});
|
|
|
|
assert.match(
|
|
html,
|
|
/<button disabled onclick="window\.location\.href='subminer:\/\/first-run-setup\?action=install-command-line-launcher'">Install command-line launcher<\/button>/,
|
|
);
|
|
});
|
|
|
|
test('first-run setup navigation handler prevents default and dispatches supported action', async () => {
|
|
const calls: string[] = [];
|
|
const handleNavigation = createHandleFirstRunSetupNavigationHandler({
|
|
parseSubmissionUrl: (url) => parseFirstRunSetupSubmissionUrl(url),
|
|
handleAction: async (submission) => {
|
|
calls.push(submission.action);
|
|
},
|
|
logError: (message) => calls.push(message),
|
|
});
|
|
|
|
const prevented = handleNavigation({
|
|
url: 'subminer://first-run-setup?action=refresh',
|
|
preventDefault: () => calls.push('preventDefault'),
|
|
});
|
|
|
|
assert.equal(prevented, true);
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
assert.deepEqual(calls, ['preventDefault', 'refresh']);
|
|
});
|
|
|
|
test('first-run setup navigation handler swallows stale custom-scheme actions', () => {
|
|
const calls: string[] = [];
|
|
const handleNavigation = createHandleFirstRunSetupNavigationHandler({
|
|
parseSubmissionUrl: (url) => parseFirstRunSetupSubmissionUrl(url),
|
|
handleAction: async (submission) => {
|
|
calls.push(submission.action);
|
|
},
|
|
logError: (message) => calls.push(message),
|
|
});
|
|
|
|
const prevented = handleNavigation({
|
|
url: 'subminer://first-run-setup?action=skip-plugin',
|
|
preventDefault: () => calls.push('preventDefault'),
|
|
});
|
|
|
|
assert.equal(prevented, true);
|
|
assert.deepEqual(calls, ['preventDefault']);
|
|
});
|
|
|
|
test('opening first-run setup shows and focuses window after content loads', async () => {
|
|
const calls: string[] = [];
|
|
const handler = createOpenFirstRunSetupWindowHandler({
|
|
maybeFocusExistingSetupWindow: () => false,
|
|
createSetupWindow: () =>
|
|
({
|
|
webContents: {
|
|
on: () => {},
|
|
},
|
|
loadURL: async () => {
|
|
calls.push('load');
|
|
},
|
|
on: () => {},
|
|
isDestroyed: () => false,
|
|
close: () => {},
|
|
show: () => calls.push('show'),
|
|
focus: () => calls.push('focus'),
|
|
}) as never,
|
|
getSetupSnapshot: async () => ({
|
|
configReady: true,
|
|
dictionaryCount: 1,
|
|
canFinish: true,
|
|
externalYomitanConfigured: false,
|
|
pluginStatus: 'installed',
|
|
pluginInstallPathSummary: null,
|
|
mpvExecutablePath: '',
|
|
mpvExecutablePathStatus: 'blank',
|
|
windowsMpvShortcuts: {
|
|
supported: false,
|
|
startMenuEnabled: true,
|
|
desktopEnabled: true,
|
|
startMenuInstalled: false,
|
|
desktopInstalled: false,
|
|
status: 'optional',
|
|
},
|
|
commandLineLauncher: createCommandLineLauncherSnapshot(),
|
|
message: null,
|
|
}),
|
|
buildSetupHtml: () => '<html></html>',
|
|
parseSubmissionUrl: () => null,
|
|
handleAction: async () => undefined,
|
|
markSetupInProgress: async () => {
|
|
calls.push('in-progress');
|
|
},
|
|
markSetupCancelled: async () => undefined,
|
|
isSetupCompleted: () => true,
|
|
shouldQuitWhenClosedIncomplete: () => false,
|
|
quitApp: () => {},
|
|
clearSetupWindow: () => {},
|
|
setSetupWindow: () => {
|
|
calls.push('set');
|
|
},
|
|
encodeURIComponent: (value) => value,
|
|
logError: () => {},
|
|
});
|
|
|
|
handler();
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
|
|
assert.deepEqual(calls, ['set', 'show', 'focus', 'in-progress', 'load', 'show', 'focus']);
|
|
});
|
|
|
|
test('opening first-run setup skips rendering if window is destroyed after snapshot', async () => {
|
|
const calls: string[] = [];
|
|
let destroyed = false;
|
|
const handler = createOpenFirstRunSetupWindowHandler({
|
|
maybeFocusExistingSetupWindow: () => false,
|
|
createSetupWindow: () =>
|
|
({
|
|
webContents: {
|
|
on: () => {},
|
|
},
|
|
loadURL: async () => {
|
|
calls.push('load');
|
|
},
|
|
on: () => {},
|
|
isDestroyed: () => destroyed,
|
|
close: () => {},
|
|
show: () => calls.push('show'),
|
|
focus: () => calls.push('focus'),
|
|
}) as never,
|
|
getSetupSnapshot: async () => {
|
|
calls.push('snapshot');
|
|
destroyed = true;
|
|
return {
|
|
configReady: true,
|
|
dictionaryCount: 1,
|
|
canFinish: true,
|
|
externalYomitanConfigured: false,
|
|
pluginStatus: 'installed',
|
|
pluginInstallPathSummary: null,
|
|
mpvExecutablePath: '',
|
|
mpvExecutablePathStatus: 'blank',
|
|
windowsMpvShortcuts: {
|
|
supported: false,
|
|
startMenuEnabled: true,
|
|
desktopEnabled: true,
|
|
startMenuInstalled: false,
|
|
desktopInstalled: false,
|
|
status: 'optional',
|
|
},
|
|
commandLineLauncher: createCommandLineLauncherSnapshot(),
|
|
message: null,
|
|
};
|
|
},
|
|
buildSetupHtml: () => {
|
|
calls.push('build');
|
|
return '<html></html>';
|
|
},
|
|
parseSubmissionUrl: () => null,
|
|
handleAction: async () => undefined,
|
|
markSetupInProgress: async () => {
|
|
calls.push('in-progress');
|
|
},
|
|
markSetupCancelled: async () => undefined,
|
|
isSetupCompleted: () => true,
|
|
shouldQuitWhenClosedIncomplete: () => false,
|
|
quitApp: () => {},
|
|
clearSetupWindow: () => {},
|
|
setSetupWindow: () => {
|
|
calls.push('set');
|
|
},
|
|
encodeURIComponent: (value) => value,
|
|
logError: () => {},
|
|
});
|
|
|
|
handler();
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
|
|
assert.deepEqual(calls, ['set', 'show', 'focus', 'in-progress', 'snapshot']);
|
|
});
|
|
|
|
test('first-run setup action can skip rerender after launching another window', async () => {
|
|
const calls: string[] = [];
|
|
let navigateHandler: ((event: unknown, url: string) => void) | undefined;
|
|
const handler = createOpenFirstRunSetupWindowHandler({
|
|
maybeFocusExistingSetupWindow: () => false,
|
|
createSetupWindow: () =>
|
|
({
|
|
webContents: {
|
|
on: (_event: 'will-navigate', callback: (event: unknown, url: string) => void) => {
|
|
navigateHandler = callback;
|
|
},
|
|
},
|
|
loadURL: async () => {
|
|
calls.push('load');
|
|
},
|
|
on: () => {},
|
|
isDestroyed: () => false,
|
|
close: () => {},
|
|
show: () => calls.push('show'),
|
|
focus: () => calls.push('focus'),
|
|
}) as never,
|
|
getSetupSnapshot: async () => ({
|
|
configReady: true,
|
|
dictionaryCount: 1,
|
|
canFinish: true,
|
|
externalYomitanConfigured: false,
|
|
pluginStatus: 'installed',
|
|
pluginInstallPathSummary: null,
|
|
mpvExecutablePath: '',
|
|
mpvExecutablePathStatus: 'blank',
|
|
windowsMpvShortcuts: {
|
|
supported: false,
|
|
startMenuEnabled: true,
|
|
desktopEnabled: true,
|
|
startMenuInstalled: false,
|
|
desktopInstalled: false,
|
|
status: 'optional',
|
|
},
|
|
commandLineLauncher: createCommandLineLauncherSnapshot(),
|
|
message: null,
|
|
}),
|
|
buildSetupHtml: () => '<html></html>',
|
|
parseSubmissionUrl: (url) => parseFirstRunSetupSubmissionUrl(url),
|
|
handleAction: async () => {
|
|
calls.push('action');
|
|
return { skipRender: true };
|
|
},
|
|
markSetupInProgress: async () => {
|
|
calls.push('in-progress');
|
|
},
|
|
markSetupCancelled: async () => undefined,
|
|
isSetupCompleted: () => true,
|
|
shouldQuitWhenClosedIncomplete: () => false,
|
|
quitApp: () => {},
|
|
clearSetupWindow: () => {},
|
|
setSetupWindow: () => {
|
|
calls.push('set');
|
|
},
|
|
encodeURIComponent: (value) => value,
|
|
logError: () => {},
|
|
});
|
|
|
|
handler();
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
navigateHandler?.(
|
|
{ preventDefault: () => calls.push('preventDefault') },
|
|
'subminer://first-run-setup?action=open-config-settings',
|
|
);
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
|
|
assert.deepEqual(calls, [
|
|
'set',
|
|
'show',
|
|
'focus',
|
|
'in-progress',
|
|
'load',
|
|
'show',
|
|
'focus',
|
|
'preventDefault',
|
|
'action',
|
|
]);
|
|
});
|
|
|
|
test('closing incomplete first-run setup quits app outside background mode', async () => {
|
|
const calls: string[] = [];
|
|
let closedHandler: (() => void) | undefined;
|
|
const handler = createOpenFirstRunSetupWindowHandler({
|
|
maybeFocusExistingSetupWindow: () => false,
|
|
createSetupWindow: () =>
|
|
({
|
|
webContents: {
|
|
on: () => {},
|
|
},
|
|
loadURL: async () => undefined,
|
|
on: (event: 'closed', callback: () => void) => {
|
|
if (event === 'closed') {
|
|
closedHandler = callback;
|
|
}
|
|
},
|
|
isDestroyed: () => false,
|
|
close: () => calls.push('close-window'),
|
|
focus: () => {},
|
|
}) as never,
|
|
getSetupSnapshot: async () => ({
|
|
configReady: false,
|
|
dictionaryCount: 0,
|
|
canFinish: false,
|
|
externalYomitanConfigured: false,
|
|
pluginStatus: 'required',
|
|
pluginInstallPathSummary: null,
|
|
mpvExecutablePath: '',
|
|
mpvExecutablePathStatus: 'blank',
|
|
windowsMpvShortcuts: {
|
|
supported: false,
|
|
startMenuEnabled: true,
|
|
desktopEnabled: true,
|
|
startMenuInstalled: false,
|
|
desktopInstalled: false,
|
|
status: 'optional',
|
|
},
|
|
commandLineLauncher: createCommandLineLauncherSnapshot(),
|
|
message: null,
|
|
}),
|
|
buildSetupHtml: () => '<html></html>',
|
|
parseSubmissionUrl: () => null,
|
|
handleAction: async () => undefined,
|
|
markSetupInProgress: async () => undefined,
|
|
markSetupCancelled: async () => {
|
|
calls.push('cancelled');
|
|
},
|
|
isSetupCompleted: () => false,
|
|
shouldQuitWhenClosedIncomplete: () => true,
|
|
quitApp: () => {
|
|
calls.push('quit');
|
|
},
|
|
clearSetupWindow: () => {
|
|
calls.push('clear');
|
|
},
|
|
setSetupWindow: () => {
|
|
calls.push('set');
|
|
},
|
|
encodeURIComponent: (value) => value,
|
|
logError: () => {},
|
|
});
|
|
|
|
handler();
|
|
if (typeof closedHandler !== 'function') {
|
|
throw new Error('expected closed handler');
|
|
}
|
|
closedHandler();
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
|
|
assert.deepEqual(calls, ['set', 'cancelled', 'clear', 'quit']);
|
|
});
|
|
|
|
test('closing completed first-run setup quits app when completion policy allows it', async () => {
|
|
const calls: string[] = [];
|
|
let closedHandler: (() => void) | undefined;
|
|
const handler = createOpenFirstRunSetupWindowHandler({
|
|
maybeFocusExistingSetupWindow: () => false,
|
|
createSetupWindow: () =>
|
|
({
|
|
webContents: {
|
|
on: () => {},
|
|
},
|
|
loadURL: async () => undefined,
|
|
on: (event: 'closed', callback: () => void) => {
|
|
if (event === 'closed') {
|
|
closedHandler = callback;
|
|
}
|
|
},
|
|
isDestroyed: () => false,
|
|
close: () => calls.push('close-window'),
|
|
focus: () => {},
|
|
}) as never,
|
|
getSetupSnapshot: async () => ({
|
|
configReady: true,
|
|
dictionaryCount: 1,
|
|
canFinish: true,
|
|
externalYomitanConfigured: false,
|
|
pluginStatus: 'installed',
|
|
pluginInstallPathSummary: null,
|
|
mpvExecutablePath: '',
|
|
mpvExecutablePathStatus: 'blank',
|
|
windowsMpvShortcuts: {
|
|
supported: false,
|
|
startMenuEnabled: true,
|
|
desktopEnabled: true,
|
|
startMenuInstalled: false,
|
|
desktopInstalled: false,
|
|
status: 'optional',
|
|
},
|
|
commandLineLauncher: createCommandLineLauncherSnapshot(),
|
|
message: null,
|
|
}),
|
|
buildSetupHtml: () => '<html></html>',
|
|
parseSubmissionUrl: () => null,
|
|
handleAction: async () => undefined,
|
|
markSetupInProgress: async () => undefined,
|
|
markSetupCancelled: async () => {
|
|
calls.push('cancelled');
|
|
},
|
|
isSetupCompleted: () => true,
|
|
shouldQuitWhenClosedIncomplete: () => true,
|
|
shouldQuitWhenClosedCompleted: () => true,
|
|
quitApp: () => {
|
|
calls.push('quit');
|
|
},
|
|
clearSetupWindow: () => {
|
|
calls.push('clear');
|
|
},
|
|
setSetupWindow: () => {
|
|
calls.push('set');
|
|
},
|
|
encodeURIComponent: (value) => value,
|
|
logError: () => {},
|
|
});
|
|
|
|
handler();
|
|
if (typeof closedHandler !== 'function') {
|
|
throw new Error('expected closed handler');
|
|
}
|
|
closedHandler();
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
|
|
assert.deepEqual(calls, ['set', 'clear', 'quit']);
|
|
});
|
|
|
|
test('Hachidori setup names the active dictionary backend', () => {
|
|
const html = buildFirstRunSetupHtml({
|
|
dictionaryBackend: 'hachidori',
|
|
configReady: true,
|
|
dictionaryCount: 0,
|
|
canFinish: false,
|
|
externalYomitanConfigured: false,
|
|
pluginStatus: 'installed',
|
|
pluginInstallPathSummary: null,
|
|
mpvExecutablePath: '',
|
|
mpvExecutablePathStatus: 'blank',
|
|
windowsMpvShortcuts: {
|
|
supported: false,
|
|
startMenuEnabled: true,
|
|
desktopEnabled: true,
|
|
startMenuInstalled: false,
|
|
desktopInstalled: false,
|
|
status: 'optional',
|
|
},
|
|
commandLineLauncher: createCommandLineLauncherSnapshot(),
|
|
message: null,
|
|
});
|
|
assert.match(html, /Hachidori dictionaries/);
|
|
assert.match(html, /Open Hachidori Settings/);
|
|
assert.match(html, /Install at least one Hachidori dictionary/);
|
|
assert.doesNotMatch(html, /Open Yomitan Settings/);
|
|
assert.match(html, /Link host/);
|
|
assert.match(html, /<details class="external-host">/);
|
|
assert.match(html, /Use an external dictionary host/);
|
|
assert.match(html, /keep the browser and its sharing relay running/);
|
|
assert.match(html, /You can close its browser management page/);
|
|
});
|
|
|
|
test('setup parses link and unlink actions without losing the host address', () => {
|
|
const address = 'ws://127.0.0.1:38771/link';
|
|
assert.deepEqual(
|
|
parseFirstRunSetupSubmissionUrl(
|
|
'subminer://first-run-setup?action=link-hachidori-host&address=' +
|
|
encodeURIComponent(address),
|
|
),
|
|
{ action: 'link-hachidori-host', address },
|
|
);
|
|
assert.deepEqual(
|
|
parseFirstRunSetupSubmissionUrl('subminer://first-run-setup?action=unlink-hachidori-host'),
|
|
{ action: 'unlink-hachidori-host' },
|
|
);
|
|
});
|