@@ -140,4 +140,174 @@ describe('GuiIcon Tests', () => {
140140
141141 expect ( render ) . not . toThrow ( ) ;
142142 } ) ;
143+
144+ // https://github.com/antvis/S2/issues/3125
145+ describe ( 'CSP-compatible Path mode rendering' , ( ) => {
146+ test ( 'should render built-in SVG icons using Path mode' , ( ) => {
147+ const errSpy = jest . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { } ) ;
148+
149+ const icon = new GuiIcon ( {
150+ name : 'Plus' ,
151+ x : 10 ,
152+ y : 10 ,
153+ width : 16 ,
154+ height : 16 ,
155+ } ) ;
156+
157+ // Path 模式下应该有 iconPathShapes
158+ expect ( icon . iconPathShapes . length ) . toBeGreaterThan ( 0 ) ;
159+ // Path 模式下不应该使用 Image
160+ expect ( icon . iconImageShape ) . toBeUndefined ( ) ;
161+ expect ( icon ) . toBeInstanceOf ( Group ) ;
162+ expect ( errSpy ) . not . toHaveBeenCalled ( ) ;
163+
164+ errSpy . mockRestore ( ) ;
165+ } ) ;
166+
167+ test ( 'should render Minus icon using Path mode' , ( ) => {
168+ const errSpy = jest . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { } ) ;
169+
170+ const icon = new GuiIcon ( {
171+ name : 'Minus' ,
172+ x : 0 ,
173+ y : 0 ,
174+ width : 16 ,
175+ height : 16 ,
176+ } ) ;
177+
178+ expect ( icon . iconPathShapes . length ) . toBeGreaterThan ( 0 ) ;
179+ expect ( icon . iconImageShape ) . toBeUndefined ( ) ;
180+ expect ( errSpy ) . not . toHaveBeenCalled ( ) ;
181+
182+ errSpy . mockRestore ( ) ;
183+ } ) ;
184+
185+ test ( 'should apply cursor style to Path mode icons' , ( ) => {
186+ const icon = new GuiIcon ( {
187+ name : 'Plus' ,
188+ x : 0 ,
189+ y : 0 ,
190+ width : 16 ,
191+ height : 16 ,
192+ cursor : 'pointer' ,
193+ } ) ;
194+
195+ expect ( icon . iconPathShapes . length ) . toBeGreaterThan ( 0 ) ;
196+
197+ // 第一个 shape 是 hitArea Rect,应该有 cursor 样式
198+ const hitAreaRect = icon . iconPathShapes [ 0 ] ;
199+
200+ expect ( hitAreaRect . style . cursor ) . toBe ( 'pointer' ) ;
201+ } ) ;
202+
203+ test ( 'should render tree icons in tree mode table without errors' , async ( ) => {
204+ const errSpy = jest . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { } ) ;
205+
206+ const s2 = createPivotSheet ( {
207+ width : 400 ,
208+ height : 300 ,
209+ hierarchyType : 'tree' ,
210+ } ) ;
211+
212+ await s2 . render ( ) ;
213+
214+ // 验证没有 CSP 或 SVG 解析错误
215+ expect ( errSpy ) . not . toHaveBeenCalled ( ) ;
216+
217+ // 验证 row cells 存在 (tree mode 应该有展开折叠图标)
218+ const rowCells = s2 . facet . getRowCells ( ) ;
219+
220+ expect ( rowCells . length ) . toBeGreaterThan ( 0 ) ;
221+
222+ s2 . destroy ( ) ;
223+ errSpy . mockRestore ( ) ;
224+ } ) ;
225+
226+ test ( 'should update fill color in Path mode' , ( ) => {
227+ const icon = new GuiIcon ( {
228+ name : 'Plus' ,
229+ x : 0 ,
230+ y : 0 ,
231+ width : 16 ,
232+ height : 16 ,
233+ fill : 'red' ,
234+ } ) ;
235+
236+ expect ( icon . iconPathShapes . length ) . toBeGreaterThan ( 0 ) ;
237+
238+ // 更新 fill
239+ icon . setImageAttrs ( { fill : 'blue' } ) ;
240+
241+ // Path shapes 的 fill 应该更新
242+ const pathShape = icon . iconPathShapes [ 1 ] ; // 第一个是 hitArea
243+
244+ expect ( pathShape . style . fill ) . toBe ( 'blue' ) ;
245+ } ) ;
246+
247+ test ( 'should reRender icon with new name in Path mode' , ( ) => {
248+ const icon = new GuiIcon ( {
249+ name : 'Plus' ,
250+ x : 0 ,
251+ y : 0 ,
252+ width : 16 ,
253+ height : 16 ,
254+ } ) ;
255+
256+ expect ( icon . iconPathShapes . length ) . toBeGreaterThan ( 0 ) ;
257+
258+ // reRender with new name
259+ icon . reRender ( {
260+ name : 'Minus' ,
261+ x : 0 ,
262+ y : 0 ,
263+ width : 16 ,
264+ height : 16 ,
265+ } ) ;
266+
267+ expect ( icon . name ) . toBe ( 'Minus' ) ;
268+ expect ( icon . iconPathShapes . length ) . toBeGreaterThan ( 0 ) ;
269+ } ) ;
270+
271+ test ( 'should updatePosition correctly in Path mode' , ( ) => {
272+ const icon = new GuiIcon ( {
273+ name : 'Plus' ,
274+ x : 0 ,
275+ y : 0 ,
276+ width : 16 ,
277+ height : 16 ,
278+ } ) ;
279+
280+ icon . updatePosition ( { x : 50 , y : 100 } ) ;
281+
282+ // 验证 path shapes 的 transform 包含新位置
283+ const pathShape = icon . iconPathShapes [ 1 ] ;
284+
285+ expect ( pathShape . style . transform ) . toContain ( '50' ) ;
286+ expect ( pathShape . style . transform ) . toContain ( '100' ) ;
287+ } ) ;
288+
289+ test ( 'should toggle visibility in Path mode' , ( ) => {
290+ const icon = new GuiIcon ( {
291+ name : 'Plus' ,
292+ x : 0 ,
293+ y : 0 ,
294+ width : 16 ,
295+ height : 16 ,
296+ } ) ;
297+
298+ // 隐藏
299+ icon . toggleVisibility ( false ) ;
300+
301+ icon . iconPathShapes . forEach ( ( shape ) => {
302+ expect ( shape . style . visibility ) . toBe ( 'hidden' ) ;
303+ } ) ;
304+
305+ // 显示
306+ icon . toggleVisibility ( true ) ;
307+
308+ icon . iconPathShapes . forEach ( ( shape ) => {
309+ expect ( shape . style . visibility ) . toBe ( 'visible' ) ;
310+ } ) ;
311+ } ) ;
312+ } ) ;
143313} ) ;
0 commit comments