@@ -33,6 +33,12 @@ vi.mock("@xyflow/react", async () => {
3333
3434import 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) */
3743const 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
114120describe ( "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