123import type { JSX } from "solid-js";4import { afterEach, describe, expect, it, vi } from "vitest";5import { Onboarding } from "./Onboarding";6import { AdoptVaultView } from "./AdoptVaultView";7import { flushView, inputText, renderView } from "./view-test-utils";89function button(container: HTMLElement, text: string): HTMLButtonElement {10 const found = Array.from(container.querySelectorAll("button")).find((b) =>11 (b.textContent ?? "").includes(text),12 );13 if (!found) throw new Error(`button not found: ${text}`);14 return found as HTMLButtonElement;15}1617async function enterStrongPassword(container: HTMLElement) {18 inputText(container.querySelector("#adopt-password") as HTMLInputElement, "9xQ!vZ2pLm74Tr#Ke8Wd5Bn");19 inputText(container.querySelector("#adopt-confirm-password") as HTMLInputElement, "9xQ!vZ2pLm74Tr#Ke8Wd5Bn");20 await flushView();21}2223describe("vault adoption onboarding", () => {24 const cleanups: Array<() => void> = [];2526 afterEach(() => {27 vi.useRealTimers();28 while (cleanups.length) cleanups.pop()!();29 });3031 function mounted(view: () => JSX.Element) {32 const rendered = renderView(view);33 cleanups.push(rendered.dispose);34 return rendered;35 }3637 it("T-A21: Onboarding offers Create vault, Restore from backup, and I already have Peach", () => {38 vi.useFakeTimers();39 const onComplete = vi.fn();40 const onRestore = vi.fn();41 const onAdopt = vi.fn();42 const view = mounted(() => <Onboarding onComplete={onComplete} onRestore={onRestore} onAdopt={onAdopt} />);4344 button(view.container, "next").click();45 vi.advanceTimersByTime(220);46 button(view.container, "next").click();47 vi.advanceTimersByTime(220);4849 expect(view.container.textContent).toContain("Create vault");50 expect(view.container.textContent).toContain("Restore from backup");51 expect(view.container.textContent).toContain("I already have Peach");52 button(view.container, "I already have Peach").click();53 expect(onAdopt).toHaveBeenCalledTimes(1);54 });5556 it("T-A22: password screen uses the same minimum-length gate as create-vault", () => {57 const view = mounted(() => <AdoptVaultView onCancel={() => {}} onDone={() => {}} startAdoption={vi.fn()} />);58 const continueButton = button(view.container, "Continue");59 expect(continueButton.disabled).toBe(true);60 inputText(view.container.querySelector("#adopt-password") as HTMLInputElement, "short");61 inputText(view.container.querySelector("#adopt-confirm-password") as HTMLInputElement, "short");62 expect(view.container.textContent).toContain("need 7 more characters");63 expect(continueButton.disabled).toBe(true);64 });6566 it("T-A23: adoption flow reaches the SAS screen", async () => {67 const startAdoption = vi.fn(async ({ confirmSAS }: { confirmSAS(sas: string): Promise<boolean> }) => {68 await confirmSAS("895245");69 });70 const view = mounted(() => <AdoptVaultView onCancel={() => {}} onDone={() => {}} startAdoption={startAdoption} />);71 await enterStrongPassword(view.container);72 button(view.container, "Continue").click();73 button(view.container, "Pair a device").click();74 await flushView();7576 expect(view.container.textContent).toContain("Confirm this code matches");77 expect(view.container.textContent).toContain("8 9 5 2 4 5");78 });7980 it("T-A24: success leaves a vault and opens Peach", async () => {81 const onDone = vi.fn();82 const view = mounted(83 () => (84 <AdoptVaultView85 onCancel={() => {}}86 onDone={onDone}87 startAdoption={vi.fn(async () => {})}88 />89 ),90 );91 await enterStrongPassword(view.container);92 button(view.container, "Continue").click();93 button(view.container, "Pair a device").click();94 await flushView();9596 expect(view.container.textContent).toContain("Joined your vault.");97 button(view.container, "Open Peach").click();98 expect(onDone).toHaveBeenCalledTimes(1);99 });100101 it("T-A25: SAS cancel keeps receiver vault-less and returns to the password step", async () => {102 const createVault = vi.fn();103 const view = mounted(104 () => (105 <AdoptVaultView106 onCancel={() => {}}107 onDone={() => {}}108 startAdoption={vi.fn(async ({ confirmSAS }) => {109 const ok = await confirmSAS("895245");110 if (ok) await createVault();111 else throw new Error("sas_rejected");112 })}113 />114 ),115 );116 await enterStrongPassword(view.container);117 button(view.container, "Continue").click();118 button(view.container, "Pair a device").click();119 await flushView();120 button(view.container, "Cancel").click();121 await flushView();122123 expect(createVault).not.toHaveBeenCalled();124 expect(view.container.textContent).toContain("Set a password for this device.");125 });126});