Skip to content

Commit cd85e4c

Browse files
committed
test(ui): stabilise graph focus assertions
1 parent 531eb93 commit cd85e4c

1 file changed

Lines changed: 24 additions & 18 deletions

File tree

‎app/src/components/KnowledgeGraphView.test.tsx‎

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ vi.mock("@xyflow/react", async () => {
3333

3434
import KnowledgeGraphView from "./KnowledgeGraphView";
3535

36+
/** 等待上限 = 唯一真源:本地 18 处曾各自写死 5000,与 `vitest.config.ts` 的 `testTimeout: 15000`
37+
* 不一致 ⇒ 全量并发(jsdom + 多棵树的解包/普查)下 `waitFor` 先超时 ⇒ **既有负载敏感 flake**
38+
* (批 3 `a7bd1899`、批 4 收口评审 I-4 各复现过一次:9 次抽样 2 次红,全落本文件「局部聚焦」)。
39+
* 这里只统一**等待上限**,断言与用例语义一字未改(不放宽断言、不加睡眠掩盖)。 */
40+
const WAIT = { timeout: 15000 } as const;
41+
3642
/** 全场景快照 fixture:5 节点 + 4 边(2 link + 1 trace + 1 belong) */
3743
const snapshot: GraphSnapshot = {
3844
nodes: [
@@ -75,7 +81,7 @@ describe("KnowledgeGraphView 加载与图层", () => {
7581
await waitFor(() => {
7682
const edges = rfProps[rfProps.length - 1].edges as unknown[];
7783
expect(edges).toHaveLength(2);
78-
}, { timeout: 5000 });
84+
}, WAIT);
7985
const edges = rfProps[rfProps.length - 1].edges as unknown[];
8086
expect(edges.every((e) => (e as { id: string }).id.startsWith("link:"))).toBe(true);
8187
// 节点全集渲染(5 个)
@@ -86,38 +92,38 @@ describe("KnowledgeGraphView 加载与图层", () => {
8692

8793
it("开溯源层 → trace 边进入画布", async () => {
8894
renderGraph();
89-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), { timeout: 5000 });
95+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), WAIT);
9096
fireEvent.click(screen.getByTestId("graph-layer-trace"));
9197
await waitFor(() => {
9298
const edges = rfProps[rfProps.length - 1].edges as unknown[];
9399
expect(edges).toHaveLength(3);
94-
}, { timeout: 5000 });
100+
}, WAIT);
95101
// 再开归属层 → 4 边全量
96102
fireEvent.click(screen.getByTestId("graph-layer-belong"));
97103
await waitFor(() => {
98104
const edges = rfProps[rfProps.length - 1].edges as unknown[];
99105
expect(edges).toHaveLength(4);
100-
}, { timeout: 5000 });
106+
}, WAIT);
101107
});
102108

103109
it("关引用层 → link 边消失", async () => {
104110
renderGraph();
105-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), { timeout: 5000 });
111+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), WAIT);
106112
fireEvent.click(screen.getByTestId("graph-layer-link"));
107113
await waitFor(() => {
108114
const edges = rfProps[rfProps.length - 1].edges as unknown[];
109115
expect(edges).toHaveLength(0);
110-
}, { timeout: 5000 });
116+
}, WAIT);
111117
});
112118
});
113119

114120
describe("KnowledgeGraphView 局部聚焦", () => {
115121
it("单击节点 → 1~2 度邻居展开、范围外淡出", async () => {
116122
renderGraph();
117-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), { timeout: 5000 });
123+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), WAIT);
118124
const onNodeClick = rfProps[0].onNodeClick as (e: unknown, node: { id: string }) => void;
119125
onNodeClick(null, { id: "note:1" });
120-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(1), { timeout: 5000 });
126+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(1), WAIT);
121127
const nodes = rfProps[rfProps.length - 1].nodes as { id: string; style?: { opacity?: number } }[];
122128
const dimmed = nodes.filter((n) => n.style?.opacity === 0.15).map((n) => n.id);
123129
// note:1 的邻居:concept:1(link)、note:2(trace——图层默认关)、group:1(belong 关)
@@ -129,26 +135,26 @@ describe("KnowledgeGraphView 局部聚焦", () => {
129135

130136
it("点击空白取消聚焦(全部恢复实显)", async () => {
131137
renderGraph();
132-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), { timeout: 5000 });
138+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), WAIT);
133139
const onNodeClick = rfProps[0].onNodeClick as (e: unknown, node: { id: string }) => void;
134140
onNodeClick(null, { id: "note:1" });
135-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(1), { timeout: 5000 });
141+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(1), WAIT);
136142
const onPaneClick = rfProps[rfProps.length - 1].onPaneClick as () => void;
137143
onPaneClick();
138-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(2), { timeout: 5000 });
144+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(2), WAIT);
139145
const nodes = rfProps[rfProps.length - 1].nodes as { style?: { opacity?: number } }[];
140146
expect(nodes.every((n) => !n.style || n.style.opacity !== 0.15)).toBe(true);
141147
});
142148

143149
it("再点同一节点取消聚焦(toggle)", async () => {
144150
renderGraph();
145-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), { timeout: 5000 });
151+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), WAIT);
146152
const onNodeClick = rfProps[0].onNodeClick as (e: unknown, node: { id: string }) => void;
147153
onNodeClick(null, { id: "note:1" });
148-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(1), { timeout: 5000 });
154+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(1), WAIT);
149155
const handler2 = rfProps[rfProps.length - 1].onNodeClick as (e: unknown, node: { id: string }) => void;
150156
handler2(null, { id: "note:1" });
151-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(2), { timeout: 5000 });
157+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(2), WAIT);
152158
const nodes = rfProps[rfProps.length - 1].nodes as { style?: { opacity?: number } }[];
153159
expect(nodes.every((n) => !n.style || n.style.opacity !== 0.15)).toBe(true);
154160
});
@@ -158,7 +164,7 @@ describe("KnowledgeGraphView 双击跳转", () => {
158164
it("双击笔记节点 → onOpenNote", async () => {
159165
const onOpenNote = vi.fn();
160166
renderGraph({ onOpenNote });
161-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), { timeout: 5000 });
167+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), WAIT);
162168
const onDouble = rfProps[0].onNodeDoubleClick as (
163169
e: unknown, node: { data: { kind: string; entityId: number; systemId: number | null } },
164170
) => void;
@@ -169,7 +175,7 @@ describe("KnowledgeGraphView 双击跳转", () => {
169175
it("双击组节点 → onOpenGroup", async () => {
170176
const onOpenGroup = vi.fn();
171177
renderGraph({ onOpenGroup });
172-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), { timeout: 5000 });
178+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), WAIT);
173179
const onDouble = rfProps[0].onNodeDoubleClick as (
174180
e: unknown, node: { data: { kind: string; entityId: number; systemId: number | null } },
175181
) => void;
@@ -180,7 +186,7 @@ describe("KnowledgeGraphView 双击跳转", () => {
180186
it("双击概念/模型节点 → onOpenSystem(体系归属)", async () => {
181187
const onOpenSystem = vi.fn();
182188
renderGraph({ onOpenSystem });
183-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), { timeout: 5000 });
189+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), WAIT);
184190
const onDouble = rfProps[0].onNodeDoubleClick as (
185191
e: unknown, node: { data: { kind: string; entityId: number; systemId: number | null } },
186192
) => void;
@@ -204,7 +210,7 @@ describe("KnowledgeGraphView 空态与错误", () => {
204210
expect(await screen.findByTestId("graph-error")).toBeTruthy();
205211
// 重试成功 → 错误消失、画布出现
206212
fireEvent.click(screen.getByTestId("graph-retry"));
207-
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), { timeout: 5000 });
213+
await waitFor(() => expect(rfProps.length).toBeGreaterThan(0), WAIT);
208214
expect(screen.queryByTestId("graph-error")).toBeNull();
209215
});
210216
});

0 commit comments

Comments
 (0)