201 lines
6.3 KiB
TypeScript
201 lines
6.3 KiB
TypeScript
// @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);
|
|
});
|
|
});
|