feat: add favorites drawer management
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { beforeEach, describe, expect, test } 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("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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user