Implement sidebar tree view state persistence
This commit is contained in:
@@ -72,6 +72,27 @@ function fileUrlToPath(value) {
|
||||
return decodeURIComponent(url.pathname);
|
||||
}
|
||||
|
||||
function cssEscape(value) {
|
||||
return String(value).replace(/["\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
async function setPageRowExpanded(page, nodeId, expanded) {
|
||||
const selector = `#sidebar-tree-root .tree-row[data-shell-mode="page"][data-node-id="${cssEscape(nodeId)}"]`;
|
||||
await page.locator(selector).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
||||
const current = await page.locator(selector).first().getAttribute("aria-expanded");
|
||||
if ((current === "true") !== expanded) {
|
||||
await page.locator(`${selector} [data-rust-action="toggle"]`).first().click({ timeout: UI_TIMEOUT_MS });
|
||||
}
|
||||
await page.waitForFunction(
|
||||
({ targetNodeId, expected }) => {
|
||||
const row = document.querySelector(`#sidebar-tree-root .tree-row[data-shell-mode="page"][data-node-id="${CSS.escape(targetNodeId)}"]`);
|
||||
return row && row.getAttribute("aria-expanded") === String(expected);
|
||||
},
|
||||
{ targetNodeId: nodeId, expected: expanded },
|
||||
{ timeout: UI_TIMEOUT_MS },
|
||||
);
|
||||
}
|
||||
|
||||
async function signUp(context, baseUrl, actorId) {
|
||||
const response = await context.request.fetch(`${baseUrl}/api/auth`, {
|
||||
method: "POST",
|
||||
@@ -163,8 +184,28 @@ async function main() {
|
||||
const context = await browser.newContext({ viewport: { width: 1360, height: 920 } });
|
||||
const page = await context.newPage();
|
||||
const diagnostics = [];
|
||||
const viewStateRequests = [];
|
||||
page.on("console", (message) => diagnostics.push(`console:${message.type()}:${message.text()}`));
|
||||
page.on("pageerror", (error) => diagnostics.push(`pageerror:${error.message}`));
|
||||
page.on("request", (request) => {
|
||||
if (!request.url().includes("/api/tree/view-state")) return;
|
||||
viewStateRequests.push({
|
||||
method: request.method(),
|
||||
url: request.url(),
|
||||
postData: request.postData() || "",
|
||||
at: Date.now(),
|
||||
});
|
||||
});
|
||||
page.on("response", async (response) => {
|
||||
if (!response.url().includes("/api/tree/view-state")) return;
|
||||
const request = response.request();
|
||||
const entry = viewStateRequests.findLast((candidate) => candidate.url === request.url() && candidate.method === request.method() && !candidate.status);
|
||||
if (!entry) return;
|
||||
entry.status = response.status();
|
||||
entry.doneAt = Date.now();
|
||||
entry.durationMs = entry.doneAt - entry.at;
|
||||
entry.body = await response.text().catch(() => "");
|
||||
});
|
||||
|
||||
try {
|
||||
await waitForHttpOk(`${baseUrl}/health`, 60_000);
|
||||
@@ -182,6 +223,17 @@ async function main() {
|
||||
fileTreeUrl.searchParams.set("rootUri", rootUri);
|
||||
fileTreeUrl.searchParams.set("treeView", "filetree");
|
||||
await page.goto(fileTreeUrl.toString(), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
|
||||
await page.locator('[data-mnote-sidebar-tree-tab="page"]').click({ timeout: UI_TIMEOUT_MS });
|
||||
const rootPageTreePut = page.waitForResponse((response) => {
|
||||
const postData = response.request().postData() || "";
|
||||
return response.url().includes("/api/tree/view-state")
|
||||
&& response.request().method() === "PUT"
|
||||
&& /"treeKind":"pagetree"/.test(postData)
|
||||
&& /"scope":"root"/.test(postData);
|
||||
}, { timeout: UI_TIMEOUT_MS });
|
||||
await setPageRowExpanded(page, "local-dir:design", false);
|
||||
const rootPageTreeResponse = await rootPageTreePut;
|
||||
assert(rootPageTreeResponse.ok(), `root PageTree view-state 保存失败: ${rootPageTreeResponse.status()}`);
|
||||
await page.locator('[data-mnote-sidebar-tree-tab="filetree"]').click({ timeout: UI_TIMEOUT_MS });
|
||||
const designRow = page.locator('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path="design"]').first();
|
||||
await designRow.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
||||
@@ -220,6 +272,14 @@ async function main() {
|
||||
const pageTreeText = await page.locator("#sidebar-tree-root").innerText({ timeout: UI_TIMEOUT_MS });
|
||||
assert.match(pageTreeText, /Brief/, `星标 design 后页面树应显示 design 内 Markdown: ${pageTreeText}`);
|
||||
assert.doesNotMatch(pageTreeText, /Home|Other/, `星标 design 后页面树不应显示根目录 Markdown: ${pageTreeText}`);
|
||||
assert(
|
||||
viewStateRequests.some((entry) => entry.method === "PUT" && entry.status === 200 && /"treeKind":"pagetree"/.test(entry.postData) && /"scope":"root"/.test(entry.postData)),
|
||||
`应记录 root PageTree view-state PUT: ${JSON.stringify(viewStateRequests)}`,
|
||||
);
|
||||
assert(
|
||||
viewStateRequests.some((entry) => entry.method === "GET" && entry.status === 200 && entry.url.includes("treeKind=pagetree") && entry.url.includes("scope=design")),
|
||||
`星标 design 应读取 scoped PageTree view-state: ${JSON.stringify(viewStateRequests)}`,
|
||||
);
|
||||
|
||||
await openLocalDocument(page, baseUrl, rootUri, workspaceId, "local-md:Home.md");
|
||||
const editor = page.locator(".document-pane[data-pane-role='primary'] .editor-surface .ProseMirror[contenteditable='true']").first();
|
||||
@@ -258,7 +318,7 @@ async function main() {
|
||||
const reloadedText = await readEditorText(page);
|
||||
assert.match(reloadedText, /111/, `切换页面再回 Home.md 后编辑内容应保留: ${reloadedText}`);
|
||||
|
||||
console.log(JSON.stringify({ ok: true, task: TASK, baseUrl }, null, 2));
|
||||
console.log(JSON.stringify({ ok: true, task: TASK, baseUrl, viewStateRequests }, null, 2));
|
||||
} finally {
|
||||
await browser.close();
|
||||
server.kill("SIGINT");
|
||||
|
||||
Reference in New Issue
Block a user