Files
Endoleap/tests/feedback-scenario.mjs
T

55 lines
4.7 KiB
JavaScript

import assert from "node:assert/strict";
export async function feedbackScenario({ root, act, h, AppFeedback, IconButton, notify, dismissNotification, GraphCanvas, window, makeCanvasNode, emptyCanvas, event, tick }) {
await act(async () => { root.render(null); await tick(); });
let button, clicks = 0;
const icon = () => h("svg", { "aria-hidden": true });
const control = () => h(IconButton, { icon, label: "旋转工具", title: "旋转选中元素", ref: (value) => { button = value; }, onClick: () => { clicks++; } });
await act(async () => { root.render(control()); await tick(); });
assert.equal(button.title, "旋转选中元素", "Standalone controls retain native explanations");
await act(async () => { root.render(h(AppFeedback, null, h("div", { id: "controls" }, control()))); await tick(); });
assert.ok(document.querySelector("#controls").firstElementChild === button, "Tooltip does not wrap the button or alter toolbar geometry");
assert.equal(button.tagName, "BUTTON", "Forwarded ref still points at the actual button");
assert.equal(button.title, "", "A provider avoids duplicate native and Radix tooltips");
await act(async () => { button.focus(); await tick(); });
const tooltip = document.querySelector('[role="tooltip"]');
assert.ok(tooltip?.textContent.includes("旋转选中元素"), "Keyboard focus exposes the explanation");
assert.ok(button.getAttribute("aria-describedby"), "Explanation is associated with its trigger");
await event(document, "keydown", { key: "Escape" });
assert.ok(!document.querySelector('[role="tooltip"]'), "Escape dismisses the tooltip");
await event(button, "click"); assert.equal(clicks, 1, "Tooltips preserve business clicks");
await act(async () => {
notify({ id: "feedback-check", title: "正在处理" }); await tick();
notify({ id: "feedback-check", title: "已完成", level: "success" }); await tick();
});
assert.equal(document.querySelectorAll('[data-sonner-toast]').length, 1, "One logical notice updates instead of duplicating");
assert.ok(document.querySelector('[data-sonner-toast]').textContent.includes("已完成"));
assert.ok(document.querySelector('[aria-label="关闭通知"]'), "Notification close control is labelled");
await act(async () => { dismissNotification("feedback-check"); await tick(); });
for (let i = 0; i < 20 && document.querySelector('[data-sonner-toast]'); i++) await act(async () => { await tick(); });
assert.ok(!document.querySelector('[data-sonner-toast]'), "Dismissed notifications are removed");
console.log("PASS feedback: native fallback, forwarded ref, keyboard tooltip/Escape, clicks and stable notification IDs");
const id = "feedback-board";
const node = { ...makeCanvasNode("text", { x: 100, y: 100 }), id: "feedback-text" };
const frame = { ...makeCanvasNode("frame", { x: 500, y: 100 }), id: "feedback-frame" };
frame.data.title = "提示分区";
localStorage.setItem(`endoleap_canvas_v1:w:${id}`, JSON.stringify({ ...emptyCanvas(), nodes: [node, frame] }));
await act(async () => { root.render(h(AppFeedback, null, h(GraphCanvas, { board: { id, workspace_id: "w", title: "反馈测试", kind: "board", version: 1 } }))); await tick(); });
const textNode = document.querySelector('.react-flow__node[data-id="feedback-text"]');
await event(textNode, "mousedown", { clientX: 200, clientY: 200, button: 0, buttons: 1 });
await event(window, "mousemove", { clientX: 210, clientY: 200, buttons: 1 });
await event(window, "mousemove", { clientX: 700, clientY: 200, buttons: 1 });
await event(window, "mouseup", { clientX: 700, clientY: 200, button: 0 });
assert.ok(document.querySelector('[data-sonner-toast][data-type="success"]')?.textContent.includes("已加入「提示分区」"));
assert.ok(document.querySelector('[title="属于:提示分区"]'), "Notifications do not replace actual membership state");
const fileInput = document.querySelector('input[type="file"][accept^="image/"]');
Object.defineProperty(fileInput, "files", { configurable: true, value: [new File(["test"], "test.pdf", { type: "application/pdf" })] });
await act(async () => { fileInput.dispatchEvent(new window.Event("change", { bubbles: true })); await tick(); });
assert.ok(document.querySelector('[data-sonner-toast][data-type="error"]')?.textContent.includes("演示模式请选择"));
await event(document.querySelector('[aria-label="添加文本卡片"]'), "click");
assert.equal(document.querySelectorAll('.react-flow__node').length, 3, "An error notification never blocks editing");
await act(async () => { dismissNotification(`canvas:${id}:error`); root.render(null); await tick(); });
console.log("PASS canvas feedback: membership success, input errors and continued local editing through the application provider");
}