diff --git a/tests/favorite-folder-dialog.test.ts b/tests/favorite-folder-dialog.test.ts index e76461f..7577394 100644 --- a/tests/favorite-folder-dialog.test.ts +++ b/tests/favorite-folder-dialog.test.ts @@ -146,6 +146,33 @@ describe("favorite-folder-dialog", () => { await expect(second).resolves.toBeNull(); }); + test("removes a closed dialog keydown listener before a fresh dialog opens", async () => { + let firstSettlementCount = 0; + const first = promptForFavoriteFolderName(document, { + title: "新建收藏夹" + }); + void first.then(() => { + firstSettlementCount += 1; + }); + + readButton("cancel").click(); + await expect(first).resolves.toBeNull(); + await Promise.resolve(); + expect(firstSettlementCount).toBe(1); + + const current = promptForFavoriteFolderName(document, { + title: "重命名收藏夹" + }); + document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" })); + + await expect(current).resolves.toBeNull(); + await Promise.resolve(); + expect(firstSettlementCount).toBe(1); + expect( + document.querySelector('[data-sces-favorite-folder-dialog="root"]') + ).toBeNull(); + }); + test("traps Tab within the dialog and restores the opener focus on close", async () => { const opener = document.createElement("button"); opener.type = "button"; diff --git a/tests/favorites-drawer.test.ts b/tests/favorites-drawer.test.ts index 9f6474c..4ce58a3 100644 --- a/tests/favorites-drawer.test.ts +++ b/tests/favorites-drawer.test.ts @@ -120,12 +120,21 @@ describe("favorites-drawer", () => { expect(readTab().textContent).toContain("收藏夹"); expect(readTab().style.position).toBe("fixed"); expect(readTab().style.right).toBe("56px"); + expect(readTab().style.writingMode).toBe("vertical-rl"); + expect(readTab().getAttribute("aria-controls")).toBe(readDrawer().id); expect(readTab().getAttribute("aria-expanded")).toBe("false"); + expect(readTab().querySelector("[data-sces-favorites-unread]")).toBeNull(); + expect(readTab().querySelector("[data-sces-favorites-badge]")).toBeNull(); + expect(readTab().dataset.scesFavoritesUnread).toBeUndefined(); + expect(readTab().dataset.scesFavoritesCount).toBeUndefined(); + expect(readTab().textContent).not.toMatch(/\d/); + expect(readTab().style.color).not.toMatch(/b42318|dc2626|ef4444|red/i); expect(readDrawer().hidden).toBe(true); expect(readDrawer().style.position).toBe("fixed"); expect(readDrawer().style.right).toBe("56px"); expect(readDrawer().style.width).toBe("380px"); expect(readDrawer().getAttribute("role")).toBe("dialog"); + expect(readDrawer().getAttribute("aria-label")).toBe("收藏夹"); }); test("opens, closes, and restores focus through the trigger, close control, and Escape", () => { @@ -263,6 +272,40 @@ describe("favorites-drawer", () => { expect(onRemoveCreatorFromFolder).toHaveBeenCalledWith("author-1", firstFolder.id); }); + test("prunes selected creators that are outside the active folder before importing", async () => { + const onImportSelected = vi.fn(async (_authorIds: string[]) => {}); + const { controller } = createHarness({ handlers: { onImportSelected } }); + controller.render( + createState({ + creators: [createCreator("111", "Alpha"), createCreator("222", "Beta")], + memberships: [ + createMembership("111", firstFolder.id), + createMembership("222", secondFolder.id) + ] + }) + ); + readTab().click(); + + const firstSelection = document.querySelector( + '[data-sces-favorites-select-author-id="111"]' + ) as HTMLInputElement; + firstSelection.checked = true; + firstSelection.dispatchEvent(new Event("change", { bubbles: true })); + const secondSelection = document.querySelector( + '[data-sces-favorites-select-author-id="222"]' + ) as HTMLInputElement; + secondSelection.checked = true; + secondSelection.dispatchEvent(new Event("change", { bubbles: true })); + expect(readDrawer().textContent).toContain("导入已选 2 位达人"); + + click(`[data-sces-favorites-folder-id="${firstFolder.id}"]`); + expect(readDrawer().textContent).toContain("导入已选 1 位达人"); + click('[data-sces-favorites-drawer="import-selected"]'); + await flush(); + + expect(onImportSelected).toHaveBeenCalledWith(["111"]); + }); + test("calls create, rename, and delete handlers with the matching folder id", async () => { const onCreateFolder = vi.fn(async () => {}); const onRenameFolder = vi.fn(async (_folderId: string) => {}); @@ -351,8 +394,9 @@ describe("favorites-drawer", () => { expect(readDrawer().hidden).toBe(true); }); - test("renders errors and removes its DOM and document behavior on disposal", () => { - const { controller } = createHarness(); + test("renders errors and removes DOM and event behavior on disposal", async () => { + const onCreateFolder = vi.fn(async () => {}); + const { controller } = createHarness({ handlers: { onCreateFolder } }); controller.render(createState()); readTab().click(); controller.setError("保存失败"); @@ -360,10 +404,28 @@ describe("favorites-drawer", () => { document.querySelector('[data-sces-favorites-drawer="error"]')?.textContent ).toBe("保存失败"); + const staleCreateButton = document.querySelector( + '[data-sces-favorites-drawer="create-folder"]' + ) as HTMLButtonElement; + const removeEventListener = vi.spyOn(document, "removeEventListener"); controller.dispose(); controller.dispose(); expect(document.querySelector('[data-sces-favorites-tab="button"]')).toBeNull(); expect(document.querySelector('[data-sces-favorites-drawer="root"]')).toBeNull(); - document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" })); + expect(removeEventListener).toHaveBeenCalledWith("keydown", expect.any(Function)); + + const unrelatedButton = document.createElement("button"); + unrelatedButton.type = "button"; + document.body.append(unrelatedButton); + unrelatedButton.focus(); + const escape = new KeyboardEvent("keydown", { cancelable: true, key: "Escape" }); + document.dispatchEvent(escape); + staleCreateButton.dispatchEvent(new MouseEvent("click", { bubbles: true })); + await flush(); + + expect(escape.defaultPrevented).toBe(false); + expect(document.activeElement).toBe(unrelatedButton); + expect(onCreateFolder).not.toHaveBeenCalled(); + removeEventListener.mockRestore(); }); });