// @vitest-environment jsdom import type { JSX } from "solid-js"; import { afterEach, describe, expect, it, vi } from "vitest"; import { Onboarding } from "./Onboarding"; import { AdoptVaultView } from "./AdoptVaultView"; import { flushView, inputText, renderView } from "./view-test-utils"; function button(container: HTMLElement, text: string): HTMLButtonElement { const found = Array.from(container.querySelectorAll("button")).find((b) => (b.textContent ?? "").includes(text), ); if (!found) throw new Error(`button not found: ${text}`); return found as HTMLButtonElement; } async function enterStrongPassword(container: HTMLElement) { inputText(container.querySelector("#adopt-password") as HTMLInputElement, "9xQ!vZ2pLm74Tr#Ke8Wd5Bn"); inputText(container.querySelector("#adopt-confirm-password") as HTMLInputElement, "9xQ!vZ2pLm74Tr#Ke8Wd5Bn"); await flushView(); } describe("vault adoption onboarding", () => { const cleanups: Array<() => void> = []; afterEach(() => { vi.useRealTimers(); while (cleanups.length) cleanups.pop()!(); }); function mounted(view: () => JSX.Element) { const rendered = renderView(view); cleanups.push(rendered.dispose); return rendered; } it("T-A21: Onboarding offers Create vault, Restore from backup, and I already have Peach", () => { vi.useFakeTimers(); const onComplete = vi.fn(); const onRestore = vi.fn(); const onAdopt = vi.fn(); const view = mounted(() => ); button(view.container, "next").click(); vi.advanceTimersByTime(220); button(view.container, "next").click(); vi.advanceTimersByTime(220); expect(view.container.textContent).toContain("Create vault"); expect(view.container.textContent).toContain("Restore from backup"); expect(view.container.textContent).toContain("I already have Peach"); button(view.container, "I already have Peach").click(); expect(onAdopt).toHaveBeenCalledTimes(1); }); it("T-A22: password screen uses the same minimum-length gate as create-vault", () => { const view = mounted(() => {}} onDone={() => {}} startAdoption={vi.fn()} />); const continueButton = button(view.container, "Continue"); expect(continueButton.disabled).toBe(true); inputText(view.container.querySelector("#adopt-password") as HTMLInputElement, "short"); inputText(view.container.querySelector("#adopt-confirm-password") as HTMLInputElement, "short"); expect(view.container.textContent).toContain("need 7 more characters"); expect(continueButton.disabled).toBe(true); }); it("T-A23: adoption flow reaches the SAS screen", async () => { const startAdoption = vi.fn(async ({ confirmSAS }: { confirmSAS(sas: string): Promise }) => { await confirmSAS("895245"); }); const view = mounted(() => {}} onDone={() => {}} startAdoption={startAdoption} />); await enterStrongPassword(view.container); button(view.container, "Continue").click(); button(view.container, "Pair a device").click(); await flushView(); expect(view.container.textContent).toContain("Confirm this code matches"); expect(view.container.textContent).toContain("8 9 5 2 4 5"); }); it("T-A24: success leaves a vault and opens Peach", async () => { const onDone = vi.fn(); const view = mounted( () => ( {}} onDone={onDone} startAdoption={vi.fn(async () => {})} /> ), ); await enterStrongPassword(view.container); button(view.container, "Continue").click(); button(view.container, "Pair a device").click(); await flushView(); expect(view.container.textContent).toContain("Joined your vault."); button(view.container, "Open Peach").click(); expect(onDone).toHaveBeenCalledTimes(1); }); it("T-A25: SAS cancel keeps receiver vault-less and returns to the password step", async () => { const createVault = vi.fn(); const view = mounted( () => ( {}} onDone={() => {}} startAdoption={vi.fn(async ({ confirmSAS }) => { const ok = await confirmSAS("895245"); if (ok) await createVault(); else throw new Error("sas_rejected"); })} /> ), ); await enterStrongPassword(view.container); button(view.container, "Continue").click(); button(view.container, "Pair a device").click(); await flushView(); button(view.container, "Cancel").click(); await flushView(); expect(createVault).not.toHaveBeenCalled(); expect(view.container.textContent).toContain("Set a password for this device."); }); });