feat(tree): complete rust family runtime checklist
- add tree shell runtime artifact contracts and page/filetree/picker runtime reducers - sink tree.subtree.move write operation through Rust and formalize command event plans - harden file tree search projection contract and route thin-proxy boundaries - record completed harness tasks and move design docs into process/done
This commit is contained in:
@@ -34,6 +34,34 @@ function buildTreeShellUrl(workspaceId, params = {}) {
|
||||
return `${BASE_URL}/api/tree/shell?${search.toString()}`;
|
||||
}
|
||||
|
||||
function treeHostImplementationUsesIframe(implementation) {
|
||||
return implementation === "mnote_web_iframe_proxy" || implementation === "rust_inline_compat_host";
|
||||
}
|
||||
|
||||
async function waitForInlineTreeShellReady(page, surfaceTestId) {
|
||||
await page.waitForFunction(
|
||||
(testId) => {
|
||||
const host = document.querySelector(`[data-testid="${testId}"]`);
|
||||
if (!(host instanceof HTMLElement) || host.getClientRects().length === 0) {
|
||||
return false;
|
||||
}
|
||||
const implementation = host.getAttribute("data-tree-host-implementation") || "";
|
||||
if (implementation !== "mnote_web_iframe_proxy" && implementation !== "rust_inline_compat_host") {
|
||||
return true;
|
||||
}
|
||||
const iframe = host.querySelector(`[data-testid="${testId}-rust-iframe"]`);
|
||||
if (!(iframe instanceof HTMLIFrameElement) || iframe.getClientRects().length === 0) {
|
||||
return false;
|
||||
}
|
||||
const doc = iframe.contentDocument;
|
||||
const status = doc?.querySelector("#tree-shell-status")?.textContent ?? "";
|
||||
return Boolean(doc?.querySelector("#tree-shell-state")) && status.includes("就绪");
|
||||
},
|
||||
surfaceTestId,
|
||||
{ timeout: UI_TIMEOUT_MS },
|
||||
);
|
||||
}
|
||||
|
||||
async function renameFixtureDocuments(requestContext, fixture) {
|
||||
await renameDocument(requestContext, fixture.workspaceId, fixture.parentId, fixture.parentTitle);
|
||||
await renameDocument(requestContext, fixture.workspaceId, fixture.childAId, fixture.childATitle);
|
||||
@@ -133,7 +161,7 @@ async function getPageTreeHostDriver(page) {
|
||||
return false;
|
||||
}
|
||||
const implementation = element.getAttribute("data-tree-host-implementation") || "";
|
||||
if (implementation === "mnote_web_iframe_proxy") {
|
||||
if (implementation === "mnote_web_iframe_proxy" || implementation === "rust_inline_compat_host") {
|
||||
const iframe = element.querySelector('[data-testid="sidebar-page-tree-shell-rust-iframe"]');
|
||||
return iframe instanceof HTMLIFrameElement && iframe.getClientRects().length > 0;
|
||||
}
|
||||
@@ -143,7 +171,8 @@ async function getPageTreeHostDriver(page) {
|
||||
{ timeout: UI_TIMEOUT_MS },
|
||||
);
|
||||
const implementation = await host.getAttribute("data-tree-host-implementation");
|
||||
if (implementation === "mnote_web_iframe_proxy") {
|
||||
if (treeHostImplementationUsesIframe(implementation)) {
|
||||
await waitForInlineTreeShellReady(page, "sidebar-page-tree-shell");
|
||||
return {
|
||||
kind: "iframe",
|
||||
host,
|
||||
@@ -163,7 +192,7 @@ async function getFileTreeHostDriver(page) {
|
||||
return false;
|
||||
}
|
||||
const implementation = element.getAttribute("data-tree-host-implementation") || "";
|
||||
if (implementation === "mnote_web_iframe_proxy") {
|
||||
if (implementation === "mnote_web_iframe_proxy" || implementation === "rust_inline_compat_host") {
|
||||
const iframe = element.querySelector('[data-testid="sidebar-file-tree-shell-rust-iframe"]');
|
||||
return iframe instanceof HTMLIFrameElement && iframe.getClientRects().length > 0;
|
||||
}
|
||||
@@ -173,7 +202,8 @@ async function getFileTreeHostDriver(page) {
|
||||
{ timeout: UI_TIMEOUT_MS },
|
||||
);
|
||||
const implementation = await host.getAttribute("data-tree-host-implementation");
|
||||
if (implementation === "mnote_web_iframe_proxy") {
|
||||
if (treeHostImplementationUsesIframe(implementation)) {
|
||||
await waitForInlineTreeShellReady(page, "sidebar-file-tree-shell");
|
||||
return {
|
||||
kind: "iframe",
|
||||
host,
|
||||
@@ -837,6 +867,9 @@ async function runPickerDialogChecks(context, fixture) {
|
||||
await pickerSurface.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
||||
const searchInput = dialog.getByPlaceholder("移动到...");
|
||||
const pickerUsesIframe = (await pickerSurface.locator('[data-testid="tree-picker-surface-rust-iframe"]').count()) > 0;
|
||||
if (pickerUsesIframe) {
|
||||
await waitForInlineTreeShellReady(page, "tree-picker-surface");
|
||||
}
|
||||
return { dialog, pickerSurface, searchInput, pickerUsesIframe };
|
||||
};
|
||||
|
||||
@@ -986,8 +1019,13 @@ async function runPickerDialogChecks(context, fixture) {
|
||||
if (!(frame instanceof HTMLIFrameElement)) {
|
||||
throw new Error("picker iframe 不存在");
|
||||
}
|
||||
const escapedNodeId = CSS.escape(nodeId);
|
||||
const button = frame.contentDocument?.querySelector(
|
||||
`.tree-row[data-node-id="${nodeId}"] [data-testid="tree-node-open"]`,
|
||||
[
|
||||
`.tree-row[data-node-id="${escapedNodeId}"][data-testid="tree-picker-row"]`,
|
||||
`.tree-row[data-node-id="${escapedNodeId}"] [data-testid="tree-picker-row"]`,
|
||||
`.tree-row[data-node-id="${escapedNodeId}"] [data-testid="tree-node-open"]`,
|
||||
].join(", "),
|
||||
);
|
||||
if (!button || typeof button.click !== "function") {
|
||||
throw new Error("picker 目标页面按钮不存在");
|
||||
|
||||
Reference in New Issue
Block a user