返回 DeepSeek-Reasonix
session-title-rename.mjs
根目录 / desktop / frontend / bench / session-title-rename.mjs
1 // Real sidebar interaction with a controlled host. Durable writes are covered
2 // by Desktop tests; this gate checks per-target UI state and transcript identity.
3 import path from "node:path";
4 import { fileURLToPath } from "node:url";
5 import assert from "node:assert/strict";
6 import { createServer } from "vite";
7 import { chromium } from "playwright";
8 import { selectSession } from "./app-page-actions.mjs";
9
10 const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
11 const server = await createServer({ root, logLevel: "error", server: { host: "127.0.0.1", port: 4679, strictPort: true } });
12 await server.listen();
13 let browser;
14 try {
15 browser = await chromium.launch({ headless: true });
16 const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
17 page.setDefaultTimeout(20000);
18 const errors = [];
19 page.on("pageerror", error => errors.push(error.message));
20 await page.goto("http://127.0.0.1:4679/?mock=bench&bench=1");
21 const composer = page.locator("textarea.composer__input:not([aria-hidden=true])");
22 await composer.waitFor();
23 await selectSession(page, "bench:small-6t");
24 await page.waitForFunction(() => document.querySelector(".transcript")?.textContent?.includes("ASYNC LAYOUT EXPANSION COMPLETE"));
25 await composer.fill("keep my draft");
26 await page.evaluate(async () => {
27 const { app } = await import("/src/lib/bridge.ts");
28 const { installDesktopHostStub } = await import("/src/__tests__/desktopHostStub.ts");
29 const { DESKTOP_COMMANDS } = await import("/src/generated/desktopContract.generated.ts");
30 const fallback = Object.fromEntries(DESKTOP_COMMANDS.map(key => [key, app[key]]));
31 window.__titleFixture = { requests: [], composer: document.querySelector("textarea.composer__input:not([aria-hidden=true])"), nodes: [...document.querySelectorAll("[data-chat-anchor-key]")] };
32 installDesktopHostStub({ ...fallback, AIRenameSessionTarget: selector => new Promise((resolve, reject) => {
33 window.__titleFixture.requests.push({
34 target: selector.ref?.sessionId ? `session-id:${selector.ref.sessionId}` : selector.sessionPath || selector.topicId,
35 resolve: title => resolve({ targetKey: selector.sessionPath || selector.topicId || selector.ref?.sessionId || "", operationId: crypto.randomUUID(), committed: true, title, lifecycleGeneration: 1 }),
36 reject,
37 });
38 }) });
39 });
40 const rows = page.locator(".project-tree__topic:not(.project-tree__topic--active)");
41 const names = await rows.locator(".project-tree__topic-label").allTextContents();
42 assert(names.length >= 2, "fixture needs two background sessions");
43 const row = name => page.locator(".project-tree__topic").filter({ has: page.locator(".project-tree__topic-label", { hasText: name }) }).first();
44 const aiItem = () => page.getByRole("menuitem", { name: /AI.*(rename|Rename)|AI.*命名/ });
45 await row(names[0]).click({ button: "right" });
46 await aiItem().click();
47 await page.waitForFunction(() => window.__titleFixture.requests.length === 1);
48 await row(names[0]).click({ button: "right" });
49 assert(await page.getByRole("menuitem", { name: /renaming|命名中/i }).isDisabled(), "target alone has loading state");
50 await page.keyboard.press("Escape");
51 await row(names[1]).click({ button: "right" });
52 assert(await aiItem().isEnabled(), "another session remains independently available");
53 await aiItem().click();
54 await page.waitForFunction(() => window.__titleFixture.requests.length === 2);
55 assert(await page.evaluate(() => window.__titleFixture.requests[0].target !== window.__titleFixture.requests[1].target), "requests retain different identities");
56 await page.evaluate(() => window.__titleFixture.requests[1].resolve("Second completed"));
57 await page.getByText(/Second completed/, { exact: false }).last().waitFor();
58 await page.evaluate(() => window.__titleFixture.requests[0].reject(new Error("controller lease /private/fixture test-secret")));
59 await page.getByText(/Unable to complete this session operation|无法完成该会话操作|無法完成此會話操作/).waitFor();
60 assert(!await page.locator("body").textContent().then(text => text.includes("test-secret")), "raw host errors never reach the toast");
61 assert.equal(await composer.inputValue(), "keep my draft");
62 const unchanged = await page.evaluate(() => {
63 const f = window.__titleFixture;
64 const nodes = [...document.querySelectorAll("[data-chat-anchor-key]")];
65 return f.composer === document.querySelector("textarea.composer__input:not([aria-hidden=true])") && nodes.length === f.nodes.length && nodes.every((node, i) => node === f.nodes[i]);
66 });
67 assert(unchanged, "background rename preserves transcript hosts and composer");
68 assert.deepEqual(errors, []);
69 console.log("PASS sidebar per-target loading, out-of-order completion, sanitized errors, stable transcript and draft");
70 } finally {
71 await browser?.close();
72 await server.close();
73 }
74
74 lines Plain Text