UNPKG

@lion/ui

Version:

A package of extendable web components

372 lines (316 loc) 13.9 kB
/* eslint-disable lit-a11y/click-events-have-key-events */ import { LionButton } from '@lion/ui/button.js'; import { browserDetection } from '@lion/ui/core.js'; import { aTimeout, defineCE, expect, fixture, html, oneEvent, unsafeStatic, } from '@open-wc/testing'; import sinon from 'sinon'; export function LionButtonSuite({ klass = LionButton } = {}) { const tagStringButton = defineCE(class extends klass {}); const tagButton = unsafeStatic(tagStringButton); describe('LionButton', () => { it('has .type="button" and type="button" by default', async () => { const el = /** @type {LionButton} */ (await fixture(html`<${tagButton}>foo</${tagButton}>`)); expect(el.type).to.equal('button'); expect(el.getAttribute('type')).to.be.equal('button'); }); it('is hidden when attribute hidden is true', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton} hidden>foo</${tagButton}>`) ); expect(el).not.to.be.displayed; }); it('can be disabled imperatively', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton} disabled>foo</${tagButton}>`) ); expect(el.getAttribute('tabindex')).to.equal('-1'); expect(el.getAttribute('aria-disabled')).to.equal('true'); el.disabled = false; await el.updateComplete; expect(el.getAttribute('tabindex')).to.equal('0'); expect(el.getAttribute('aria-disabled')).to.not.exist; expect(el.hasAttribute('disabled')).to.equal(false); el.disabled = true; await el.updateComplete; expect(el.getAttribute('tabindex')).to.equal('-1'); expect(el.getAttribute('aria-disabled')).to.equal('true'); expect(el.hasAttribute('disabled')).to.equal(true); }); describe('Active', () => { it('updates "active" attribute on host when mousedown/mouseup on button', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton}>foo</${tagButton}>`) ); el.dispatchEvent(new Event('mousedown')); expect(el.active).to.be.true; await el.updateComplete; expect(el.hasAttribute('active')).to.be.true; el.dispatchEvent(new Event('mouseup')); expect(el.active).to.be.false; await el.updateComplete; expect(el.hasAttribute('active')).to.be.false; }); it('updates "active" attribute on host when mousedown on button and mouseup anywhere else', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton}>foo</${tagButton}>`) ); el.dispatchEvent(new Event('mousedown')); expect(el.active).to.be.true; await el.updateComplete; expect(el.hasAttribute('active')).to.be.true; document.dispatchEvent(new Event('mouseup')); expect(el.active).to.be.false; await el.updateComplete; expect(el.hasAttribute('active')).to.be.false; }); it('updates "active" attribute on host when space keydown/keyup on button', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton}>foo</${tagButton}>`) ); el.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' })); expect(el.active).to.be.true; await el.updateComplete; expect(el.hasAttribute('active')).to.be.true; el.dispatchEvent(new KeyboardEvent('keyup', { key: ' ' })); expect(el.active).to.be.false; await el.updateComplete; expect(el.hasAttribute('active')).to.be.false; }); it('updates "active" attribute on host when space keydown on button and space keyup anywhere else', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton}>foo</${tagButton}>`) ); el.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' })); expect(el.active).to.be.true; await el.updateComplete; expect(el.hasAttribute('active')).to.be.true; el.dispatchEvent(new KeyboardEvent('keyup', { key: ' ' })); expect(el.active).to.be.false; await el.updateComplete; expect(el.hasAttribute('active')).to.be.false; }); it('updates "active" attribute on host when enter keydown/keyup on button', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton}>foo</${tagButton}>`) ); el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); expect(el.active).to.be.true; await el.updateComplete; expect(el.hasAttribute('active')).to.be.true; el.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' })); expect(el.active).to.be.false; await el.updateComplete; expect(el.hasAttribute('active')).to.be.false; }); it('updates "active" attribute on host when enter keydown on button and space keyup anywhere else', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton}>foo</${tagButton}>`) ); el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); expect(el.active).to.be.true; await el.updateComplete; expect(el.hasAttribute('active')).to.be.true; document.body.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' })); expect(el.active).to.be.false; await el.updateComplete; expect(el.hasAttribute('active')).to.be.false; }); }); describe('Accessibility', () => { it('has a role="button" by default', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton}>foo</${tagButton}>`) ); expect(el.getAttribute('role')).to.equal('button'); el.setAttribute('role', 'foo'); await el.updateComplete; expect(el.getAttribute('role')).to.equal('foo'); }); it('does not override user provided role', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton} role="foo">foo</${tagButton}>`) ); expect(el.getAttribute('role')).to.equal('foo'); }); it('has a tabindex="0" by default', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton}>foo</${tagButton}>`) ); expect(el.getAttribute('tabindex')).to.equal('0'); }); it('has a tabindex="-1" when disabled', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton} disabled>foo</${tagButton}>`) ); expect(el.getAttribute('tabindex')).to.equal('-1'); el.disabled = false; await el.updateComplete; expect(el.getAttribute('tabindex')).to.equal('0'); el.disabled = true; await el.updateComplete; expect(el.getAttribute('tabindex')).to.equal('-1'); }); it('does not override user provided tabindex', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton} tabindex="5">foo</${tagButton}>`) ); expect(el.getAttribute('tabindex')).to.equal('5'); }); it('disabled does not override user provided tabindex', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton} tabindex="5" disabled>foo</${tagButton}>`) ); expect(el.getAttribute('tabindex')).to.equal('-1'); el.disabled = false; await el.updateComplete; expect(el.getAttribute('tabindex')).to.equal('5'); }); it('does not override aria-labelledby when provided by user', async () => { const browserDetectionStub = sinon.stub(browserDetection, 'isIE11').value(true); const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton} aria-labelledby="some-id another-id">foo</${tagButton}>`) ); expect(el.getAttribute('aria-labelledby')).to.equal('some-id another-id'); browserDetectionStub.restore(); }); it('[axe] is accessible', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton}>foo</${tagButton}>`) ); await expect(el).to.be.accessible(); }); it('[axe] is accessible when disabled', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton} disabled>foo</${tagButton}>`) ); await expect(el).to.be.accessible({ ignoredRules: ['color-contrast'] }); }); }); describe('Click event', () => { /** * @param {HTMLButtonElement | LionButton} el */ async function prepareClickEvent(el) { setTimeout(() => { el.click(); }); return oneEvent(el, 'click'); } it('is fired once', async () => { const clickSpy = /** @type {EventListener} */ (sinon.spy()); const el = /** @type {LionButton} */ ( await fixture(html` <${tagButton} @click="${clickSpy}">foo</${tagButton}> `) ); el.click(); // trying to wait for other possible redispatched events await aTimeout(0); await aTimeout(0); expect(clickSpy).to.have.been.calledOnce; }); describe('Native button behavior', async () => { /** @type {Event} */ let nativeButtonEvent; /** @type {Event} */ let lionButtonEvent; before(async () => { const nativeButtonEl = /** @type {LionButton} */ (await fixture('<button>foo</button>')); const lionButtonEl = /** @type {LionButton} */ ( await fixture(html`<${tagButton}>foo</${tagButton}>`) ); nativeButtonEvent = await prepareClickEvent(nativeButtonEl); lionButtonEvent = await prepareClickEvent(lionButtonEl); }); const sameProperties = [ 'constructor', 'composed', 'bubbles', 'cancelable', 'clientX', 'clientY', ]; sameProperties.forEach(property => { it(`has same value of the property "${property}" as in native button event`, () => { expect(lionButtonEvent[property]).to.equal(nativeButtonEvent[property]); }); }); }); describe('Event target', async () => { it('is host by default', async () => { const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton}>foo</${tagButton}>`) ); const event = await prepareClickEvent(el); expect(event.target).to.equal(el); }); const useCases = [ { container: 'div', type: 'submit' }, { container: 'div', type: 'reset' }, { container: 'div', type: 'button' }, { container: 'form', type: 'submit' }, { container: 'form', type: 'reset' }, { container: 'form', type: 'button' }, ]; useCases.forEach(useCase => { const { container, type } = useCase; const targetName = 'host'; it(`is ${targetName} with type ${type} and it is inside a ${container}`, async () => { const clickSpy = /** @type {EventListener} */ (sinon.spy(e => e.preventDefault())); const el = /** @type {LionButton} */ ( await fixture(html`<${tagButton} type="${type}">foo</${tagButton}>`) ); const tag = unsafeStatic(container); await fixture(html`<${tag} @click="${clickSpy}">${el}</${tag}>`); const event = await prepareClickEvent(el); expect(event.target).to.equal(el); }); }); }); }); describe('With click event', () => { it('behaves like native `button` when clicked', async () => { const formButtonClickedSpy = /** @type {EventListener} */ (sinon.spy()); const form = await fixture(html` <form @submit=${/** @type {EventListener} */ ev => ev.preventDefault()}> <${tagButton} @click="${formButtonClickedSpy}" type="submit">foo</${tagButton}> </form> `); const button = /** @type {LionButton} */ (form.querySelector(tagStringButton)); button.click(); expect(formButtonClickedSpy).to.have.been.calledOnce; }); it('behaves like native `button` when interacted with keyboard space', async () => { const formButtonClickedSpy = /** @type {EventListener} */ (sinon.spy()); const form = await fixture(html` <form @submit=${/** @type {EventListener} */ ev => ev.preventDefault()}> <${tagButton} @click="${formButtonClickedSpy}" type="submit">foo</${tagButton}> </form> `); const lionButton = /** @type {LionButton} */ (form.querySelector(tagStringButton)); lionButton.dispatchEvent(new KeyboardEvent('keyup', { key: ' ' })); await aTimeout(0); await aTimeout(0); expect(formButtonClickedSpy).to.have.been.calledOnce; }); it('behaves like native `button` when interacted with keyboard enter', async () => { const formButtonClickedSpy = /** @type {EventListener} */ (sinon.spy()); const form = await fixture(html` <form @submit=${/** @type {EventListener} */ ev => ev.preventDefault()}> <${tagButton} @click="${formButtonClickedSpy}" type="submit">foo</${tagButton}> </form> `); const button = /** @type {LionButton} */ (form.querySelector(tagStringButton)); button.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' })); await aTimeout(0); await aTimeout(0); expect(formButtonClickedSpy).to.have.been.calledOnce; }); }); }); }