// @vitest-environment jsdom import { beforeEach, describe, expect, test, vi } from "vitest"; import { promptForFavoriteFolderName } from "../src/content/market/favorite-folder-dialog"; function readDialog(): HTMLElement { const dialog = document.querySelector( '[data-sces-favorite-folder-dialog="root"]' ); if (!(dialog instanceof HTMLElement)) { throw new Error("Expected favorite folder dialog"); } return dialog; } function readInput(): HTMLInputElement { const input = document.querySelector( '[data-sces-favorite-folder-dialog="input"]' ); if (!(input instanceof HTMLInputElement)) { throw new Error("Expected favorite folder name input"); } return input; } function readButton(name: "confirm" | "cancel"): HTMLButtonElement { const button = document.querySelector( `[data-sces-favorite-folder-dialog="${name}"]` ); if (!(button instanceof HTMLButtonElement)) { throw new Error(`Expected ${name} button`); } return button; } describe("favorite-folder-dialog", () => { beforeEach(() => { document.body.innerHTML = ""; }); test("renders the requested title and initial name, then trims a confirmation", async () => { const result = promptForFavoriteFolderName(document, { initialValue: " 重点合作 ", title: "重命名收藏夹" }); expect(readDialog().getAttribute("role")).toBe("dialog"); expect(readDialog().textContent).toContain("重命名收藏夹"); expect(readInput().value).toBe(" 重点合作 "); expect(document.activeElement).toBe(readInput()); readButton("confirm").click(); await expect(result).resolves.toBe("重点合作"); expect( document.querySelector('[data-sces-favorite-folder-dialog="root"]') ).toBeNull(); }); test("keeps an inline error open for blank names and clears it after input", async () => { const result = promptForFavoriteFolderName(document, { title: "新建收藏夹" }); readInput().value = " "; readButton("confirm").click(); expect( document.querySelector('[data-sces-favorite-folder-dialog="error"]')?.textContent ).toBe("请输入收藏夹名称"); expect(readInput().getAttribute("aria-invalid")).toBe("true"); expect(document.activeElement).toBe(readInput()); readInput().value = "重点"; readInput().dispatchEvent(new Event("input", { bubbles: true })); expect( document.querySelector('[data-sces-favorite-folder-dialog="error"]')?.textContent ).toBe(""); readButton("cancel").click(); await expect(result).resolves.toBeNull(); }); test("cancels from its cancel button, overlay click, and Escape", async () => { const fromCancel = promptForFavoriteFolderName(document, { title: "新建收藏夹" }); readButton("cancel").click(); await expect(fromCancel).resolves.toBeNull(); const fromOverlay = promptForFavoriteFolderName(document, { title: "新建收藏夹" }); readDialog().dispatchEvent(new MouseEvent("click", { bubbles: true })); await expect(fromOverlay).resolves.toBeNull(); const fromEscape = promptForFavoriteFolderName(document, { title: "新建收藏夹" }); document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" })); await expect(fromEscape).resolves.toBeNull(); }); test("confirms a valid name with Enter", async () => { const result = promptForFavoriteFolderName(document, { title: "新建收藏夹" }); readInput().value = "待联系"; document.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" })); await expect(result).resolves.toBe("待联系"); }); test("reuses and focuses one active dialog per document, then allows a new one after close", async () => { const first = promptForFavoriteFolderName(document, { initialValue: "首个值", title: "新建收藏夹" }); const input = readInput(); input.blur(); const reused = promptForFavoriteFolderName(document, { initialValue: "不会替换", title: "重命名收藏夹" }); expect(reused).toBe(first); expect( document.querySelectorAll('[data-sces-favorite-folder-dialog="root"]') ).toHaveLength(1); expect(readInput().value).toBe("首个值"); expect(document.activeElement).toBe(input); readButton("cancel").click(); await expect(first).resolves.toBeNull(); const second = promptForFavoriteFolderName(document, { title: "新建收藏夹" }); expect( document.querySelectorAll('[data-sces-favorite-folder-dialog="root"]') ).toHaveLength(1); readButton("cancel").click(); await expect(second).resolves.toBeNull(); }); test("removes the exact captured document keydown listener when closed", async () => { const addEventListener = vi.spyOn(document, "addEventListener"); const removeEventListener = vi.spyOn(document, "removeEventListener"); try { const result = promptForFavoriteFolderName(document, { title: "新建收藏夹" }); const keydownRegistration = addEventListener.mock.calls.find( (call) => call[0] === "keydown" && call[2] === true ); if (!keydownRegistration) { throw new Error("Expected a captured keydown listener"); } const keydownHandler = keydownRegistration[1]; readButton("cancel").click(); await expect(result).resolves.toBeNull(); expect(removeEventListener).toHaveBeenCalledWith( "keydown", keydownHandler, true ); } finally { addEventListener.mockRestore(); removeEventListener.mockRestore(); } }); test("traps Tab within the dialog and restores the opener focus on close", async () => { const opener = document.createElement("button"); opener.type = "button"; opener.textContent = "打开收藏夹"; document.body.append(opener); opener.focus(); const result = promptForFavoriteFolderName(document, { title: "新建收藏夹" }); readInput().focus(); document.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab", shiftKey: true })); expect(document.activeElement).toBe(readButton("confirm")); document.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab" })); expect(document.activeElement).toBe(readInput()); readButton("cancel").click(); await expect(result).resolves.toBeNull(); expect(document.activeElement).toBe(opener); }); });