返回 DeepSeek-Reasonix
transcript-scroll-diagnostics.test.ts
根目录 / desktop / frontend / src / __tests__ / transcript-scroll-diagnostics.test.ts
1 // Run: node --import tsx src/__tests__/transcript-scroll-diagnostics.test.ts
2
3 import assert from "node:assert/strict";
4 import {
5 createTranscriptScrollDiagnostics,
6 isTranscriptScrollDiagnosticsBuild,
7 } from "../lib/transcriptScrollDiagnostics";
8
9 let now = 1_000;
10 const diagnostics = createTranscriptScrollDiagnostics({
11 maxEvents: 4,
12 now: () => now,
13 randomID: () => "0123456789abcdef0123456789abcdef",
14 environment: () => ({
15 buildCommit: "test-commit",
16 buildChannel: "test",
17 platform: "windows",
18 userAgent: "Reasonix diagnostic test",
19 devicePixelRatio: 1.25,
20 viewportWidth: 1440,
21 viewportHeight: 900,
22 reducedMotion: false,
23 transcriptWidth: 1180,
24 contentWidth: 960,
25 fontSize: 14,
26 lineHeight: 23.52,
27 processFoldPreference: "auto",
28 reasoningDisplayMode: "summary",
29 }),
30 });
31
32 diagnostics.record("scroll", { scrollTop: 10 });
33 assert.equal(diagnostics.getSnapshot().eventCount, 0, "idle recorder ignores events");
34
35 diagnostics.start();
36 now += 10;
37 diagnostics.record("wheel", { deltaY: -120 });
38 now += 10;
39 diagnostics.record("scroll", {
40 scrollTop: 120,
41 scrollHeight: 2_400,
42 clientHeight: 800,
43 bottomDistance: 1_480,
44 transcriptText: "PRIVATE_TRANSCRIPT_CANARY",
45 rowKey: "raw-session-row-key",
46 path: "C:\\Users\\private-user\\secret",
47 token: "TOKEN_CANARY",
48 } as never);
49 now += 10;
50 diagnostics.mark();
51 now += 10;
52 diagnostics.record("items-rendered", { mountedRows: 12, totalRows: 100, firstVisibleIndex: 44 });
53 now += 10;
54 diagnostics.record("scroll-write", { owner: "jump", writeKind: "scrollToIndex", targetIndex: 45 });
55
56 const active = diagnostics.getSnapshot();
57 assert.equal(active.status, "recording");
58 assert.equal(active.eventCount, 4, "ring buffer remains bounded");
59 assert.equal(active.droppedEventCount, 2, "ring buffer reports dropped events");
60 assert.equal(active.markerCount, 1, "issue marker is retained in summary");
61
62 const payload = diagnostics.stop();
63 assert.equal(payload.schemaVersion, 2);
64 assert.equal(payload.summary.eventCount, 4);
65 assert.equal(payload.summary.droppedEventCount, 3);
66 assert.equal(payload.summary.markerCount, 1);
67 assert.equal(payload.manifest.platform, "windows");
68 assert.equal(payload.events[payload.events.length - 1]?.type, "stop");
69
70 const serialized = JSON.stringify(payload);
71 for (const forbidden of [
72 "PRIVATE_TRANSCRIPT_CANARY",
73 "raw-session-row-key",
74 "private-user",
75 "TOKEN_CANARY",
76 "transcriptText",
77 "rowKey",
78 "path",
79 "token",
80 ]) {
81 assert.equal(serialized.includes(forbidden), false, `payload excludes ${forbidden}`);
82 }
83
84 const frozenCount = diagnostics.getSnapshot().eventCount;
85 now += 10;
86 diagnostics.record("scroll", { scrollTop: 999 });
87 assert.equal(diagnostics.getSnapshot().eventCount, frozenCount, "stopped recorder remains frozen");
88
89 diagnostics.start();
90 assert.equal(diagnostics.getSnapshot().eventCount, 1, "new recording clears the old trace");
91 diagnostics.reset();
92 assert.equal(diagnostics.getSnapshot().status, "idle");
93 assert.equal(diagnostics.getSnapshot().eventCount, 0);
94
95 const markerOverflow = createTranscriptScrollDiagnostics({
96 maxEvents: 4,
97 now: () => now,
98 randomID: () => "fedcba9876543210fedcba9876543210",
99 environment: () => ({
100 buildCommit: "test-commit",
101 buildChannel: "test",
102 platform: "windows",
103 userAgent: "Reasonix diagnostic test",
104 devicePixelRatio: 1,
105 viewportWidth: 1280,
106 viewportHeight: 720,
107 reducedMotion: false,
108 transcriptWidth: 1040,
109 contentWidth: 960,
110 fontSize: 14,
111 lineHeight: 23.52,
112 processFoldPreference: "expanded",
113 reasoningDisplayMode: "expanded",
114 }),
115 });
116 markerOverflow.start();
117 markerOverflow.mark();
118 for (let i = 0; i < 4; i += 1) markerOverflow.record("scroll", { scrollTop: i });
119 const overflowPayload = markerOverflow.stop();
120 assert.equal(overflowPayload.summary.markerCount, 0, "summary only counts markers retained in the bounded trace");
121 assert.equal(overflowPayload.events.some((event) => event.type === "mark"), false, "evicted markers do not survive serialization");
122
123 const detailDiagnostics = createTranscriptScrollDiagnostics({
124 now: () => now,
125 randomID: () => "abcdef0123456789abcdef0123456789",
126 environment: () => ({
127 buildCommit: "detail-test",
128 buildChannel: "test",
129 platform: "windows",
130 userAgent: "Reasonix detail diagnostic test",
131 devicePixelRatio: 1.38,
132 viewportWidth: 1920,
133 viewportHeight: 1080,
134 reducedMotion: true,
135 transcriptWidth: 1600,
136 contentWidth: 960,
137 fontSize: 15,
138 lineHeight: 25.2,
139 processFoldPreference: "auto",
140 reasoningDisplayMode: "legacy-collapsed",
141 }),
142 });
143 detailDiagnostics.start();
144 detailDiagnostics.record("row-measure", {
145 rowIndex: 44,
146 rowKind: "reasoning",
147 layoutVariant: "reasoning-summary",
148 estimateSource: "static",
149 estimatedSize: 64,
150 previousSize: 64,
151 measuredSize: 64,
152 sizeDelta: 0,
153 contentRevision: 3,
154 foldState: "closed",
155 disclosureCount: 1,
156 rowKey: "PRIVATE_ROW_KEY",
157 transcriptText: "PRIVATE_TRANSCRIPT_CANARY",
158 });
159 detailDiagnostics.record("geometry-contract-violation", {
160 rowIndex: 45,
161 rowKind: "tool",
162 layoutVariant: "tool-collapsed",
163 estimateSource: "static",
164 estimatedSize: 37,
165 measuredSize: 55,
166 sizeDelta: 18,
167 relativeError: 18 / 37,
168 rowKey: "PRIVATE_ROW_KEY_2",
169 });
170 detailDiagnostics.record("scroll-state", {
171 source: "jump-bottom",
172 previousMode: "manual",
173 mode: "tail-follow",
174 atBottom: true,
175 scrollable: true,
176 readerIntent: false,
177 canClaimTail: false,
178 tailCommand: true,
179 scrollTop: 4_000,
180 scrollHeight: 5_000,
181 clientHeight: 800,
182 bottomDistance: 200,
183 });
184 detailDiagnostics.record("scroll-write", {
185 owner: "tail-follow",
186 writeKind: "scrollTo",
187 source: "jump-bottom",
188 phase: "settle",
189 targetTop: 5_000,
190 settleFrame: 2,
191 offBottomFrames: 2,
192 stagnantFrames: 0,
193 });
194 detailDiagnostics.record("geometry-revision", {
195 geometryRevision: 12,
196 sources: ["row-measure", "items-rendered", "private-source"],
197 scrollHeight: 5_400,
198 footerHeight: 220,
199 viewport: 800,
200 mounted: 40,
201 total: 420,
202 transient: true,
203 });
204 detailDiagnostics.record("scroll-write", {
205 owner: "anchor-compensation",
206 writeKind: "scrollBy",
207 source: "reader-stability",
208 phase: "correct-offset",
209 sequence: 13,
210 generation: 4,
211 ownershipEpoch: 8,
212 geometryRevision: 12,
213 transactionId: 3,
214 rejectedReason: "duplicate-revision-phase",
215 });
216 const detailPayload = detailDiagnostics.stop();
217 const rowMeasurement = detailPayload.events.find((event) => event.type === "row-measure");
218 assert.deepEqual(rowMeasurement, {
219 t: 0,
220 type: "row-measure",
221 rowIndex: 44,
222 estimatedSize: 64,
223 previousSize: 64,
224 measuredSize: 64,
225 sizeDelta: 0,
226 contentRevision: 3,
227 disclosureCount: 1,
228 rowKind: "reasoning",
229 layoutVariant: "reasoning-summary",
230 estimateSource: "static",
231 foldState: "closed",
232 });
233 const geometryViolation = detailPayload.events.find((event) => event.type === "geometry-contract-violation");
234 assert.deepEqual(geometryViolation, {
235 t: 0,
236 type: "geometry-contract-violation",
237 rowIndex: 45,
238 estimatedSize: 37,
239 measuredSize: 55,
240 sizeDelta: 18,
241 relativeError: 0.49,
242 rowKind: "tool",
243 layoutVariant: "tool-collapsed",
244 estimateSource: "static",
245 });
246 const geometryRevision = detailPayload.events.find((event) => event.type === "geometry-revision");
247 assert.deepEqual(geometryRevision?.sources, ["row-measure", "items-rendered"], "geometry sources keep only fixed, content-free enums");
248 const anchorWrite = detailPayload.events.find((event) => event.owner === "anchor-compensation");
249 assert.equal(anchorWrite?.owner, "anchor-compensation", "known anchor writers do not degrade to other");
250 assert.equal(anchorWrite?.ownershipEpoch, 8, "writer ownership epochs survive sanitization");
251 assert.equal(anchorWrite?.rejectedReason, "duplicate-revision-phase", "writer rejection reasons survive sanitization");
252 const detailSerialized = JSON.stringify(detailPayload);
253 assert.equal(detailSerialized.includes("PRIVATE_ROW_KEY"), false, "row measurements exclude stable row keys");
254 assert.equal(detailSerialized.includes("PRIVATE_TRANSCRIPT_CANARY"), false, "row measurements exclude transcript text");
255 assert.equal(detailSerialized.includes("PRIVATE_ROW_KEY_2"), false, "geometry violations exclude stable row keys");
256
257 assert.equal(isTranscriptScrollDiagnosticsBuild("test", false), true, "test builds expose diagnostics");
258 assert.equal(isTranscriptScrollDiagnosticsBuild("preview", false), true, "preview builds expose diagnostics");
259 assert.equal(isTranscriptScrollDiagnosticsBuild("canary", false), true, "canary builds expose diagnostics");
260 assert.equal(isTranscriptScrollDiagnosticsBuild("stable", true), true, "development server exposes diagnostics");
261 assert.equal(isTranscriptScrollDiagnosticsBuild("stable", false), false, "stable production hides diagnostics");
262
263 console.log("transcript scroll diagnostics tests passed");
264
264 lines TYPESCRIPT