Skip to content

Commit c029881

Browse files
authored
test: extract and extend select focus related tests (#9575)
1 parent 9147428 commit c029881

File tree

2 files changed

+193
-107
lines changed

2 files changed

+193
-107
lines changed

packages/select/test/accessibility.test.js

Lines changed: 192 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import { 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';
45
import './not-animated-styles.js';
56
import '../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

99
describe('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
});

packages/select/test/select.test.js

Lines changed: 1 addition & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
nextRender,
1111
nextUpdate,
1212
oneEvent,
13-
tab,
1413
} from '@vaadin/testing-helpers';
1514
import sinon from 'sinon';
1615
import './not-animated-styles.js';
@@ -295,22 +294,6 @@ describe('vaadin-select', () => {
295294
expect(select.opened).to.be.false;
296295
});
297296

298-
it('should focus the menu when opening the overlay', async () => {
299-
const spy = sinon.spy(select._menuElement, 'focus');
300-
select.opened = true;
301-
await oneEvent(overlay, 'vaadin-overlay-open');
302-
expect(spy.calledOnce).to.be.true;
303-
});
304-
305-
it('should restore attribute focus-ring if it was initially set before opening', async () => {
306-
select.setAttribute('focus-ring', '');
307-
select.opened = true;
308-
await oneEvent(overlay, 'vaadin-overlay-open');
309-
select.opened = false;
310-
await nextUpdate(select);
311-
expect(select.hasAttribute('focus-ring')).to.be.true;
312-
});
313-
314297
it('should open the overlay on click event on value button', () => {
315298
expect(select.opened).to.be.false;
316299
click(valueButton);
@@ -384,10 +367,9 @@ describe('vaadin-select', () => {
384367
});
385368

386369
describe('overlay opened', () => {
387-
let menu, overlay;
370+
let overlay;
388371

389372
beforeEach(async () => {
390-
menu = select._menuElement;
391373
overlay = select._overlayElement;
392374
await nextUpdate(select);
393375
select.focus();
@@ -402,29 +384,6 @@ describe('vaadin-select', () => {
402384
expect(overlay.opened).to.be.false;
403385
});
404386

405-
it('should restore focused state on closing the overlay if phone', async () => {
406-
select._phone = true;
407-
await nextUpdate(select);
408-
click(select._items[1]);
409-
expect(select.hasAttribute('focused')).to.be.true;
410-
});
411-
412-
it('should focus the button on closing the overlay if phone', async () => {
413-
const focusedSpy = sinon.spy(valueButton, 'focus');
414-
select._phone = true;
415-
await nextUpdate(select);
416-
click(select._items[1]);
417-
await nextUpdate(select);
418-
expect(focusedSpy.called).to.be.true;
419-
});
420-
421-
it('should focus the button before moving the focus to next selectable element', async () => {
422-
const focusedSpy = sinon.spy(valueButton, 'focus');
423-
tab(menu);
424-
await nextUpdate(select);
425-
expect(focusedSpy.called).to.be.true;
426-
});
427-
428387
it('should close the overlay when clicking on the overlay', async () => {
429388
overlay.click();
430389
await nextUpdate(select);
@@ -553,33 +512,6 @@ describe('vaadin-select', () => {
553512
});
554513
});
555514

556-
describe('focus', () => {
557-
it('should be focusable', () => {
558-
select.focus();
559-
expect(select.hasAttribute('focused')).to.be.true;
560-
});
561-
562-
it('should focus on required indicator click', () => {
563-
select.shadowRoot.querySelector('[part="required-indicator"]').click();
564-
expect(select.hasAttribute('focused')).to.be.true;
565-
});
566-
});
567-
568-
describe('focus when overlay opened', () => {
569-
it('should keep focused state after opening overlay when focused', async () => {
570-
select.focus();
571-
select.opened = true;
572-
await nextUpdate(select);
573-
expect(select.hasAttribute('focused')).to.be.true;
574-
});
575-
576-
it('should not set focused state after opening overlay if not focused', async () => {
577-
select.opened = true;
578-
await nextUpdate(select);
579-
expect(select.hasAttribute('focused')).to.be.false;
580-
});
581-
});
582-
583515
describe('change event', () => {
584516
let menu, changeSpy;
585517

0 commit comments

Comments
 (0)