@@ -19,6 +19,17 @@ const measures = [
1919 } ,
2020] ;
2121
22+ function activePointLabelShould ( containerSelector : string , ...matchers : string [ ] ) {
23+ cy . get ( containerSelector )
24+ . should ( 'have.attr' , 'aria-activedescendant' )
25+ . then ( ( activeId ) => {
26+ let chain = cy . get ( `#${ CSS . escape ( activeId as string ) } ` ) . should ( 'have.attr' , 'aria-label' ) ;
27+ for ( const m of matchers ) {
28+ chain = chain . and ( 'contain' , m ) ;
29+ }
30+ } ) ;
31+ }
32+
2233describe ( 'ScatterChart' , ( ) => {
2334 it ( 'Basic' , ( ) => {
2435 cy . mount ( < ScatterChart dataset = { scatterComplexDataSet } measures = { measures } /> ) ;
@@ -66,7 +77,204 @@ describe('ScatterChart', () => {
6677 it ( 'Loading Placeholder' , ( ) => {
6778 cy . mount ( < ScatterChart dataset = { [ ] } measures = { [ ] } /> ) ;
6879 cy . get ( '.recharts-scatter' ) . should ( 'not.exist' ) ;
69- cy . contains ( 'Loading...' ) . should ( 'exist' ) ;
80+ cy . findByText ( 'Loading...' ) . should ( 'exist' ) ;
81+ } ) ;
82+
83+ it ( 'accessibilityLayer: keyboard navigation, Enter, blur/re-focus, consumer handlers' , ( ) => {
84+ const chartConfig = { accessibilityLayer : true } ;
85+ const containerSelector = '[aria-roledescription="chart"]' ;
86+ const singleDataset = [
87+ {
88+ label : 'Series A' ,
89+ data : [
90+ { users : 100 , sessions : 200 , volume : 300 } ,
91+ { users : 50 , sessions : 150 , volume : 250 } ,
92+ { users : 200 , sessions : 400 , volume : 500 } ,
93+ ] ,
94+ } ,
95+ ] ;
96+
97+ const onDataPointClick = cy . spy ( ) . as ( 'onDataPointClick' ) ;
98+ const onBlur = cy . spy ( ) . as ( 'onBlur' ) ;
99+ const onFocus = cy . spy ( ) . as ( 'onFocus' ) ;
100+ const onKeyDownCapture = cy . spy ( ) . as ( 'onKeyDownCapture' ) ;
101+
102+ cy . mount (
103+ < >
104+ < button > before</ button >
105+ < ScatterChart
106+ dataset = { singleDataset }
107+ measures = { measures }
108+ chartConfig = { chartConfig }
109+ onDataPointClick = { onDataPointClick }
110+ onBlur = { onBlur }
111+ onFocus = { onFocus }
112+ onKeyDownCapture = { onKeyDownCapture }
113+ />
114+ < button > after</ button >
115+ </ > ,
116+ ) ;
117+ cy . get ( '[role="img"][aria-label]' ) . should ( 'have.length' , 3 ) ;
118+
119+ cy . findByText ( 'before' ) . focus ( ) ;
120+
121+ // container focused, first scatter "active"
122+ cy . realPress ( 'Tab' ) ;
123+ cy . focused ( )
124+ . should ( 'have.attr' , 'tabindex' , '0' )
125+ . should ( 'have.attr' , 'role' , 'application' )
126+ . should ( 'have.attr' , 'aria-roledescription' , 'chart' ) ;
127+ cy . get ( '@onFocus' ) . should ( 'have.been.calledOnce' ) ;
128+ activePointLabelShould ( containerSelector , 'Number: 50' ) ;
129+ cy . get ( '[data-point-focused]' ) . should ( 'have.length' , 1 ) ;
130+
131+ // 2nd scatter "active" - forward by X
132+ cy . realPress ( 'ArrowRight' ) ;
133+ activePointLabelShould ( containerSelector , 'Number: 100' ) ;
134+ cy . get ( '@onKeyDownCapture' ) . should ( 'have.been.called' ) ;
135+
136+ // 3rd scatter "active"
137+ cy . realPress ( 'ArrowRight' ) ;
138+ activePointLabelShould ( containerSelector , 'Number: 200' ) ;
139+
140+ // 3rd scatter "active" -> last one
141+ cy . realPress ( 'ArrowRight' ) ;
142+ activePointLabelShould ( containerSelector , 'Number: 200' ) ;
143+
144+ // 2nd scatter "active"
145+ cy . realPress ( 'ArrowLeft' ) ;
146+ activePointLabelShould ( containerSelector , 'Number: 100' ) ;
147+
148+ cy . realPress ( 'Enter' ) ;
149+ cy . get ( '@onDataPointClick' ) . should (
150+ 'have.been.calledWith' ,
151+ Cypress . sinon . match ( {
152+ detail : Cypress . sinon . match ( { payload : singleDataset [ 0 ] . data [ 0 ] } ) ,
153+ } ) ,
154+ ) ;
155+
156+ cy . get ( '[role="img"][aria-label]' ) . eq ( 2 ) . click ( ) ;
157+ cy . get ( '@onDataPointClick' ) . should ( 'have.been.calledTwice' ) ;
158+ activePointLabelShould ( containerSelector , 'Number: 200' ) ;
159+
160+ // Leave chart
161+ cy . realPress ( 'Tab' ) ;
162+ cy . focused ( ) . should ( 'contain.text' , 'after' ) ;
163+ cy . get ( containerSelector ) . should ( 'not.have.attr' , 'aria-activedescendant' ) ;
164+ cy . get ( '[data-point-focused]' ) . should ( 'not.exist' ) ;
165+ cy . get ( '@onBlur' ) . should ( 'have.been.called' ) ;
166+
167+ // Reenter chart
168+ cy . realPress ( [ 'Shift' , 'Tab' ] ) ;
169+ cy . focused ( ) . should ( 'have.attr' , 'aria-roledescription' , 'chart' ) ;
170+ activePointLabelShould ( containerSelector , 'Number: 200' ) ;
171+
172+ cy . realPress ( 'ArrowLeft' ) ;
173+ activePointLabelShould ( containerSelector , 'Number: 100' ) ;
174+ cy . realPress ( 'ArrowLeft' ) ;
175+ activePointLabelShould ( containerSelector , 'Number: 50' ) ;
176+ cy . realPress ( 'ArrowLeft' ) ;
177+ activePointLabelShould ( containerSelector , 'Number: 50' ) ;
178+ } ) ;
179+
180+ it ( 'accessibilityLayer: multi-dataset points sorted by X then datasetIndex' , ( ) => {
181+ const chartConfig = { accessibilityLayer : true } ;
182+ const containerSelector = '[aria-roledescription="chart"]' ;
183+ const multiDataset = [
184+ {
185+ label : 'Alpha' ,
186+ data : [ { users : 30 , sessions : 100 , volume : 200 } ] ,
187+ } ,
188+ {
189+ label : 'Beta' ,
190+ data : [
191+ { users : 30 , sessions : 150 , volume : 250 } ,
192+ { users : 60 , sessions : 300 , volume : 400 } ,
193+ ] ,
194+ } ,
195+ ] ;
196+
197+ cy . mount (
198+ < >
199+ < button > before</ button >
200+ < ScatterChart dataset = { multiDataset } measures = { measures } chartConfig = { chartConfig } />
201+ </ > ,
202+ ) ;
203+
204+ cy . get ( '[role="img"][aria-label]' ) . should ( 'have.length' , 3 ) ;
205+ cy . findByText ( 'before' ) . focus ( ) ;
206+ cy . realPress ( 'Tab' ) ;
207+
208+ // Same X value (30): sorted by dataset index, Alpha (0) before Beta (1)
209+ activePointLabelShould ( containerSelector , 'Alpha' ) ;
210+ cy . realPress ( 'ArrowRight' ) ;
211+ activePointLabelShould ( containerSelector , 'Beta' , 'Number: 30' ) ;
212+ cy . realPress ( 'ArrowRight' ) ;
213+ activePointLabelShould ( containerSelector , 'Beta' , 'Number: 60' ) ;
214+ } ) ;
215+
216+ it ( 'accessibilityLayer: multiple charts' , ( ) => {
217+ const chartConfig = { accessibilityLayer : true } ;
218+ const singleDataset = [
219+ {
220+ label : 'Series A' ,
221+ data : [
222+ { users : 100 , sessions : 200 , volume : 300 } ,
223+ { users : 50 , sessions : 150 , volume : 250 } ,
224+ ] ,
225+ } ,
226+ ] ;
227+
228+ cy . mount (
229+ < >
230+ < button > before</ button >
231+ < ScatterChart
232+ dataset = { singleDataset }
233+ measures = { measures }
234+ chartConfig = { chartConfig }
235+ aria-roledescription = "chart1"
236+ />
237+ < ScatterChart
238+ dataset = { singleDataset }
239+ measures = { measures }
240+ chartConfig = { chartConfig }
241+ aria-roledescription = "chart2"
242+ />
243+ < button > after</ button >
244+ </ > ,
245+ ) ;
246+
247+ cy . get ( '[role="img"][id]' ) . then ( ( $els ) => {
248+ const ids = [ ...$els ] . map ( ( el ) => el . id ) ;
249+ expect ( new Set ( ids ) . size ) . to . equal ( ids . length ) ;
250+ } ) ;
251+
252+ cy . findByText ( 'before' ) . focus ( ) ;
253+ cy . realPress ( 'Tab' ) ;
254+ cy . focused ( ) . should ( 'have.attr' , 'aria-roledescription' , 'chart1' ) ;
255+ cy . realPress ( 'ArrowRight' ) ;
256+ activePointLabelShould ( '[aria-roledescription="chart1"]:first' , 'Number: 100' ) ;
257+
258+ cy . realPress ( 'Tab' ) ;
259+ cy . focused ( ) . should ( 'have.attr' , 'aria-roledescription' , 'chart2' ) ;
260+ cy . realPress ( 'ArrowRight' ) ;
261+ activePointLabelShould ( '[aria-roledescription="chart2"]:first' , 'Number: 100' ) ;
262+
263+ cy . realPress ( 'Tab' ) ;
264+ cy . focused ( ) . should ( 'contain.text' , 'after' ) ;
265+ } ) ;
266+
267+ [ false , true ] . forEach ( ( accessibilityLayer ) => {
268+ it ( `empty dataset (accessibilityLayer: ${ accessibilityLayer } )` , ( ) => {
269+ cy . mount ( < ScatterChart dataset = { [ ] } measures = { measures } chartConfig = { { accessibilityLayer } } /> ) ;
270+ cy . get ( '.recharts-scatter' ) . should ( 'not.exist' ) ;
271+ cy . findByText ( 'Loading...' ) . should ( 'exist' ) ;
272+ if ( accessibilityLayer ) {
273+ cy . get ( '[aria-roledescription="chart"]' )
274+ . should ( 'have.attr' , 'tabindex' , '0' )
275+ . should ( 'not.have.attr' , 'role' , 'application' ) ;
276+ }
277+ } ) ;
70278 } ) ;
71279
72280 testChartLegendConfig ( ScatterChart , { dataset : complexDataSet , measures } ) ;
0 commit comments