@automattic/charts 4.1.1 → 4.3.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 +39 -0
- package/TOKENS.md +1 -1
- package/dist/index.cjs +1335 -348
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +30 -1
- package/dist/index.d.cts +129 -8
- package/dist/index.d.ts +129 -8
- package/dist/index.js +1336 -351
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/charts/area-chart/area-chart.tsx +42 -10
- package/src/charts/area-chart/private/overlays.tsx +1 -2
- package/src/charts/area-chart/private/validate-data.ts +2 -3
- package/src/charts/area-chart/test/area-chart.test.tsx +72 -1
- package/src/charts/bar-chart/bar-chart.tsx +137 -70
- package/src/charts/bar-chart/index.ts +1 -1
- package/src/charts/bar-chart/private/band-highlight.tsx +87 -0
- package/src/charts/bar-chart/private/band-scale.ts +13 -0
- package/src/charts/bar-chart/private/band-tooltip.tsx +138 -0
- package/src/charts/bar-chart/private/comparison-bars.tsx +5 -10
- package/src/charts/bar-chart/private/index.ts +3 -0
- package/src/charts/bar-chart/private/test/band-highlight.test.tsx +135 -0
- package/src/charts/bar-chart/private/test/band-tooltip.test.ts +26 -0
- package/src/charts/bar-chart/private/test/value-domain.test.ts +100 -0
- package/src/charts/bar-chart/private/truncated-tick-component.tsx +1 -1
- package/src/charts/bar-chart/private/use-bar-chart-options.ts +13 -40
- package/src/charts/bar-chart/private/value-domain.ts +90 -0
- package/src/charts/bar-chart/test/bar-chart-pointer-events.test.tsx +11 -23
- package/src/charts/bar-chart/test/bar-chart.test.tsx +1058 -19
- package/src/charts/bar-chart/types.ts +39 -0
- package/src/charts/bar-list-chart/bar-list-chart.tsx +5 -3
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +1 -1
- package/src/charts/conversion-funnel-chart/types.ts +4 -2
- package/src/charts/geo-chart/types.ts +4 -2
- package/src/charts/heatmap-chart/build-calendar-data.ts +5 -11
- package/src/charts/heatmap-chart/build-month-calendar-data.ts +124 -0
- package/src/charts/heatmap-chart/heatmap-chart.module.scss +32 -0
- package/src/charts/heatmap-chart/heatmap-chart.tsx +191 -114
- package/src/charts/heatmap-chart/index.ts +6 -0
- package/src/charts/heatmap-chart/private/civil-day.ts +26 -0
- package/src/charts/heatmap-chart/private/column-groups.ts +71 -0
- package/src/charts/heatmap-chart/private/heatmap-legend.tsx +19 -3
- package/src/charts/heatmap-chart/private/index.ts +2 -0
- package/src/charts/heatmap-chart/private/keyboard-navigation.ts +201 -0
- package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +36 -0
- package/src/charts/heatmap-chart/test/build-month-calendar-data.test.ts +194 -0
- package/src/charts/heatmap-chart/test/column-groups.test.ts +83 -0
- package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +428 -2
- package/src/charts/heatmap-chart/test/keyboard-navigation.test.ts +25 -0
- package/src/charts/heatmap-chart/test/use-month-calendar-heatmap-data.test.tsx +90 -0
- package/src/charts/heatmap-chart/test/zoned-month-calendar-data.test.ts +23 -0
- package/src/charts/heatmap-chart/types.ts +59 -3
- package/src/charts/heatmap-chart/use-month-calendar-heatmap-data.ts +34 -0
- package/src/charts/leaderboard-chart/types.ts +13 -14
- package/src/charts/line-chart/line-chart.tsx +149 -36
- package/src/charts/line-chart/private/index.ts +1 -0
- package/src/charts/line-chart/private/line-chart-annotation.tsx +1 -1
- package/src/charts/line-chart/private/line-chart-glyph.tsx +17 -3
- package/src/charts/line-chart/private/nearest-pointer-events.tsx +155 -0
- package/src/charts/line-chart/test/line-chart.test.tsx +604 -9
- package/src/charts/line-chart/types.ts +4 -3
- package/src/charts/private/readings.ts +34 -0
- package/src/charts/private/test/readings.test.ts +29 -0
- package/src/charts/private/test/whole-number-ticks.test.tsx +57 -0
- package/src/charts/private/time-axis-options.ts +1 -1
- package/src/charts/private/whole-number-ticks.tsx +79 -0
- package/src/charts/private/with-responsive/with-responsive.tsx +2 -2
- package/src/charts/private/x-zoom/test/x-zoom.test.tsx +1 -3
- package/src/charts/private/x-zoom/x-zoom.tsx +1 -2
- package/src/components/legend/hooks/test/use-chart-legend-items.test.tsx +124 -0
- package/src/components/legend/hooks/use-chart-legend-items.ts +60 -2
- package/src/components/legend/private/base-legend.tsx +37 -22
- package/src/components/legend/test/legend.test.tsx +84 -0
- package/src/components/legend/types.ts +2 -0
- package/src/components/legend/utils/index.ts +0 -1
- package/src/components/tooltip/accessible-tooltip.tsx +128 -41
- package/src/components/tooltip/base-tooltip.module.scss +6 -1
- package/src/components/tooltip/private/bounded-tooltip.tsx +12 -6
- package/src/components/tooltip/test/accessible-tooltip.test.tsx +128 -0
- package/src/components/tooltip/test/bounded-tooltip.test.tsx +17 -0
- package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +7 -0
- package/src/components/tooltip/xy-chart-tooltip.tsx +9 -10
- package/src/hooks/test/use-chart-margin.test.tsx +35 -0
- package/src/hooks/test/use-element-size.test.tsx +13 -13
- package/src/hooks/use-chart-margin.tsx +7 -2
- package/src/hooks/use-xychart-theme.ts +1 -1
- package/src/index.ts +7 -1
- package/src/providers/chart-context/global-charts-provider.tsx +1 -1
- package/src/providers/chart-context/test/chart-context.test.tsx +4 -4
- package/src/providers/chart-context/themes.ts +1 -0
- package/src/styles/test/chart-scope.test.ts +1 -1
- package/src/types.ts +14 -13
- package/src/utils/format-metric-value.ts +4 -4
- package/src/utils/get-edge-tick-widths.ts +1 -1
- package/src/utils/get-styles.ts +1 -1
- package/src/visx/types.ts +7 -10
- package/src/components/legend/utils/label-transform-factory.ts +0 -24
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
render,
|
|
3
|
+
renderHook,
|
|
4
|
+
screen,
|
|
5
|
+
within,
|
|
6
|
+
act,
|
|
7
|
+
waitFor,
|
|
8
|
+
fireEvent,
|
|
9
|
+
} from '@testing-library/react';
|
|
2
10
|
import userEvent from '@testing-library/user-event';
|
|
3
11
|
import { GlobalChartsProvider } from '../../../providers';
|
|
4
12
|
import { useGlobalChartsContext } from '../../../providers/chart-context/hooks/use-global-charts-context';
|
|
13
|
+
import { steadyTrafficData } from '../../../stories/sample-data';
|
|
5
14
|
import BarChart, { BarChartUnresponsive } from '../bar-chart';
|
|
6
15
|
import { useBarChartOptions } from '../private';
|
|
7
16
|
import type { GlobalChartsContextValue } from '../../../providers/chart-context/types';
|
|
@@ -30,6 +39,17 @@ describe( 'BarChart', () => {
|
|
|
30
39
|
],
|
|
31
40
|
};
|
|
32
41
|
|
|
42
|
+
// visx renders bars and grid lines without accessible roles or configurable test IDs.
|
|
43
|
+
const getBarRects = () => {
|
|
44
|
+
// eslint-disable-next-line testing-library/no-node-access -- See the visx node constraint above.
|
|
45
|
+
return screen.getByRole( 'grid' ).querySelectorAll( '.visx-bar-group rect' );
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const getGridLines = () => {
|
|
49
|
+
// eslint-disable-next-line testing-library/no-node-access -- visx grid lines cannot receive test IDs.
|
|
50
|
+
return screen.getByRole( 'grid' ).querySelectorAll( '.visx-rows line' );
|
|
51
|
+
};
|
|
52
|
+
|
|
33
53
|
const renderWithTheme = ( props = {}, children = undefined ) => {
|
|
34
54
|
return render(
|
|
35
55
|
<GlobalChartsProvider>
|
|
@@ -40,6 +60,450 @@ describe( 'BarChart', () => {
|
|
|
40
60
|
);
|
|
41
61
|
};
|
|
42
62
|
|
|
63
|
+
describe( 'pointer selection and focus return', () => {
|
|
64
|
+
const screenTransform = Object.getOwnPropertyDescriptor( SVGElement.prototype, 'getScreenCTM' );
|
|
65
|
+
beforeAll( () => {
|
|
66
|
+
// jsdom lacks the SVG transform used by visx to convert pointer coordinates.
|
|
67
|
+
Object.defineProperty( SVGElement.prototype, 'getScreenCTM', {
|
|
68
|
+
configurable: true,
|
|
69
|
+
value: () => null,
|
|
70
|
+
} );
|
|
71
|
+
} );
|
|
72
|
+
|
|
73
|
+
afterAll( () => {
|
|
74
|
+
if ( screenTransform ) {
|
|
75
|
+
Object.defineProperty( SVGElement.prototype, 'getScreenCTM', screenTransform );
|
|
76
|
+
} else {
|
|
77
|
+
Reflect.deleteProperty( SVGElement.prototype, 'getScreenCTM' );
|
|
78
|
+
}
|
|
79
|
+
} );
|
|
80
|
+
|
|
81
|
+
const barGeometry = () => {
|
|
82
|
+
const bars = getBarRects();
|
|
83
|
+
return Array.from( bars, bar =>
|
|
84
|
+
[ 'x', 'y', 'width', 'height' ].map( name => Number( bar.getAttribute( name ) ) )
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const pointer = ( type: string, clientX: number, clientY: number ) => {
|
|
89
|
+
// visx owns the pointer capture rect and does not expose an attribute prop for it.
|
|
90
|
+
// eslint-disable-next-line testing-library/no-node-access
|
|
91
|
+
const target = screen.getByRole( 'grid' ).querySelector( 'svg > rect[fill="transparent"]' );
|
|
92
|
+
fireEvent( target, new MouseEvent( type, { bubbles: true, clientX, clientY } ) );
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const hover = ( clientX: number, clientY: number ) =>
|
|
96
|
+
pointer( 'pointermove', clientX, clientY );
|
|
97
|
+
|
|
98
|
+
test( 'Escape returns focus to the grid without reopening either tooltip', async () => {
|
|
99
|
+
jest.useFakeTimers();
|
|
100
|
+
try {
|
|
101
|
+
const user = userEvent.setup( { advanceTimers: jest.advanceTimersByTime } );
|
|
102
|
+
renderWithTheme( { withTooltips: true } );
|
|
103
|
+
const chart = screen.getByRole( 'grid', { name: /bar chart/i } );
|
|
104
|
+
await user.tab();
|
|
105
|
+
await user.keyboard( '{ArrowRight}' );
|
|
106
|
+
expect( screen.getByTestId( 'chart-tooltip-0' ) ).toHaveFocus();
|
|
107
|
+
await user.keyboard( '{Escape}' );
|
|
108
|
+
await act( async () => {
|
|
109
|
+
jest.advanceTimersByTime( 5000 );
|
|
110
|
+
} );
|
|
111
|
+
expect( chart ).toHaveFocus();
|
|
112
|
+
expect( screen.queryByTestId( /^chart-tooltip-\d+$/ ) ).not.toBeInTheDocument();
|
|
113
|
+
expect( screen.queryByRole( 'tooltip' ) ).not.toBeInTheDocument();
|
|
114
|
+
} finally {
|
|
115
|
+
jest.useRealTimers();
|
|
116
|
+
}
|
|
117
|
+
} );
|
|
118
|
+
|
|
119
|
+
// The pointer never moves, so visx never fires the leave that would normally close this.
|
|
120
|
+
test( 'closes a hover tooltip when the series it describes is hidden', async () => {
|
|
121
|
+
let context: GlobalChartsContextValue;
|
|
122
|
+
const Grab = () => {
|
|
123
|
+
context = useGlobalChartsContext();
|
|
124
|
+
return null;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
render(
|
|
128
|
+
<GlobalChartsProvider>
|
|
129
|
+
<Grab />
|
|
130
|
+
<BarChart { ...defaultProps } withTooltips chartId="test-hover-then-hide" />
|
|
131
|
+
</GlobalChartsProvider>
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const [ [ x0, , width ] ] = barGeometry();
|
|
135
|
+
hover( x0 + width / 2, 150 );
|
|
136
|
+
await expect( screen.findByRole( 'tooltip' ) ).resolves.toHaveTextContent( 'Jan 1' );
|
|
137
|
+
|
|
138
|
+
act( () => context.toggleSeriesVisibility( 'test-hover-then-hide', 'Series A' ) );
|
|
139
|
+
|
|
140
|
+
expect( screen.queryByRole( 'tooltip' ) ).not.toBeInTheDocument();
|
|
141
|
+
} );
|
|
142
|
+
|
|
143
|
+
test( 'keeps band correction after BarGroup subscriptions on mount and rerender', async () => {
|
|
144
|
+
const chart = ( className: string ) => (
|
|
145
|
+
<GlobalChartsProvider>
|
|
146
|
+
<BarChart { ...defaultProps } withTooltips className={ className } />
|
|
147
|
+
</GlobalChartsProvider>
|
|
148
|
+
);
|
|
149
|
+
const { rerender } = render( chart( 'before' ) );
|
|
150
|
+
const [ [ x0, , width ], [ x1 ] ] = barGeometry();
|
|
151
|
+
const clientX = ( x0 + width / 2 + x1 + width / 2 ) / 2 - 2;
|
|
152
|
+
hover( clientX, 150 );
|
|
153
|
+
await expect( screen.findByRole( 'tooltip' ) ).resolves.toHaveTextContent( 'Jan 1' );
|
|
154
|
+
rerender( chart( 'after' ) );
|
|
155
|
+
hover( x1 + width / 2, 150 );
|
|
156
|
+
await waitFor( () => expect( screen.getByRole( 'tooltip' ) ).toHaveTextContent( 'Jan 2' ) );
|
|
157
|
+
hover( clientX, 150 );
|
|
158
|
+
await waitFor( () => expect( screen.getByRole( 'tooltip' ) ).toHaveTextContent( 'Jan 1' ) );
|
|
159
|
+
} );
|
|
160
|
+
|
|
161
|
+
test( 'clears the highlight callback when the pointer leaves the plot', async () => {
|
|
162
|
+
const onBandHighlightChange = jest.fn();
|
|
163
|
+
renderWithTheme( { withTooltips: true, withBandHighlight: true, onBandHighlightChange } );
|
|
164
|
+
const [ [ x, , width ] ] = barGeometry();
|
|
165
|
+
hover( x + width / 2, 150 );
|
|
166
|
+
await expect(
|
|
167
|
+
screen.findByTestId( 'bar-chart-band-highlight' )
|
|
168
|
+
).resolves.toBeInTheDocument();
|
|
169
|
+
pointer( 'pointerout', x + width / 2, 150 );
|
|
170
|
+
await waitFor( () => expect( onBandHighlightChange ).toHaveBeenLastCalledWith( null ) );
|
|
171
|
+
expect( screen.queryByTestId( 'bar-chart-band-highlight' ) ).not.toBeInTheDocument();
|
|
172
|
+
} );
|
|
173
|
+
|
|
174
|
+
test.each( [ 'comparison', 'hidden' ] )(
|
|
175
|
+
'selects the visible primary band with a %s series',
|
|
176
|
+
async mode => {
|
|
177
|
+
const onBandHighlightChange = jest.fn();
|
|
178
|
+
const onPointerUp = jest.fn();
|
|
179
|
+
renderWithTheme( {
|
|
180
|
+
withTooltips: true,
|
|
181
|
+
withBandHighlight: true,
|
|
182
|
+
onBandHighlightChange,
|
|
183
|
+
onPointerUp,
|
|
184
|
+
defaultHiddenSeries: mode === 'hidden' ? [ 'Other' ] : [],
|
|
185
|
+
data: [
|
|
186
|
+
{ ...defaultProps.data[ 0 ], group: 'scores' },
|
|
187
|
+
{
|
|
188
|
+
label: 'Other',
|
|
189
|
+
group: 'scores',
|
|
190
|
+
options: mode === 'comparison' ? { type: 'comparison' } : {},
|
|
191
|
+
data: defaultProps.data[ 0 ].data,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
} );
|
|
195
|
+
const [ [ x, , width ] ] = barGeometry();
|
|
196
|
+
hover( x + width / 2, 150 );
|
|
197
|
+
const highlight = await screen.findByTestId( 'bar-chart-band-highlight' );
|
|
198
|
+
const otherSlot =
|
|
199
|
+
Number( highlight.getAttribute( 'x' ) ) +
|
|
200
|
+
Number( highlight.getAttribute( 'width' ) ) * 0.85;
|
|
201
|
+
hover( otherSlot, 150 );
|
|
202
|
+
pointer( 'pointerup', otherSlot, 150 );
|
|
203
|
+
for ( const callback of [ onBandHighlightChange, onPointerUp ] ) {
|
|
204
|
+
expect( callback ).not.toHaveBeenCalledWith(
|
|
205
|
+
expect.objectContaining( { key: 'Other' } )
|
|
206
|
+
);
|
|
207
|
+
expect( callback ).toHaveBeenLastCalledWith(
|
|
208
|
+
expect.objectContaining( {
|
|
209
|
+
key: 'Series A',
|
|
210
|
+
index: 0,
|
|
211
|
+
datum: defaultProps.data[ 0 ].data[ 0 ],
|
|
212
|
+
} )
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
test( 'reports the tooltip datum when pressing and releasing in a padded gap', async () => {
|
|
219
|
+
const onPointerDown = jest.fn();
|
|
220
|
+
const onPointerUp = jest.fn();
|
|
221
|
+
renderWithTheme( { withTooltips: true, onPointerDown, onPointerUp } );
|
|
222
|
+
const [ [ x0, , width ], [ x1 ] ] = barGeometry();
|
|
223
|
+
const clientX = ( x0 + width / 2 + x1 + width / 2 ) / 2 - 2;
|
|
224
|
+
hover( clientX, 150 );
|
|
225
|
+
await expect( screen.findByRole( 'tooltip' ) ).resolves.toHaveTextContent( 'Jan 1' );
|
|
226
|
+
pointer( 'pointerdown', clientX, 150 );
|
|
227
|
+
pointer( 'pointerup', clientX, 150 );
|
|
228
|
+
for ( const callback of [ onPointerDown, onPointerUp ] ) {
|
|
229
|
+
expect( callback ).toHaveBeenCalledTimes( 1 );
|
|
230
|
+
expect( callback ).toHaveBeenLastCalledWith(
|
|
231
|
+
expect.objectContaining( {
|
|
232
|
+
key: 'Series A',
|
|
233
|
+
index: 0,
|
|
234
|
+
datum: defaultProps.data[ 0 ].data[ 0 ],
|
|
235
|
+
} )
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
} );
|
|
239
|
+
|
|
240
|
+
test( 'selects the painted grouped bar at the outer edge of a padded band', async () => {
|
|
241
|
+
renderWithTheme( {
|
|
242
|
+
withTooltips: true,
|
|
243
|
+
data: [ 'A', 'B', 'C' ].map( label => ( {
|
|
244
|
+
label,
|
|
245
|
+
data: [
|
|
246
|
+
{ label: 'Jan 1', value: 50 },
|
|
247
|
+
{ label: 'Jan 2', value: 50 },
|
|
248
|
+
{ label: 'Jan 3', value: 50 },
|
|
249
|
+
],
|
|
250
|
+
} ) ),
|
|
251
|
+
} );
|
|
252
|
+
const [ x, y, width, height ] = barGeometry()[ 6 ];
|
|
253
|
+
hover( x + width - 1, y + height / 2 );
|
|
254
|
+
await expect( screen.findByRole( 'tooltip' ) ).resolves.toHaveTextContent( 'Jan 1' );
|
|
255
|
+
} );
|
|
256
|
+
|
|
257
|
+
test.each( [ 'vertical', 'horizontal' ] )(
|
|
258
|
+
'selects the long grouped bar near its base in a %s chart',
|
|
259
|
+
async orientation => {
|
|
260
|
+
renderWithTheme( {
|
|
261
|
+
withTooltips: true,
|
|
262
|
+
orientation,
|
|
263
|
+
data: [
|
|
264
|
+
{
|
|
265
|
+
label: 'Series A',
|
|
266
|
+
data: [
|
|
267
|
+
{ label: 'Jan 1', value: 100 },
|
|
268
|
+
{ label: 'Jan 2', value: 100 },
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
label: 'Series B',
|
|
273
|
+
data: [
|
|
274
|
+
{ label: 'Jan 1', value: 10 },
|
|
275
|
+
{ label: 'Jan 2', value: 10 },
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
} );
|
|
280
|
+
const [ [ x, y, width, height ] ] = barGeometry();
|
|
281
|
+
hover(
|
|
282
|
+
orientation === 'horizontal' ? x + 2 : x + width / 2,
|
|
283
|
+
orientation === 'horizontal' ? y + height / 2 : y + height - 2
|
|
284
|
+
);
|
|
285
|
+
await expect( screen.findByRole( 'tooltip' ) ).resolves.toHaveTextContent( 'Series A' );
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
} );
|
|
289
|
+
|
|
290
|
+
test( 'warns once when highlight options lack tooltips across callback changes and remounts', () => {
|
|
291
|
+
const warn = jest.spyOn( console, 'warn' ).mockImplementation( () => {} );
|
|
292
|
+
try {
|
|
293
|
+
const onBandHighlightChange = jest.fn();
|
|
294
|
+
const { rerender, unmount } = renderWithTheme( {
|
|
295
|
+
withBandHighlight: true,
|
|
296
|
+
onBandHighlightChange,
|
|
297
|
+
} );
|
|
298
|
+
for ( let i = 0; i < 4; i++ ) {
|
|
299
|
+
rerender(
|
|
300
|
+
<GlobalChartsProvider>
|
|
301
|
+
<BarChart { ...defaultProps } withBandHighlight onBandHighlightChange={ jest.fn() } />
|
|
302
|
+
</GlobalChartsProvider>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
unmount();
|
|
306
|
+
renderWithTheme( { withBandHighlight: true, onBandHighlightChange } );
|
|
307
|
+
expect( warn ).toHaveBeenCalledTimes( 1 );
|
|
308
|
+
expect( screen.getByRole( 'grid' ) ).toBeInTheDocument();
|
|
309
|
+
expect( warn ).toHaveBeenCalledWith(
|
|
310
|
+
'[Charts] BarChart: withBandHighlight and onBandHighlightChange require withTooltips.'
|
|
311
|
+
);
|
|
312
|
+
expect( onBandHighlightChange ).not.toHaveBeenCalled();
|
|
313
|
+
expect( screen.queryByTestId( 'bar-chart-band-highlight' ) ).not.toBeInTheDocument();
|
|
314
|
+
} finally {
|
|
315
|
+
warn.mockRestore();
|
|
316
|
+
}
|
|
317
|
+
} );
|
|
318
|
+
|
|
319
|
+
test( 'reports band bounds and point indexes during keyboard navigation and clears them on Escape', async () => {
|
|
320
|
+
const user = userEvent.setup();
|
|
321
|
+
const onBandHighlightChange = jest.fn();
|
|
322
|
+
renderWithTheme( { withTooltips: true, onBandHighlightChange } );
|
|
323
|
+
await user.tab();
|
|
324
|
+
await user.keyboard( '{ArrowRight}' );
|
|
325
|
+
expect( onBandHighlightChange ).toHaveBeenLastCalledWith(
|
|
326
|
+
expect.objectContaining( {
|
|
327
|
+
datum: expect.objectContaining( { value: 10 } ),
|
|
328
|
+
key: 'Series A',
|
|
329
|
+
index: 0,
|
|
330
|
+
x: expect.any( Number ),
|
|
331
|
+
y: expect.any( Number ),
|
|
332
|
+
width: expect.any( Number ),
|
|
333
|
+
height: expect.any( Number ),
|
|
334
|
+
} )
|
|
335
|
+
);
|
|
336
|
+
expect( screen.queryByTestId( 'bar-chart-band-highlight' ) ).not.toBeInTheDocument();
|
|
337
|
+
await user.keyboard( '{ArrowRight}' );
|
|
338
|
+
expect( onBandHighlightChange ).toHaveBeenLastCalledWith(
|
|
339
|
+
expect.objectContaining( {
|
|
340
|
+
datum: expect.objectContaining( { value: 20 } ),
|
|
341
|
+
key: 'Series A',
|
|
342
|
+
index: 1,
|
|
343
|
+
} )
|
|
344
|
+
);
|
|
345
|
+
await user.keyboard( '{Escape}' );
|
|
346
|
+
await waitFor( () => expect( onBandHighlightChange ).toHaveBeenLastCalledWith( null ) );
|
|
347
|
+
} );
|
|
348
|
+
|
|
349
|
+
test.each( [ 'vertical', 'horizontal' ] )(
|
|
350
|
+
'draws the active band across a %s plot',
|
|
351
|
+
async orientation => {
|
|
352
|
+
const user = userEvent.setup();
|
|
353
|
+
const onBandHighlightChange = jest.fn();
|
|
354
|
+
renderWithTheme( {
|
|
355
|
+
withTooltips: true,
|
|
356
|
+
withBandHighlight: true,
|
|
357
|
+
orientation,
|
|
358
|
+
onBandHighlightChange,
|
|
359
|
+
} );
|
|
360
|
+
await user.tab();
|
|
361
|
+
await user.keyboard( '{ArrowRight}' );
|
|
362
|
+
const highlight = screen.getByTestId( 'bar-chart-band-highlight' );
|
|
363
|
+
expect( highlight.matches( 'svg > :first-child' ) ).toBe( true );
|
|
364
|
+
const bounds = onBandHighlightChange.mock.calls.at( -1 )[ 0 ];
|
|
365
|
+
expect( Number( highlight.getAttribute( 'width' ) ) ).toBe( bounds.width );
|
|
366
|
+
expect( Number( highlight.getAttribute( 'height' ) ) ).toBe( bounds.height );
|
|
367
|
+
expect( bounds.width ).toBeGreaterThan( 0 );
|
|
368
|
+
expect( bounds.height ).toBeGreaterThan( 0 );
|
|
369
|
+
const bar = getBarRects()[ 0 ];
|
|
370
|
+
const bandAxis = orientation === 'vertical' ? 'x' : 'y';
|
|
371
|
+
const bandSize = orientation === 'vertical' ? 'width' : 'height';
|
|
372
|
+
const plotSize = orientation === 'vertical' ? 'height' : 'width';
|
|
373
|
+
const barStart = Number( bar.getAttribute( bandAxis ) );
|
|
374
|
+
const barSize = Number( bar.getAttribute( bandSize ) );
|
|
375
|
+
expect( Number( highlight.getAttribute( bandAxis ) ) ).toBeLessThanOrEqual( barStart );
|
|
376
|
+
expect(
|
|
377
|
+
Number( highlight.getAttribute( bandAxis ) ) + Number( highlight.getAttribute( bandSize ) )
|
|
378
|
+
).toBeGreaterThanOrEqual( barStart + barSize );
|
|
379
|
+
expect( Number( highlight.getAttribute( bandSize ) ) ).toBeLessThan( barSize * 2 );
|
|
380
|
+
expect( Number( highlight.getAttribute( plotSize ) ) ).toBeGreaterThan(
|
|
381
|
+
Number( bar.getAttribute( plotSize ) )
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
test( 'aligns horizontal grid lines with explicit value ticks', () => {
|
|
387
|
+
renderWithTheme( {
|
|
388
|
+
gridVisibility: 'x',
|
|
389
|
+
options: {
|
|
390
|
+
axis: { y: { tickValues: [ 0, 50, 100 ] } },
|
|
391
|
+
scale: { y: { domain: [ 0, 100 ] } },
|
|
392
|
+
},
|
|
393
|
+
} );
|
|
394
|
+
const lines = getGridLines();
|
|
395
|
+
expect( lines ).toHaveLength( 3 );
|
|
396
|
+
} );
|
|
397
|
+
|
|
398
|
+
describe( 'Whole-number value ticks', () => {
|
|
399
|
+
const wholeNumberData: SeriesData[] = [
|
|
400
|
+
{
|
|
401
|
+
label: 'Series A',
|
|
402
|
+
data: [
|
|
403
|
+
{ label: 'Mon', value: 0 },
|
|
404
|
+
{ label: 'Tue', value: 1 },
|
|
405
|
+
{ label: 'Wed', value: 1 },
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
];
|
|
409
|
+
|
|
410
|
+
test( 'labels a whole-number range smaller than the tick count once per whole number', () => {
|
|
411
|
+
renderWithTheme( { data: wholeNumberData } );
|
|
412
|
+
const chart = screen.getByRole( 'grid', { name: /bar chart/i } );
|
|
413
|
+
const ticks = within( chart )
|
|
414
|
+
.getAllByText( /^-?[\d.,]+$/ )
|
|
415
|
+
.map( el => el.textContent );
|
|
416
|
+
expect( ticks.sort() ).toEqual( [ '0', '1' ] );
|
|
417
|
+
} );
|
|
418
|
+
|
|
419
|
+
test( 'labels a horizontal whole-number range once per whole number', () => {
|
|
420
|
+
renderWithTheme( { data: wholeNumberData, orientation: 'horizontal' } );
|
|
421
|
+
const chart = screen.getByRole( 'grid', { name: /bar chart/i } );
|
|
422
|
+
const ticks = within( chart )
|
|
423
|
+
.getAllByText( /^-?[\d.,]+$/ )
|
|
424
|
+
.map( el => el.textContent );
|
|
425
|
+
expect( ticks.sort() ).toEqual( [ '0', '1' ] );
|
|
426
|
+
} );
|
|
427
|
+
|
|
428
|
+
test( 'draws a flat series of ones on a 0 and 1 axis', () => {
|
|
429
|
+
renderWithTheme( {
|
|
430
|
+
data: [
|
|
431
|
+
{
|
|
432
|
+
label: 'Series A',
|
|
433
|
+
data: [
|
|
434
|
+
{ label: 'Mon', value: 1 },
|
|
435
|
+
{ label: 'Tue', value: 1 },
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
} );
|
|
440
|
+
const chart = screen.getByRole( 'grid', { name: /bar chart/i } );
|
|
441
|
+
const ticks = within( chart )
|
|
442
|
+
.getAllByText( /^-?[\d.,]+$/ )
|
|
443
|
+
.map( el => el.textContent );
|
|
444
|
+
expect( ticks.sort() ).toEqual( [ '0', '1' ] );
|
|
445
|
+
} );
|
|
446
|
+
|
|
447
|
+
test.each( [ 'vertical', 'horizontal' ] as const )(
|
|
448
|
+
'keeps every tick on a value domain the caller pinned (%s)',
|
|
449
|
+
orientation => {
|
|
450
|
+
const valueAxis = orientation === 'horizontal' ? 'x' : 'y';
|
|
451
|
+
renderWithTheme( {
|
|
452
|
+
orientation,
|
|
453
|
+
data: [
|
|
454
|
+
{
|
|
455
|
+
label: 'Series A',
|
|
456
|
+
data: [
|
|
457
|
+
{ label: 'Mon', value: 0 },
|
|
458
|
+
{ label: 'Tue', value: 0 },
|
|
459
|
+
],
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
options: {
|
|
463
|
+
[ `${ valueAxis }Scale` ]: { domain: [ 0, 1 ] },
|
|
464
|
+
axis: {
|
|
465
|
+
[ valueAxis ]: { tickFormat: ( value: number ) => `${ Math.round( value * 100 ) }%` },
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
} );
|
|
469
|
+
const chart = screen.getByRole( 'grid', { name: /bar chart/i } );
|
|
470
|
+
expect( within( chart ).getAllByText( /^\d+%$/ ) ).toHaveLength( 6 );
|
|
471
|
+
}
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
test( "keeps a caller's value tickValues", () => {
|
|
475
|
+
renderWithTheme( {
|
|
476
|
+
data: wholeNumberData,
|
|
477
|
+
options: { axis: { y: { tickValues: [ 0, 0.5, 1 ] } } },
|
|
478
|
+
} );
|
|
479
|
+
const chart = screen.getByRole( 'grid', { name: /bar chart/i } );
|
|
480
|
+
expect( within( chart ).getAllByText( /^-?[\d.,]+$/ ) ).toHaveLength( 3 );
|
|
481
|
+
} );
|
|
482
|
+
} );
|
|
483
|
+
|
|
484
|
+
test( 'draws category grid lines at the x axis numTicks', () => {
|
|
485
|
+
const data: SeriesData[] = [
|
|
486
|
+
{
|
|
487
|
+
label: 'Series A',
|
|
488
|
+
data: Array.from( { length: 30 }, ( _, i ) => ( { label: `Day ${ i + 1 }`, value: i } ) ),
|
|
489
|
+
},
|
|
490
|
+
];
|
|
491
|
+
const countColumns = ( numTicks: number ) => {
|
|
492
|
+
const { container, unmount } = renderWithTheme( {
|
|
493
|
+
data,
|
|
494
|
+
gridVisibility: 'y',
|
|
495
|
+
options: { axis: { x: { numTicks } } },
|
|
496
|
+
} );
|
|
497
|
+
// See the visx node constraint at getBarRects.
|
|
498
|
+
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
|
|
499
|
+
const count = container.querySelectorAll( '.visx-columns line' ).length;
|
|
500
|
+
unmount();
|
|
501
|
+
return count;
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
expect( countColumns( 15 ) ).toBeGreaterThan( countColumns( 4 ) );
|
|
505
|
+
} );
|
|
506
|
+
|
|
43
507
|
describe( 'Data Validation', () => {
|
|
44
508
|
test( 'handles empty data array', () => {
|
|
45
509
|
renderWithTheme( { data: [] } );
|
|
@@ -75,18 +539,52 @@ describe( 'BarChart', () => {
|
|
|
75
539
|
expect( screen.getByRole( 'grid', { name: /bar chart/i } ) ).toBeInTheDocument();
|
|
76
540
|
} );
|
|
77
541
|
|
|
78
|
-
test( '
|
|
542
|
+
test( 'renders a bucket with no reading instead of failing the whole chart', () => {
|
|
543
|
+
renderWithTheme( {
|
|
544
|
+
data: [
|
|
545
|
+
{
|
|
546
|
+
label: 'Series A',
|
|
547
|
+
data: [
|
|
548
|
+
{ date: new Date( '2024-01-01' ), value: null as number | null, label: 'Jan 1' },
|
|
549
|
+
{ date: new Date( '2024-01-02' ), value: 20, label: 'Jan 2' },
|
|
550
|
+
],
|
|
551
|
+
options: {},
|
|
552
|
+
},
|
|
553
|
+
],
|
|
554
|
+
} );
|
|
555
|
+
expect( screen.queryByText( /invalid data/i ) ).not.toBeInTheDocument();
|
|
556
|
+
expect( screen.getByRole( 'grid', { name: /bar chart/i } ) ).toBeInTheDocument();
|
|
557
|
+
} );
|
|
558
|
+
|
|
559
|
+
test( 'draws no bar for a bucket with no reading', () => {
|
|
79
560
|
renderWithTheme( {
|
|
80
561
|
data: [
|
|
81
562
|
{
|
|
82
563
|
label: 'Series A',
|
|
83
564
|
data: [
|
|
84
565
|
{ date: new Date( '2024-01-01' ), value: null as number | null, label: 'Jan 1' },
|
|
566
|
+
{ date: new Date( '2024-01-02' ), value: 20, label: 'Jan 2' },
|
|
567
|
+
{ date: new Date( '2024-01-03' ), value: 30, label: 'Jan 3' },
|
|
568
|
+
],
|
|
569
|
+
options: {},
|
|
570
|
+
},
|
|
571
|
+
],
|
|
572
|
+
} );
|
|
573
|
+
expect( getBarRects() ).toHaveLength( 2 );
|
|
574
|
+
} );
|
|
575
|
+
|
|
576
|
+
test( 'still rejects undefined values', () => {
|
|
577
|
+
renderWithTheme( {
|
|
578
|
+
data: [
|
|
579
|
+
{
|
|
580
|
+
label: 'Series A',
|
|
581
|
+
data: [
|
|
85
582
|
{
|
|
86
|
-
date: new Date( '2024-01-
|
|
583
|
+
date: new Date( '2024-01-01' ),
|
|
87
584
|
value: undefined as number | undefined,
|
|
88
|
-
label: 'Jan
|
|
585
|
+
label: 'Jan 1',
|
|
89
586
|
},
|
|
587
|
+
{ date: new Date( '2024-01-02' ), value: 20, label: 'Jan 2' },
|
|
90
588
|
],
|
|
91
589
|
options: {},
|
|
92
590
|
},
|
|
@@ -927,6 +1425,54 @@ describe( 'BarChart', () => {
|
|
|
927
1425
|
} );
|
|
928
1426
|
|
|
929
1427
|
describe( 'Pattern', () => {
|
|
1428
|
+
test( 'uses point colors with a series fallback and preserves pattern fills', () => {
|
|
1429
|
+
const data: SeriesData[] = [
|
|
1430
|
+
{
|
|
1431
|
+
label: 'Scores',
|
|
1432
|
+
data: [
|
|
1433
|
+
{
|
|
1434
|
+
label: 'First day',
|
|
1435
|
+
value: 80,
|
|
1436
|
+
color: 'var(--wpds-color-foreground-content-success-weak)',
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
label: 'Second day',
|
|
1440
|
+
value: 40,
|
|
1441
|
+
color: 'var(--wpds-color-foreground-content-error-weak)',
|
|
1442
|
+
},
|
|
1443
|
+
{ label: 'Third day', value: 60 },
|
|
1444
|
+
],
|
|
1445
|
+
},
|
|
1446
|
+
];
|
|
1447
|
+
const { rerender } = render( <BarChart { ...defaultProps } data={ data } /> );
|
|
1448
|
+
const getBars = getBarRects;
|
|
1449
|
+
const bars = getBars();
|
|
1450
|
+
expect( bars ).toHaveLength( 3 );
|
|
1451
|
+
expect( bars[ 0 ] ).toHaveAttribute( 'fill', data[ 0 ].data[ 0 ].color );
|
|
1452
|
+
expect( bars[ 1 ] ).toHaveAttribute( 'fill', data[ 0 ].data[ 1 ].color );
|
|
1453
|
+
const fallbackFill = bars[ 2 ].getAttribute( 'fill' );
|
|
1454
|
+
expect( fallbackFill ).toBeTruthy();
|
|
1455
|
+
rerender(
|
|
1456
|
+
<BarChart
|
|
1457
|
+
{ ...defaultProps }
|
|
1458
|
+
data={ [
|
|
1459
|
+
{
|
|
1460
|
+
...data[ 0 ],
|
|
1461
|
+
data: data[ 0 ].data.map( point => ( { ...point, color: undefined } ) ),
|
|
1462
|
+
},
|
|
1463
|
+
] }
|
|
1464
|
+
/>
|
|
1465
|
+
);
|
|
1466
|
+
expect( getBars()[ 0 ] ).toHaveAttribute( 'fill', fallbackFill );
|
|
1467
|
+
rerender( <BarChart { ...defaultProps } data={ data } withPatterns /> );
|
|
1468
|
+
for ( const bar of getBars() ) {
|
|
1469
|
+
expect( bar ).toHaveAttribute(
|
|
1470
|
+
'fill',
|
|
1471
|
+
expect.stringMatching( /^url\(#bar-pattern-.+-0\)$/ )
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
} );
|
|
1475
|
+
|
|
930
1476
|
test( 'renders with patterns', () => {
|
|
931
1477
|
renderWithTheme( { withPatterns: true } );
|
|
932
1478
|
expect( screen.getByRole( 'grid', { name: /bar chart/i } ) ).toBeInTheDocument();
|
|
@@ -1225,6 +1771,57 @@ describe( 'BarChart', () => {
|
|
|
1225
1771
|
} );
|
|
1226
1772
|
} );
|
|
1227
1773
|
|
|
1774
|
+
describe( 'ARIA grid boundaries', () => {
|
|
1775
|
+
// Three points, so a clamp at a boundary lands on a different index than a wrap would.
|
|
1776
|
+
const threePointData = [
|
|
1777
|
+
{
|
|
1778
|
+
label: 'Series A',
|
|
1779
|
+
data: [
|
|
1780
|
+
{ date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' },
|
|
1781
|
+
{ date: new Date( '2024-01-02' ), value: 20, label: 'Jan 2' },
|
|
1782
|
+
{ date: new Date( '2024-01-03' ), value: 30, label: 'Jan 3' },
|
|
1783
|
+
],
|
|
1784
|
+
options: {},
|
|
1785
|
+
},
|
|
1786
|
+
];
|
|
1787
|
+
|
|
1788
|
+
test( 'right arrow at the last point stays put and keeps the highlight (no escape, no wrap)', async () => {
|
|
1789
|
+
const user = userEvent.setup();
|
|
1790
|
+
renderWithTheme( { withTooltips: true, data: threePointData } );
|
|
1791
|
+
|
|
1792
|
+
const chart = screen.getByRole( 'grid', { name: /bar chart/i } );
|
|
1793
|
+
chart.focus();
|
|
1794
|
+
|
|
1795
|
+
// Move to the first, then on to the last (third) point.
|
|
1796
|
+
await user.keyboard( '{ArrowRight}' );
|
|
1797
|
+
expect( screen.getByTestId( 'chart-tooltip-0' ) ).toHaveFocus();
|
|
1798
|
+
await user.keyboard( '{ArrowRight}{ArrowRight}' );
|
|
1799
|
+
expect( screen.getByTestId( 'chart-tooltip-2' ) ).toHaveFocus();
|
|
1800
|
+
|
|
1801
|
+
// Right arrow at the last point must not move focus: the highlighted
|
|
1802
|
+
// bar/tooltip stays visible and focused (ARIA grid: focus does not move).
|
|
1803
|
+
await user.keyboard( '{ArrowRight}' );
|
|
1804
|
+
expect( screen.getByTestId( 'chart-tooltip-2' ) ).toBeInTheDocument();
|
|
1805
|
+
expect( screen.getByTestId( 'chart-tooltip-2' ) ).toHaveFocus();
|
|
1806
|
+
expect( screen.queryByTestId( 'chart-tooltip-0' ) ).not.toBeInTheDocument();
|
|
1807
|
+
// Focus did not escape back to the chart container.
|
|
1808
|
+
expect( chart ).not.toHaveFocus();
|
|
1809
|
+
} );
|
|
1810
|
+
|
|
1811
|
+
// Left arrow as the first key is the hard case: it clamps from the unselected -1, and a wrap
|
|
1812
|
+
// would land on the last point instead.
|
|
1813
|
+
test( 'left arrow as the first key selects the first point', async () => {
|
|
1814
|
+
const user = userEvent.setup();
|
|
1815
|
+
renderWithTheme( { withTooltips: true, data: threePointData } );
|
|
1816
|
+
|
|
1817
|
+
screen.getByRole( 'grid', { name: /bar chart/i } ).focus();
|
|
1818
|
+
await user.keyboard( '{ArrowLeft}' );
|
|
1819
|
+
|
|
1820
|
+
expect( screen.getByTestId( 'chart-tooltip-0' ) ).toHaveFocus();
|
|
1821
|
+
expect( screen.queryByTestId( 'chart-tooltip-2' ) ).not.toBeInTheDocument();
|
|
1822
|
+
} );
|
|
1823
|
+
} );
|
|
1824
|
+
|
|
1228
1825
|
describe( 'Comparison tooltip', () => {
|
|
1229
1826
|
test( 'tooltip shows both the primary and comparison values', async () => {
|
|
1230
1827
|
const user = userEvent.setup();
|
|
@@ -1301,36 +1898,120 @@ describe( 'BarChart', () => {
|
|
|
1301
1898
|
expect( tooltip ).toHaveTextContent( 'Previous period' );
|
|
1302
1899
|
expect( tooltip ).toHaveTextContent( '25' );
|
|
1303
1900
|
} );
|
|
1304
|
-
} );
|
|
1305
1901
|
|
|
1306
|
-
|
|
1307
|
-
test( 'tab key exits navigation when reaching end of data points', async () => {
|
|
1902
|
+
test( 'reports a bucket with no reading as having no data', async () => {
|
|
1308
1903
|
const user = userEvent.setup();
|
|
1309
1904
|
renderWithTheme( {
|
|
1905
|
+
withTooltips: true,
|
|
1310
1906
|
data: [
|
|
1311
1907
|
{
|
|
1312
1908
|
label: 'Series A',
|
|
1313
1909
|
data: [
|
|
1314
|
-
{ date: new Date( '2024-01-01' ), value:
|
|
1910
|
+
{ date: new Date( '2024-01-01' ), value: null as number | null, label: 'Jan 1' },
|
|
1315
1911
|
{ date: new Date( '2024-01-02' ), value: 20, label: 'Jan 2' },
|
|
1316
1912
|
],
|
|
1317
1913
|
options: {},
|
|
1318
1914
|
},
|
|
1319
|
-
{
|
|
1320
|
-
label: 'Series B',
|
|
1321
|
-
data: [
|
|
1322
|
-
{ date: new Date( '2024-01-01' ), value: 15, label: 'Jan 1' },
|
|
1323
|
-
{ date: new Date( '2024-01-02' ), value: 25, label: 'Jan 2' },
|
|
1324
|
-
],
|
|
1325
|
-
options: {},
|
|
1326
|
-
},
|
|
1327
1915
|
],
|
|
1328
1916
|
} );
|
|
1329
1917
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1918
|
+
screen.getByRole( 'grid', { name: /bar chart/i } ).focus();
|
|
1919
|
+
await user.keyboard( '{ArrowRight}' );
|
|
1332
1920
|
|
|
1333
|
-
|
|
1921
|
+
const tooltip = screen.getByTestId( 'chart-tooltip-0' );
|
|
1922
|
+
expect( tooltip ).toHaveTextContent( 'Jan 1: No data' );
|
|
1923
|
+
expect( tooltip ).not.toHaveTextContent( 'Jan 1: 0' );
|
|
1924
|
+
} );
|
|
1925
|
+
|
|
1926
|
+
test( 'keeps both periods in the tooltip when the comparison period has no reading', async () => {
|
|
1927
|
+
const user = userEvent.setup();
|
|
1928
|
+
renderWithTheme( {
|
|
1929
|
+
withTooltips: true,
|
|
1930
|
+
data: [
|
|
1931
|
+
{
|
|
1932
|
+
label: 'This period',
|
|
1933
|
+
group: 'views',
|
|
1934
|
+
data: [
|
|
1935
|
+
{ date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' },
|
|
1936
|
+
{ date: new Date( '2024-01-02' ), value: 20, label: 'Jan 2' },
|
|
1937
|
+
],
|
|
1938
|
+
},
|
|
1939
|
+
{
|
|
1940
|
+
label: 'Previous period',
|
|
1941
|
+
group: 'views',
|
|
1942
|
+
options: { type: 'comparison' as const },
|
|
1943
|
+
data: [
|
|
1944
|
+
{ date: new Date( '2024-01-01' ), value: null as number | null, label: 'Jan 1' },
|
|
1945
|
+
{ date: new Date( '2024-01-02' ), value: 25, label: 'Jan 2' },
|
|
1946
|
+
],
|
|
1947
|
+
},
|
|
1948
|
+
],
|
|
1949
|
+
} );
|
|
1950
|
+
|
|
1951
|
+
screen.getByRole( 'grid', { name: /bar chart/i } ).focus();
|
|
1952
|
+
await user.keyboard( '{ArrowRight}' );
|
|
1953
|
+
|
|
1954
|
+
const tooltip = screen.getByTestId( 'chart-tooltip-0' );
|
|
1955
|
+
expect( tooltip ).toHaveTextContent( 'This period: 10' );
|
|
1956
|
+
expect( tooltip ).toHaveTextContent( 'Previous period: No data' );
|
|
1957
|
+
} );
|
|
1958
|
+
|
|
1959
|
+
test( 'draws no comparison shadow for a comparison bucket with no reading', () => {
|
|
1960
|
+
renderWithTheme( {
|
|
1961
|
+
data: [
|
|
1962
|
+
{
|
|
1963
|
+
label: 'This period',
|
|
1964
|
+
group: 'views',
|
|
1965
|
+
data: [
|
|
1966
|
+
{ date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' },
|
|
1967
|
+
{ date: new Date( '2024-01-02' ), value: 20, label: 'Jan 2' },
|
|
1968
|
+
],
|
|
1969
|
+
},
|
|
1970
|
+
{
|
|
1971
|
+
label: 'Previous period',
|
|
1972
|
+
group: 'views',
|
|
1973
|
+
options: { type: 'comparison' as const },
|
|
1974
|
+
data: [
|
|
1975
|
+
{ date: new Date( '2024-01-01' ), value: null as number | null, label: 'Jan 1' },
|
|
1976
|
+
{ date: new Date( '2024-01-02' ), value: 25, label: 'Jan 2' },
|
|
1977
|
+
],
|
|
1978
|
+
},
|
|
1979
|
+
],
|
|
1980
|
+
} );
|
|
1981
|
+
|
|
1982
|
+
expect( screen.queryByTestId( 'bar-chart-comparison-1-0' ) ).not.toBeInTheDocument();
|
|
1983
|
+
expect( screen.getByTestId( 'bar-chart-comparison-1-1' ) ).toBeInTheDocument();
|
|
1984
|
+
} );
|
|
1985
|
+
} );
|
|
1986
|
+
|
|
1987
|
+
describe( 'Tab Key Navigation', () => {
|
|
1988
|
+
test( 'tab key exits navigation when reaching end of data points', async () => {
|
|
1989
|
+
const user = userEvent.setup();
|
|
1990
|
+
renderWithTheme( {
|
|
1991
|
+
data: [
|
|
1992
|
+
{
|
|
1993
|
+
label: 'Series A',
|
|
1994
|
+
data: [
|
|
1995
|
+
{ date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' },
|
|
1996
|
+
{ date: new Date( '2024-01-02' ), value: 20, label: 'Jan 2' },
|
|
1997
|
+
],
|
|
1998
|
+
options: {},
|
|
1999
|
+
},
|
|
2000
|
+
{
|
|
2001
|
+
label: 'Series B',
|
|
2002
|
+
data: [
|
|
2003
|
+
{ date: new Date( '2024-01-01' ), value: 15, label: 'Jan 1' },
|
|
2004
|
+
{ date: new Date( '2024-01-02' ), value: 25, label: 'Jan 2' },
|
|
2005
|
+
],
|
|
2006
|
+
options: {},
|
|
2007
|
+
},
|
|
2008
|
+
],
|
|
2009
|
+
} );
|
|
2010
|
+
|
|
2011
|
+
const chart = screen.getByRole( 'grid', { name: /bar chart/i } );
|
|
2012
|
+
chart.focus();
|
|
2013
|
+
|
|
2014
|
+
// Chart should be in focus.
|
|
1334
2015
|
expect( chart ).toHaveFocus();
|
|
1335
2016
|
|
|
1336
2017
|
// Clicking tab should not open any tooltips.
|
|
@@ -1416,6 +2097,77 @@ describe( 'BarChart', () => {
|
|
|
1416
2097
|
expect( screen.getByTestId( 'chart-tooltip-0' ) ).toBeInTheDocument();
|
|
1417
2098
|
expect( screen.getByTestId( 'chart-tooltip-0' ) ).toHaveTextContent( 'Series A' );
|
|
1418
2099
|
} );
|
|
2100
|
+
|
|
2101
|
+
describe( 'with a leading bucket that has no reading', () => {
|
|
2102
|
+
const data = [
|
|
2103
|
+
{
|
|
2104
|
+
label: 'Series A',
|
|
2105
|
+
data: [
|
|
2106
|
+
{ date: new Date( '2024-01-01' ), value: null as number | null, label: 'Jan 1' },
|
|
2107
|
+
{ date: new Date( '2024-01-02' ), value: 20, label: 'Jan 2' },
|
|
2108
|
+
{ date: new Date( '2024-01-03' ), value: 30, label: 'Jan 3' },
|
|
2109
|
+
],
|
|
2110
|
+
options: {},
|
|
2111
|
+
},
|
|
2112
|
+
];
|
|
2113
|
+
|
|
2114
|
+
test( 'outlines nothing when the selected bucket has no bar', async () => {
|
|
2115
|
+
const user = userEvent.setup();
|
|
2116
|
+
renderWithTheme( { withTooltips: true, data } );
|
|
2117
|
+
|
|
2118
|
+
screen.getByRole( 'grid', { name: /bar chart/i } ).focus();
|
|
2119
|
+
await user.keyboard( '{ArrowRight}' );
|
|
2120
|
+
|
|
2121
|
+
expect( screen.getByTestId( 'chart-tooltip-0' ) ).toHaveTextContent( 'No data' );
|
|
2122
|
+
expect( screen.queryByTestId( 'bar-chart-keyboard-highlight' ) ).not.toBeInTheDocument();
|
|
2123
|
+
} );
|
|
2124
|
+
|
|
2125
|
+
test( 'outlines the first rendered bar for the first bucket with a reading', async () => {
|
|
2126
|
+
const user = userEvent.setup();
|
|
2127
|
+
renderWithTheme( { withTooltips: true, data } );
|
|
2128
|
+
|
|
2129
|
+
screen.getByRole( 'grid', { name: /bar chart/i } ).focus();
|
|
2130
|
+
await user.keyboard( '{ArrowRight}{ArrowRight}' );
|
|
2131
|
+
|
|
2132
|
+
expect( screen.getByTestId( 'chart-tooltip-1' ) ).toHaveTextContent( '20' );
|
|
2133
|
+
expect( screen.getByTestId( 'bar-chart-keyboard-highlight' ) ).toHaveTextContent(
|
|
2134
|
+
/\.visx-bar:nth-child\(1\) \{/
|
|
2135
|
+
);
|
|
2136
|
+
} );
|
|
2137
|
+
|
|
2138
|
+
test( 'skips the bars an earlier series left out when outlining a later series', async () => {
|
|
2139
|
+
const user = userEvent.setup();
|
|
2140
|
+
const twoSeries = [
|
|
2141
|
+
{
|
|
2142
|
+
label: 'Series A',
|
|
2143
|
+
data: [
|
|
2144
|
+
{ date: new Date( '2024-01-01' ), value: null as number | null, label: 'Jan 1' },
|
|
2145
|
+
{ date: new Date( '2024-01-02' ), value: 20, label: 'Jan 2' },
|
|
2146
|
+
{ date: new Date( '2024-01-03' ), value: 30, label: 'Jan 3' },
|
|
2147
|
+
],
|
|
2148
|
+
options: {},
|
|
2149
|
+
},
|
|
2150
|
+
{
|
|
2151
|
+
label: 'Series B',
|
|
2152
|
+
data: [
|
|
2153
|
+
{ date: new Date( '2024-01-01' ), value: 5, label: 'Jan 1' },
|
|
2154
|
+
{ date: new Date( '2024-01-02' ), value: 15, label: 'Jan 2' },
|
|
2155
|
+
{ date: new Date( '2024-01-03' ), value: 25, label: 'Jan 3' },
|
|
2156
|
+
],
|
|
2157
|
+
options: {},
|
|
2158
|
+
},
|
|
2159
|
+
];
|
|
2160
|
+
renderWithTheme( { withTooltips: true, data: twoSeries } );
|
|
2161
|
+
|
|
2162
|
+
screen.getByRole( 'grid', { name: /bar chart/i } ).focus();
|
|
2163
|
+
await user.keyboard( '{ArrowRight}{ArrowRight}{ArrowRight}{ArrowRight}' );
|
|
2164
|
+
|
|
2165
|
+
expect( screen.getByTestId( 'chart-tooltip-3' ) ).toHaveTextContent( '15' );
|
|
2166
|
+
expect( screen.getByTestId( 'bar-chart-keyboard-highlight' ) ).toHaveTextContent(
|
|
2167
|
+
/\.visx-bar:nth-child\(4\) \{/
|
|
2168
|
+
);
|
|
2169
|
+
} );
|
|
2170
|
+
} );
|
|
1419
2171
|
} );
|
|
1420
2172
|
|
|
1421
2173
|
test( 'keyboard navigation works with custom tooltip renderer', async () => {
|
|
@@ -1711,6 +2463,83 @@ describe( 'BarChart', () => {
|
|
|
1711
2463
|
} );
|
|
1712
2464
|
} );
|
|
1713
2465
|
|
|
2466
|
+
describe( 'Value axis baseline', () => {
|
|
2467
|
+
it( 'starts the value axis at zero in horizontal charts', () => {
|
|
2468
|
+
const { result } = renderHook( () => useBarChartOptions( steadyTrafficData, true, {} ) );
|
|
2469
|
+
const xScale = result.current.xScale as { domain?: number[] };
|
|
2470
|
+
expect( xScale.domain ).toEqual( [ 0, 989 ] );
|
|
2471
|
+
} );
|
|
2472
|
+
|
|
2473
|
+
it( 'lets a caller opt out of the zero baseline', () => {
|
|
2474
|
+
const { result } = renderHook( () =>
|
|
2475
|
+
useBarChartOptions( steadyTrafficData, false, { yScale: { zero: false } } )
|
|
2476
|
+
);
|
|
2477
|
+
const yScale = result.current.yScale as { domain?: number[] };
|
|
2478
|
+
expect( yScale.domain ).toBeUndefined();
|
|
2479
|
+
} );
|
|
2480
|
+
|
|
2481
|
+
it( 'reads the opt-out from the x scale of a horizontal chart', () => {
|
|
2482
|
+
const { result } = renderHook( () =>
|
|
2483
|
+
useBarChartOptions( steadyTrafficData, true, { xScale: { zero: false } } )
|
|
2484
|
+
);
|
|
2485
|
+
const xScale = result.current.xScale as { domain?: number[] };
|
|
2486
|
+
expect( xScale.domain ).toBeUndefined();
|
|
2487
|
+
} );
|
|
2488
|
+
|
|
2489
|
+
it( 'keeps an explicit domain as given', () => {
|
|
2490
|
+
const { result } = renderHook( () =>
|
|
2491
|
+
useBarChartOptions( steadyTrafficData, false, { yScale: { domain: [ 900, 1000 ] } } )
|
|
2492
|
+
);
|
|
2493
|
+
const yScale = result.current.yScale as { domain?: number[] };
|
|
2494
|
+
expect( yScale.domain ).toEqual( [ 900, 1000 ] );
|
|
2495
|
+
} );
|
|
2496
|
+
|
|
2497
|
+
it( 'renders an explicit domain without stretching it to zero', () => {
|
|
2498
|
+
render(
|
|
2499
|
+
<BarChart
|
|
2500
|
+
data={ steadyTrafficData }
|
|
2501
|
+
width={ 400 }
|
|
2502
|
+
height={ 300 }
|
|
2503
|
+
options={ { yScale: { domain: [ 900, 1000 ] } } }
|
|
2504
|
+
/>
|
|
2505
|
+
);
|
|
2506
|
+
const heights = Array.from( getBarRects() ).map( bar =>
|
|
2507
|
+
parseFloat( bar.getAttribute( 'height' ) || '0' )
|
|
2508
|
+
);
|
|
2509
|
+
// 921 sits at 21% of a 900–1000 axis; on a 0–1000 axis it would be 92%.
|
|
2510
|
+
expect( Math.min( ...heights ) / Math.max( ...heights ) ).toBeLessThan( 0.5 );
|
|
2511
|
+
} );
|
|
2512
|
+
|
|
2513
|
+
it( 'keeps zero in the domain when the caller opts out but a comparison series is present', () => {
|
|
2514
|
+
const data = [
|
|
2515
|
+
...steadyTrafficData,
|
|
2516
|
+
{
|
|
2517
|
+
label: 'Previous',
|
|
2518
|
+
options: { type: 'comparison' as const },
|
|
2519
|
+
data: steadyTrafficData[ 0 ].data.map( point => ( {
|
|
2520
|
+
...point,
|
|
2521
|
+
value: point.value - 10,
|
|
2522
|
+
} ) ),
|
|
2523
|
+
},
|
|
2524
|
+
];
|
|
2525
|
+
const { result } = renderHook( () =>
|
|
2526
|
+
useBarChartOptions( data, false, { yScale: { zero: false } } )
|
|
2527
|
+
);
|
|
2528
|
+
const yScale = result.current.yScale as { domain?: number[] };
|
|
2529
|
+
expect( yScale.domain ).toEqual( [ 0, 989 ] );
|
|
2530
|
+
} );
|
|
2531
|
+
|
|
2532
|
+
it( 'renders every bar of steady traffic at most a few percent shorter than the tallest', () => {
|
|
2533
|
+
render( <BarChart data={ steadyTrafficData } width={ 400 } height={ 300 } /> );
|
|
2534
|
+
const heights = Array.from( getBarRects() ).map( bar =>
|
|
2535
|
+
parseFloat( bar.getAttribute( 'height' ) || '0' )
|
|
2536
|
+
);
|
|
2537
|
+
expect( heights ).toHaveLength( 7 );
|
|
2538
|
+
const tallest = Math.max( ...heights );
|
|
2539
|
+
expect( Math.min( ...heights ) / tallest ).toBeGreaterThan( 0.9 );
|
|
2540
|
+
} );
|
|
2541
|
+
} );
|
|
2542
|
+
|
|
1714
2543
|
describe( 'Comparison Series', () => {
|
|
1715
2544
|
it( 'renders a translucent shadow behind the primary bar for a comparison series', () => {
|
|
1716
2545
|
const data = [
|
|
@@ -2350,4 +3179,214 @@ describe( 'BarChart', () => {
|
|
|
2350
3179
|
expect( screen.getAllByRole( 'button' )[ 1 ] ).toHaveAttribute( 'aria-pressed', 'true' );
|
|
2351
3180
|
} );
|
|
2352
3181
|
} );
|
|
3182
|
+
|
|
3183
|
+
describe( 'Keyboard navigation with a hidden primary series', () => {
|
|
3184
|
+
const mountWithVisibilityToggle = () => {
|
|
3185
|
+
let context: GlobalChartsContextValue;
|
|
3186
|
+
const Grab = () => {
|
|
3187
|
+
context = useGlobalChartsContext();
|
|
3188
|
+
return null;
|
|
3189
|
+
};
|
|
3190
|
+
|
|
3191
|
+
render(
|
|
3192
|
+
<GlobalChartsProvider>
|
|
3193
|
+
<Grab />
|
|
3194
|
+
<BarChartUnresponsive
|
|
3195
|
+
width={ 500 }
|
|
3196
|
+
height={ 300 }
|
|
3197
|
+
withTooltips
|
|
3198
|
+
chartId="test-hide-while-navigating"
|
|
3199
|
+
data={ [
|
|
3200
|
+
{
|
|
3201
|
+
label: 'Series A',
|
|
3202
|
+
data: [
|
|
3203
|
+
{ label: 'Jan', value: 10 },
|
|
3204
|
+
{ label: 'Feb', value: 20 },
|
|
3205
|
+
{ label: 'Mar', value: 30 },
|
|
3206
|
+
],
|
|
3207
|
+
options: {},
|
|
3208
|
+
},
|
|
3209
|
+
{
|
|
3210
|
+
label: 'Series B',
|
|
3211
|
+
data: [
|
|
3212
|
+
{ label: 'Jan', value: 15 },
|
|
3213
|
+
{ label: 'Feb', value: 25 },
|
|
3214
|
+
{ label: 'Mar', value: 35 },
|
|
3215
|
+
],
|
|
3216
|
+
options: {},
|
|
3217
|
+
},
|
|
3218
|
+
] }
|
|
3219
|
+
/>
|
|
3220
|
+
</GlobalChartsProvider>
|
|
3221
|
+
);
|
|
3222
|
+
|
|
3223
|
+
return ( label: string ) =>
|
|
3224
|
+
act( () => context.toggleSeriesVisibility( 'test-hide-while-navigating', label ) );
|
|
3225
|
+
};
|
|
3226
|
+
|
|
3227
|
+
it( 'moves the selection onto a visible bar when the selected series is hidden', async () => {
|
|
3228
|
+
const user = userEvent.setup();
|
|
3229
|
+
const toggle = mountWithVisibilityToggle();
|
|
3230
|
+
|
|
3231
|
+
screen.getByRole( 'grid', { name: /bar chart/i } ).focus();
|
|
3232
|
+
for ( let i = 0; i < 6; i++ ) {
|
|
3233
|
+
await user.keyboard( '{ArrowRight}' );
|
|
3234
|
+
}
|
|
3235
|
+
expect( screen.getByTestId( 'chart-tooltip-5' ) ).toHaveTextContent( 'Series B' );
|
|
3236
|
+
|
|
3237
|
+
toggle( 'Series B' );
|
|
3238
|
+
|
|
3239
|
+
expect( screen.getByTestId( 'chart-tooltip-2' ) ).toHaveTextContent( 'Series A' );
|
|
3240
|
+
expect( screen.queryByTestId( 'chart-tooltip-5' ) ).not.toBeInTheDocument();
|
|
3241
|
+
|
|
3242
|
+
await user.keyboard( '{ArrowLeft}' );
|
|
3243
|
+
expect( screen.getByTestId( 'chart-tooltip-1' ) ).toHaveTextContent( 'Series A' );
|
|
3244
|
+
} );
|
|
3245
|
+
|
|
3246
|
+
it( 'leaves focus on the legend item that hid the selected series', async () => {
|
|
3247
|
+
const user = userEvent.setup();
|
|
3248
|
+
|
|
3249
|
+
renderWithTheme( {
|
|
3250
|
+
withTooltips: true,
|
|
3251
|
+
showLegend: true,
|
|
3252
|
+
legend: { interactive: true },
|
|
3253
|
+
chartId: 'test-hide-from-legend-while-navigating',
|
|
3254
|
+
data: [
|
|
3255
|
+
{
|
|
3256
|
+
label: 'Series A',
|
|
3257
|
+
data: [
|
|
3258
|
+
{ label: 'Jan', value: 10 },
|
|
3259
|
+
{ label: 'Feb', value: 20 },
|
|
3260
|
+
{ label: 'Mar', value: 30 },
|
|
3261
|
+
],
|
|
3262
|
+
options: {},
|
|
3263
|
+
},
|
|
3264
|
+
{
|
|
3265
|
+
label: 'Series B',
|
|
3266
|
+
data: [
|
|
3267
|
+
{ label: 'Jan', value: 15 },
|
|
3268
|
+
{ label: 'Feb', value: 25 },
|
|
3269
|
+
{ label: 'Mar', value: 35 },
|
|
3270
|
+
],
|
|
3271
|
+
options: {},
|
|
3272
|
+
},
|
|
3273
|
+
],
|
|
3274
|
+
} );
|
|
3275
|
+
|
|
3276
|
+
screen.getByRole( 'grid', { name: /bar chart/i } ).focus();
|
|
3277
|
+
for ( let i = 0; i < 6; i++ ) {
|
|
3278
|
+
await user.keyboard( '{ArrowRight}' );
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
const seriesBToggle = screen.getByRole( 'button', { name: /Series B: visible/i } );
|
|
3282
|
+
await user.click( seriesBToggle );
|
|
3283
|
+
|
|
3284
|
+
expect( seriesBToggle ).toHaveFocus();
|
|
3285
|
+
expect( screen.queryAllByTestId( /^chart-tooltip-/ ) ).toHaveLength( 0 );
|
|
3286
|
+
// Clearing the selection drops the keyboard test id, so assert on the role too:
|
|
3287
|
+
// visx's own hide is debounced, and the stale datum repaints as a different series.
|
|
3288
|
+
expect( screen.queryByRole( 'tooltip' ) ).not.toBeInTheDocument();
|
|
3289
|
+
} );
|
|
3290
|
+
|
|
3291
|
+
// The point count is unchanged by a one-for-one swap, so only the visible set reveals it.
|
|
3292
|
+
it( 'clears the selection when a swap keeps the count but changes which series are visible', async () => {
|
|
3293
|
+
const user = userEvent.setup();
|
|
3294
|
+
let context: GlobalChartsContextValue;
|
|
3295
|
+
const Grab = () => {
|
|
3296
|
+
context = useGlobalChartsContext();
|
|
3297
|
+
return null;
|
|
3298
|
+
};
|
|
3299
|
+
|
|
3300
|
+
render(
|
|
3301
|
+
<GlobalChartsProvider>
|
|
3302
|
+
<Grab />
|
|
3303
|
+
<BarChartUnresponsive
|
|
3304
|
+
width={ 500 }
|
|
3305
|
+
height={ 300 }
|
|
3306
|
+
withTooltips
|
|
3307
|
+
chartId="test-equal-count-swap"
|
|
3308
|
+
defaultHiddenSeries={ [ 'Series B' ] }
|
|
3309
|
+
data={ [
|
|
3310
|
+
{
|
|
3311
|
+
label: 'Series A',
|
|
3312
|
+
data: [
|
|
3313
|
+
{ label: 'Jan', value: 10 },
|
|
3314
|
+
{ label: 'Feb', value: 20 },
|
|
3315
|
+
{ label: 'Mar', value: 30 },
|
|
3316
|
+
],
|
|
3317
|
+
options: {},
|
|
3318
|
+
},
|
|
3319
|
+
{
|
|
3320
|
+
label: 'Series B',
|
|
3321
|
+
data: [
|
|
3322
|
+
{ label: 'Jan', value: 15 },
|
|
3323
|
+
{ label: 'Feb', value: 25 },
|
|
3324
|
+
{ label: 'Mar', value: 35 },
|
|
3325
|
+
],
|
|
3326
|
+
options: {},
|
|
3327
|
+
},
|
|
3328
|
+
] }
|
|
3329
|
+
/>
|
|
3330
|
+
</GlobalChartsProvider>
|
|
3331
|
+
);
|
|
3332
|
+
|
|
3333
|
+
screen.getByRole( 'grid', { name: /bar chart/i } ).focus();
|
|
3334
|
+
await user.keyboard( '{ArrowRight}{ArrowRight}' );
|
|
3335
|
+
expect( screen.getByTestId( 'chart-tooltip-1' ) ).toHaveTextContent( 'Series A' );
|
|
3336
|
+
|
|
3337
|
+
// Move focus out of the chart, then swap which series is hidden without changing the count.
|
|
3338
|
+
screen.getByRole( 'grid', { name: /bar chart/i } ).blur();
|
|
3339
|
+
act( () => context.setChartHiddenSeries( 'test-equal-count-swap', [ 'Series A' ] ) );
|
|
3340
|
+
|
|
3341
|
+
expect( screen.queryAllByTestId( /^chart-tooltip-/ ) ).toHaveLength( 0 );
|
|
3342
|
+
expect( screen.queryByRole( 'tooltip' ) ).not.toBeInTheDocument();
|
|
3343
|
+
} );
|
|
3344
|
+
|
|
3345
|
+
it( 'stops at the last visible slot on a standard chart', async () => {
|
|
3346
|
+
const user = userEvent.setup();
|
|
3347
|
+
|
|
3348
|
+
renderWithTheme( {
|
|
3349
|
+
withTooltips: true,
|
|
3350
|
+
showLegend: true,
|
|
3351
|
+
legend: { interactive: true },
|
|
3352
|
+
chartId: 'test-hide-primary-standard',
|
|
3353
|
+
data: [
|
|
3354
|
+
{
|
|
3355
|
+
label: 'Series A',
|
|
3356
|
+
data: [
|
|
3357
|
+
{ label: 'Jan', value: 10 },
|
|
3358
|
+
{ label: 'Feb', value: 20 },
|
|
3359
|
+
{ label: 'Mar', value: 30 },
|
|
3360
|
+
],
|
|
3361
|
+
options: {},
|
|
3362
|
+
},
|
|
3363
|
+
{
|
|
3364
|
+
label: 'Series B',
|
|
3365
|
+
data: [
|
|
3366
|
+
{ label: 'Jan', value: 15 },
|
|
3367
|
+
{ label: 'Feb', value: 25 },
|
|
3368
|
+
{ label: 'Mar', value: 35 },
|
|
3369
|
+
],
|
|
3370
|
+
options: {},
|
|
3371
|
+
},
|
|
3372
|
+
],
|
|
3373
|
+
} );
|
|
3374
|
+
|
|
3375
|
+
await user.click( screen.getByRole( 'button', { name: /Series B: visible/i } ) );
|
|
3376
|
+
|
|
3377
|
+
const chart = screen.getByRole( 'grid', { name: /bar chart/i } );
|
|
3378
|
+
chart.focus();
|
|
3379
|
+
|
|
3380
|
+
for ( let i = 0; i < 5; i++ ) {
|
|
3381
|
+
await user.keyboard( '{ArrowRight}' );
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
const tooltips = screen.queryAllByTestId( /^chart-tooltip-/ );
|
|
3385
|
+
expect( tooltips ).toHaveLength( 1 );
|
|
3386
|
+
expect( screen.getByTestId( 'chart-tooltip-2' ) ).toBeInTheDocument();
|
|
3387
|
+
expect( tooltips[ 0 ] ).toHaveTextContent( 'Series A' );
|
|
3388
|
+
expect( tooltips[ 0 ] ).not.toHaveTextContent( 'Series B' );
|
|
3389
|
+
expect( screen.queryByTestId( 'chart-tooltip-3' ) ).not.toBeInTheDocument();
|
|
3390
|
+
} );
|
|
3391
|
+
} );
|
|
2353
3392
|
} );
|