Skip to content

Commit d17b673

Browse files
committed
add tests
1 parent 1ce4563 commit d17b673

3 files changed

Lines changed: 203 additions & 2 deletions

File tree

cypress/support/utils.tsx

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getRGBColor } from '@ui5/webcomponents-base/dist/util/ColorConversion.js';
22
import type { ComponentType } from 'react';
3+
import { useState } from 'react';
34

45
export function cypressPassThroughTestsFactory(Component: ComponentType, props?: Record<string, unknown>) {
56
it('Pass Through HTML Standard Props', () => {
@@ -101,6 +102,202 @@ export function testChartLegendConfig(Component, props) {
101102
});
102103
}
103104

105+
export function testPieSectorFocus(Component, props, { only }: { only?: boolean } = {}) {
106+
const chartConfig = { accessibilityLayer: true };
107+
const containerSelector = '[aria-roledescription="chart"]';
108+
const test = only ? it.only : it;
109+
110+
test('sector focus - keyboard navigation: Tab, arrows, Enter', () => {
111+
const onDataPointClick = cy.spy().as('onDataPointClick');
112+
cy.mount(
113+
<>
114+
<button>before</button>
115+
<Component {...props} noAnimation chartConfig={chartConfig} onDataPointClick={onDataPointClick} />
116+
<button>after</button>
117+
</>,
118+
);
119+
120+
cy.findByText('before').focus();
121+
cy.realPress('Tab');
122+
cy.focused()
123+
.should('have.attr', 'tabindex', '0')
124+
.should('have.attr', 'role', 'application')
125+
.should('have.attr', 'aria-roledescription', 'chart');
126+
127+
cy.realPress('Tab');
128+
cy.focused()
129+
.should('have.attr', 'data-sector-index', '0')
130+
.and('have.attr', 'role', 'img')
131+
.and('have.attr', 'aria-label');
132+
133+
cy.realPress('ArrowRight');
134+
cy.focused().should('have.attr', 'data-sector-index', '1');
135+
cy.realPress('ArrowLeft');
136+
cy.focused().should('have.attr', 'data-sector-index', '0');
137+
138+
// Wraps from first to last
139+
cy.realPress('ArrowLeft');
140+
cy.focused().should('have.attr', 'data-sector-index', String(props.dataset.length - 1));
141+
142+
cy.realPress('Enter');
143+
cy.get('@onDataPointClick').should(
144+
'have.been.calledWith',
145+
Cypress.sinon.match({
146+
detail: Cypress.sinon.match({
147+
dataIndex: props.dataset.length - 1,
148+
}),
149+
}),
150+
);
151+
152+
cy.realPress(['Shift', 'Tab']);
153+
cy.focused().should('have.attr', 'aria-roledescription', 'chart').and('have.attr', 'tabindex', '0');
154+
});
155+
156+
test('sector focus - activeSegment with Enter and Space', () => {
157+
const onDataPointClick = cy.spy().as('onDataPointClick');
158+
const StatefulChart = () => {
159+
const [activeSegment, setActiveSegment] = useState(3);
160+
return (
161+
<>
162+
<button>before</button>
163+
<Component
164+
{...props}
165+
noAnimation
166+
chartConfig={{ ...chartConfig, activeSegment }}
167+
onDataPointClick={(e) => {
168+
onDataPointClick(e);
169+
setActiveSegment(e.detail.dataIndex);
170+
}}
171+
/>
172+
</>
173+
);
174+
};
175+
cy.mount(<StatefulChart />);
176+
cy.findByText('before').focus();
177+
cy.realPress('Tab');
178+
179+
// Tab focuses the activeSegment
180+
cy.realPress('Tab');
181+
cy.focused().should('have.attr', 'data-sector-index', '3');
182+
183+
cy.realPress('ArrowRight');
184+
cy.focused().should('have.attr', 'data-sector-index', '4');
185+
cy.realPress('Enter');
186+
cy.get('@onDataPointClick').should(
187+
'have.been.calledWith',
188+
Cypress.sinon.match({
189+
detail: Cypress.sinon.match({
190+
dataIndex: 4,
191+
}),
192+
}),
193+
);
194+
cy.get('.recharts-active-shape').should('exist');
195+
cy.focused().should('have.attr', 'data-sector-index', '4');
196+
197+
cy.realPress('ArrowRight');
198+
cy.focused().should('have.attr', 'data-sector-index', '5');
199+
cy.realPress('Space');
200+
cy.get('@onDataPointClick').should(
201+
'have.been.calledWith',
202+
Cypress.sinon.match({
203+
detail: Cypress.sinon.match({
204+
dataIndex: 5,
205+
}),
206+
}),
207+
);
208+
cy.focused().should('have.attr', 'data-sector-index', '5');
209+
});
210+
211+
test('sector focus - activeSegment out of bounds is clamped', () => {
212+
cy.mount(
213+
<>
214+
<button>before</button>
215+
<Component {...props} noAnimation chartConfig={{ ...chartConfig, activeSegment: 999 }} />
216+
</>,
217+
);
218+
cy.findByText('before').focus();
219+
cy.realPress('Tab');
220+
cy.realPress('Tab');
221+
cy.focused().should('have.attr', 'data-sector-index', String(props.dataset.length - 1));
222+
});
223+
224+
test('sector focus - empty dataset is non-interactive', () => {
225+
cy.mount(<Component {...props} dataset={[]} noAnimation chartConfig={chartConfig} />);
226+
cy.get(containerSelector)
227+
.should('have.attr', 'tabindex', '0')
228+
.should('have.attr', 'aria-roledescription', 'chart')
229+
.should('not.have.attr', 'role', 'application');
230+
});
231+
232+
test('sector focus - dataset shrink resets keyboard state', () => {
233+
const initialDataset = props.dataset;
234+
const smallDataset = initialDataset.slice(0, 3);
235+
const baseProps = { ...props, noAnimation: true, chartConfig };
236+
const StatefulChart = () => {
237+
const [ds, setDs] = useState(initialDataset);
238+
return (
239+
<>
240+
<button>before</button>
241+
<button onClick={() => setDs(smallDataset)}>shrink</button>
242+
<Component {...baseProps} dataset={ds} />
243+
</>
244+
);
245+
};
246+
cy.mount(<StatefulChart />);
247+
cy.findByText('before').focus();
248+
cy.realPress('Tab');
249+
cy.realPress('Tab');
250+
cy.realPress('Tab');
251+
252+
for (let i = 0; i < 5; i++) {
253+
cy.realPress('ArrowRight');
254+
}
255+
cy.focused().should('have.attr', 'data-sector-index', '5');
256+
257+
cy.findByText('shrink').click();
258+
cy.get(containerSelector).should('have.attr', 'tabindex', '0');
259+
260+
cy.findByText('before').focus();
261+
cy.realPress('Tab');
262+
cy.realPress('Tab');
263+
cy.realPress('Tab');
264+
cy.focused().should('have.attr', 'data-sector-index');
265+
});
266+
267+
test('sector focus - consumer event handlers are composed with internal handlers', () => {
268+
const onBlur = cy.spy().as('onBlur');
269+
const onFocus = cy.spy().as('onFocus');
270+
const onKeyDownCapture = cy.spy().as('onKeyDownCapture');
271+
cy.mount(
272+
<>
273+
<button>before</button>
274+
<Component
275+
{...props}
276+
noAnimation
277+
chartConfig={chartConfig}
278+
onBlur={onBlur}
279+
onFocus={onFocus}
280+
onKeyDownCapture={onKeyDownCapture}
281+
/>
282+
<button>after</button>
283+
</>,
284+
);
285+
286+
cy.findByText('before').focus();
287+
cy.realPress('Tab');
288+
cy.get('@onFocus').should('have.been.calledOnce');
289+
290+
cy.realPress('Tab');
291+
cy.get('@onKeyDownCapture').should('have.been.called');
292+
cy.focused().should('have.attr', 'data-sector-index', '0');
293+
294+
cy.findByText('after').click();
295+
cy.get('@onBlur').should('have.been.called');
296+
// raf defers exitSectorMode, so wait for tabindex to flip back
297+
cy.get(containerSelector).should('have.attr', 'tabindex', '0');
298+
});
299+
}
300+
104301
export function testStackAggregateTotals(Component, props) {
105302
it('showStackAggregateTotals', () => {
106303
const { dataset, measures } = props;

packages/charts/src/components/DonutChart/DonutChart.cy.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { complexDataSet, simpleDataSet } from '../../resources/DemoProps.js';
22
import { DonutChart } from './index.js';
3-
import { cypressPassThroughTestsFactory, testChartLegendConfig } from '@/cypress/support/utils';
3+
import { cypressPassThroughTestsFactory, testChartLegendConfig, testPieSectorFocus } from '@/cypress/support/utils';
44

55
const dimension = {
66
accessor: 'name',
@@ -63,4 +63,6 @@ describe('DonutChart', () => {
6363
cypressPassThroughTestsFactory(DonutChart, { dimension: {}, measure: {} });
6464

6565
testChartLegendConfig(DonutChart, { dataset: complexDataSet, dimension, measure });
66+
67+
testPieSectorFocus(DonutChart, { dataset: simpleDataSet, dimension, measure });
6668
});

packages/charts/src/components/PieChart/PieChart.cy.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Text as RechartsText } from 'recharts';
22
import { complexDataSet, simpleDataSet } from '../../resources/DemoProps.js';
33
import { PieChart } from './index.js';
4-
import { cypressPassThroughTestsFactory, testChartLegendConfig } from '@/cypress/support/utils';
4+
import { cypressPassThroughTestsFactory, testChartLegendConfig, testPieSectorFocus } from '@/cypress/support/utils';
55

66
const dimension = {
77
accessor: 'name',
@@ -80,4 +80,6 @@ describe('PieChart', () => {
8080
});
8181

8282
testChartLegendConfig(PieChart, { dataset: complexDataSet, dimension, measure });
83+
84+
testPieSectorFocus(PieChart, { dataset: simpleDataSet, dimension, measure });
8385
});

0 commit comments

Comments
 (0)