From 4e9d8349201d2e8c927c282ae16c4274558dbc6e Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sat, 2 May 2026 19:15:35 -0400 Subject: [PATCH] feat(ext/setup): hand off completion to fullscreen vault tab (P2) After successful device registration (state.configPushed = true), the wizard now opens vault.html in a new tab and closes the setup tab. Both create-new and attach-existing flows funnel through the same finishSetup() handler. Closing the setup tab is best-effort -- chrome.tabs.remove failures don't block the vault open. Add src/__stubs__/relicario_wasm.stub.ts + vitest.config alias so setup.ts can be imported in unit tests without the runtime WASM file. Exclude the stubs dir from the webpack/tsc build in tsconfig.json. --- .../src/__stubs__/relicario_wasm.stub.ts | 13 +++++++ extension/src/setup/__tests__/setup.test.ts | 37 +++++++++++++++++++ extension/src/setup/setup.ts | 18 +++++++++ extension/tsconfig.json | 2 +- extension/vitest.config.ts | 8 ++++ 5 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 extension/src/__stubs__/relicario_wasm.stub.ts create mode 100644 extension/src/setup/__tests__/setup.test.ts diff --git a/extension/src/__stubs__/relicario_wasm.stub.ts b/extension/src/__stubs__/relicario_wasm.stub.ts new file mode 100644 index 0000000..04d867a --- /dev/null +++ b/extension/src/__stubs__/relicario_wasm.stub.ts @@ -0,0 +1,13 @@ +// Stub for the runtime-only WASM module. Used by vitest so that modules +// importing relicario_wasm.js can be loaded in a Node/happy-dom environment. +// Individual tests that exercise WASM calls should mock the relevant exports. + +export default async function init(): Promise {} +export const unlock = (): never => { throw new Error('wasm stub: unlock not mocked'); }; +export const lock = (): void => {}; +export const manifest_encrypt = (): never => { throw new Error('wasm stub: manifest_encrypt not mocked'); }; +export const manifest_decrypt = (): never => { throw new Error('wasm stub: manifest_decrypt not mocked'); }; +export const settings_encrypt = (): never => { throw new Error('wasm stub: settings_encrypt not mocked'); }; +export const default_vault_settings_json = (): string => '{}'; +export const embed_image_secret = (): never => { throw new Error('wasm stub: embed_image_secret not mocked'); }; +export const register_device = (): never => { throw new Error('wasm stub: register_device not mocked'); }; diff --git a/extension/src/setup/__tests__/setup.test.ts b/extension/src/setup/__tests__/setup.test.ts new file mode 100644 index 0000000..c148f83 --- /dev/null +++ b/extension/src/setup/__tests__/setup.test.ts @@ -0,0 +1,37 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { finishSetup } from '../setup'; + +describe('finishSetup', () => { + beforeEach(() => { + (global as any).chrome = { + tabs: { + create: vi.fn(() => Promise.resolve({ id: 999 })), + getCurrent: vi.fn(() => Promise.resolve({ id: 42 })), + remove: vi.fn(() => Promise.resolve()), + }, + runtime: { + getURL: vi.fn((p: string) => `chrome-extension://abc/${p}`), + }, + }; + }); + + it('opens vault.html in a new tab', async () => { + await finishSetup(); + expect(chrome.runtime.getURL).toHaveBeenCalledWith('vault.html'); + expect(chrome.tabs.create).toHaveBeenCalledWith({ + url: 'chrome-extension://abc/vault.html', + }); + }); + + it('closes the current setup tab after opening the vault tab', async () => { + await finishSetup(); + expect(chrome.tabs.getCurrent).toHaveBeenCalled(); + expect(chrome.tabs.remove).toHaveBeenCalledWith(42); + }); + + it('still opens the vault tab even if closing the setup tab fails', async () => { + (chrome.tabs.remove as any).mockRejectedValueOnce(new Error('no permission')); + await expect(finishSetup()).resolves.not.toThrow(); + expect(chrome.tabs.create).toHaveBeenCalled(); + }); +}); diff --git a/extension/src/setup/setup.ts b/extension/src/setup/setup.ts index bd49f55..50895ee 100644 --- a/extension/src/setup/setup.ts +++ b/extension/src/setup/setup.ts @@ -1101,6 +1101,7 @@ function attachStep5(): void { state.configPushed = true; render(); + void finishSetup(); } catch (err: unknown) { console.error('[relicario setup] register device failed:', err); state.error = `Failed to register device: ${err instanceof Error ? err.message : String(err)}`; @@ -1131,6 +1132,23 @@ function attachStep5(): void { }); } +// --- Completion handoff --- + +/// Open the fullscreen vault tab and best-effort close the setup tab. +export async function finishSetup(): Promise { + const vaultUrl = chrome.runtime.getURL('vault.html'); + await chrome.tabs.create({ url: vaultUrl }); + try { + const current = await chrome.tabs.getCurrent(); + if (current?.id !== undefined) { + await chrome.tabs.remove(current.id); + } + } catch { + // Setup tab may not be closeable (e.g., opened as popup rather than a tab). + // The vault tab is open — that's the user-visible success. + } +} + // --- Boot --- document.addEventListener('DOMContentLoaded', () => { diff --git a/extension/tsconfig.json b/extension/tsconfig.json index 89512ab..bcffb72 100644 --- a/extension/tsconfig.json +++ b/extension/tsconfig.json @@ -12,5 +12,5 @@ "baseUrl": "." }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "wasm", "src/**/__tests__/**"] + "exclude": ["node_modules", "dist", "wasm", "src/**/__tests__/**", "src/__stubs__/**"] } diff --git a/extension/vitest.config.ts b/extension/vitest.config.ts index 02569a1..563f5f4 100644 --- a/extension/vitest.config.ts +++ b/extension/vitest.config.ts @@ -1,6 +1,14 @@ import { defineConfig } from 'vitest/config'; +import path from 'path'; export default defineConfig({ + resolve: { + alias: { + // Stub the runtime-only WASM module so unit tests can import setup.ts. + '../relicario_wasm.js': path.resolve(__dirname, 'src/__stubs__/relicario_wasm.stub.ts'), + 'relicario-wasm': path.resolve(__dirname, 'src/__stubs__/relicario_wasm.stub.ts'), + }, + }, test: { environment: 'happy-dom', include: ['src/**/__tests__/**/*.test.ts'],