feat(tree): checkpoint resource lifecycle work
提交当前顶层 mnote Git 工作区,范围集中在 04-tree-domain 的 resource/trash/filetree 生命周期、mnote-web resource_trash 路由、Convex/Next 兼容接口、sidebar/file-tree 客户端适配、smoke 脚本与对应设计/bug 记录。 不包含被 ignore 的 design/05-editor-mainline/reference-code/leptos-tiptap 嵌套仓库改动。新增 smoke 的测试密码改为运行时读取 MNOTE_E2E_PASSWORD,避免提交明文 credential assignment。
This commit is contained in:
@@ -90,6 +90,22 @@ async function closeContextMenu(page) {
|
||||
}
|
||||
}
|
||||
|
||||
async function dispatchFolderContextMenu(page, rowSelector) {
|
||||
await page.evaluate(
|
||||
({ rowSelector }) => {
|
||||
const row = document.querySelector(rowSelector);
|
||||
if (!(row instanceof HTMLElement)) throw new Error("filetree folder row 不存在");
|
||||
row.dispatchEvent(new MouseEvent("contextmenu", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: 24,
|
||||
clientY: 64,
|
||||
}));
|
||||
},
|
||||
{ rowSelector },
|
||||
);
|
||||
}
|
||||
|
||||
async function dispatchRootContextMenu(page) {
|
||||
await page.evaluate(() => {
|
||||
const root = document.querySelector("#sidebar-file-tree-root .tree-root");
|
||||
@@ -256,6 +272,15 @@ async function run() {
|
||||
});
|
||||
const page = await context.newPage();
|
||||
const requests = [];
|
||||
const navigationEvents = [];
|
||||
page.on("framenavigated", (frame) => {
|
||||
if (frame === page.mainFrame()) {
|
||||
navigationEvents.push({
|
||||
url: frame.url(),
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
});
|
||||
page.on("request", (request) => {
|
||||
if (
|
||||
request.url().includes("/api/tree/commands") ||
|
||||
@@ -449,7 +474,7 @@ async function run() {
|
||||
await page.locator(readonlyRow).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
||||
await page.locator(imageRow).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
||||
|
||||
await dispatchRootContextMenu(page);
|
||||
await dispatchFolderContextMenu(page, docsRow);
|
||||
await waitForContextMenu(page);
|
||||
await expectMenuAction(page, "newPage", { disabled: false });
|
||||
await expectMenuAction(page, "newFolder", { disabled: false });
|
||||
@@ -496,7 +521,7 @@ async function run() {
|
||||
await page.locator(readonlyRow).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
||||
await page.locator(imageRow).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
||||
|
||||
await dispatchRootContextMenu(page);
|
||||
await dispatchFolderContextMenu(page, docsRow);
|
||||
await waitForContextMenu(page);
|
||||
await clickMenuAction(page, "newFolder");
|
||||
let createRenameInput = page.locator(".tree-rename-input").first();
|
||||
@@ -712,13 +737,19 @@ async function run() {
|
||||
assert(!fs.existsSync(path.join(root, "readonly-dir", "readonly-drop.txt")), "readonly 目标预检失败后不应写入文件");
|
||||
|
||||
await page.goto(treeUrl(root, "page"), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
|
||||
let watcherNavigationStart = navigationEvents.length;
|
||||
fs.writeFileSync(path.join(root, "docs", "watcher-added.md"), "# Watcher Added\n", "utf8");
|
||||
await waitForText(page, "Watcher Added");
|
||||
assert(navigationEvents.length === watcherNavigationStart, "外部新增 Markdown 后 page tree 不应发生浏览器导航或 reload");
|
||||
await page.goto(treeUrl(root, "filetree"), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
|
||||
watcherNavigationStart = navigationEvents.length;
|
||||
fs.writeFileSync(path.join(root, "docs", "watcher-asset.txt"), "watcher asset", "utf8");
|
||||
await waitForText(page, "watcher-asset.txt");
|
||||
assert(navigationEvents.length === watcherNavigationStart, "外部新增非 md 资源后 filetree 不应发生浏览器导航或 reload");
|
||||
watcherNavigationStart = navigationEvents.length;
|
||||
fs.rmSync(path.join(root, "docs", "watcher-asset.txt"));
|
||||
await page.getByText("watcher-asset.txt", { exact: true }).waitFor({ state: "detached", timeout: UI_TIMEOUT_MS });
|
||||
assert(navigationEvents.length === watcherNavigationStart, "外部删除非 md 资源后 filetree 不应发生浏览器导航或 reload");
|
||||
|
||||
await page.goto(convexTreeUrl("filetree"), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
|
||||
await waitForText(page, "工作区首页");
|
||||
|
||||
Reference in New Issue
Block a user