11import { expect } from '@vaadin/chai-plugins' ;
2- import { sendKeys } from '@vaadin/test-runner-commands' ;
3- import { fixtureSync , nextRender , nextUpdate } from '@vaadin/testing-helpers' ;
2+ import { resetMouse , sendKeys , sendMouse , sendMouseToElement } from '@vaadin/test-runner-commands' ;
3+ import { fixtureSync , nextRender , nextUpdate , oneEvent } from '@vaadin/testing-helpers' ;
4+ import sinon from 'sinon' ;
45import './not-animated-styles.js' ;
56import '../src/vaadin-select.js' ;
6- import '@vaadin/item/src/vaadin-item.js' ;
7- import '@vaadin/list-box/src/vaadin-list-box.js' ;
7+ import { getDeepActiveElement } from '@vaadin/a11y-base/src/focus-utils.js' ;
88
99describe ( 'accessibility' , ( ) => {
10- let select , valueButton ;
10+ let select , valueButton , firstGlobalFocusable , lastGlobalFocusable ;
1111
1212 beforeEach ( async ( ) => {
13- select = fixtureSync ( `<vaadin-select label="Label"></vaadin-select>` ) ;
13+ const wrapper = fixtureSync ( `
14+ <div>
15+ <input id="first-global-focusable" />
16+ <vaadin-select label="Label"></vaadin-select>
17+ <input id="last-global-focusable" />
18+ </div>
19+ ` ) ;
20+ [ firstGlobalFocusable , select , lastGlobalFocusable ] = wrapper . children ;
1421 await nextRender ( ) ;
1522 select . items = [
1623 { label : 'Option 1' , value : 'Option 1' } ,
@@ -20,43 +27,45 @@ describe('accessibility', () => {
2027 valueButton = select . querySelector ( 'vaadin-select-value-button' ) ;
2128 } ) ;
2229
23- it ( 'should toggle aria-expanded attribute on the value button on open' , async ( ) => {
24- select . opened = true ;
25- await nextUpdate ( select ) ;
26- expect ( valueButton . getAttribute ( 'aria-expanded' ) ) . to . equal ( 'true' ) ;
30+ describe ( 'ARIA attributes' , ( ) => {
31+ it ( 'should toggle aria-expanded attribute on the value button on open' , async ( ) => {
32+ select . opened = true ;
33+ await nextUpdate ( select ) ;
34+ expect ( valueButton . getAttribute ( 'aria-expanded' ) ) . to . equal ( 'true' ) ;
2735
28- select . opened = false ;
29- await nextUpdate ( select ) ;
30- expect ( valueButton . getAttribute ( 'aria-expanded' ) ) . to . equal ( 'false' ) ;
31- } ) ;
36+ select . opened = false ;
37+ await nextUpdate ( select ) ;
38+ expect ( valueButton . getAttribute ( 'aria-expanded' ) ) . to . equal ( 'false' ) ;
39+ } ) ;
3240
33- it ( 'should add aria-live attribute on first-letter shortcut selection' , async ( ) => {
34- select . focus ( ) ;
35- await sendKeys ( { press : 'o' } ) ;
36- await nextUpdate ( select ) ;
37- expect ( valueButton . getAttribute ( 'aria-live' ) ) . to . equal ( 'polite' ) ;
38- } ) ;
41+ it ( 'should add aria-live attribute on first-letter shortcut selection' , async ( ) => {
42+ select . focus ( ) ;
43+ await sendKeys ( { press : 'o' } ) ;
44+ await nextUpdate ( select ) ;
45+ expect ( valueButton . getAttribute ( 'aria-live' ) ) . to . equal ( 'polite' ) ;
46+ } ) ;
3947
40- it ( 'should remove aria-live attribute on dropdown open' , async ( ) => {
41- select . focus ( ) ;
42- await sendKeys ( { press : 'o' } ) ;
43- select . opened = true ;
44- await nextUpdate ( select ) ;
45- expect ( valueButton . hasAttribute ( 'aria-live' ) ) . to . be . false ;
46- } ) ;
48+ it ( 'should remove aria-live attribute on dropdown open' , async ( ) => {
49+ select . focus ( ) ;
50+ await sendKeys ( { press : 'o' } ) ;
51+ select . opened = true ;
52+ await nextUpdate ( select ) ;
53+ expect ( valueButton . hasAttribute ( 'aria-live' ) ) . to . be . false ;
54+ } ) ;
4755
48- it ( 'should append item id to `aria-labelledby` when an item is selected' , async ( ) => {
49- select . value = 'Option 1' ;
50- await nextUpdate ( select ) ;
51- const labelId = select . querySelector ( '[slot=label]' ) . id ;
52- expect ( valueButton . getAttribute ( 'aria-labelledby' ) . split ( ' ' ) ) . to . have . members ( [ select . _itemId , labelId ] ) ;
53- } ) ;
56+ it ( 'should append item id to `aria-labelledby` when an item is selected' , async ( ) => {
57+ select . value = 'Option 1' ;
58+ await nextUpdate ( select ) ;
59+ const labelId = select . querySelector ( '[slot=label]' ) . id ;
60+ expect ( valueButton . getAttribute ( 'aria-labelledby' ) . split ( ' ' ) ) . to . have . members ( [ select . _itemId , labelId ] ) ;
61+ } ) ;
5462
55- it ( 'should append item id to `aria-labelledby` when placeholder is set' , async ( ) => {
56- select . placeholder = 'placeholder' ;
57- await nextUpdate ( select ) ;
58- const labelId = select . querySelector ( '[slot=label]' ) . id ;
59- expect ( valueButton . getAttribute ( 'aria-labelledby' ) . split ( ' ' ) ) . to . have . members ( [ select . _itemId , labelId ] ) ;
63+ it ( 'should append item id to `aria-labelledby` when placeholder is set' , async ( ) => {
64+ select . placeholder = 'placeholder' ;
65+ await nextUpdate ( select ) ;
66+ const labelId = select . querySelector ( '[slot=label]' ) . id ;
67+ expect ( valueButton . getAttribute ( 'aria-labelledby' ) . split ( ' ' ) ) . to . have . members ( [ select . _itemId , labelId ] ) ;
68+ } ) ;
6069 } ) ;
6170
6271 describe ( 'accessible-name' , ( ) => {
@@ -307,4 +316,149 @@ describe('accessibility', () => {
307316 } ) ;
308317 } ) ;
309318 } ) ;
319+
320+ describe ( 'focus' , ( ) => {
321+ let overlay , listBox ;
322+
323+ beforeEach ( ( ) => {
324+ overlay = select . _overlayElement ;
325+ listBox = select . _menuElement ;
326+ } ) ;
327+
328+ describe ( 'focused' , ( ) => {
329+ it ( 'should set focused attribute when calling focus()' , ( ) => {
330+ select . focus ( ) ;
331+ expect ( select . hasAttribute ( 'focused' ) ) . to . be . true ;
332+ } ) ;
333+
334+ it ( 'should focus on required indicator click' , ( ) => {
335+ select . shadowRoot . querySelector ( '[part="required-indicator"]' ) . click ( ) ;
336+ expect ( select . hasAttribute ( 'focused' ) ) . to . be . true ;
337+ } ) ;
338+ } ) ;
339+
340+ describe ( 'opening' , ( ) => {
341+ it ( 'should keep focused state after opening overlay when focused' , async ( ) => {
342+ select . focus ( ) ;
343+ select . opened = true ;
344+ await oneEvent ( overlay , 'vaadin-overlay-open' ) ;
345+ expect ( select . hasAttribute ( 'focused' ) ) . to . be . true ;
346+ } ) ;
347+
348+ it ( 'should not set focused state after opening overlay if not focused' , async ( ) => {
349+ select . opened = true ;
350+ await oneEvent ( overlay , 'vaadin-overlay-open' ) ;
351+ expect ( select . hasAttribute ( 'focused' ) ) . to . be . false ;
352+ } ) ;
353+
354+ it ( 'should focus the list-box when opening the overlay' , async ( ) => {
355+ const spy = sinon . spy ( listBox , 'focus' ) ;
356+ select . opened = true ;
357+ await oneEvent ( overlay , 'vaadin-overlay-open' ) ;
358+ expect ( spy . calledOnce ) . to . be . true ;
359+ } ) ;
360+ } ) ;
361+
362+ describe ( 'focus-ring' , ( ) => {
363+ beforeEach ( ( ) => {
364+ select . focus ( ) ;
365+ } ) ;
366+
367+ afterEach ( async ( ) => {
368+ await resetMouse ( ) ;
369+ } ) ;
370+
371+ it ( 'should restore focus-ring attribute on item click if it was set before opening' , async ( ) => {
372+ select . setAttribute ( 'focus-ring' , '' ) ;
373+ select . opened = true ;
374+ await oneEvent ( overlay , 'vaadin-overlay-open' ) ;
375+
376+ const item = listBox . querySelector ( 'vaadin-select-item' ) ;
377+ await sendMouseToElement ( { type : 'click' , element : item } ) ;
378+ await nextRender ( ) ;
379+
380+ expect ( select . hasAttribute ( 'focus-ring' ) ) . to . be . true ;
381+ } ) ;
382+
383+ it ( 'should not set focus-ring attribute on item click if it was not set before opening' , async ( ) => {
384+ select . opened = true ;
385+ await oneEvent ( overlay , 'vaadin-overlay-open' ) ;
386+
387+ const item = listBox . querySelector ( 'vaadin-select-item' ) ;
388+ await sendMouseToElement ( { type : 'click' , element : item } ) ;
389+ await nextRender ( ) ;
390+
391+ expect ( select . hasAttribute ( 'focus-ring' ) ) . to . be . false ;
392+ } ) ;
393+
394+ it ( 'should set focus-ring attribute on item Enter if it was not set before opening' , async ( ) => {
395+ select . opened = true ;
396+ await oneEvent ( overlay , 'vaadin-overlay-open' ) ;
397+
398+ await sendKeys ( { press : 'Enter' } ) ;
399+ await nextRender ( ) ;
400+
401+ expect ( select . hasAttribute ( 'focus-ring' ) ) . to . be . true ;
402+ } ) ;
403+
404+ it ( 'should set focus-ring attribute on item Escape if it was not set before opening' , async ( ) => {
405+ select . opened = true ;
406+ await oneEvent ( overlay , 'vaadin-overlay-open' ) ;
407+
408+ await sendKeys ( { press : 'Escape' } ) ;
409+ await nextRender ( ) ;
410+
411+ expect ( select . hasAttribute ( 'focus-ring' ) ) . to . be . true ;
412+ } ) ;
413+ } ) ;
414+
415+ describe ( 'opened' , ( ) => {
416+ beforeEach ( async ( ) => {
417+ select . focus ( ) ;
418+ select . opened = true ;
419+ await oneEvent ( overlay , 'vaadin-overlay-open' ) ;
420+ } ) ;
421+
422+ afterEach ( async ( ) => {
423+ await resetMouse ( ) ;
424+ } ) ;
425+
426+ it ( 'should focus next focusable on menu item Tab' , async ( ) => {
427+ await sendKeys ( { press : 'Tab' } ) ;
428+ await nextRender ( ) ;
429+ expect ( getDeepActiveElement ( ) ) . to . equal ( lastGlobalFocusable ) ;
430+ } ) ;
431+
432+ it ( 'should focus previous focusable on menu item Shift + Tab' , async ( ) => {
433+ await sendKeys ( { press : 'Shift+Tab' } ) ;
434+ await nextRender ( ) ;
435+ expect ( getDeepActiveElement ( ) ) . to . equal ( firstGlobalFocusable ) ;
436+ } ) ;
437+
438+ it ( 'should restore focus to the value button on item Enter' , async ( ) => {
439+ await sendKeys ( { press : 'Enter' } ) ;
440+ await nextRender ( ) ;
441+ expect ( getDeepActiveElement ( ) ) . to . equal ( valueButton ) ;
442+ } ) ;
443+
444+ it ( 'should restore focus to the value button on item Escape' , async ( ) => {
445+ await sendKeys ( { press : 'Escape' } ) ;
446+ await nextRender ( ) ;
447+ expect ( getDeepActiveElement ( ) ) . to . equal ( valueButton ) ;
448+ } ) ;
449+
450+ it ( 'should restore focus to the value button on item click' , async ( ) => {
451+ const item = listBox . querySelector ( 'vaadin-select-item' ) ;
452+ await sendMouseToElement ( { type : 'click' , element : item } ) ;
453+ await nextRender ( ) ;
454+ expect ( getDeepActiveElement ( ) ) . to . equal ( valueButton ) ;
455+ } ) ;
456+
457+ it ( 'should restore focus to the value button on outside click' , async ( ) => {
458+ await sendMouse ( { type : 'click' , position : [ 100 , 100 ] } ) ;
459+ await nextRender ( ) ;
460+ expect ( getDeepActiveElement ( ) ) . to . equal ( valueButton ) ;
461+ } ) ;
462+ } ) ;
463+ } ) ;
310464} ) ;
0 commit comments