feat: integrate and pass automated a11y testing

This commit is contained in:
erik 2019-10-09 16:06:00 +02:00
parent 4d029a5d4d
commit e1a417b041
17 changed files with 408 additions and 2 deletions

View file

@ -137,7 +137,7 @@ export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement))
if (!this.constructor._button) {
this.constructor._button = document.createElement('button');
this.constructor._button.setAttribute('tabindex', '-1');
this.constructor._button.setAttribute('aria-hidden', true);
this.constructor._button.setAttribute('aria-hidden', 'true');
}
return this.constructor._button.cloneNode();
},

View file

@ -228,6 +228,16 @@ describe('lion-button', () => {
expect(el._nativeButtonNode.getAttribute('aria-hidden')).to.equal('true');
});
it('is accessible', async () => {
const el = await fixture(`<lion-button>foo</lion-button>`);
await expect(el).to.be.accessible();
});
it('is accessible when disabled', async () => {
const el = await fixture(`<lion-button disabled>foo</lion-button>`);
await expect(el).to.be.accessible();
});
});
describe('form integration', () => {

View file

@ -1193,4 +1193,37 @@ describe('<lion-calendar>', () => {
*/
});
});
describe('Accessibility', () => {
it('is accessible', async () => {
const el = await fixture(
html`
<lion-calendar></lion-calendar>
`,
);
await expect(el).to.be.accessible();
});
it('is accessible with a date selected', async () => {
const today = new Date();
const selectedDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
const el = await fixture(
html`
<lion-calendar .selectedDate="${selectedDate}"></lion-calendar>
`,
);
await expect(el).to.be.accessible();
});
it('is accessible with disabled dates', async () => {
const el = await fixture(
html`
<lion-calendar
.disableDates=${day => day.getDay() === 6 || day.getDay() === 0}
></lion-calendar>
`,
);
await expect(el).to.be.accessible();
});
});
});

View file

@ -25,4 +25,78 @@ describe('<lion-checkbox-group>', () => {
el.formElements['sports[]'][0].checked = true;
expect(el.hasFeedbackFor).to.deep.equal([]);
});
it('is accessible', async () => {
const el = await fixture(html`
<lion-checkbox-group name="scientistsGroup" label="Who are your favorite scientists?">
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.modelValue=${{ value: 'Marie Curie', checked: false }}
></lion-checkbox>
</lion-checkbox-group>
`);
await expect(el).to.be.accessible();
});
it('is accessible when pre-selected', async () => {
const el = await fixture(html`
<lion-checkbox-group name="scientistsGroup" label="Who are your favorite scientists?">
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
.choiceChecked=${true}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.modelValue=${{ value: 'Marie Curie', checked: true }}
></lion-checkbox>
</lion-checkbox-group>
`);
await expect(el).to.be.accessible();
});
it('is accessible when disabled', async () => {
const el = await fixture(html`
<lion-checkbox-group
name="scientistsGroup"
label="Who are your favorite scientists?"
disabled
>
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.modelValue=${{ value: 'Marie Curie', checked: true }}
></lion-checkbox>
</lion-checkbox-group>
`);
await expect(el).to.be.accessible();
});
});

View file

@ -71,6 +71,24 @@ describe('lion-icon', () => {
expect(el.hasAttribute('aria-label')).to.equal(false);
});
it('is accessible with an aria label', async () => {
const el = await fixture(
html`
<lion-icon .svg=${heartSvg} aria-label="Love"></lion-icon>
`,
);
await expect(el).to.be.accessible();
});
it('is accessible without an aria label', async () => {
const el = await fixture(
html`
<lion-icon .svg=${heartSvg}></lion-icon>
`,
);
await expect(el).to.be.accessible();
});
it('expects svg-icons to have the attribute `focusable="false"` so the icon doesn\'t appear in tab-order in IE/Edge', async () => {
const icon = await fixture(
html`

View file

@ -118,4 +118,25 @@ describe('<lion-input-amount>', () => {
expect(el._currencyDisplayNode.getAttribute('aria-label')).to.equal('Philippine pisos');
});
});
it('is accessible', async () => {
const el = await fixture(
`<lion-input-amount><label slot="label">Label</label></lion-input-amount>`,
);
await expect(el).to.be.accessible();
});
it('is accessible when readonly', async () => {
const el = await fixture(
`<lion-input-amount readonly .modelValue=${'123'}><label slot="label">Label</label></lion-input-amount>`,
);
await expect(el).to.be.accessible();
});
it('is accessible when disabled', async () => {
const el = await fixture(
`<lion-input-amount disabled><label slot="label">Label</label></lion-input-amount>`,
);
await expect(el).to.be.accessible();
});
});

View file

@ -96,4 +96,27 @@ describe('<lion-input-date>', () => {
await el.updateComplete;
expect(el.formattedValue).to.equal('15/06/2017'); // should stay british
});
it('is accessible', async () => {
const el = await fixture(
`<lion-input-date><label slot="label">Label</label></lion-input-date>`,
);
await expect(el).to.be.accessible();
});
it('is accessible when readonly', async () => {
const el = await fixture(
`<lion-input-date readonly .modelValue=${new Date(
'2017/06/15',
)}><label slot="label">Label</label></lion-input-date>`,
);
await expect(el).to.be.accessible();
});
it('is accessible when disabled', async () => {
const el = await fixture(
`<lion-input-date disabled><label slot="label">Label</label></lion-input-date>`,
);
await expect(el).to.be.accessible();
});
});

View file

@ -301,6 +301,50 @@ describe('<lion-input-datepicker>', () => {
await elObj.closeCalendar();
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal('false');
});
it('is accessible when closed', async () => {
const el = await fixture(html`
<lion-input-datepicker></lion-input-datepicker>
`);
const elObj = new DatepickerInputObject(el);
await expect(elObj.invokerEl).to.be.accessible();
await expect(elObj.calendarEl).to.be.accessible();
});
it('is accessible when open', async () => {
const el = await fixture(html`
<lion-input-datepicker></lion-input-datepicker>
`);
const elObj = new DatepickerInputObject(el);
await elObj.openCalendar();
await expect(elObj.calendarEl).to.be.accessible();
elObj.overlayCloseButtonEl.click();
});
it('has accessible invoker when open', async () => {
const el = await fixture(html`
<lion-input-datepicker></lion-input-datepicker>
`);
const elObj = new DatepickerInputObject(el);
await elObj.openCalendar();
await expect(elObj.invokerEl).to.be.accessible();
elObj.overlayCloseButtonEl.click();
});
it('is accessible with a disabled date', async () => {
const no15th = d => d.getDate() !== 15;
const el = await fixture(html`
<lion-input-datepicker .validators=${[new IsDateDisabled(no15th)]}> </lion-input-datepicker>
`);
const elObj = new DatepickerInputObject(el);
await elObj.openCalendar();
await expect(elObj.calendarEl).to.be.accessible();
elObj.overlayCloseButtonEl.click();
});
});
describe.skip('Subclassers', () => {

View file

@ -15,4 +15,25 @@ describe('<lion-input-email>', () => {
expect(el.hasFeedbackFor).to.deep.equal(['error']);
expect(el.validationStates.error.IsEmail).to.be.true;
});
it('is accessible', async () => {
const lionInputEmail = await fixture(
`<lion-input-email><label slot="label">Label</label></lion-input-email>`,
);
await expect(lionInputEmail).to.be.accessible();
});
it('is accessible when readonly', async () => {
const lionInputEmail = await fixture(
`<lion-input-email readonly><label slot="label">Label</label></lion-input-email>`,
);
await expect(lionInputEmail).to.be.accessible();
});
it('is accessible when disabled', async () => {
const lionInputEmail = await fixture(
`<lion-input-email disabled><label slot="label">Label</label></lion-input-email>`,
);
await expect(lionInputEmail).to.be.accessible();
});
});

View file

@ -53,4 +53,25 @@ describe('<lion-input-iban>', () => {
expect(el.validationStates.error).to.have.a.property('IsIBAN');
expect(el.validationStates.error).to.have.a.property('IsCountryIBAN');
});
it('is accessible', async () => {
const el = await fixture(
`<lion-input-iban><label slot="label">Label</label></lion-input-iban>`,
);
await expect(el).to.be.accessible();
});
it('is accessible when readonly', async () => {
const el = await fixture(
`<lion-input-iban readonly><label slot="label">Label</label></lion-input-iban>`,
);
await expect(el).to.be.accessible();
});
it('is accessible when disabled', async () => {
const el = await fixture(
`<lion-input-iban disabled><label slot="label">Label</label></lion-input-iban>`,
);
await expect(el).to.be.accessible();
});
});

View file

@ -41,4 +41,21 @@ describe('<lion-input>', () => {
expect(el.getAttribute('placeholder')).to.equal('foo');
expect(el._inputNode.getAttribute('placeholder')).to.equal('foo');
});
it('is accessible', async () => {
const el = await fixture(`<lion-input><label slot="label">Label</label></lion-input>`);
await expect(el).to.be.accessible();
});
it('is accessible when readonly', async () => {
const el = await fixture(
`<lion-input readonly .modelValue=${'read only'}><label slot="label">Label</label></lion-input>`,
);
await expect(el).to.be.accessible();
});
it('is accessible when disabled', async () => {
const el = await fixture(`<lion-input disabled><label slot="label">Label</label></lion-input>`);
await expect(el).to.be.accessible();
});
});

View file

@ -221,7 +221,7 @@ export class OverlayController {
if (this.invokerNode) {
this.invokerNode.setAttribute('aria-expanded', this.isShown);
}
if (!this.contentNode.hasAttribute('role')) {
if (!this.contentNode.role) {
this.contentNode.setAttribute('role', 'dialog');
}
}

View file

@ -265,4 +265,52 @@ describe('<lion-radio-group>', () => {
el.children[1].checked = true;
expect(el.touched, `focused via a mouse click, group should be touched`).to.be.true;
});
it('is accessible', async () => {
const el = await fixture(html`
<lion-radio-group>
<label slot="label">My group</label>
<lion-radio name="gender[]" value="male">
<label slot="label">male</label>
</lion-radio>
<lion-radio name="gender[]" value="female" checked>
<label slot="label">female</label>
</lion-radio>
</lion-radio-group>
`);
await nextFrame();
await expect(el).to.be.accessible();
});
it('is accessible when the group is disabled', async () => {
const el = await fixture(html`
<lion-radio-group disabled>
<label slot="label">My group</label>
<lion-radio name="gender[]" value="male">
<label slot="label">male</label>
</lion-radio>
<lion-radio name="gender[]" value="female">
<label slot="label">female</label>
</lion-radio>
</lion-radio-group>
`);
await nextFrame();
await expect(el).to.be.accessible();
});
it('is accessible when an option is disabled', async () => {
const el = await fixture(html`
<lion-radio-group>
<label slot="label">My group</label>
<lion-radio name="gender[]" value="male" disabled>
<label slot="label">male</label>
</lion-radio>
<lion-radio name="gender[]" value="female">
<label slot="label">female</label>
</lion-radio>
</lion-radio-group>
`);
await nextFrame();
await expect(el).to.be.accessible();
});
});

View file

@ -400,6 +400,34 @@ describe('lion-select-rich', () => {
expect(el._invokerNode.getAttribute('aria-expanded')).to.equal('true');
});
it('is accessible when closed', async () => {
const el = await fixture(html`
<lion-select-rich label="age">
<lion-options slot="input">
<lion-option .choiceValue=${10}>Item 1</lion-option>
<lion-option .choiceValue=${20}>Item 2</lion-option>
</lion-options>
</lion-select-rich>
`);
await expect(el).to.be.accessible();
});
it('is accessible when opened', async () => {
const el = await fixture(html`
<lion-select-rich label="age">
<lion-options slot="input">
<lion-option .choiceValue=${10}>Item 1</lion-option>
<lion-option .choiceValue=${20}>Item 2</lion-option>
</lion-options>
</lion-select-rich>
`);
el.opened = true;
await el.updateComplete;
await el.updateComplete; // need 2 awaits as overlay.show is an async function
await expect(el).to.be.accessible();
});
});
describe('Use cases', () => {

View file

@ -15,4 +15,17 @@ describe('lion-select', () => {
`);
expect(lionSelect.querySelector('select').value).to.equal('nr2');
});
it('is accessible', async () => {
const lionSelect = await fixture(html`
<lion-select .modelValue="${'nr2'}">
<label slot="label">Label</label>
<select slot="input">
<option value="nr1">Item 1</option>
<option value="nr2">Item 2</option>
</select>
</lion-select>
`);
await expect(lionSelect).to.be.accessible();
});
});

View file

@ -139,4 +139,14 @@ describe('<lion-textarea>', () => {
.to.be.below(el.clientHeight)
.and.to.be.below(el.scrollHeight);
});
it('is accessible', async () => {
const el = await fixture(`<lion-textarea label="Label"></lion-textarea>`);
await expect(el).to.be.accessible();
});
it('is accessible when disabled', async () => {
const el = await fixture(`<lion-textarea label="Label" disabled></lion-textarea>`);
await expect(el).to.be.accessible();
});
});

View file

@ -117,5 +117,30 @@ describe('lion-tooltip', () => {
const content = el.querySelector('[slot=content]');
expect(content.getAttribute('role')).to.be.equal('tooltip');
});
it('should be accessible when closed', async () => {
const el = await fixture(html`
<lion-tooltip>
<div slot="content">Hey there</div>
<lion-button slot="invoker">Tooltip button</lion-button>
</lion-tooltip>
`);
await expect(el).to.be.accessible;
});
it('should be accessible when opened', async () => {
const el = await fixture(html`
<lion-tooltip>
<div slot="content">Hey there</div>
<lion-button slot="invoker">Tooltip button</lion-button>
</lion-tooltip>
`);
const invoker = el.querySelector('[slot="invoker"]');
const eventFocusIn = new Event('focusin');
invoker.dispatchEvent(eventFocusIn);
await el.updateComplete;
await expect(el).to.be.accessible;
});
});
});