@automattic/charts 4.7.0 → 4.9.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/CHANGELOG.md +30 -0
- package/TOKENS.md +6 -3
- package/dist/index.cjs +834 -321
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +68 -17
- package/dist/index.d.cts +102 -28
- package/dist/index.d.ts +102 -28
- package/dist/index.js +838 -327
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/charts/area-chart/area-chart.tsx +7 -4
- package/src/charts/area-chart/test/area-chart.test.tsx +24 -15
- package/src/charts/area-chart/test/tooltip-resolution-branches.test.tsx +1 -1
- package/src/charts/area-chart/test/tooltip-resolution.test.tsx +2 -2
- package/src/charts/area-chart/types.ts +2 -0
- package/src/charts/bar-chart/bar-chart.module.scss +7 -0
- package/src/charts/bar-chart/bar-chart.tsx +25 -16
- package/src/charts/bar-chart/private/comparison-bars.tsx +1 -0
- package/src/charts/bar-chart/test/bar-chart.test.tsx +106 -62
- package/src/charts/bar-chart/types.ts +2 -0
- package/src/charts/bar-list-chart/test/bar-list-chart.test.tsx +3 -1
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +1 -1
- package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +9 -0
- package/src/charts/heatmap-chart/heatmap-chart.module.scss +16 -8
- package/src/charts/heatmap-chart/heatmap-chart.tsx +183 -66
- package/src/charts/heatmap-chart/private/column-groups.ts +79 -8
- package/src/charts/heatmap-chart/private/fit-compact-cells.ts +72 -0
- package/src/charts/heatmap-chart/private/heatmap-context.ts +2 -2
- package/src/charts/heatmap-chart/private/heatmap-legend.tsx +15 -3
- package/src/charts/heatmap-chart/private/heatmap-scale.ts +74 -0
- package/src/charts/heatmap-chart/private/index.ts +6 -3
- package/src/charts/heatmap-chart/private/use-heatmap-colors.ts +24 -6
- package/src/charts/heatmap-chart/test/column-groups.test.ts +41 -4
- package/src/charts/heatmap-chart/test/fit-compact-cells.test.ts +59 -0
- package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +231 -8
- package/src/charts/heatmap-chart/test/heatmap-scale.test.ts +78 -0
- package/src/charts/heatmap-chart/test/use-heatmap-colors.test.ts +18 -6
- package/src/charts/heatmap-chart/types.ts +6 -3
- package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +46 -4
- package/src/charts/leaderboard-chart/leaderboard-chart.tsx +37 -1
- package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +53 -0
- package/src/charts/leaderboard-chart/types.ts +6 -1
- package/src/charts/line-chart/line-chart.tsx +7 -4
- package/src/charts/line-chart/private/line-chart-annotation-label-popover.tsx +19 -9
- package/src/charts/line-chart/test/default-tooltip-formatting.test.tsx +1 -1
- package/src/charts/line-chart/test/line-chart.test.tsx +45 -35
- package/src/charts/line-chart/test/tooltip-resolution.test.tsx +2 -2
- package/src/charts/line-chart/types.ts +5 -3
- package/src/charts/pie-chart/pie-chart.tsx +153 -143
- package/src/charts/pie-chart/test/pie-chart.test.tsx +171 -0
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +126 -124
- package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +55 -1
- package/src/charts/private/chart-composition/use-chart-children.ts +11 -6
- package/src/charts/private/pie-keyboard-navigation/index.ts +4 -0
- package/src/charts/private/pie-keyboard-navigation/order-arcs-for-navigation.ts +10 -0
- package/src/charts/private/pie-keyboard-navigation/pie-keyboard-navigation.module.scss +16 -0
- package/src/charts/private/pie-keyboard-navigation/pie-selection-announcement.tsx +14 -0
- package/src/charts/private/pie-keyboard-navigation/pie-selection-output.tsx +82 -0
- package/src/charts/private/pie-keyboard-navigation/pie-tooltip.tsx +44 -0
- package/src/charts/private/pie-keyboard-navigation/selected-segment-ring.tsx +47 -0
- package/src/charts/private/pie-keyboard-navigation/test/order-arcs-for-navigation.test.ts +34 -0
- package/src/charts/private/pie-keyboard-navigation/test/selected-segment-ring.test.ts +14 -0
- package/src/charts/private/pie-keyboard-navigation/use-pie-keyboard-navigation.ts +139 -0
- package/src/charts/private/test/label-text-color.test.ts +6 -8
- package/src/charts/private/x-zoom/test/x-zoom.test.tsx +3 -2
- package/src/components/tooltip/base-tooltip.tsx +31 -32
- package/src/components/tooltip/index.ts +3 -1
- package/src/components/tooltip/private/bounded-tooltip.tsx +15 -15
- package/src/components/tooltip/private/label-value-content.tsx +8 -0
- package/src/components/tooltip/private/xy-chart-tooltip-overlay.tsx +336 -0
- package/src/components/tooltip/test/tool-tip.test.tsx +8 -6
- package/src/components/tooltip/test/tooltip-box.test.tsx +53 -0
- package/src/components/tooltip/test/xy-chart-tooltip-overlay.test.tsx +445 -0
- package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +283 -365
- package/src/components/tooltip/{base-tooltip.module.scss → tooltip-box.module.scss} +5 -8
- package/src/components/tooltip/tooltip-box.tsx +43 -0
- package/src/components/tooltip/types.ts +7 -1
- package/src/components/tooltip/xy-chart-tooltip.tsx +410 -300
- package/src/hooks/test/use-chart-margin.test.tsx +8 -4
- package/src/hooks/use-chart-margin.tsx +21 -18
- package/src/index.ts +8 -3
- package/src/providers/chart-context/global-charts-provider.tsx +5 -14
- package/src/providers/chart-context/hooks/use-default-hidden-series.ts +1 -1
- package/src/providers/chart-context/private/catalog-pointers.ts +1 -0
- package/src/providers/chart-context/private/resolve-opaque-hex.ts +16 -0
- package/src/providers/chart-context/test/use-default-hidden-series.test.tsx +1 -1
- package/src/styles/chart-scope.scss +1 -0
- package/src/test-utils/zoned-parsing-suite.ts +0 -10
- package/src/types.ts +18 -2
- package/src/utils/test/date-formatting.test.ts +0 -10
- package/src/components/tooltip/accessible-tooltip.tsx +0 -433
- package/src/components/tooltip/test/accessible-tooltip.test.tsx +0 -331
|
@@ -42,12 +42,12 @@ describe( 'BarChart', () => {
|
|
|
42
42
|
// visx renders bars and grid lines without accessible roles or configurable test IDs.
|
|
43
43
|
const getBarRects = () => {
|
|
44
44
|
// eslint-disable-next-line testing-library/no-node-access -- See the visx node constraint above.
|
|
45
|
-
return screen.getByRole( '
|
|
45
|
+
return screen.getByRole( 'application' ).querySelectorAll( '.visx-bar-group rect' );
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
const getGridLines = () => {
|
|
49
49
|
// eslint-disable-next-line testing-library/no-node-access -- visx grid lines cannot receive test IDs.
|
|
50
|
-
return screen.getByRole( '
|
|
50
|
+
return screen.getByRole( 'application' ).querySelectorAll( '.visx-rows line' );
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
const renderWithTheme = ( props = {}, children = undefined ) => {
|
|
@@ -151,20 +151,21 @@ describe( 'BarChart', () => {
|
|
|
151
151
|
|
|
152
152
|
const pointer = ( type: string, clientX: number, clientY: number ) => {
|
|
153
153
|
// visx owns the pointer capture rect and does not expose an attribute prop for it.
|
|
154
|
+
const chart = screen.getByRole( 'application' );
|
|
154
155
|
// eslint-disable-next-line testing-library/no-node-access
|
|
155
|
-
const target =
|
|
156
|
+
const target = chart.querySelector( 'svg > rect[fill="transparent"]' );
|
|
156
157
|
fireEvent( target, new MouseEvent( type, { bubbles: true, clientX, clientY } ) );
|
|
157
158
|
};
|
|
158
159
|
|
|
159
160
|
const hover = ( clientX: number, clientY: number ) =>
|
|
160
161
|
pointer( 'pointermove', clientX, clientY );
|
|
161
162
|
|
|
162
|
-
test( 'Escape returns focus to the
|
|
163
|
+
test( 'Escape returns focus to the chart without reopening either tooltip', async () => {
|
|
163
164
|
jest.useFakeTimers();
|
|
164
165
|
try {
|
|
165
166
|
const user = userEvent.setup( { advanceTimers: jest.advanceTimersByTime } );
|
|
166
167
|
renderWithTheme( { withTooltips: true } );
|
|
167
|
-
const chart = screen.getByRole( '
|
|
168
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
168
169
|
await user.tab();
|
|
169
170
|
await user.keyboard( '{ArrowRight}' );
|
|
170
171
|
expect( screen.getByTestId( 'chart-tooltip-0' ) ).toHaveFocus();
|
|
@@ -369,7 +370,7 @@ describe( 'BarChart', () => {
|
|
|
369
370
|
unmount();
|
|
370
371
|
renderWithTheme( { withBandHighlight: true, onBandHighlightChange } );
|
|
371
372
|
expect( warn ).toHaveBeenCalledTimes( 1 );
|
|
372
|
-
expect( screen.getByRole( '
|
|
373
|
+
expect( screen.getByRole( 'application' ) ).toBeInTheDocument();
|
|
373
374
|
expect( warn ).toHaveBeenCalledWith(
|
|
374
375
|
'[Charts] BarChart: withBandHighlight and onBandHighlightChange require withTooltips.'
|
|
375
376
|
);
|
|
@@ -473,7 +474,7 @@ describe( 'BarChart', () => {
|
|
|
473
474
|
|
|
474
475
|
test( 'labels a whole-number range smaller than the tick count once per whole number', () => {
|
|
475
476
|
renderWithTheme( { data: wholeNumberData } );
|
|
476
|
-
const chart = screen.getByRole( '
|
|
477
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
477
478
|
const ticks = within( chart )
|
|
478
479
|
.getAllByText( /^-?[\d.,]+$/ )
|
|
479
480
|
.map( el => el.textContent );
|
|
@@ -482,7 +483,7 @@ describe( 'BarChart', () => {
|
|
|
482
483
|
|
|
483
484
|
test( 'labels a horizontal whole-number range once per whole number', () => {
|
|
484
485
|
renderWithTheme( { data: wholeNumberData, orientation: 'horizontal' } );
|
|
485
|
-
const chart = screen.getByRole( '
|
|
486
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
486
487
|
const ticks = within( chart )
|
|
487
488
|
.getAllByText( /^-?[\d.,]+$/ )
|
|
488
489
|
.map( el => el.textContent );
|
|
@@ -501,7 +502,7 @@ describe( 'BarChart', () => {
|
|
|
501
502
|
},
|
|
502
503
|
],
|
|
503
504
|
} );
|
|
504
|
-
const chart = screen.getByRole( '
|
|
505
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
505
506
|
const ticks = within( chart )
|
|
506
507
|
.getAllByText( /^-?[\d.,]+$/ )
|
|
507
508
|
.map( el => el.textContent );
|
|
@@ -530,7 +531,7 @@ describe( 'BarChart', () => {
|
|
|
530
531
|
},
|
|
531
532
|
},
|
|
532
533
|
} );
|
|
533
|
-
const chart = screen.getByRole( '
|
|
534
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
534
535
|
expect( within( chart ).getAllByText( /^\d+%$/ ) ).toHaveLength( 6 );
|
|
535
536
|
}
|
|
536
537
|
);
|
|
@@ -540,7 +541,7 @@ describe( 'BarChart', () => {
|
|
|
540
541
|
data: wholeNumberData,
|
|
541
542
|
options: { axis: { y: { tickValues: [ 0, 0.5, 1 ] } } },
|
|
542
543
|
} );
|
|
543
|
-
const chart = screen.getByRole( '
|
|
544
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
544
545
|
expect( within( chart ).getAllByText( /^-?[\d.,]+$/ ) ).toHaveLength( 3 );
|
|
545
546
|
} );
|
|
546
547
|
} );
|
|
@@ -584,7 +585,7 @@ describe( 'BarChart', () => {
|
|
|
584
585
|
},
|
|
585
586
|
],
|
|
586
587
|
} );
|
|
587
|
-
expect( screen.getByRole( '
|
|
588
|
+
expect( screen.getByRole( 'application', { name: /bar chart/i } ) ).toBeInTheDocument();
|
|
588
589
|
} );
|
|
589
590
|
|
|
590
591
|
test( 'handles negative values', () => {
|
|
@@ -600,7 +601,7 @@ describe( 'BarChart', () => {
|
|
|
600
601
|
},
|
|
601
602
|
],
|
|
602
603
|
} );
|
|
603
|
-
expect( screen.getByRole( '
|
|
604
|
+
expect( screen.getByRole( 'application', { name: /bar chart/i } ) ).toBeInTheDocument();
|
|
604
605
|
} );
|
|
605
606
|
|
|
606
607
|
test( 'renders a bucket with no reading instead of failing the whole chart', () => {
|
|
@@ -617,7 +618,7 @@ describe( 'BarChart', () => {
|
|
|
617
618
|
],
|
|
618
619
|
} );
|
|
619
620
|
expect( screen.queryByText( /invalid data/i ) ).not.toBeInTheDocument();
|
|
620
|
-
expect( screen.getByRole( '
|
|
621
|
+
expect( screen.getByRole( 'application', { name: /bar chart/i } ) ).toBeInTheDocument();
|
|
621
622
|
} );
|
|
622
623
|
|
|
623
624
|
test( 'draws no bar for a bucket with no reading', () => {
|
|
@@ -736,7 +737,7 @@ describe( 'BarChart', () => {
|
|
|
736
737
|
expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
|
|
737
738
|
const html = document.body.innerHTML;
|
|
738
739
|
expect( html.indexOf( 'data-testid="legend-horizontal"' ) ).toBeLessThan(
|
|
739
|
-
html.indexOf( 'role="
|
|
740
|
+
html.indexOf( 'role="application"' )
|
|
740
741
|
);
|
|
741
742
|
} );
|
|
742
743
|
} );
|
|
@@ -744,7 +745,7 @@ describe( 'BarChart', () => {
|
|
|
744
745
|
describe( 'Grid tick counts', () => {
|
|
745
746
|
const getPositions = ( selector: string, coordinate: string ) => {
|
|
746
747
|
// eslint-disable-next-line testing-library/no-node-access -- See the visx node constraint above.
|
|
747
|
-
const lines = screen.getByRole( '
|
|
748
|
+
const lines = screen.getByRole( 'application' ).querySelectorAll( selector );
|
|
748
749
|
return Array.from( lines, line => Number( line.getAttribute( coordinate ) ) );
|
|
749
750
|
};
|
|
750
751
|
|
|
@@ -806,24 +807,49 @@ describe( 'BarChart', () => {
|
|
|
806
807
|
);
|
|
807
808
|
} );
|
|
808
809
|
|
|
810
|
+
describe( 'Axis display', () => {
|
|
811
|
+
test.each( [
|
|
812
|
+
[ 'vertical', 'x', 'y' ],
|
|
813
|
+
[ 'vertical', 'y', 'x' ],
|
|
814
|
+
[ 'horizontal', 'x', 'y' ],
|
|
815
|
+
[ 'horizontal', 'y', 'x' ],
|
|
816
|
+
] )( 'skips a hidden axis (%s, hidden %s)', ( orientation, hidden, shown ) => {
|
|
817
|
+
renderWithTheme( {
|
|
818
|
+
orientation,
|
|
819
|
+
options: {
|
|
820
|
+
axis: {
|
|
821
|
+
[ hidden ]: { display: false, axisClassName: 'test-hidden-axis' },
|
|
822
|
+
[ shown ]: { axisClassName: 'test-shown-axis' },
|
|
823
|
+
},
|
|
824
|
+
},
|
|
825
|
+
} );
|
|
826
|
+
const chart = screen.getByRole( 'application' );
|
|
827
|
+
|
|
828
|
+
// eslint-disable-next-line testing-library/no-node-access -- See the visx node constraint above.
|
|
829
|
+
expect( chart.querySelector( '.test-hidden-axis' ) ).toBeNull();
|
|
830
|
+
// eslint-disable-next-line testing-library/no-node-access -- See the visx node constraint above.
|
|
831
|
+
expect( chart.querySelector( '.test-shown-axis' ) ).not.toBeNull();
|
|
832
|
+
} );
|
|
833
|
+
} );
|
|
834
|
+
|
|
809
835
|
describe( 'Grid Visibility', () => {
|
|
810
836
|
test( 'renders with different grid visibility options', () => {
|
|
811
837
|
const { rerender } = renderWithTheme( { gridVisibility: 'x' } );
|
|
812
|
-
expect( screen.getByRole( '
|
|
838
|
+
expect( screen.getByRole( 'application', { name: /bar chart/i } ) ).toBeInTheDocument();
|
|
813
839
|
|
|
814
840
|
rerender(
|
|
815
841
|
<GlobalChartsProvider>
|
|
816
842
|
<BarChart { ...defaultProps } gridVisibility="y" />
|
|
817
843
|
</GlobalChartsProvider>
|
|
818
844
|
);
|
|
819
|
-
expect( screen.getByRole( '
|
|
845
|
+
expect( screen.getByRole( 'application', { name: /bar chart/i } ) ).toBeInTheDocument();
|
|
820
846
|
|
|
821
847
|
rerender(
|
|
822
848
|
<GlobalChartsProvider>
|
|
823
849
|
<BarChart { ...defaultProps } gridVisibility="xy" />
|
|
824
850
|
</GlobalChartsProvider>
|
|
825
851
|
);
|
|
826
|
-
expect( screen.getByRole( '
|
|
852
|
+
expect( screen.getByRole( 'application', { name: /bar chart/i } ) ).toBeInTheDocument();
|
|
827
853
|
} );
|
|
828
854
|
} );
|
|
829
855
|
|
|
@@ -905,7 +931,7 @@ describe( 'BarChart', () => {
|
|
|
905
931
|
|
|
906
932
|
// Scoped to the chart: @visx/text parks a measuring node on document.body,
|
|
907
933
|
// which a negative assertion would otherwise match.
|
|
908
|
-
const inChart = () => within( screen.getByRole( '
|
|
934
|
+
const inChart = () => within( screen.getByRole( 'application', { name: /bar chart/i } ) );
|
|
909
935
|
|
|
910
936
|
test( 'leaves tick values alone when a bucket is labelled rather than dated', () => {
|
|
911
937
|
// visx builds the band domain from `label || date`, so a labelled bucket
|
|
@@ -1109,7 +1135,7 @@ describe( 'BarChart', () => {
|
|
|
1109
1135
|
// on document.body, which outlives RTL's cleanup still holding the last
|
|
1110
1136
|
// string measured. Query inside the chart so a previous test's measurement
|
|
1111
1137
|
// can't answer for this one's axis.
|
|
1112
|
-
const inChart = () => within( screen.getByRole( '
|
|
1138
|
+
const inChart = () => within( screen.getByRole( 'application', { name: /bar chart/i } ) );
|
|
1113
1139
|
|
|
1114
1140
|
test( 'renders distinct date ticks for daily buckets within a year', () => {
|
|
1115
1141
|
renderWithTheme( {
|
|
@@ -1545,7 +1571,7 @@ describe( 'BarChart', () => {
|
|
|
1545
1571
|
],
|
|
1546
1572
|
} );
|
|
1547
1573
|
|
|
1548
|
-
screen.getByRole( '
|
|
1574
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
1549
1575
|
await user.keyboard( '{ArrowRight}' );
|
|
1550
1576
|
|
|
1551
1577
|
// The month tick reads "2024" here; the tooltip names the bucket itself.
|
|
@@ -1604,7 +1630,7 @@ describe( 'BarChart', () => {
|
|
|
1604
1630
|
|
|
1605
1631
|
test( 'renders with patterns', () => {
|
|
1606
1632
|
renderWithTheme( { withPatterns: true } );
|
|
1607
|
-
expect( screen.getByRole( '
|
|
1633
|
+
expect( screen.getByRole( 'application', { name: /bar chart/i } ) ).toBeInTheDocument();
|
|
1608
1634
|
|
|
1609
1635
|
// Check that pattern definitions container is present
|
|
1610
1636
|
expect( screen.getByTestId( 'bar-chart-patterns' ) ).toBeInTheDocument();
|
|
@@ -1612,7 +1638,7 @@ describe( 'BarChart', () => {
|
|
|
1612
1638
|
|
|
1613
1639
|
test( 'renders without patterns by default', () => {
|
|
1614
1640
|
renderWithTheme( { withPatterns: false } );
|
|
1615
|
-
expect( screen.getByRole( '
|
|
1641
|
+
expect( screen.getByRole( 'application', { name: /bar chart/i } ) ).toBeInTheDocument();
|
|
1616
1642
|
|
|
1617
1643
|
// Check that no pattern definitions container is present
|
|
1618
1644
|
expect( screen.queryByTestId( 'bar-chart-patterns' ) ).not.toBeInTheDocument();
|
|
@@ -1654,17 +1680,26 @@ describe( 'BarChart', () => {
|
|
|
1654
1680
|
describe( 'Chart Focus and Accessibility Attributes', () => {
|
|
1655
1681
|
test( 'chart container has proper accessibility attributes', () => {
|
|
1656
1682
|
renderWithTheme();
|
|
1657
|
-
const chart = screen.getByRole( '
|
|
1683
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
1658
1684
|
|
|
1659
1685
|
expect( chart ).toHaveAttribute( 'tabIndex', '0' );
|
|
1660
|
-
expect( chart ).toHaveAttribute( 'role', '
|
|
1686
|
+
expect( chart ).toHaveAttribute( 'role', 'application' );
|
|
1661
1687
|
expect( chart ).toHaveAttribute( 'aria-label', 'Bar chart' );
|
|
1662
1688
|
} );
|
|
1663
1689
|
|
|
1690
|
+
test.each( [
|
|
1691
|
+
[ undefined, 'Bar chart' ],
|
|
1692
|
+
[ 'Views over time', 'Views over time' ],
|
|
1693
|
+
] )( 'names the chart container from ariaLabel %p', ( ariaLabel, name ) => {
|
|
1694
|
+
renderWithTheme( { ariaLabel } );
|
|
1695
|
+
expect( screen.getByRole( 'application', { name } ) ).toBeInTheDocument();
|
|
1696
|
+
expect( screen.queryByLabelText( 'XYChart' ) ).not.toBeInTheDocument();
|
|
1697
|
+
} );
|
|
1698
|
+
|
|
1664
1699
|
test( 'chart container can receive focus', async () => {
|
|
1665
1700
|
const user = userEvent.setup();
|
|
1666
1701
|
renderWithTheme();
|
|
1667
|
-
const chart = screen.getByRole( '
|
|
1702
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
1668
1703
|
|
|
1669
1704
|
await user.tab();
|
|
1670
1705
|
expect( chart ).toHaveFocus();
|
|
@@ -1694,7 +1729,7 @@ describe( 'BarChart', () => {
|
|
|
1694
1729
|
const onDatumActivate = jest.fn();
|
|
1695
1730
|
renderWithTheme( { withTooltips: true, data: [ SERIES_A ], onDatumActivate } );
|
|
1696
1731
|
|
|
1697
|
-
screen.getByRole( '
|
|
1732
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
1698
1733
|
await user.keyboard( '{ArrowRight}{ArrowRight}{Enter}' );
|
|
1699
1734
|
|
|
1700
1735
|
expect( onDatumActivate ).toHaveBeenCalledTimes( 1 );
|
|
@@ -1710,7 +1745,7 @@ describe( 'BarChart', () => {
|
|
|
1710
1745
|
const onDatumActivate = jest.fn();
|
|
1711
1746
|
renderWithTheme( { withTooltips: true, data: [ SERIES_A ], onDatumActivate } );
|
|
1712
1747
|
|
|
1713
|
-
screen.getByRole( '
|
|
1748
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
1714
1749
|
await user.keyboard( '{ArrowRight}[Space]' );
|
|
1715
1750
|
|
|
1716
1751
|
expect( onDatumActivate ).toHaveBeenCalledWith( {
|
|
@@ -1728,7 +1763,7 @@ describe( 'BarChart', () => {
|
|
|
1728
1763
|
const onDatumActivate = jest.fn();
|
|
1729
1764
|
renderWithTheme( { withTooltips: true, data: [ SERIES_A, SERIES_B ], onDatumActivate } );
|
|
1730
1765
|
|
|
1731
|
-
screen.getByRole( '
|
|
1766
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
1732
1767
|
await user.keyboard( '{ArrowRight}{ArrowRight}{Enter}' );
|
|
1733
1768
|
|
|
1734
1769
|
expect( onDatumActivate ).toHaveBeenCalledWith( {
|
|
@@ -1743,7 +1778,7 @@ describe( 'BarChart', () => {
|
|
|
1743
1778
|
const onDatumActivate = jest.fn();
|
|
1744
1779
|
renderWithTheme( { withTooltips: true, data: [ SERIES_A ], onDatumActivate } );
|
|
1745
1780
|
|
|
1746
|
-
screen.getByRole( '
|
|
1781
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
1747
1782
|
await user.keyboard( '{Enter}' );
|
|
1748
1783
|
|
|
1749
1784
|
expect( onDatumActivate ).not.toHaveBeenCalled();
|
|
@@ -1777,7 +1812,7 @@ describe( 'BarChart', () => {
|
|
|
1777
1812
|
],
|
|
1778
1813
|
} );
|
|
1779
1814
|
|
|
1780
|
-
const chart = screen.getByRole( '
|
|
1815
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
1781
1816
|
chart.focus();
|
|
1782
1817
|
|
|
1783
1818
|
// Single tab should focus on the first tooltip.
|
|
@@ -1828,7 +1863,7 @@ describe( 'BarChart', () => {
|
|
|
1828
1863
|
],
|
|
1829
1864
|
} );
|
|
1830
1865
|
|
|
1831
|
-
const chart = screen.getByRole( '
|
|
1866
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
1832
1867
|
chart.focus();
|
|
1833
1868
|
|
|
1834
1869
|
await user.keyboard( '{ArrowRight}' );
|
|
@@ -1877,7 +1912,7 @@ describe( 'BarChart', () => {
|
|
|
1877
1912
|
],
|
|
1878
1913
|
} );
|
|
1879
1914
|
|
|
1880
|
-
const chart = screen.getByRole( '
|
|
1915
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
1881
1916
|
chart.focus();
|
|
1882
1917
|
|
|
1883
1918
|
// Right arrow key should focus on the first tooltip.
|
|
@@ -1900,7 +1935,7 @@ describe( 'BarChart', () => {
|
|
|
1900
1935
|
} );
|
|
1901
1936
|
} );
|
|
1902
1937
|
|
|
1903
|
-
describe( '
|
|
1938
|
+
describe( 'Keyboard navigation boundaries', () => {
|
|
1904
1939
|
// Three points, so a clamp at a boundary lands on a different index than a wrap would.
|
|
1905
1940
|
const threePointData = [
|
|
1906
1941
|
{
|
|
@@ -1918,7 +1953,7 @@ describe( 'BarChart', () => {
|
|
|
1918
1953
|
const user = userEvent.setup();
|
|
1919
1954
|
renderWithTheme( { withTooltips: true, data: threePointData } );
|
|
1920
1955
|
|
|
1921
|
-
const chart = screen.getByRole( '
|
|
1956
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
1922
1957
|
chart.focus();
|
|
1923
1958
|
|
|
1924
1959
|
// Move to the first, then on to the last (third) point.
|
|
@@ -1928,7 +1963,7 @@ describe( 'BarChart', () => {
|
|
|
1928
1963
|
expect( screen.getByTestId( 'chart-tooltip-2' ) ).toHaveFocus();
|
|
1929
1964
|
|
|
1930
1965
|
// Right arrow at the last point must not move focus: the highlighted
|
|
1931
|
-
// bar/tooltip stays visible and focused (
|
|
1966
|
+
// bar/tooltip stays visible and focused (focus does not move).
|
|
1932
1967
|
await user.keyboard( '{ArrowRight}' );
|
|
1933
1968
|
expect( screen.getByTestId( 'chart-tooltip-2' ) ).toBeInTheDocument();
|
|
1934
1969
|
expect( screen.getByTestId( 'chart-tooltip-2' ) ).toHaveFocus();
|
|
@@ -1943,7 +1978,7 @@ describe( 'BarChart', () => {
|
|
|
1943
1978
|
const user = userEvent.setup();
|
|
1944
1979
|
renderWithTheme( { withTooltips: true, data: threePointData } );
|
|
1945
1980
|
|
|
1946
|
-
screen.getByRole( '
|
|
1981
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
1947
1982
|
await user.keyboard( '{ArrowLeft}' );
|
|
1948
1983
|
|
|
1949
1984
|
expect( screen.getByTestId( 'chart-tooltip-0' ) ).toHaveFocus();
|
|
@@ -1977,7 +2012,7 @@ describe( 'BarChart', () => {
|
|
|
1977
2012
|
],
|
|
1978
2013
|
} );
|
|
1979
2014
|
|
|
1980
|
-
const chart = screen.getByRole( '
|
|
2015
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
1981
2016
|
chart.focus();
|
|
1982
2017
|
|
|
1983
2018
|
await user.keyboard( '{ArrowRight}' );
|
|
@@ -2012,7 +2047,7 @@ describe( 'BarChart', () => {
|
|
|
2012
2047
|
],
|
|
2013
2048
|
} );
|
|
2014
2049
|
|
|
2015
|
-
const chart = screen.getByRole( '
|
|
2050
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
2016
2051
|
chart.focus();
|
|
2017
2052
|
|
|
2018
2053
|
// Slot 0 is the first primary bar (Mon); slot 1 must be the SECOND primary
|
|
@@ -2044,7 +2079,7 @@ describe( 'BarChart', () => {
|
|
|
2044
2079
|
],
|
|
2045
2080
|
} );
|
|
2046
2081
|
|
|
2047
|
-
screen.getByRole( '
|
|
2082
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
2048
2083
|
await user.keyboard( '{ArrowRight}' );
|
|
2049
2084
|
|
|
2050
2085
|
const tooltip = screen.getByTestId( 'chart-tooltip-0' );
|
|
@@ -2077,7 +2112,7 @@ describe( 'BarChart', () => {
|
|
|
2077
2112
|
],
|
|
2078
2113
|
} );
|
|
2079
2114
|
|
|
2080
|
-
screen.getByRole( '
|
|
2115
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
2081
2116
|
await user.keyboard( '{ArrowRight}' );
|
|
2082
2117
|
|
|
2083
2118
|
const tooltip = screen.getByTestId( 'chart-tooltip-0' );
|
|
@@ -2137,7 +2172,7 @@ describe( 'BarChart', () => {
|
|
|
2137
2172
|
],
|
|
2138
2173
|
} );
|
|
2139
2174
|
|
|
2140
|
-
const chart = screen.getByRole( '
|
|
2175
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
2141
2176
|
chart.focus();
|
|
2142
2177
|
|
|
2143
2178
|
// Chart should be in focus.
|
|
@@ -2177,7 +2212,7 @@ describe( 'BarChart', () => {
|
|
|
2177
2212
|
],
|
|
2178
2213
|
} );
|
|
2179
2214
|
|
|
2180
|
-
const chart = screen.getByRole( '
|
|
2215
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
2181
2216
|
chart.focus();
|
|
2182
2217
|
|
|
2183
2218
|
// Navigate to the first bar
|
|
@@ -2212,7 +2247,7 @@ describe( 'BarChart', () => {
|
|
|
2212
2247
|
],
|
|
2213
2248
|
} );
|
|
2214
2249
|
|
|
2215
|
-
const chart = screen.getByRole( '
|
|
2250
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
2216
2251
|
chart.focus();
|
|
2217
2252
|
|
|
2218
2253
|
// Navigate to the first bar (Series A, Jan 1)
|
|
@@ -2244,7 +2279,7 @@ describe( 'BarChart', () => {
|
|
|
2244
2279
|
const user = userEvent.setup();
|
|
2245
2280
|
renderWithTheme( { withTooltips: true, data } );
|
|
2246
2281
|
|
|
2247
|
-
screen.getByRole( '
|
|
2282
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
2248
2283
|
await user.keyboard( '{ArrowRight}' );
|
|
2249
2284
|
|
|
2250
2285
|
expect( screen.getByTestId( 'chart-tooltip-0' ) ).toHaveTextContent( 'No data' );
|
|
@@ -2255,7 +2290,7 @@ describe( 'BarChart', () => {
|
|
|
2255
2290
|
const user = userEvent.setup();
|
|
2256
2291
|
renderWithTheme( { withTooltips: true, data } );
|
|
2257
2292
|
|
|
2258
|
-
screen.getByRole( '
|
|
2293
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
2259
2294
|
await user.keyboard( '{ArrowRight}{ArrowRight}' );
|
|
2260
2295
|
|
|
2261
2296
|
expect( screen.getByTestId( 'chart-tooltip-1' ) ).toHaveTextContent( '20' );
|
|
@@ -2288,7 +2323,7 @@ describe( 'BarChart', () => {
|
|
|
2288
2323
|
];
|
|
2289
2324
|
renderWithTheme( { withTooltips: true, data: twoSeries } );
|
|
2290
2325
|
|
|
2291
|
-
screen.getByRole( '
|
|
2326
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
2292
2327
|
await user.keyboard( '{ArrowRight}{ArrowRight}{ArrowRight}{ArrowRight}' );
|
|
2293
2328
|
|
|
2294
2329
|
expect( screen.getByTestId( 'chart-tooltip-3' ) ).toHaveTextContent( '15' );
|
|
@@ -2315,7 +2350,7 @@ describe( 'BarChart', () => {
|
|
|
2315
2350
|
|
|
2316
2351
|
renderWithTheme( { withTooltips: true, renderTooltip: customTooltipRenderer } );
|
|
2317
2352
|
|
|
2318
|
-
const chart = screen.getByRole( '
|
|
2353
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
2319
2354
|
chart.focus();
|
|
2320
2355
|
|
|
2321
2356
|
// Click on right arrow key to focus on the first tooltip.
|
|
@@ -2347,7 +2382,9 @@ describe( 'BarChart', () => {
|
|
|
2347
2382
|
],
|
|
2348
2383
|
} );
|
|
2349
2384
|
|
|
2350
|
-
const svgElement = screen
|
|
2385
|
+
const svgElement = screen
|
|
2386
|
+
.getByRole( 'application', { name: /bar chart/i } )
|
|
2387
|
+
.querySelector( 'svg' );
|
|
2351
2388
|
const bars = svgElement?.querySelectorAll( '.visx-bar-group rect' );
|
|
2352
2389
|
|
|
2353
2390
|
// Should have 2 bars
|
|
@@ -2374,7 +2411,9 @@ describe( 'BarChart', () => {
|
|
|
2374
2411
|
],
|
|
2375
2412
|
} );
|
|
2376
2413
|
|
|
2377
|
-
const svgElement = screen
|
|
2414
|
+
const svgElement = screen
|
|
2415
|
+
.getByRole( 'application', { name: /bar chart/i } )
|
|
2416
|
+
.querySelector( 'svg' );
|
|
2378
2417
|
const bars = svgElement?.querySelectorAll( '.visx-bar-group rect' );
|
|
2379
2418
|
|
|
2380
2419
|
// Should have 2 bars
|
|
@@ -2407,7 +2446,9 @@ describe( 'BarChart', () => {
|
|
|
2407
2446
|
orientation: 'horizontal',
|
|
2408
2447
|
} );
|
|
2409
2448
|
|
|
2410
|
-
const svgElement = screen
|
|
2449
|
+
const svgElement = screen
|
|
2450
|
+
.getByRole( 'application', { name: /bar chart/i } )
|
|
2451
|
+
.querySelector( 'svg' );
|
|
2411
2452
|
const bars = svgElement?.querySelectorAll( '.visx-bar-group rect' );
|
|
2412
2453
|
|
|
2413
2454
|
// Both bars should be visible in horizontal mode
|
|
@@ -2436,7 +2477,9 @@ describe( 'BarChart', () => {
|
|
|
2436
2477
|
],
|
|
2437
2478
|
} );
|
|
2438
2479
|
|
|
2439
|
-
const svgElement = screen
|
|
2480
|
+
const svgElement = screen
|
|
2481
|
+
.getByRole( 'application', { name: /bar chart/i } )
|
|
2482
|
+
.querySelector( 'svg' );
|
|
2440
2483
|
const bars = svgElement?.querySelectorAll( '.visx-bar-group rect' );
|
|
2441
2484
|
|
|
2442
2485
|
expect( bars?.length ).toBe( 2 );
|
|
@@ -2476,7 +2519,7 @@ describe( 'BarChart', () => {
|
|
|
2476
2519
|
},
|
|
2477
2520
|
} );
|
|
2478
2521
|
|
|
2479
|
-
expect( screen.getByRole( '
|
|
2522
|
+
expect( screen.getByRole( 'application', { name: /bar chart/i } ) ).toBeInTheDocument();
|
|
2480
2523
|
} );
|
|
2481
2524
|
|
|
2482
2525
|
test( 'truncates labels with CSS text-overflow ellipsis', () => {
|
|
@@ -2569,7 +2612,7 @@ describe( 'BarChart', () => {
|
|
|
2569
2612
|
} );
|
|
2570
2613
|
|
|
2571
2614
|
// Chart should still render without errors
|
|
2572
|
-
expect( screen.getByRole( '
|
|
2615
|
+
expect( screen.getByRole( 'application', { name: /bar chart/i } ) ).toBeInTheDocument();
|
|
2573
2616
|
|
|
2574
2617
|
// Labels should still be present and have minimum width applied
|
|
2575
2618
|
const label = screen.getByText( /Very Long Category Label One/i );
|
|
@@ -2865,7 +2908,7 @@ describe( 'BarChart', () => {
|
|
|
2865
2908
|
</GlobalChartsProvider>
|
|
2866
2909
|
);
|
|
2867
2910
|
|
|
2868
|
-
const chart = screen.getByRole( '
|
|
2911
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
2869
2912
|
chart.focus();
|
|
2870
2913
|
|
|
2871
2914
|
// Navigate through all 4 primary slots (2 series × 2 data points)
|
|
@@ -3151,8 +3194,9 @@ describe( 'BarChart', () => {
|
|
|
3151
3194
|
</GlobalChartsProvider>
|
|
3152
3195
|
);
|
|
3153
3196
|
|
|
3197
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
3154
3198
|
// eslint-disable-next-line testing-library/no-node-access
|
|
3155
|
-
const svgElement =
|
|
3199
|
+
const svgElement = chart.querySelector( 'svg' );
|
|
3156
3200
|
// eslint-disable-next-line testing-library/no-node-access
|
|
3157
3201
|
expect( svgElement?.querySelectorAll( '.visx-bar-group rect' ) ).toHaveLength( 2 );
|
|
3158
3202
|
|
|
@@ -3357,7 +3401,7 @@ describe( 'BarChart', () => {
|
|
|
3357
3401
|
const user = userEvent.setup();
|
|
3358
3402
|
const toggle = mountWithVisibilityToggle();
|
|
3359
3403
|
|
|
3360
|
-
screen.getByRole( '
|
|
3404
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
3361
3405
|
for ( let i = 0; i < 6; i++ ) {
|
|
3362
3406
|
await user.keyboard( '{ArrowRight}' );
|
|
3363
3407
|
}
|
|
@@ -3402,7 +3446,7 @@ describe( 'BarChart', () => {
|
|
|
3402
3446
|
],
|
|
3403
3447
|
} );
|
|
3404
3448
|
|
|
3405
|
-
screen.getByRole( '
|
|
3449
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
3406
3450
|
for ( let i = 0; i < 6; i++ ) {
|
|
3407
3451
|
await user.keyboard( '{ArrowRight}' );
|
|
3408
3452
|
}
|
|
@@ -3459,12 +3503,12 @@ describe( 'BarChart', () => {
|
|
|
3459
3503
|
</GlobalChartsProvider>
|
|
3460
3504
|
);
|
|
3461
3505
|
|
|
3462
|
-
screen.getByRole( '
|
|
3506
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).focus();
|
|
3463
3507
|
await user.keyboard( '{ArrowRight}{ArrowRight}' );
|
|
3464
3508
|
expect( screen.getByTestId( 'chart-tooltip-1' ) ).toHaveTextContent( 'Series A' );
|
|
3465
3509
|
|
|
3466
3510
|
// Move focus out of the chart, then swap which series is hidden without changing the count.
|
|
3467
|
-
screen.getByRole( '
|
|
3511
|
+
screen.getByRole( 'application', { name: /bar chart/i } ).blur();
|
|
3468
3512
|
act( () => context.setChartHiddenSeries( 'test-equal-count-swap', [ 'Series A' ] ) );
|
|
3469
3513
|
|
|
3470
3514
|
expect( screen.queryAllByTestId( /^chart-tooltip-/ ) ).toHaveLength( 0 );
|
|
@@ -3503,7 +3547,7 @@ describe( 'BarChart', () => {
|
|
|
3503
3547
|
|
|
3504
3548
|
await user.click( screen.getByRole( 'button', { name: /Series B: visible/i } ) );
|
|
3505
3549
|
|
|
3506
|
-
const chart = screen.getByRole( '
|
|
3550
|
+
const chart = screen.getByRole( 'application', { name: /bar chart/i } );
|
|
3507
3551
|
chart.focus();
|
|
3508
3552
|
|
|
3509
3553
|
for ( let i = 0; i < 5; i++ ) {
|
|
@@ -19,6 +19,8 @@ export type BandHighlightSelection = {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export interface BarChartProps extends BaseChartProps< SeriesData[] >, SeriesVisibilityProps {
|
|
22
|
+
/** Accessible name of the chart. Defaults to a localized "Bar chart". */
|
|
23
|
+
ariaLabel?: string;
|
|
22
24
|
/**
|
|
23
25
|
* Legend configuration. Supports `collapseGroups` on top of the shared options.
|
|
24
26
|
*/
|
|
@@ -72,7 +72,9 @@ describe( 'BarListChart', () => {
|
|
|
72
72
|
/* eslint-disable testing-library/no-node-access */
|
|
73
73
|
const barFills = () =>
|
|
74
74
|
Array.from(
|
|
75
|
-
screen
|
|
75
|
+
screen
|
|
76
|
+
.getByRole( 'application' )
|
|
77
|
+
.querySelectorAll< SVGRectElement >( '.visx-bar-group rect' )
|
|
76
78
|
).map( bar => bar.getAttribute( 'fill' ) );
|
|
77
79
|
/* eslint-enable testing-library/no-node-access */
|
|
78
80
|
|
|
@@ -427,7 +427,7 @@ const ConversionFunnelChartInternal: FC< ConversionFunnelChartProps > = ( {
|
|
|
427
427
|
className={ standaloneScopeClass }
|
|
428
428
|
style={ tooltipStyle }
|
|
429
429
|
>
|
|
430
|
-
{ tooltipContent }
|
|
430
|
+
<div role="tooltip">{ tooltipContent }</div>
|
|
431
431
|
</BoundedTooltip>
|
|
432
432
|
);
|
|
433
433
|
} )() }
|
|
@@ -430,6 +430,15 @@ describe( 'ConversionFunnelChart', () => {
|
|
|
430
430
|
} );
|
|
431
431
|
} );
|
|
432
432
|
|
|
433
|
+
it( 'exposes the tooltip content with role="tooltip"', async () => {
|
|
434
|
+
const user = userEvent.setup();
|
|
435
|
+
renderWithoutTheme( <ConversionFunnelChart { ...defaultProps } /> );
|
|
436
|
+
|
|
437
|
+
await user.click( screen.getByRole( 'button', { name: /cart/i } ) );
|
|
438
|
+
|
|
439
|
+
expect( screen.getByRole( 'tooltip' ) ).toHaveTextContent( 'Cart' );
|
|
440
|
+
} );
|
|
441
|
+
|
|
433
442
|
it( 'merges tooltipStyle onto the tooltip box', async () => {
|
|
434
443
|
const user = userEvent.setup();
|
|
435
444
|
renderWithoutTheme(
|