返回 DeepSeek-Reasonix
turn-outline.mjs
根目录 / desktop / frontend / bench / turn-outline.mjs
1 import assert from "node:assert/strict";
2 import { mkdir, mkdtemp, copyFile, rm, writeFile } from "node:fs/promises";
3 import { createRequire } from "node:module";
4 import { tmpdir } from "node:os";
5 import path from "node:path";
6 import { fileURLToPath } from "node:url";
7 import { createServer } from "vite";
8 import { chromium, _electron } from "playwright";
9
10 const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
11 const electron = process.argv.includes("--electron");
12 const evidence = process.env.REASONIX_OUTLINE_EVIDENCE ?? path.join(tmpdir(), "reasonix-turn-outline-evidence");
13 await mkdir(evidence, { recursive: true });
14 const host = await mkdtemp(path.join(tmpdir(), "reasonix-outline-host-"));
15 const server = await createServer({ root, server: { host: "127.0.0.1", port: 0 }, clearScreen: false }); await server.listen();
16 const url = new URL("bench/turn-outline.html", server.resolvedUrls.local[0]).href;
17 let browser, app;
18 const report = { host: electron ? "electron" : "chromium", complete: false, errors: [] };
19 try {
20 let page;
21 if (electron) {
22 await copyFile(path.join(root, "bench/transcript-layout-electron.cjs"), path.join(host, "main.cjs"));
23 app = await _electron.launch({ executablePath: createRequire(path.join(root, "../electron/package.json"))("electron"), args: [path.join(host, "main.cjs")], env: { ...process.env, REASONIX_LAYOUT_URL: url } });
24 page = await app.firstWindow();
25 } else { browser = await chromium.launch({ headless: true }); page = await browser.newPage({ viewport: { width: 1280, height: 900 } }); await page.goto(url); }
26 page.on("pageerror", error => report.errors.push(error.message));
27 await page.waitForFunction(() => Boolean(window.turnOutlineFixture));
28 const rail = page.locator(".dsh-TurnNavigator-frame"); await rail.waitFor();
29 const inspect = () => page.evaluate(() => window.turnOutlineFixture.inspect());
30 const go = async key => { await rail.focus(); await rail.press(key); await rail.press("Enter"); };
31 await go("Home"); await page.locator('[data-chat-anchor-key="m:u1"]').waitFor();
32 const scroller = page.locator(".dsh-TurnNavigator-scroller");
33 await scroller.evaluate(el => el.scrollTo({ top: 10000 * 10 }));
34 await page.locator('[data-nav-turn="m:u10001"]').waitFor();
35 await page.locator('[data-nav-turn="m:u10001"]').focus(); await page.keyboard.press("Enter");
36 await page.locator('[data-chat-anchor-key="m:u10001"]').waitFor();
37 await go("End"); await page.locator('[data-chat-anchor-key="m:u25000"]').waitFor();
38 assert.equal((await inspect()).reads.length, 3);
39 assert.ok(await page.locator("[data-nav-turn]").count() <= 52);
40 assert.ok((await inspect()).cacheEntries <= 768);
41 assert.ok((await inspect()).stateItems <= 96);
42 await rail.focus(); await rail.press("Home");
43 await page.locator('[data-nav-turn="m:u1"]').waitFor();
44 // Pointer input belongs to the rail column; mark buttons are keyboard-only.
45 await rail.hover({ position: { x: 14, y: 6 } });
46 await page.getByRole("tooltip").filter({ hasText: "Question 1" }).waitFor();
47 await page.setViewportSize({ width: 700, height: 800 });
48 for (const theme of ["light", "dark"]) {
49 await page.evaluate(theme => { document.documentElement.dataset.theme = theme; }, theme);
50 assert.ok(await page.locator("[data-nav-turn]").count() <= 52);
51 await page.screenshot({ path: path.join(evidence, `${report.host}-${theme}-narrow.png`) });
52 }
53 await page.screenshot({ path: path.join(evidence, `${report.host}-outline.png`) });
54 // Native browser wheel input must cancel a delayed target's visible commit.
55 await page.evaluate(() => window.turnOutlineFixture.delay()); await go("Home");
56 await page.waitForFunction(() => window.turnOutlineFixture.inspect().reads.length === 4);
57 await page.locator(".chat-flow-scroll").evaluate(el => { window.outlineWheelReceived = false; el.addEventListener("wheel", () => { window.outlineWheelReceived = true; }, { once: true }); });
58 await page.locator(".chat-flow-scroll").hover(); await page.mouse.wheel(0, -80);
59 await page.waitForFunction(() => window.outlineWheelReceived === true);
60 await page.evaluate(() => window.turnOutlineFixture.resume());
61 await page.waitForFunction(() => !document.querySelector('[aria-busy="true"][data-nav-turn]'));
62 assert.deepEqual((await inspect()).users, ["u25000"]);
63 await page.evaluate(() => window.turnOutlineFixture.delay()); await go("Home");
64 await page.waitForFunction(() => window.turnOutlineFixture.inspect().reads.length === 5);
65 await page.evaluate(() => window.turnOutlineFixture.switchSession());
66 await page.evaluate(() => window.turnOutlineFixture.resume());
67 await page.locator('[data-chat-anchor-key="m:u25000"]').waitFor();
68 assert.deepEqual((await inspect()).users, ["u25000"]);
69 await go("Home");
70 await page.locator('[data-chat-anchor-key="m:u1"]').waitFor();
71 const beforeLatest = await inspect();
72 await page.evaluate(() => window.turnOutlineFixture.delay());
73 await page.locator(".chat-to-bottom").click();
74 await page.waitForFunction(count => window.turnOutlineFixture.inspect().reads.length === count + 1, beforeLatest.reads.length);
75 // A later click on a mounted turn must cancel the pending return-to-latest.
76 await go("Home");
77 await page.evaluate(() => window.turnOutlineFixture.resume());
78 await page.waitForFunction(count => window.turnOutlineFixture.inspect().completedReads === count + 1, beforeLatest.completedReads);
79 assert.deepEqual((await inspect()).users, beforeLatest.users);
80 assert.equal(await page.locator(".chat-flow-scroll").getAttribute("data-scroll-mode"), "reader");
81 await page.locator(".chat-to-bottom").click();
82 await page.locator('[data-chat-anchor-key="m:u25000"]').waitFor();
83 await page.waitForFunction(() => document.querySelector(".chat-flow-scroll")?.getAttribute("data-scroll-mode") === "tail");
84 report.result = await inspect(); report.complete = true; assert.deepEqual(report.errors, []);
85 } finally {
86 await writeFile(path.join(evidence, `${report.host}.json`), JSON.stringify(report, null, 2));
87 await app?.close(); await browser?.close(); await server.close(); await rm(host, { recursive: true, force: true });
88 }
89 console.log(JSON.stringify(report));
90
90 lines Plain Text