返回 DeepSeek-Reasonix
dock-view-state.mjs
根目录 / desktop / frontend / bench / dock-view-state.mjs
1 import assert from "node:assert/strict";
2 import path from "node:path";
3 import { fileURLToPath } from "node:url";
4 import { tmpdir } from "node:os";
5 import { startPreviewServer } from "./vite-preview-server.mjs";
6 import { selectSession } from "./app-page-actions.mjs";
7
8 const frontendDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9 process.env.PLAYWRIGHT_BROWSERS_PATH = !process.env.PLAYWRIGHT_BROWSERS_PATH || process.env.PLAYWRIGHT_BROWSERS_PATH === ".pw-browsers"
10 ? path.join(frontendDir, ".pw-browsers")
11 : process.env.PLAYWRIGHT_BROWSERS_PATH;
12 const { chromium } = await import("playwright");
13 const preview = process.env.REASONIX_DOCK_URL ? null : await startPreviewServer(frontendDir, 4663);
14 const url = process.env.REASONIX_DOCK_URL ?? "http://127.0.0.1:4663";
15 const browser = await chromium.launch({ headless: true, executablePath: process.env.CHROME_EXECUTABLE });
16 try {
17 const context = await browser.newContext({ viewport: { width: 1440, height: 1000 }, locale: "en-US" });
18 await context.addInitScript(() => localStorage.setItem("reasonix-lang", "en"));
19 const page = await context.newPage();
20 const errors = [];
21 page.on("pageerror", error => errors.push(error.message));
22 await page.goto(url + "/?mock=bench&bench=1&app-lifecycle-probe=1");
23 await page.locator("textarea.composer__input:not([aria-hidden=true])").waitFor();
24 await selectSession(page, "bench:small-6t");
25 // The benchmark fixture hydrates this session asynchronously. Wait for the
26 // authoritative session snapshot before mutating dock state, otherwise the
27 // late hydrate can replace the empty-state picker while Playwright clicks it.
28 await page.waitForFunction(() => document.querySelector('.transcript')?.textContent?.includes('ASYNC LAYOUT EXPANSION COMPLETE'));
29 const tabs = page.locator('.workbench-dock__tabs [role="tab"]');
30 const overview = page.getByRole("tab", { name: "Overview", exact: true });
31 await overview.waitFor();
32 assert.equal(await overview.count(), 1, "fresh expanded workspace dock defaults to Overview");
33 await overview.locator(".workbench-dock__tab-close").click();
34 await page.locator(".workbench-dock").waitFor({ state: "detached" });
35 assert.equal(await tabs.count(), 0, "closing the last tab removes the dock");
36 await page.locator(".topicbar__chrome-btn--workspace[aria-pressed=false]").click();
37 await page.locator(".tab-picker__item").filter({ hasText: /^Files$/ }).click();
38 await page.locator('[data-workspace-path="README.md"]').click();
39 await page.waitForFunction(() => document.querySelector(".workspace-preview__body")?.textContent?.includes("Browser-dev workspace preview."));
40 await page.locator('[data-workspace-path="go.mod"]').click();
41 await page.locator('[data-workspace-path="README.md"]').click();
42 await page.locator(".workbench-dock__tab-add").click();
43 await page.locator(".tab-add-menu__item").filter({ hasText: /^Files$/ }).click();
44 assert.equal(await tabs.count(), 2);
45 assert.equal(await page.locator(".workspace-panel").count(), 1, "only the active view mounts");
46 assert.equal(await page.locator(".workspace-preview__body").count(), 0, "duplicate view starts empty");
47 await page.locator('[data-workspace-path="go.mod"]').click();
48 const selected = () => document.querySelector(".workspace-tree__row--active")?.getAttribute("data-workspace-path");
49 await page.waitForFunction(selected => document.querySelector(".workspace-tree__row--active")?.getAttribute("data-workspace-path") === selected, "go.mod");
50 await tabs.nth(0).click();
51 assert.equal(await page.evaluate(selected), "README.md", "first view restores selection");
52 await page.locator('.workspace-iconbtn:has(svg.lucide-x)').click();
53 assert.equal(await page.evaluate(selected), "go.mod", "first view restores its inner open-file stack");
54 await page.locator('[data-workspace-path="README.md"]').click();
55 await tabs.nth(1).click();
56 assert.equal(await page.evaluate(selected), "go.mod", "second view restores selection");
57 await page.getByPlaceholder("Filter files…", { exact: true }).fill("mod");
58 await tabs.nth(0).click();
59 assert.equal(await page.getByPlaceholder("Filter files…", { exact: true }).inputValue(), "");
60 await tabs.nth(1).click();
61 assert.equal(await page.getByPlaceholder("Filter files…", { exact: true }).inputValue(), "mod");
62 await tabs.nth(1).locator(".workbench-dock__tab-close").click();
63 await page.locator(".workbench-dock__tab-overview").click();
64 await page.getByRole("menuitem", { name: /^Files Closed/ }).click();
65 assert.equal(await page.evaluate(selected), "go.mod", "reopen restores the original view");
66 assert.equal(await page.getByPlaceholder("Filter files…", { exact: true }).inputValue(), "mod", "reopen restores filtering");
67 await page.reload();
68 await page.locator('[data-workspace-path="go.mod"]').waitFor();
69 assert.equal(await tabs.count(), 2, "restart restores both views");
70 assert.equal(await page.evaluate(selected), "go.mod", "restart restores the active view");
71 assert.equal(await page.getByPlaceholder("Filter files…", { exact: true }).inputValue(), "mod", "restart restores filtering");
72 await tabs.nth(0).click();
73 assert.equal(await page.evaluate(selected), "README.md", "restart keeps the other view independent");
74 await page.locator('.project-tree__folder-main:has(svg.lucide-cloud)').click();
75 await page.locator('.project-tree__topic-main:has-text("Remote demo session")').click();
76 await page.locator(".remote-surface--ready").waitFor();
77 assert.equal(await tabs.count(), 0, "other project does not inherit local dock tabs");
78 await selectSession(page, "bench:small-6t");
79 await page.locator('[data-workspace-path="README.md"]').waitFor();
80 assert.equal(await tabs.count(), 2);
81 assert.equal(await page.evaluate(selected), "README.md", "returning to the project restores its view");
82
83 // Native inert behavior, including hit-testing without the management overlay.
84 await page.evaluate(() => {
85 window.dockBackground = document.querySelector(".topicbar__chrome-btn--workspace");
86 window.dockBackgroundClicks = 0;
87 window.dockBackground.addEventListener("click", () => window.dockBackgroundClicks++);
88 });
89 await page.locator("button:has(svg.lucide-settings)").last().click();
90 await page.locator(".settings-screen").waitFor();
91 assert(await page.evaluate(() => Boolean(window.dockBackground.closest("[inert]"))));
92 assert(await page.evaluate(() => {
93 window.dockBackground.focus();
94 return document.activeElement !== window.dockBackground;
95 }), "ancestor inert prevents background focus");
96 for (let i = 0; i < 6; i++) {
97 await page.keyboard.press("Tab");
98 assert(await page.evaluate(() => !document.activeElement?.closest("[inert]")), "Tab stays outside inert controls");
99 }
100 const point = await page.evaluate(() => {
101 document.querySelector(".settings-screen").style.pointerEvents = "none";
102 const rect = window.dockBackground.getBoundingClientRect();
103 return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
104 });
105 await page.mouse.click(point.x, point.y);
106 assert.equal(await page.evaluate(() => window.dockBackgroundClicks), 0, "inert prevents background interaction without relying on an overlay");
107 await page.evaluate(() => { document.querySelector(".settings-screen").style.pointerEvents = ""; });
108 await page.locator(".settings-screen .management-screen__back").click();
109 assert(await page.evaluate(() => !window.dockBackground.closest("[inert]")));
110 assert.equal(await page.evaluate(selected), "README.md", "management preserves panel state");
111 await tabs.nth(0).click({ button: "right" });
112 assert.equal(await page.locator('.context-menu [role="menuitem"]').count(), 3, "outer menu has only tab actions");
113 await page.keyboard.press("Escape");
114 await tabs.nth(0).focus();
115 await page.keyboard.press("Shift+F10");
116 assert.equal(await page.locator('.context-menu [role="menuitem"]').count(), 3);
117 assert(await page.locator(".context-menu").evaluate(menu => {
118 const r = menu.getBoundingClientRect();
119 return r.left >= 0 && r.right <= innerWidth && r.top >= 0 && r.bottom <= innerHeight;
120 }), "flat keyboard menu stays inside the viewport");
121 await page.keyboard.press("Escape");
122 await page.locator(".topicbar__chrome-btn--launcher").click();
123 await page.locator(".dock-launcher").waitFor();
124 await page.locator(".topicbar__chrome-btn--launcher").click();
125 await page.locator(".dock-launcher").waitFor({ state: "detached" });
126 await page.screenshot({ path: path.join(tmpdir(), "reasonix-dock-views-desktop.png") });
127 await page.setViewportSize({ width: 960, height: 720 });
128 await page.screenshot({ path: path.join(tmpdir(), "reasonix-dock-views-narrow.png") });
129 assert.deepEqual(errors, []);
130 console.log("PASS independent file views, close/reopen, restart, tab-only menu and management inert behavior");
131 } finally {
132 await browser.close();
133 await preview?.httpServer.close();
134 }
135
135 lines Plain Text