@@ -674,4 +674,142 @@ describe("security utilities", () => {
674674 expect ( result ) . not . toContain ( "javascript:" ) ;
675675 } ) ;
676676 } ) ;
677+
678+ describe ( "nodeLabelLayout" , ( ) => {
679+ it ( "uses stacked layout by default (value above name)" , ( ) => {
680+ const mockChart = createMockChart ( ) ;
681+ const mockEcharts = createMockEcharts ( mockChart ) ;
682+
683+ render (
684+ < SankeyChart
685+ echarts = { mockEcharts as any }
686+ nodes = { [ { name : "Node A" , value : 100 } ] }
687+ links = { [ ] }
688+ /> ,
689+ ) ;
690+
691+ const options = mockChart . setOption . mock . calls [ 0 ] [ 0 ] ;
692+ const formatter = options . series [ 0 ] . label . formatter ;
693+
694+ const result = formatter ( { name : "Node A" } ) ;
695+ // Stacked layout: value\nname (newline between)
696+ expect ( result ) . toContain ( "\n" ) ;
697+ expect ( result ) . toMatch ( / \{ v a l u e \| .* \} \n \{ n a m e \| .* \} / ) ;
698+ } ) ;
699+
700+ it ( "uses inline layout when nodeLabelLayout is 'inline'" , ( ) => {
701+ const mockChart = createMockChart ( ) ;
702+ const mockEcharts = createMockEcharts ( mockChart ) ;
703+
704+ render (
705+ < SankeyChart
706+ echarts = { mockEcharts as any }
707+ nodes = { [ { name : "Node A" , value : 100 } ] }
708+ links = { [ ] }
709+ nodeLabelLayout = "inline"
710+ /> ,
711+ ) ;
712+
713+ const options = mockChart . setOption . mock . calls [ 0 ] [ 0 ] ;
714+ const formatter = options . series [ 0 ] . label . formatter ;
715+
716+ const result = formatter ( { name : "Node A" } ) ;
717+ // Inline layout: name value (space between, no newline)
718+ expect ( result ) . not . toContain ( "\n" ) ;
719+ expect ( result ) . toMatch ( / \{ n a m e \| .* \} \{ v a l u e \| .* \} / ) ;
720+ } ) ;
721+
722+ it ( "sets lineHeight for stacked layout" , ( ) => {
723+ const mockChart = createMockChart ( ) ;
724+ const mockEcharts = createMockEcharts ( mockChart ) ;
725+
726+ render (
727+ < SankeyChart
728+ echarts = { mockEcharts as any }
729+ nodes = { [ { name : "Node A" , value : 100 } ] }
730+ links = { [ ] }
731+ nodeLabelLayout = "stacked"
732+ /> ,
733+ ) ;
734+
735+ const options = mockChart . setOption . mock . calls [ 0 ] [ 0 ] ;
736+ expect ( options . series [ 0 ] . label . rich . value . lineHeight ) . toBe ( 16 ) ;
737+ } ) ;
738+
739+ it ( "does not set lineHeight for inline layout" , ( ) => {
740+ const mockChart = createMockChart ( ) ;
741+ const mockEcharts = createMockEcharts ( mockChart ) ;
742+
743+ render (
744+ < SankeyChart
745+ echarts = { mockEcharts as any }
746+ nodes = { [ { name : "Node A" , value : 100 } ] }
747+ links = { [ ] }
748+ nodeLabelLayout = "inline"
749+ /> ,
750+ ) ;
751+
752+ const options = mockChart . setOption . mock . calls [ 0 ] [ 0 ] ;
753+ expect ( options . series [ 0 ] . label . rich . value . lineHeight ) . toBeUndefined ( ) ;
754+ } ) ;
755+ } ) ;
756+
757+ describe ( "auto-assigned node colors" , ( ) => {
758+ it ( "uses auto-assigned colors in link tooltips when nodes have no explicit color" , ( ) => {
759+ const mockChart = createMockChart ( ) ;
760+ const mockEcharts = createMockEcharts ( mockChart ) ;
761+
762+ render (
763+ < SankeyChart
764+ echarts = { mockEcharts as any }
765+ nodes = { [
766+ { name : "Source" } , // No explicit color
767+ { name : "Target" } , // No explicit color
768+ ] }
769+ links = { [ { source : 0 , target : 1 , value : 50 } ] }
770+ /> ,
771+ ) ;
772+
773+ const options = mockChart . setOption . mock . calls [ 0 ] [ 0 ] ;
774+ const formatter = options . tooltip . formatter ;
775+
776+ // Simulate link tooltip
777+ const result = formatter ( {
778+ dataType : "edge" ,
779+ data : { source : "Source" , target : "Target" , value : 50 } ,
780+ } ) ;
781+
782+ // Should NOT contain the fallback gray color #666
783+ // Should contain auto-assigned categorical colors
784+ expect ( result ) . not . toContain ( "#666" ) ;
785+ } ) ;
786+
787+ it ( "assigns consistent colors to nodes without explicit colors" , ( ) => {
788+ const mockChart = createMockChart ( ) ;
789+ const mockEcharts = createMockEcharts ( mockChart ) ;
790+
791+ render (
792+ < SankeyChart
793+ echarts = { mockEcharts as any }
794+ nodes = { [
795+ { name : "Node A" } , // No color - gets categorical(0)
796+ { name : "Node B" } , // No color - gets categorical(1)
797+ { name : "Node C" , color : "#custom" } , // Explicit color
798+ ] }
799+ links = { [ ] }
800+ /> ,
801+ ) ;
802+
803+ const options = mockChart . setOption . mock . calls [ 0 ] [ 0 ] ;
804+ const nodeData = options . series [ 0 ] . data ;
805+
806+ // First two nodes should have auto-assigned colors (not undefined)
807+ expect ( nodeData [ 0 ] . itemStyle . color ) . toBeDefined ( ) ;
808+ expect ( nodeData [ 1 ] . itemStyle . color ) . toBeDefined ( ) ;
809+ // Third node should have explicit color
810+ expect ( nodeData [ 2 ] . itemStyle . color ) . toBe ( "#custom" ) ;
811+ // Auto-assigned colors should be different
812+ expect ( nodeData [ 0 ] . itemStyle . color ) . not . toBe ( nodeData [ 1 ] . itemStyle . color ) ;
813+ } ) ;
814+ } ) ;
677815} ) ;
0 commit comments