@automattic/charts 0.56.7 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +28 -98
- package/CHANGELOG.md +30 -0
- package/dist/charts/bar-chart/index.cjs +7 -6
- package/dist/charts/bar-chart/index.cjs.map +1 -1
- package/dist/charts/bar-chart/index.css +12 -24
- package/dist/charts/bar-chart/index.css.map +1 -1
- package/dist/charts/bar-chart/index.d.cts +3 -4
- package/dist/charts/bar-chart/index.d.ts +3 -4
- package/dist/charts/bar-chart/index.js +6 -5
- package/dist/charts/bar-list-chart/index.cjs +8 -7
- package/dist/charts/bar-list-chart/index.cjs.map +1 -1
- package/dist/charts/bar-list-chart/index.css +12 -24
- package/dist/charts/bar-list-chart/index.css.map +1 -1
- package/dist/charts/bar-list-chart/index.d.cts +3 -3
- package/dist/charts/bar-list-chart/index.d.ts +3 -3
- package/dist/charts/bar-list-chart/index.js +7 -6
- package/dist/charts/conversion-funnel-chart/index.cjs +5 -6
- package/dist/charts/conversion-funnel-chart/index.cjs.map +1 -1
- package/dist/charts/conversion-funnel-chart/index.css +0 -94
- package/dist/charts/conversion-funnel-chart/index.css.map +1 -1
- package/dist/charts/conversion-funnel-chart/index.d.cts +1 -1
- package/dist/charts/conversion-funnel-chart/index.d.ts +1 -1
- package/dist/charts/conversion-funnel-chart/index.js +4 -5
- package/dist/charts/geo-chart/index.cjs +4 -4
- package/dist/charts/geo-chart/index.css +0 -94
- package/dist/charts/geo-chart/index.css.map +1 -1
- package/dist/charts/geo-chart/index.d.cts +1 -1
- package/dist/charts/geo-chart/index.d.ts +1 -1
- package/dist/charts/geo-chart/index.js +3 -3
- package/dist/charts/leaderboard-chart/index.cjs +7 -6
- package/dist/charts/leaderboard-chart/index.cjs.map +1 -1
- package/dist/charts/leaderboard-chart/index.css +20 -33
- package/dist/charts/leaderboard-chart/index.css.map +1 -1
- package/dist/charts/leaderboard-chart/index.d.cts +3 -3
- package/dist/charts/leaderboard-chart/index.d.ts +3 -3
- package/dist/charts/leaderboard-chart/index.js +6 -5
- package/dist/charts/line-chart/index.cjs +7 -6
- package/dist/charts/line-chart/index.cjs.map +1 -1
- package/dist/charts/line-chart/index.css +12 -24
- package/dist/charts/line-chart/index.css.map +1 -1
- package/dist/charts/line-chart/index.d.cts +3 -4
- package/dist/charts/line-chart/index.d.ts +3 -4
- package/dist/charts/line-chart/index.js +6 -5
- package/dist/charts/pie-chart/index.cjs +7 -7
- package/dist/charts/pie-chart/index.css +12 -24
- package/dist/charts/pie-chart/index.css.map +1 -1
- package/dist/charts/pie-chart/index.d.cts +7 -13
- package/dist/charts/pie-chart/index.d.ts +7 -13
- package/dist/charts/pie-chart/index.js +6 -6
- package/dist/charts/pie-semi-circle-chart/index.cjs +7 -7
- package/dist/charts/pie-semi-circle-chart/index.css +12 -24
- package/dist/charts/pie-semi-circle-chart/index.css.map +1 -1
- package/dist/charts/pie-semi-circle-chart/index.d.cts +7 -13
- package/dist/charts/pie-semi-circle-chart/index.d.ts +7 -13
- package/dist/charts/pie-semi-circle-chart/index.js +6 -6
- package/dist/charts/sparkline/index.cjs +8 -7
- package/dist/charts/sparkline/index.cjs.map +1 -1
- package/dist/charts/sparkline/index.css +12 -24
- package/dist/charts/sparkline/index.css.map +1 -1
- package/dist/charts/sparkline/index.js +7 -6
- package/dist/{chunk-RFSHE3HL.js → chunk-2I67QUIV.js} +84 -431
- package/dist/chunk-2I67QUIV.js.map +1 -0
- package/dist/{chunk-OMS5QIJN.js → chunk-2ICEEQOC.js} +31 -25
- package/dist/chunk-2ICEEQOC.js.map +1 -0
- package/dist/{chunk-GWBS65VC.js → chunk-4B7BL2DD.js} +3 -3
- package/dist/{chunk-7FDQGBY7.js → chunk-4OXMTKAL.js} +24 -24
- package/dist/chunk-4OXMTKAL.js.map +1 -0
- package/dist/{chunk-SSFFCBCF.js → chunk-B6NLZFRW.js} +32 -26
- package/dist/chunk-B6NLZFRW.js.map +1 -0
- package/dist/{chunk-3EXJP67N.cjs → chunk-BBAUQOW6.cjs} +9 -9
- package/dist/{chunk-3EXJP67N.cjs.map → chunk-BBAUQOW6.cjs.map} +1 -1
- package/dist/{chunk-NQJE2CC7.cjs → chunk-CMMHCTBX.cjs} +45 -45
- package/dist/chunk-CMMHCTBX.cjs.map +1 -0
- package/dist/{chunk-O2JIANHK.cjs → chunk-CPPXJATQ.cjs} +51 -45
- package/dist/chunk-CPPXJATQ.cjs.map +1 -0
- package/dist/{chunk-MDRCAGKZ.js → chunk-DKU775VC.js} +3 -3
- package/dist/{chunk-BXFD7JIG.cjs → chunk-GRA7Y2ZG.cjs} +46 -46
- package/dist/chunk-GRA7Y2ZG.cjs.map +1 -0
- package/dist/{chunk-TE63Y5PX.js → chunk-JJIMABHT.js} +10 -3
- package/dist/chunk-JJIMABHT.js.map +1 -0
- package/dist/{chunk-KHQPN77E.js → chunk-KJHWXOCZ.js} +4 -4
- package/dist/{chunk-6CCZL2JJ.js → chunk-KRWGSOJ2.js} +30 -2
- package/dist/chunk-KRWGSOJ2.js.map +1 -0
- package/dist/{chunk-VPAEBI2F.js → chunk-LTFH7SEG.js} +24 -24
- package/dist/chunk-LTFH7SEG.js.map +1 -0
- package/dist/{chunk-E62LCBGD.js → chunk-MUNOKLLE.js} +3 -3
- package/dist/{chunk-ZVGEDXDP.cjs → chunk-MXGLYWVP.cjs} +10 -3
- package/dist/chunk-MXGLYWVP.cjs.map +1 -0
- package/dist/{chunk-55ZCOYDF.cjs → chunk-OYC34VTO.cjs} +252 -827
- package/dist/chunk-OYC34VTO.cjs.map +1 -0
- package/dist/{chunk-CAFJRZPZ.cjs → chunk-PQL5I3F6.cjs} +17 -17
- package/dist/{chunk-CAFJRZPZ.cjs.map → chunk-PQL5I3F6.cjs.map} +1 -1
- package/dist/{chunk-UFRBUT2D.cjs → chunk-REZTQ4PH.cjs} +87 -24
- package/dist/chunk-REZTQ4PH.cjs.map +1 -0
- package/dist/{chunk-RCY6XLGU.cjs → chunk-TZRUHQOH.cjs} +36 -8
- package/dist/chunk-TZRUHQOH.cjs.map +1 -0
- package/dist/{chunk-XD2HV7M5.js → chunk-UTYVIOWZ.js} +226 -801
- package/dist/chunk-UTYVIOWZ.js.map +1 -0
- package/dist/{chunk-YAXY5L7I.cjs → chunk-W2LDIX26.cjs} +5 -5
- package/dist/{chunk-YAXY5L7I.cjs.map → chunk-W2LDIX26.cjs.map} +1 -1
- package/dist/{chunk-K6TGILHX.cjs → chunk-WSG64BVN.cjs} +6 -6
- package/dist/{chunk-K6TGILHX.cjs.map → chunk-WSG64BVN.cjs.map} +1 -1
- package/dist/chunk-WTQYGUNF.js +400 -0
- package/dist/chunk-WTQYGUNF.js.map +1 -0
- package/dist/{chunk-YDVHT7GS.cjs → chunk-WYK7EL5R.cjs} +100 -447
- package/dist/chunk-WYK7EL5R.cjs.map +1 -0
- package/dist/{chunk-X7JL2NYJ.cjs → chunk-XC4KHJYX.cjs} +51 -45
- package/dist/chunk-XC4KHJYX.cjs.map +1 -0
- package/dist/chunk-XVBH5XHE.cjs +400 -0
- package/dist/chunk-XVBH5XHE.cjs.map +1 -0
- package/dist/{chunk-IS5YYLTV.js → chunk-YAFQVVDI.js} +85 -22
- package/dist/chunk-YAFQVVDI.js.map +1 -0
- package/dist/components/legend/index.cjs +4 -3
- package/dist/components/legend/index.cjs.map +1 -1
- package/dist/components/legend/index.css +12 -24
- package/dist/components/legend/index.css.map +1 -1
- package/dist/components/legend/index.d.cts +4 -4
- package/dist/components/legend/index.d.ts +4 -4
- package/dist/components/legend/index.js +3 -2
- package/dist/components/tooltip/index.d.cts +1 -1
- package/dist/components/tooltip/index.d.ts +1 -1
- package/dist/hooks/index.cjs +3 -5
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.css +0 -94
- package/dist/hooks/index.css.map +1 -1
- package/dist/hooks/index.d.cts +9 -13
- package/dist/hooks/index.d.ts +9 -13
- package/dist/hooks/index.js +2 -4
- package/dist/index.cjs +18 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +20 -33
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +17 -16
- package/dist/{leaderboard-chart-COtgamhe.d.cts → leaderboard-chart-BSbg0ufV.d.cts} +3 -11
- package/dist/{leaderboard-chart-BSgEw_Um.d.ts → leaderboard-chart-odEYxxEC.d.ts} +3 -11
- package/dist/{legend-C9ahiwOt.d.cts → legend-DFkosEvC.d.cts} +1 -1
- package/dist/{legend-jjMmhSg3.d.ts → legend-DLswHhOk.d.ts} +1 -1
- package/dist/providers/index.cjs +3 -3
- package/dist/providers/index.css +0 -94
- package/dist/providers/index.css.map +1 -1
- package/dist/providers/index.d.cts +3 -3
- package/dist/providers/index.d.ts +3 -3
- package/dist/providers/index.js +2 -2
- package/dist/{themes-CVR5rmIs.d.cts → themes-D0qc5JaW.d.cts} +2 -2
- package/dist/{themes-DQzmaSze.d.ts → themes-itO4Ht5g.d.ts} +2 -2
- package/dist/{types-BBwg4Evw.d.cts → types-B5f6XQ7Q.d.cts} +1 -1
- package/dist/{types-DQNnq5Fr.d.ts → types-BsHooDbM.d.ts} +1 -1
- package/dist/{types-C05PdDJa.d.cts → types-BuSrRM4p.d.ts} +15 -23
- package/dist/{types-CzdN7rUe.d.cts → types-ChOUI9-N.d.cts} +90 -46
- package/dist/{types-CzdN7rUe.d.ts → types-ChOUI9-N.d.ts} +90 -46
- package/dist/{types-C05PdDJa.d.ts → types-Dfw9VOKI.d.cts} +15 -23
- package/dist/utils/index.cjs +2 -2
- package/dist/utils/index.d.cts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/package.json +10 -8
- package/src/charts/bar-chart/bar-chart.tsx +19 -19
- package/src/charts/bar-chart/test/bar-chart.test.tsx +78 -31
- package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +2 -2
- package/src/charts/leaderboard-chart/hooks/use-leaderboard-legend-items.ts +0 -2
- package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +9 -10
- package/src/charts/leaderboard-chart/leaderboard-chart.tsx +124 -102
- package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +61 -33
- package/src/charts/leaderboard-chart/test/use-leaderboard-legend-items.test.tsx +2 -5
- package/src/charts/leaderboard-chart/types.ts +2 -15
- package/src/charts/line-chart/line-chart.tsx +18 -17
- package/src/charts/line-chart/test/line-chart.test.tsx +49 -27
- package/src/charts/line-chart/types.ts +0 -1
- package/src/charts/pie-chart/pie-chart.tsx +23 -23
- package/src/charts/pie-chart/test/composition-api.test.tsx +41 -0
- package/src/charts/pie-chart/test/pie-chart.test.tsx +9 -9
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +21 -24
- package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +33 -5
- package/src/charts/private/chart-composition/index.ts +2 -0
- package/src/charts/private/chart-composition/render-legend-slot.ts +22 -0
- package/src/charts/private/chart-composition/test/render-legend-slot.test.tsx +60 -0
- package/src/charts/private/chart-composition/test/use-chart-children.test.tsx +91 -0
- package/src/charts/private/chart-composition/use-chart-children.ts +34 -2
- package/src/components/legend/private/base-legend.module.scss +19 -37
- package/src/components/legend/private/base-legend.tsx +32 -24
- package/src/components/legend/test/legend.test.tsx +148 -52
- package/src/components/legend/types.ts +23 -24
- package/src/hooks/index.ts +0 -1
- package/src/hooks/test/use-zero-value-display.test.tsx +206 -0
- package/src/hooks/use-zero-value-display.ts +52 -23
- package/src/providers/chart-context/test/chart-context.test.tsx +12 -6
- package/src/providers/chart-context/themes.ts +6 -4
- package/src/types.ts +93 -44
- package/src/utils/date-parsing.ts +10 -1
- package/src/utils/get-styles.ts +1 -1
- package/src/utils/test/date-parsing.test.ts +12 -0
- package/src/utils/test/get-styles.test.ts +12 -10
- package/src/utils/test/resolve-css-var.test.ts +2 -2
- package/tsup.config.ts +1 -1
- package/dist/chunk-55ZCOYDF.cjs.map +0 -1
- package/dist/chunk-6CCZL2JJ.js.map +0 -1
- package/dist/chunk-7FDQGBY7.js.map +0 -1
- package/dist/chunk-BXFD7JIG.cjs.map +0 -1
- package/dist/chunk-IS5YYLTV.js.map +0 -1
- package/dist/chunk-KNIMXN6Z.js +0 -51
- package/dist/chunk-KNIMXN6Z.js.map +0 -1
- package/dist/chunk-NQJE2CC7.cjs.map +0 -1
- package/dist/chunk-O2JIANHK.cjs.map +0 -1
- package/dist/chunk-OMS5QIJN.js.map +0 -1
- package/dist/chunk-RCY6XLGU.cjs.map +0 -1
- package/dist/chunk-RFSHE3HL.js.map +0 -1
- package/dist/chunk-SSFFCBCF.js.map +0 -1
- package/dist/chunk-SUDERBUA.cjs +0 -51
- package/dist/chunk-SUDERBUA.cjs.map +0 -1
- package/dist/chunk-TE63Y5PX.js.map +0 -1
- package/dist/chunk-UFRBUT2D.cjs.map +0 -1
- package/dist/chunk-VPAEBI2F.js.map +0 -1
- package/dist/chunk-X7JL2NYJ.cjs.map +0 -1
- package/dist/chunk-XD2HV7M5.js.map +0 -1
- package/dist/chunk-YDVHT7GS.cjs.map +0 -1
- package/dist/chunk-ZVGEDXDP.cjs.map +0 -1
- package/src/hooks/use-has-legend-child.ts +0 -22
- /package/dist/{chunk-GWBS65VC.js.map → chunk-4B7BL2DD.js.map} +0 -0
- /package/dist/{chunk-MDRCAGKZ.js.map → chunk-DKU775VC.js.map} +0 -0
- /package/dist/{chunk-KHQPN77E.js.map → chunk-KJHWXOCZ.js.map} +0 -0
- /package/dist/{chunk-E62LCBGD.js.map → chunk-MUNOKLLE.js.map} +0 -0
|
@@ -53,13 +53,15 @@ describe( 'LineChart', () => {
|
|
|
53
53
|
],
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
const renderWithTheme = ( props = {}, themeName = 'default' ) => {
|
|
56
|
+
const renderWithTheme = ( props = {}, themeName = 'default', children = undefined ) => {
|
|
57
57
|
const theme = THEME_MAP[ themeName ];
|
|
58
58
|
|
|
59
59
|
return render(
|
|
60
60
|
<GlobalChartsProvider theme={ theme }>
|
|
61
61
|
{ /* @ts-expect-error TODO Fix the missing props */ }
|
|
62
|
-
<LineChart { ...defaultProps } { ...props }
|
|
62
|
+
<LineChart { ...defaultProps } { ...props }>
|
|
63
|
+
{ children }
|
|
64
|
+
</LineChart>
|
|
63
65
|
</GlobalChartsProvider>
|
|
64
66
|
);
|
|
65
67
|
};
|
|
@@ -130,36 +132,56 @@ describe( 'LineChart', () => {
|
|
|
130
132
|
} );
|
|
131
133
|
|
|
132
134
|
describe( 'Legend', () => {
|
|
135
|
+
const multiSeriesData = [
|
|
136
|
+
{
|
|
137
|
+
label: 'Series A',
|
|
138
|
+
data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
label: 'Series B',
|
|
142
|
+
data: [ { date: new Date( '2024-01-01' ), value: 20, label: 'Jan 1' } ],
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
|
|
133
146
|
test( 'shows legend when showLegend is true', () => {
|
|
134
|
-
renderWithTheme( {
|
|
135
|
-
showLegend: true,
|
|
136
|
-
data: [
|
|
137
|
-
{
|
|
138
|
-
label: 'Series A',
|
|
139
|
-
data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
label: 'Series B',
|
|
143
|
-
data: [ { date: new Date( '2024-01-01' ), value: 20, label: 'Jan 1' } ],
|
|
144
|
-
},
|
|
145
|
-
],
|
|
146
|
-
} );
|
|
147
|
+
renderWithTheme( { showLegend: true, data: multiSeriesData } );
|
|
147
148
|
expect( screen.getByText( 'Series A' ) ).toBeInTheDocument();
|
|
148
149
|
expect( screen.getByText( 'Series B' ) ).toBeInTheDocument();
|
|
149
150
|
} );
|
|
150
151
|
|
|
151
152
|
test( 'hides legend when showLegend is false', () => {
|
|
152
|
-
renderWithTheme( {
|
|
153
|
-
showLegend: false,
|
|
154
|
-
data: [
|
|
155
|
-
{
|
|
156
|
-
label: 'Series A',
|
|
157
|
-
data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
|
-
} );
|
|
153
|
+
renderWithTheme( { showLegend: false, data: multiSeriesData } );
|
|
161
154
|
expect( screen.queryByText( 'Series A' ) ).not.toBeInTheDocument();
|
|
162
155
|
} );
|
|
156
|
+
|
|
157
|
+
test( 'renders composition legend as child component', () => {
|
|
158
|
+
renderWithTheme( { data: multiSeriesData }, 'default', <LineChart.Legend /> );
|
|
159
|
+
|
|
160
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
|
|
161
|
+
expect( screen.getByText( 'Series A' ) ).toBeInTheDocument();
|
|
162
|
+
expect( screen.getByText( 'Series B' ) ).toBeInTheDocument();
|
|
163
|
+
} );
|
|
164
|
+
|
|
165
|
+
test( 'renders composition legend regardless of showLegend value', () => {
|
|
166
|
+
renderWithTheme(
|
|
167
|
+
{ data: multiSeriesData, showLegend: false },
|
|
168
|
+
'default',
|
|
169
|
+
<LineChart.Legend />
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
|
|
173
|
+
} );
|
|
174
|
+
|
|
175
|
+
test( 'renders composition legend in top position', () => {
|
|
176
|
+
renderWithTheme( { data: multiSeriesData }, 'default', <LineChart.Legend position="top" /> );
|
|
177
|
+
|
|
178
|
+
// Legend should appear before the chart content in DOM order
|
|
179
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
|
|
180
|
+
const html = document.body.innerHTML;
|
|
181
|
+
expect( html.indexOf( 'data-testid="legend-horizontal"' ) ).toBeLessThan(
|
|
182
|
+
html.indexOf( 'role="grid"' )
|
|
183
|
+
);
|
|
184
|
+
} );
|
|
163
185
|
} );
|
|
164
186
|
|
|
165
187
|
describe( 'Gradient Fill', () => {
|
|
@@ -1178,7 +1200,7 @@ describe( 'LineChart', () => {
|
|
|
1178
1200
|
{ ...defaultProps }
|
|
1179
1201
|
withGradientFill={ false }
|
|
1180
1202
|
showLegend={ true }
|
|
1181
|
-
|
|
1203
|
+
legend={ { interactive: true } }
|
|
1182
1204
|
chartId="test-interactive-chart"
|
|
1183
1205
|
/>
|
|
1184
1206
|
</GlobalChartsProvider>
|
|
@@ -1200,7 +1222,7 @@ describe( 'LineChart', () => {
|
|
|
1200
1222
|
{ ...defaultProps }
|
|
1201
1223
|
withGradientFill={ false }
|
|
1202
1224
|
showLegend={ true }
|
|
1203
|
-
|
|
1225
|
+
legend={ { interactive: false } }
|
|
1204
1226
|
chartId="test-non-interactive-chart"
|
|
1205
1227
|
/>
|
|
1206
1228
|
</GlobalChartsProvider>
|
|
@@ -1218,7 +1240,7 @@ describe( 'LineChart', () => {
|
|
|
1218
1240
|
{ ...defaultProps }
|
|
1219
1241
|
withGradientFill={ false }
|
|
1220
1242
|
showLegend={ true }
|
|
1221
|
-
|
|
1243
|
+
legend={ { interactive: true } }
|
|
1222
1244
|
// No chartId provided
|
|
1223
1245
|
/>
|
|
1224
1246
|
</GlobalChartsProvider>
|
|
@@ -18,7 +18,12 @@ import {
|
|
|
18
18
|
} from '../../providers';
|
|
19
19
|
import { attachSubComponents } from '../../utils';
|
|
20
20
|
import { getStringWidth } from '../../visx/text';
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
ChartSVG,
|
|
23
|
+
ChartHTML,
|
|
24
|
+
useChartChildren,
|
|
25
|
+
renderLegendSlot,
|
|
26
|
+
} from '../private/chart-composition';
|
|
22
27
|
import { RadialWipeAnimation } from '../private/radial-wipe-animation/';
|
|
23
28
|
import { SingleChartContext } from '../private/single-chart-context';
|
|
24
29
|
import { withResponsive, ResponsiveConfig } from '../private/with-responsive';
|
|
@@ -93,13 +98,6 @@ export interface PieChartProps extends BaseChartProps< DataPointPercentage[] > {
|
|
|
93
98
|
*/
|
|
94
99
|
legendValueDisplay?: LegendValueDisplay;
|
|
95
100
|
|
|
96
|
-
/**
|
|
97
|
-
* Enable interactive legend items that can toggle segment visibility.
|
|
98
|
-
* Requires chartId and GlobalChartsProvider.
|
|
99
|
-
* When segments are hidden, percentages are recalculated so visible segments total 100%.
|
|
100
|
-
*/
|
|
101
|
-
legendInteractive?: boolean;
|
|
102
|
-
|
|
103
101
|
/**
|
|
104
102
|
* Use the children prop to render additional elements on the chart.
|
|
105
103
|
*/
|
|
@@ -169,13 +167,7 @@ const PieChartInternal = ( {
|
|
|
169
167
|
withTooltips = false,
|
|
170
168
|
className,
|
|
171
169
|
showLegend = false,
|
|
172
|
-
|
|
173
|
-
legendPosition = 'bottom',
|
|
174
|
-
legendAlignment = 'center',
|
|
175
|
-
legendMaxWidth,
|
|
176
|
-
legendTextOverflow = 'wrap',
|
|
177
|
-
legendItemClassName,
|
|
178
|
-
legendShape = 'circle',
|
|
170
|
+
legend = {},
|
|
179
171
|
width: propWidth,
|
|
180
172
|
height: propHeight,
|
|
181
173
|
size,
|
|
@@ -186,13 +178,15 @@ const PieChartInternal = ( {
|
|
|
186
178
|
cornerScale = 0,
|
|
187
179
|
showLabels = true,
|
|
188
180
|
legendValueDisplay = 'percentage',
|
|
189
|
-
legendInteractive = false,
|
|
190
181
|
children = null,
|
|
191
182
|
tooltipOffsetX = 0,
|
|
192
183
|
tooltipOffsetY = -15,
|
|
193
184
|
renderTooltip = renderDefaultPieTooltip,
|
|
194
185
|
gap = 'md',
|
|
195
186
|
}: PieChartProps ) => {
|
|
187
|
+
const legendInteractive = legend.interactive ?? false;
|
|
188
|
+
const legendPosition = legend.position ?? 'bottom';
|
|
189
|
+
|
|
196
190
|
const providerTheme = useGlobalChartsTheme();
|
|
197
191
|
const chartId = useChartId( providedChartId );
|
|
198
192
|
const [ svgWrapperRef, svgWrapperWidth, svgWrapperHeight ] = useElementSize< HTMLDivElement >();
|
|
@@ -236,7 +230,10 @@ const PieChartInternal = ( {
|
|
|
236
230
|
const { isValid, message } = validateData( data );
|
|
237
231
|
|
|
238
232
|
// Process children to extract compound components
|
|
239
|
-
const { svgChildren, htmlChildren, otherChildren } = useChartChildren(
|
|
233
|
+
const { svgChildren, htmlChildren, legendChildren, otherChildren } = useChartChildren(
|
|
234
|
+
children,
|
|
235
|
+
'PieChart'
|
|
236
|
+
);
|
|
240
237
|
|
|
241
238
|
// Memoize metadata to prevent unnecessary re-registration
|
|
242
239
|
const chartMetadata = useMemo(
|
|
@@ -314,13 +311,14 @@ const PieChartInternal = ( {
|
|
|
314
311
|
|
|
315
312
|
const legendElement = showLegend && (
|
|
316
313
|
<Legend
|
|
317
|
-
orientation={
|
|
314
|
+
orientation={ legend.orientation ?? 'horizontal' }
|
|
318
315
|
position={ legendPosition }
|
|
319
|
-
alignment={
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
316
|
+
alignment={ legend.alignment ?? 'center' }
|
|
317
|
+
labelStyles={ legend.labelStyles }
|
|
318
|
+
itemClassName={ legend.itemClassName }
|
|
319
|
+
itemStyles={ legend.itemStyles }
|
|
320
|
+
shapeStyles={ legend.shapeStyles }
|
|
321
|
+
shape={ legend.shape ?? 'circle' }
|
|
324
322
|
chartId={ chartId }
|
|
325
323
|
interactive={ legendInteractive }
|
|
326
324
|
/>
|
|
@@ -351,6 +349,7 @@ const PieChartInternal = ( {
|
|
|
351
349
|
} }
|
|
352
350
|
>
|
|
353
351
|
{ legendPosition === 'top' && legendElement }
|
|
352
|
+
{ renderLegendSlot( legendChildren, 'top' ) }
|
|
354
353
|
|
|
355
354
|
<div className={ styles[ 'pie-chart__svg-wrapper' ] } ref={ svgWrapperRef }>
|
|
356
355
|
<svg
|
|
@@ -483,6 +482,7 @@ const PieChartInternal = ( {
|
|
|
483
482
|
</div>
|
|
484
483
|
|
|
485
484
|
{ legendPosition === 'bottom' && legendElement }
|
|
485
|
+
{ renderLegendSlot( legendChildren, 'bottom' ) }
|
|
486
486
|
|
|
487
487
|
{ withTooltips && tooltipOpen && tooltipData && (
|
|
488
488
|
<TooltipInPortal top={ tooltipTop || 0 } left={ tooltipLeft || 0 }>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import { Group } from '@visx/group';
|
|
3
3
|
import '@testing-library/jest-dom';
|
|
4
|
+
import { GlobalChartsProvider } from '../../../providers';
|
|
4
5
|
import { PieChartUnresponsive as PieChart } from '../index';
|
|
5
6
|
|
|
6
7
|
describe( 'PieChart Composition API', () => {
|
|
@@ -10,6 +11,16 @@ describe( 'PieChart Composition API', () => {
|
|
|
10
11
|
{ label: 'C', value: 30, percentage: 30 },
|
|
11
12
|
];
|
|
12
13
|
|
|
14
|
+
const renderWithChildren = ( props = {}, children = undefined ) => {
|
|
15
|
+
return render(
|
|
16
|
+
<GlobalChartsProvider>
|
|
17
|
+
<PieChart data={ mockData } size={ 400 } { ...props }>
|
|
18
|
+
{ children }
|
|
19
|
+
</PieChart>
|
|
20
|
+
</GlobalChartsProvider>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
13
24
|
describe( 'Compound Components', () => {
|
|
14
25
|
it( 'renders PieChart.SVG children inside the SVG element', () => {
|
|
15
26
|
render(
|
|
@@ -148,4 +159,34 @@ describe( 'PieChart Composition API', () => {
|
|
|
148
159
|
expect( svg!.contains( newHtml ) ).toBe( false );
|
|
149
160
|
} );
|
|
150
161
|
} );
|
|
162
|
+
|
|
163
|
+
describe( 'Composition Legend', () => {
|
|
164
|
+
test( 'renders composition legend as child component', () => {
|
|
165
|
+
renderWithChildren( {}, <PieChart.Legend /> );
|
|
166
|
+
|
|
167
|
+
const legendItems = screen.getAllByTestId( 'legend-item' );
|
|
168
|
+
expect( legendItems ).toHaveLength( 3 );
|
|
169
|
+
expect( legendItems[ 0 ] ).toHaveTextContent( 'A' );
|
|
170
|
+
expect( legendItems[ 1 ] ).toHaveTextContent( 'B' );
|
|
171
|
+
expect( legendItems[ 2 ] ).toHaveTextContent( 'C' );
|
|
172
|
+
} );
|
|
173
|
+
|
|
174
|
+
test( 'renders composition legend regardless of showLegend value', () => {
|
|
175
|
+
renderWithChildren( { showLegend: false }, <PieChart.Legend /> );
|
|
176
|
+
|
|
177
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 3 );
|
|
178
|
+
} );
|
|
179
|
+
|
|
180
|
+
test( 'renders composition legend in top position', () => {
|
|
181
|
+
renderWithChildren( {}, <PieChart.Legend position="top" /> );
|
|
182
|
+
|
|
183
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 3 );
|
|
184
|
+
|
|
185
|
+
// Legend should appear before the chart SVG in DOM order
|
|
186
|
+
const html = document.body.innerHTML;
|
|
187
|
+
expect( html.indexOf( 'data-testid="legend-horizontal"' ) ).toBeLessThan(
|
|
188
|
+
html.indexOf( 'data-testid="pie-segment"' )
|
|
189
|
+
);
|
|
190
|
+
} );
|
|
191
|
+
} );
|
|
151
192
|
} );
|
|
@@ -60,7 +60,7 @@ describe( 'PieChart', () => {
|
|
|
60
60
|
test( 'renders legend when showLegend is true', () => {
|
|
61
61
|
renderWithTheme( {
|
|
62
62
|
showLegend: true,
|
|
63
|
-
|
|
63
|
+
legend: { position: 'top' },
|
|
64
64
|
} );
|
|
65
65
|
|
|
66
66
|
// Check that legend container is rendered using accessible queries
|
|
@@ -72,7 +72,7 @@ describe( 'PieChart', () => {
|
|
|
72
72
|
test( 'renders correct number of legend items', () => {
|
|
73
73
|
renderWithTheme( {
|
|
74
74
|
showLegend: true,
|
|
75
|
-
|
|
75
|
+
legend: { position: 'top' },
|
|
76
76
|
} );
|
|
77
77
|
|
|
78
78
|
// Use getAllByTestId to find legend items
|
|
@@ -83,7 +83,7 @@ describe( 'PieChart', () => {
|
|
|
83
83
|
test( 'chart renders with legend at top position', () => {
|
|
84
84
|
renderWithTheme( {
|
|
85
85
|
showLegend: true,
|
|
86
|
-
|
|
86
|
+
legend: { position: 'top' },
|
|
87
87
|
} );
|
|
88
88
|
|
|
89
89
|
// Verify the chart renders without errors when legend is at top
|
|
@@ -98,7 +98,7 @@ describe( 'PieChart', () => {
|
|
|
98
98
|
test( 'chart renders with legend at bottom position', () => {
|
|
99
99
|
renderWithTheme( {
|
|
100
100
|
showLegend: true,
|
|
101
|
-
|
|
101
|
+
legend: { position: 'bottom' },
|
|
102
102
|
} );
|
|
103
103
|
|
|
104
104
|
// Verify the chart renders without errors when legend is at bottom
|
|
@@ -379,7 +379,7 @@ describe( 'PieChart', () => {
|
|
|
379
379
|
renderWithTheme( {
|
|
380
380
|
data: testData,
|
|
381
381
|
showLegend: true,
|
|
382
|
-
|
|
382
|
+
legend: { interactive: true },
|
|
383
383
|
chartId: 'test-interactive-pie-chart',
|
|
384
384
|
} );
|
|
385
385
|
|
|
@@ -411,7 +411,7 @@ describe( 'PieChart', () => {
|
|
|
411
411
|
renderWithTheme( {
|
|
412
412
|
data: testData,
|
|
413
413
|
showLegend: true,
|
|
414
|
-
|
|
414
|
+
legend: { interactive: true },
|
|
415
415
|
chartId: 'test-all-hidden-pie-chart',
|
|
416
416
|
} );
|
|
417
417
|
|
|
@@ -450,7 +450,7 @@ describe( 'PieChart', () => {
|
|
|
450
450
|
renderWithTheme( {
|
|
451
451
|
data: testData,
|
|
452
452
|
showLegend: true,
|
|
453
|
-
|
|
453
|
+
legend: { interactive: false },
|
|
454
454
|
chartId: 'test-non-interactive-pie-chart',
|
|
455
455
|
} );
|
|
456
456
|
|
|
@@ -474,7 +474,7 @@ describe( 'PieChart', () => {
|
|
|
474
474
|
renderWithTheme( {
|
|
475
475
|
data: testData,
|
|
476
476
|
showLegend: true,
|
|
477
|
-
|
|
477
|
+
legend: { interactive: true },
|
|
478
478
|
chartId: 'test-color-consistency-pie-chart',
|
|
479
479
|
} );
|
|
480
480
|
|
|
@@ -506,7 +506,7 @@ describe( 'PieChart', () => {
|
|
|
506
506
|
renderWithTheme( {
|
|
507
507
|
data: testData,
|
|
508
508
|
showLegend: true,
|
|
509
|
-
|
|
509
|
+
legend: { interactive: true },
|
|
510
510
|
legendValueDisplay: 'percentage',
|
|
511
511
|
chartId: 'test-percentage-recalc-pie-chart',
|
|
512
512
|
} );
|
|
@@ -17,7 +17,12 @@ import {
|
|
|
17
17
|
GlobalChartsContext,
|
|
18
18
|
} from '../../providers';
|
|
19
19
|
import { attachSubComponents } from '../../utils';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
ChartSVG,
|
|
22
|
+
ChartHTML,
|
|
23
|
+
useChartChildren,
|
|
24
|
+
renderLegendSlot,
|
|
25
|
+
} from '../private/chart-composition';
|
|
21
26
|
import { RadialWipeAnimation } from '../private/radial-wipe-animation';
|
|
22
27
|
import { SingleChartContext } from '../private/single-chart-context';
|
|
23
28
|
import { withResponsive } from '../private/with-responsive';
|
|
@@ -98,13 +103,6 @@ export interface PieSemiCircleChartProps extends BaseChartProps< DataPointPercen
|
|
|
98
103
|
*/
|
|
99
104
|
legendValueDisplay?: LegendValueDisplay;
|
|
100
105
|
|
|
101
|
-
/**
|
|
102
|
-
* Enable interactive legend items that can toggle segment visibility.
|
|
103
|
-
* Requires chartId and GlobalChartsProvider.
|
|
104
|
-
* When segments are hidden, percentages are recalculated so visible segments total 100%.
|
|
105
|
-
*/
|
|
106
|
-
legendInteractive?: boolean;
|
|
107
|
-
|
|
108
106
|
/**
|
|
109
107
|
* Horizontal offset for tooltip positioning in pixels (default: 0)
|
|
110
108
|
*/
|
|
@@ -167,15 +165,8 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
167
165
|
clockwise = true,
|
|
168
166
|
withTooltips = false,
|
|
169
167
|
showLegend = false,
|
|
170
|
-
|
|
171
|
-
legendPosition = 'bottom',
|
|
172
|
-
legendAlignment = 'center',
|
|
173
|
-
legendMaxWidth,
|
|
174
|
-
legendTextOverflow = 'wrap',
|
|
175
|
-
legendItemClassName,
|
|
176
|
-
legendShape = 'circle',
|
|
168
|
+
legend = {},
|
|
177
169
|
legendValueDisplay = 'percentage',
|
|
178
|
-
legendInteractive = false,
|
|
179
170
|
label,
|
|
180
171
|
animation,
|
|
181
172
|
note,
|
|
@@ -186,6 +177,9 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
186
177
|
renderTooltip = renderDefaultPieSemiCircleTooltip,
|
|
187
178
|
gap = 'md',
|
|
188
179
|
} ) => {
|
|
180
|
+
const legendInteractive = legend.interactive ?? false;
|
|
181
|
+
const legendPosition = legend.position ?? 'bottom';
|
|
182
|
+
|
|
189
183
|
const chartId = useChartId( providedChartId );
|
|
190
184
|
// Measure the SVG wrapper to calculate constrained dimensions
|
|
191
185
|
const [ svgWrapperRef, svgWrapperWidth, svgWrapperHeight ] = useElementSize< HTMLDivElement >();
|
|
@@ -277,7 +271,7 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
277
271
|
const legendItems = useChartLegendItems( legendData, legendOptions );
|
|
278
272
|
|
|
279
273
|
// Process children to extract compound components
|
|
280
|
-
const { svgChildren, htmlChildren, otherChildren } = useChartChildren(
|
|
274
|
+
const { svgChildren, htmlChildren, legendChildren, otherChildren } = useChartChildren(
|
|
281
275
|
children,
|
|
282
276
|
'PieSemiCircleChart'
|
|
283
277
|
);
|
|
@@ -349,13 +343,14 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
349
343
|
|
|
350
344
|
const legendElement = showLegend && (
|
|
351
345
|
<Legend
|
|
352
|
-
orientation={
|
|
346
|
+
orientation={ legend.orientation ?? 'horizontal' }
|
|
353
347
|
position={ legendPosition }
|
|
354
|
-
alignment={
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
348
|
+
alignment={ legend.alignment ?? 'center' }
|
|
349
|
+
labelStyles={ legend.labelStyles }
|
|
350
|
+
itemClassName={ legend.itemClassName }
|
|
351
|
+
itemStyles={ legend.itemStyles }
|
|
352
|
+
shapeStyles={ legend.shapeStyles }
|
|
353
|
+
shape={ legend.shape ?? 'circle' }
|
|
359
354
|
chartId={ chartId }
|
|
360
355
|
interactive={ legendInteractive }
|
|
361
356
|
/>
|
|
@@ -388,6 +383,7 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
388
383
|
data-testid="pie-chart-container"
|
|
389
384
|
>
|
|
390
385
|
{ legendPosition === 'top' && legendElement }
|
|
386
|
+
{ renderLegendSlot( legendChildren, 'top' ) }
|
|
391
387
|
|
|
392
388
|
<div ref={ svgWrapperRef } className={ styles[ 'pie-semi-circle-chart__svg-wrapper' ] }>
|
|
393
389
|
<svg
|
|
@@ -484,7 +480,8 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
|
|
|
484
480
|
</svg>
|
|
485
481
|
</div>
|
|
486
482
|
|
|
487
|
-
{ legendPosition
|
|
483
|
+
{ legendPosition === 'bottom' && legendElement }
|
|
484
|
+
{ renderLegendSlot( legendChildren, 'bottom' ) }
|
|
488
485
|
|
|
489
486
|
{ withTooltips && tooltipOpen && tooltipData && (
|
|
490
487
|
<TooltipInPortal top={ tooltipTop || 0 } left={ tooltipLeft || 0 }>
|
|
@@ -29,10 +29,10 @@ const mockData = [
|
|
|
29
29
|
];
|
|
30
30
|
|
|
31
31
|
// Helper function to render component with providers
|
|
32
|
-
const renderPieChart = props =>
|
|
32
|
+
const renderPieChart = ( props, children = undefined ) =>
|
|
33
33
|
render(
|
|
34
34
|
<GlobalChartsProvider>
|
|
35
|
-
<PieSemiCircleChart { ...props }
|
|
35
|
+
<PieSemiCircleChart { ...props }>{ children }</PieSemiCircleChart>
|
|
36
36
|
</GlobalChartsProvider>
|
|
37
37
|
);
|
|
38
38
|
|
|
@@ -256,6 +256,34 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
256
256
|
} );
|
|
257
257
|
} );
|
|
258
258
|
|
|
259
|
+
describe( 'Composition Legend', () => {
|
|
260
|
+
test( 'renders composition legend as child component', () => {
|
|
261
|
+
renderPieChart( { data: mockData }, <PieSemiCircleChart.Legend /> );
|
|
262
|
+
|
|
263
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
|
|
264
|
+
expect( screen.getByText( 'Category A' ) ).toBeInTheDocument();
|
|
265
|
+
expect( screen.getByText( 'Category B' ) ).toBeInTheDocument();
|
|
266
|
+
} );
|
|
267
|
+
|
|
268
|
+
test( 'renders composition legend regardless of showLegend value', () => {
|
|
269
|
+
renderPieChart( { data: mockData, showLegend: false }, <PieSemiCircleChart.Legend /> );
|
|
270
|
+
|
|
271
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
|
|
272
|
+
} );
|
|
273
|
+
|
|
274
|
+
test( 'renders composition legend in top position', () => {
|
|
275
|
+
renderPieChart( { data: mockData }, <PieSemiCircleChart.Legend position="top" /> );
|
|
276
|
+
|
|
277
|
+
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
|
|
278
|
+
|
|
279
|
+
// Legend should appear before the chart SVG in DOM order
|
|
280
|
+
const html = document.body.innerHTML;
|
|
281
|
+
expect( html.indexOf( 'data-testid="legend-horizontal"' ) ).toBeLessThan(
|
|
282
|
+
html.indexOf( 'data-testid="pie-chart-svg"' )
|
|
283
|
+
);
|
|
284
|
+
} );
|
|
285
|
+
} );
|
|
286
|
+
|
|
259
287
|
describe( 'Interactive Legend', () => {
|
|
260
288
|
test( 'filters segments when interactive legend is enabled and segment is toggled', async () => {
|
|
261
289
|
const user = userEvent.setup();
|
|
@@ -267,7 +295,7 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
267
295
|
renderPieChart( {
|
|
268
296
|
data: testData,
|
|
269
297
|
showLegend: true,
|
|
270
|
-
|
|
298
|
+
legend: { interactive: true },
|
|
271
299
|
chartId: 'test-interactive-semi-circle-chart',
|
|
272
300
|
} );
|
|
273
301
|
|
|
@@ -299,7 +327,7 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
299
327
|
renderPieChart( {
|
|
300
328
|
data: testData,
|
|
301
329
|
showLegend: true,
|
|
302
|
-
|
|
330
|
+
legend: { interactive: true },
|
|
303
331
|
chartId: 'test-all-hidden-semi-circle-chart',
|
|
304
332
|
} );
|
|
305
333
|
|
|
@@ -326,7 +354,7 @@ describe( 'PieSemiCircleChart', () => {
|
|
|
326
354
|
renderPieChart( {
|
|
327
355
|
data: testData,
|
|
328
356
|
showLegend: true,
|
|
329
|
-
|
|
357
|
+
legend: { interactive: false },
|
|
330
358
|
chartId: 'test-non-interactive-semi-circle-chart',
|
|
331
359
|
} );
|
|
332
360
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { ChartSVG } from './chart-svg';
|
|
2
2
|
export { ChartHTML } from './chart-html';
|
|
3
|
+
export { renderLegendSlot } from './render-legend-slot';
|
|
3
4
|
export { useChartChildren } from './use-chart-children';
|
|
5
|
+
export type { LegendChild } from './use-chart-children';
|
|
4
6
|
export type { BaseChartSubComponents, ChartComponentWithComposition } from './types';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createElement, Fragment } from 'react';
|
|
2
|
+
import type { LegendChild } from './use-chart-children';
|
|
3
|
+
import type { LegendPosition } from '../../../types';
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Renders legend children filtered by position slot.
|
|
8
|
+
*
|
|
9
|
+
* @param {LegendChild[]} legendChildren - The legend children to filter and render
|
|
10
|
+
* @param {LegendPosition} position - The position slot to render
|
|
11
|
+
* @return {ReactNode[]} Array of legend elements for the given position
|
|
12
|
+
*/
|
|
13
|
+
export function renderLegendSlot(
|
|
14
|
+
legendChildren: LegendChild[],
|
|
15
|
+
position: LegendPosition
|
|
16
|
+
): ReactNode[] {
|
|
17
|
+
return legendChildren
|
|
18
|
+
.filter( l => l.position === position )
|
|
19
|
+
.map( ( l, i ) =>
|
|
20
|
+
createElement( Fragment, { key: `legend-${ position }-${ i }` }, l.element )
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createElement } from 'react';
|
|
2
|
+
import { renderLegendSlot } from '../render-legend-slot';
|
|
3
|
+
import type { LegendChild } from '../use-chart-children';
|
|
4
|
+
|
|
5
|
+
const makeLegend = ( position: 'top' | 'bottom', label: string ): LegendChild => ( {
|
|
6
|
+
element: createElement( 'div', null, label ),
|
|
7
|
+
position,
|
|
8
|
+
} );
|
|
9
|
+
|
|
10
|
+
describe( 'renderLegendSlot', () => {
|
|
11
|
+
it( 'should return an empty array when given no children', () => {
|
|
12
|
+
expect( renderLegendSlot( [], 'top' ) ).toHaveLength( 0 );
|
|
13
|
+
} );
|
|
14
|
+
|
|
15
|
+
it( 'should return an empty array when no children match the position', () => {
|
|
16
|
+
const children = [ makeLegend( 'bottom', 'Legend 1' ) ];
|
|
17
|
+
|
|
18
|
+
expect( renderLegendSlot( children, 'top' ) ).toHaveLength( 0 );
|
|
19
|
+
} );
|
|
20
|
+
|
|
21
|
+
it( 'should return a single matching child', () => {
|
|
22
|
+
const children = [ makeLegend( 'top', 'Legend 1' ) ];
|
|
23
|
+
const view = renderLegendSlot( children, 'top' );
|
|
24
|
+
|
|
25
|
+
expect( view ).toHaveLength( 1 );
|
|
26
|
+
expect( view[ 0 ] ).toHaveProperty( 'key', 'legend-top-0' );
|
|
27
|
+
} );
|
|
28
|
+
|
|
29
|
+
it( 'should return multiple matching children in order', () => {
|
|
30
|
+
const children = [ makeLegend( 'bottom', 'Legend 1' ), makeLegend( 'bottom', 'Legend 2' ) ];
|
|
31
|
+
const view = renderLegendSlot( children, 'bottom' );
|
|
32
|
+
|
|
33
|
+
expect( view ).toHaveLength( 2 );
|
|
34
|
+
expect( view[ 0 ] ).toHaveProperty( 'key', 'legend-bottom-0' );
|
|
35
|
+
expect( view[ 1 ] ).toHaveProperty( 'key', 'legend-bottom-1' );
|
|
36
|
+
} );
|
|
37
|
+
|
|
38
|
+
it( 'should filter out children with a different position', () => {
|
|
39
|
+
const children = [
|
|
40
|
+
makeLegend( 'top', 'Top Legend' ),
|
|
41
|
+
makeLegend( 'bottom', 'Bottom Legend' ),
|
|
42
|
+
makeLegend( 'top', 'Another Top Legend' ),
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
expect( renderLegendSlot( children, 'top' ) ).toHaveLength( 2 );
|
|
46
|
+
expect( renderLegendSlot( children, 'bottom' ) ).toHaveLength( 1 );
|
|
47
|
+
} );
|
|
48
|
+
|
|
49
|
+
it( 'should produce distinct keys for top and bottom slots', () => {
|
|
50
|
+
const children = [ makeLegend( 'top', 'Top' ), makeLegend( 'bottom', 'Bottom' ) ];
|
|
51
|
+
const [ topElement, bottomElement ] = [
|
|
52
|
+
renderLegendSlot( children, 'top' )[ 0 ],
|
|
53
|
+
renderLegendSlot( children, 'bottom' )[ 0 ],
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
expect( topElement ).toHaveProperty( 'key', 'legend-top-0' );
|
|
57
|
+
expect( bottomElement ).toHaveProperty( 'key', 'legend-bottom-0' );
|
|
58
|
+
expect( topElement ).not.toEqual( bottomElement );
|
|
59
|
+
} );
|
|
60
|
+
} );
|