Skip to content

Commit

Permalink
chore: add more test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
derekmoss committed Mar 6, 2024
1 parent 9013468 commit 9b10e95
Showing 1 changed file with 212 additions and 48 deletions.
260 changes: 212 additions & 48 deletions src/lib/button-area/button-area.test.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,35 @@
import { expect } from '@esm-bundle/chai';
import { spy } from 'sinon';
import { fixture, html } from '@open-wc/testing';
import { elementUpdated, fixture, html } from '@open-wc/testing';
import { sendKeys } from '@web/test-runner-commands';
import { timer } from '@tylertech/forge-testing';
import { getShadowElement } from '@tylertech/forge-core';
import { BUTTON_AREA_CONSTANTS } from './button-area-constants';
import { IButtonAreaComponent } from './button-area';
import type { IStateLayerComponent } from '../state-layer';
import { TOUCH_DELAY_MS, type IStateLayerComponent, STATE_LAYER_CONSTANTS } from '../state-layer';
import type { IFocusIndicatorComponent } from '../focus-indicator';

import './button-area';

const FULL_TEMPLATE = html`<forge-button-area>
<button slot="button" type="button">Go to detail</button>
<div class="content">
<div>
<span>Heading</span>
<span>Content</span>
</div>
<forge-icon-button>
<button type="button" aria-label="Favorite">
<forge-icon name="favorite"></forge-icon>
</button>
<forge-tooltip>Favorite</forge-tooltip>
</forge-icon-button>
<forge-icon name="chevron_right"></forge-icon>
</div>
</forge-button-area>`;

const DATA_FORGE_IGNORE_TEMPLATE = html`<forge-button-area><button slot="button" data-forge-ignore type="button">Data forge ignore</button></forge-button-area>`;

describe('Button Area', () => {
it('should initialize', async () => {
const el = await fixture<IButtonAreaComponent>(html`<forge-button-area></forge-button-area>`);

const rootEl = getRootEl(el);
const stateLayer = getStateLayer(el);
const focusIndicator = getFocusIndicator(el);

const { el, root, stateLayer, focusIndicator } = await createFixture({});

expect(el.shadowRoot).not.to.be.null;
expect(rootEl.getAttribute('part')).to.equal('root');
expect(rootEl.classList.contains(BUTTON_AREA_CONSTANTS.classes.ROOT)).to.be.true;
expect(root.getAttribute('part')).to.equal('root');
expect(root.classList.contains(BUTTON_AREA_CONSTANTS.classes.ROOT)).to.be.true;
expect(stateLayer.disabled).to.be.false;
expect(focusIndicator).to.be.ok;
});

it('should be accessible', async () => {
const el = await fixture<IButtonAreaComponent>(FULL_TEMPLATE);
const { el } = await createFixture({});

await expect(el).to.be.accessible();
});

it('should handle click', async () => {
const el = await fixture<IButtonAreaComponent>(FULL_TEMPLATE);
const { el } = await createFixture({});
const clickSpy = spy();
el.addEventListener('click', clickSpy);

Expand All @@ -59,8 +39,7 @@ describe('Button Area', () => {
});

it('should dispatch click event when button is clicked', async () => {
const el = await fixture<IButtonAreaComponent>(FULL_TEMPLATE);
const button = getButtonEl(el);
const { el, button } = await createFixture({});
const clickSpy = spy();
el.addEventListener('click', clickSpy);

Expand All @@ -69,42 +48,227 @@ describe('Button Area', () => {
expect(clickSpy.calledOnce).to.be.true;
});

it('should not dispatch click event when disabled', async () => {
const el = await fixture<IButtonAreaComponent>(FULL_TEMPLATE);
const button = getButtonEl(el);
it('should handle keydown', async () => {
const { el, button } = await createFixture({});
const keydownSpy = spy();
el.addEventListener('keydown', keydownSpy);

button.focus();
await pressKey('Enter');
await pressKey(' ');
expect(keydownSpy.called).to.be.true;
});

it('should animate state layer when handling keydown', async () => {
const { button, stateLayer } = await createFixture({});
const stateLayerSurface = getStateLayerSurfaceEl(stateLayer);
const animateSpy = spy(stateLayerSurface, 'animate');

button.focus();
pressKey('z');
await timer(TOUCH_DELAY_MS);
expect(animateSpy).to.not.have.been.called;
expect(stateLayerSurface.classList.contains(STATE_LAYER_CONSTANTS.classes.PRESSED)).to.be.false;

pressKey('Enter');
await timer(TOUCH_DELAY_MS);

expect(animateSpy).to.have.been.called;
expect(stateLayerSurface.classList.contains(STATE_LAYER_CONSTANTS.classes.PRESSED)).to.be.true;
});

it('should not dispatch click events when disabled', async () => {
const { el } = await createFixture({ disabled: true });
const heading = getHeadingEl(el);
const clickSpy = spy();
el.addEventListener('click', clickSpy);

el.disabled = true;
button.click();

await elementUpdated(el);

heading.click();

expect(clickSpy.called).to.be.false;
});

it('should not animate state layer when disabled', async () => {
const { el, stateLayer } = await createFixture({});
const stateLayerSurface = getStateLayerSurfaceEl(stateLayer);
const heading = getHeadingEl(el);

el.disabled = true;

await elementUpdated(el);

heading.click();

expect(stateLayerSurface.classList.contains(STATE_LAYER_CONSTANTS.classes.PRESSED)).to.be.false;
});

it('should not dispatch click event when ignored children are clicked', async () => {
const el = await fixture<IButtonAreaComponent>(DATA_FORGE_IGNORE_TEMPLATE);
const button = getButtonEl(el);
const { el, stateLayer } = await createFixture({}, true, true);
const stateLayerSurface = getStateLayerSurfaceEl(stateLayer);
const ignoredButton = getIngoredButtonEl(el);
const clickSpy = spy();
el.addEventListener('click', clickSpy);

button.click();
ignoredButton.click();

expect(clickSpy.called).to.be.false;
expect(stateLayerSurface.classList.contains(STATE_LAYER_CONSTANTS.classes.PRESSED)).to.be.false;
});

it('should not dispatch pointer events when ignored children are clicked', async () => {
const { el, button } = await createFixture({}, true, true);
const ignoredButton = getIngoredButtonEl(el);
const pointerdownSpy = spy();
const pointerupSpy = spy();
el.addEventListener('pointerdown', pointerdownSpy);
el.addEventListener('pointerup', pointerupSpy);

ignoredButton.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true }));
ignoredButton.dispatchEvent(new PointerEvent('pointerup', { bubbles: true }));

expect(pointerdownSpy.called).to.be.false;
expect(pointerupSpy.called).to.be.false;

el.disabled = true;

await elementUpdated(el);

ignoredButton.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true }));
ignoredButton.dispatchEvent(new PointerEvent('pointerup', { bubbles: true }));

expect(pointerdownSpy.called).to.be.true;
expect(pointerupSpy.called).to.be.true;

el.disabled = false;

await elementUpdated(el);

button.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true }));
button.dispatchEvent(new PointerEvent('pointerup', { bubbles: true }));

expect(pointerdownSpy.callCount).to.be.equal(2);
expect(pointerupSpy.callCount).to.be.equal(2);
});

it('should not animate state layer when clicking on ignored children', async () => {
const { el, stateLayer } = await createFixture({}, true, true);
const stateLayerSurface = getStateLayerSurfaceEl(stateLayer);
const ignoredButton = getIngoredButtonEl(el);

ignoredButton.click();
await timer(TOUCH_DELAY_MS);

expect(stateLayerSurface.classList.contains(STATE_LAYER_CONSTANTS.classes.PRESSED)).to.be.false;
});

it('should not handle keydown when ignored children are focused', async () => {
const { el, stateLayer } = await createFixture({}, true, true);
const stateLayerSurface = getStateLayerSurfaceEl(stateLayer);
const ignoredButton = getIngoredButtonEl(el);
const keydownSpy = spy();
el.addEventListener('keydown', keydownSpy);

ignoredButton.focus();
await pressKey('Enter');

expect(keydownSpy.called).to.be.false;
expect(stateLayerSurface.classList.contains(STATE_LAYER_CONSTANTS.classes.PRESSED)).to.be.false;
});

it('should set disabled dynamically', async () => {
const { el } = await createFixture({});

el.disabled = true;

expect(el.disabled).to.be.true;
expect(el.hasAttribute('disabled')).to.be.true;
await expect(el).to.be.accessible();

el.disabled = false;

expect(el.disabled).to.be.false;
expect(el.hasAttribute('disabled')).to.be.false;
});

function getRootEl(el: IButtonAreaComponent): HTMLElement {
return el.shadowRoot?.firstElementChild as HTMLElement;
it('should set disabled if the button is disabled and is added after initialize', async () => {
const { el } = await createFixture({}, false);

expect(el.disabled).to.be.false;
expect(el.hasAttribute('disabled')).to.be.false;
await expect(el).to.be.accessible();

const button = document.createElement('button') as HTMLButtonElement;
button.setAttribute('slot', 'button');
button.setAttribute('disabled', 'true');
button.innerHTML = 'Slot Changed';
el.append(button);
await expect(el).to.be.accessible();

expect(el.disabled).to.be.true;
expect(el.hasAttribute('disabled')).to.be.true;
});

it('should set button to disabled if element is disabled and button is added after initialize', async () => {
const { el } = await createFixture({ disabled: true }, false);
await expect(el).to.be.accessible();

expect(el.disabled).to.be.true;
expect(el.hasAttribute('disabled')).to.be.true;


const button = document.createElement('button') as HTMLButtonElement;
button.setAttribute('slot', 'button');
button.innerHTML = 'Slot Changed';
el.append(button);
await expect(el).to.be.accessible();

expect(button.disabled).to.be.true;
});

function getHeadingEl(el: IButtonAreaComponent): HTMLSpanElement {
return el.querySelector('.heading') as HTMLSpanElement;
}

function getIngoredButtonEl(el: IButtonAreaComponent): HTMLButtonElement {
return el.querySelector('[data-forge-ignore]') as HTMLButtonElement;
}

function getStateLayer(el: IButtonAreaComponent): IStateLayerComponent {
return el.shadowRoot?.querySelector('forge-state-layer') as IStateLayerComponent
function getStateLayerSurfaceEl(el: IStateLayerComponent): HTMLDivElement {
return getShadowElement(el, STATE_LAYER_CONSTANTS.selectors.SURFACE) as HTMLDivElement;
}

function getFocusIndicator(el: IButtonAreaComponent): IFocusIndicatorComponent {
return el.shadowRoot?.querySelector('forge-focus-indicator') as IFocusIndicatorComponent;
function pressKey(press: 'z' | ' ' | 'Enter'): Promise<void> {
return sendKeys({ press });
}

function getButtonEl(el: IButtonAreaComponent): HTMLButtonElement {
return el.querySelector('[slot=button]') as HTMLButtonElement;
async function createFixture({ disabled }: Partial<IButtonAreaComponent> = {}, hasButton: boolean = true, hasIgnoredChildren: boolean = false): Promise<{ el: IButtonAreaComponent; root: HTMLElement; focusIndicator: IFocusIndicatorComponent; stateLayer: IStateLayerComponent; button: HTMLButtonElement; content: HTMLSlotElement; }> {
const buttonTemplate = html`<button slot="button" type="button">Go to detail</button>`;
const ignoredButton = html`<forge-icon-button data-forge-ignore>
<button type="button" aria-label="Favorite">
<forge-icon name="favorite"></forge-icon>
</button>
<forge-tooltip>Favorite</forge-tooltip>
</forge-icon-button>`;
const el = await fixture<IButtonAreaComponent>(html`<forge-button-area ?disabled=${disabled}>
${ hasButton ? buttonTemplate : null }
<div class="content">
<div>
<span class="heading">Heading</span>
<span>Content</span>
</div>
${ hasIgnoredChildren ? ignoredButton : null }
<forge-icon name="chevron_right"></forge-icon>
</div>
</forge-button-area>`);
const root = el.shadowRoot?.firstElementChild as HTMLElement;
const stateLayer = el.shadowRoot?.querySelector('forge-state-layer') as IStateLayerComponent;
const focusIndicator = el.shadowRoot?.querySelector('forge-focus-indicator') as IFocusIndicatorComponent;
const button = el.querySelector('[slot=button]') as HTMLButtonElement;
const content = el.shadowRoot?.querySelector('#content') as HTMLSlotElement;
return { el, root, focusIndicator, stateLayer, button, content }
}
});

0 comments on commit 9b10e95

Please sign in to comment.