import test from 'node:test'; import assert from 'node:assert/strict'; import { buildFirstRunSetupHtml, createHandleFirstRunSetupNavigationHandler, createMaybeFocusExistingFirstRunSetupWindowHandler, createOpenFirstRunSetupWindowHandler, parseFirstRunSetupSubmissionUrl, } from './first-run-setup-window'; import type { CommandLineLauncherSnapshot } from './command-line-launcher'; function createCommandLineLauncherSnapshot( overrides: Partial = {}, ): 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 renders macchiato setup actions and disabled finish state', () => { 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(), message: 'Waiting for dictionaries', }); assert.match(html, /SubMiner setup/); assert.doesNotMatch(html, /Install legacy mpv plugin/); assert.doesNotMatch(html, /action=install-plugin/); assert.match(html, /Ready/); assert.doesNotMatch(html, /Bundled ready/); assert.match(html, /Managed mpv launches use the bundled runtime plugin\./); assert.match(html, /Open Yomitan Settings/); assert.match(html, /Finish setup/); assert.match(html, /disabled/); assert.match(html, /html,\s*body\s*{\s*min-height:\s*100%;/); assert.match(html, /min-height:\s*100vh;/); assert.match(html, /box-sizing:\s*border-box;/); }); test('buildFirstRunSetupHtml switches plugin action to reinstall when already installed', () => { const html = buildFirstRunSetupHtml({ configReady: true, dictionaryCount: 1, canFinish: true, externalYomitanConfigured: false, pluginStatus: 'installed', pluginInstallPathSummary: '/tmp/mpv', mpvExecutablePath: 'C:\\Program Files\\mpv\\mpv.exe', mpvExecutablePathStatus: 'configured', windowsMpvShortcuts: { supported: true, startMenuEnabled: true, desktopEnabled: true, startMenuInstalled: true, desktopInstalled: false, status: 'installed', }, commandLineLauncher: createCommandLineLauncherSnapshot(), message: null, }); assert.doesNotMatch(html, /Reinstall mpv plugin/); assert.doesNotMatch(html, /action=install-plugin/); assert.match(html, /mpv executable path/); assert.match(html, /Leave blank to auto-discover mpv\.exe from PATH\./); assert.match(html, /aria-label="Path to mpv\.exe"/); assert.match(html, /SubMiner-managed mpv launches use the bundled runtime plugin\./); }); 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.match(html, /Legacy detected/); 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 { 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\./); }); 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.equal( parseFirstRunSetupSubmissionUrl('subminer://first-run-setup?action=skip-plugin'), null, ); assert.equal(parseFirstRunSetupSubmissionUrl('https://example.com'), null); }); test('buildFirstRunSetupHtml renders command-line launcher section and actions', () => { 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: 'official-script', installCommand: ['bash', '-lc', 'curl -fsSL https://bun.com/install | bash'], message: 'network failed', }, 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\. Setup can finish without Bun or the launcher\./); assert.match(html, /Bun runtime/); assert.match(html, /Failed/); assert.match(html, /bash -lc curl -fsSL https:\/\/bun\.com\/install \| bash/); assert.match(html, /Install Bun/); assert.match(html, /action=install-bun/); assert.match(html, /SubMiner launcher/); assert.match(html, /Installed, Bun missing/); assert.match(html, /\/home\/tester\/\.local\/bin\/subminer/); assert.match(html, /action=install-command-line-launcher/); assert.match( html, /