AdoptVaultView.test.tsx

4.7 KB · raw

1// @vitest-environment jsdom23import 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});