mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 04:09:12 +00:00
Alert test (#1189)
* Improved tests for SlAlert * added more test for coverage * Grouped tests in multiple subgroups * remove executing only one tests * Fix the now executing tests --------- Co-authored-by: Dominikus Hellgartner <dominikus.hellgartner@gmail.com>
This commit is contained in:
@@ -1,91 +1,305 @@
|
||||
import { expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import { clickOnElement, moveMouseOnElement } from '../../internal/test';
|
||||
import { queryByTestId } from '../../internal/test/data-testid-helpers';
|
||||
import { resetMouse } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type SlAlert from './alert';
|
||||
import type SlIconButton from '../icon-button/icon-button';
|
||||
|
||||
const getAlertContainer = (alert: SlAlert): HTMLElement => {
|
||||
return alert.shadowRoot!.querySelector<HTMLElement>('[part="base"]')!;
|
||||
};
|
||||
|
||||
const expectAlertToBeVisible = (alert: SlAlert): void => {
|
||||
const alertContainer = getAlertContainer(alert);
|
||||
const style = window.getComputedStyle(alertContainer);
|
||||
expect(style.display).not.to.equal('none');
|
||||
expect(style.visibility).not.to.equal('hidden');
|
||||
expect(style.visibility).not.to.equal('collapse');
|
||||
};
|
||||
|
||||
const expectAlertToBeInvisible = (alert: SlAlert): void => {
|
||||
const alertContainer = getAlertContainer(alert);
|
||||
const style = window.getComputedStyle(alertContainer);
|
||||
expect(style.display, 'alert should be invisible').to.equal('none');
|
||||
};
|
||||
|
||||
const expectHideAndAfterHideToBeEmittedInCorrectOrder = async (alert: SlAlert, action: () => void | Promise<void>) => {
|
||||
const hidePromise = oneEvent(alert, 'sl-hide');
|
||||
const afterHidePromise = oneEvent(alert, 'sl-after-hide');
|
||||
let afterHideHappened = false;
|
||||
oneEvent(alert, 'sl-after-hide').then(() => (afterHideHappened = true));
|
||||
|
||||
action();
|
||||
|
||||
await hidePromise;
|
||||
expect(afterHideHappened).to.be.false;
|
||||
|
||||
await afterHidePromise;
|
||||
expectAlertToBeInvisible(alert);
|
||||
};
|
||||
|
||||
const expectShowAndAfterShowToBeEmittedInCorrectOrder = async (alert: SlAlert, action: () => void | Promise<void>) => {
|
||||
const showPromise = oneEvent(alert, 'sl-show');
|
||||
const afterShowPromise = oneEvent(alert, 'sl-after-show');
|
||||
let afterShowHappened = false;
|
||||
oneEvent(alert, 'sl-after-show').then(() => (afterShowHappened = true));
|
||||
|
||||
action();
|
||||
|
||||
await showPromise;
|
||||
expect(afterShowHappened).to.be.false;
|
||||
|
||||
await afterShowPromise;
|
||||
expectAlertToBeVisible(alert);
|
||||
};
|
||||
|
||||
const getCloseButton = (alert: SlAlert): SlIconButton | null | undefined =>
|
||||
alert.shadowRoot?.querySelector<SlIconButton>('[part="close-button"]');
|
||||
|
||||
describe('<sl-alert>', () => {
|
||||
it('should be visible with the open attribute', async () => {
|
||||
const el = await fixture<SlAlert>(html` <sl-alert open>I am an alert</sl-alert> `);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
let clock: sinon.SinonFakeTimers | null = null;
|
||||
|
||||
expect(base.hidden).to.be.false;
|
||||
afterEach(async () => {
|
||||
clock?.restore();
|
||||
await resetMouse();
|
||||
});
|
||||
|
||||
it('should not be visible without the open attribute', async () => {
|
||||
const el = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert> `);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
it('renders', async () => {
|
||||
const alert = await fixture<SlAlert>(html`<sl-alert open>I am an alert</sl-alert>`);
|
||||
|
||||
expect(base.hidden).to.be.true;
|
||||
expectAlertToBeVisible(alert);
|
||||
});
|
||||
|
||||
it('should emit sl-show and sl-after-show when calling show()', async () => {
|
||||
const el = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert> `);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
const showHandler = sinon.spy();
|
||||
const afterShowHandler = sinon.spy();
|
||||
it('is accessible', async () => {
|
||||
const alert = await fixture<SlAlert>(html`<sl-alert open>I am an alert</sl-alert>`);
|
||||
|
||||
el.addEventListener('sl-show', showHandler);
|
||||
el.addEventListener('sl-after-show', afterShowHandler);
|
||||
el.show();
|
||||
|
||||
await waitUntil(() => showHandler.calledOnce);
|
||||
await waitUntil(() => afterShowHandler.calledOnce);
|
||||
|
||||
expect(showHandler).to.have.been.calledOnce;
|
||||
expect(afterShowHandler).to.have.been.calledOnce;
|
||||
expect(base.hidden).to.be.false;
|
||||
await expect(alert).to.be.accessible();
|
||||
});
|
||||
|
||||
it('should emit sl-hide and sl-after-hide when calling hide()', async () => {
|
||||
const el = await fixture<SlAlert>(html` <sl-alert open>I am an alert</sl-alert> `);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
const hideHandler = sinon.spy();
|
||||
const afterHideHandler = sinon.spy();
|
||||
describe('alert visibility', () => {
|
||||
it('should be visible with the open attribute', async () => {
|
||||
const alert = await fixture<SlAlert>(html`<sl-alert open>I am an alert</sl-alert>`);
|
||||
|
||||
el.addEventListener('sl-hide', hideHandler);
|
||||
el.addEventListener('sl-after-hide', afterHideHandler);
|
||||
el.hide();
|
||||
expectAlertToBeVisible(alert);
|
||||
});
|
||||
|
||||
await waitUntil(() => hideHandler.calledOnce);
|
||||
await waitUntil(() => afterHideHandler.calledOnce);
|
||||
it('should not be visible without the open attribute', async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert>`);
|
||||
|
||||
expect(hideHandler).to.have.been.calledOnce;
|
||||
expect(afterHideHandler).to.have.been.calledOnce;
|
||||
expect(base.hidden).to.be.true;
|
||||
expectAlertToBeInvisible(alert);
|
||||
});
|
||||
|
||||
it('should emit sl-show and sl-after-show when calling show()', async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert>`);
|
||||
|
||||
expectAlertToBeInvisible(alert);
|
||||
|
||||
await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => alert.show());
|
||||
});
|
||||
|
||||
it('should emit sl-hide and sl-after-hide when calling hide()', async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert open>I am an alert</sl-alert>`);
|
||||
|
||||
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => alert.hide());
|
||||
});
|
||||
|
||||
it('should emit sl-show and sl-after-show when setting open = true', async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert> `);
|
||||
|
||||
await expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => {
|
||||
alert.open = true;
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit sl-hide and sl-after-hide when setting open = false', async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert open>I am an alert</sl-alert> `);
|
||||
|
||||
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {
|
||||
alert.open = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit sl-show and sl-after-show when setting open = true', async () => {
|
||||
const el = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert> `);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
const showHandler = sinon.spy();
|
||||
const afterShowHandler = sinon.spy();
|
||||
describe('close button', () => {
|
||||
it('shows a close button if the alert has the closable attribute', () => async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert open closable>I am an alert</sl-alert> `);
|
||||
const closeButton = getCloseButton(alert);
|
||||
|
||||
el.addEventListener('sl-show', showHandler);
|
||||
el.addEventListener('sl-after-show', afterShowHandler);
|
||||
el.open = true;
|
||||
expect(closeButton).to.be.visible;
|
||||
});
|
||||
|
||||
await waitUntil(() => showHandler.calledOnce);
|
||||
await waitUntil(() => afterShowHandler.calledOnce);
|
||||
it('clicking the close button closes the alert', () => async () => {
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert open closable>I am an alert</sl-alert> `);
|
||||
const closeButton = getCloseButton(alert);
|
||||
|
||||
expect(showHandler).to.have.been.calledOnce;
|
||||
expect(afterShowHandler).to.have.been.calledOnce;
|
||||
expect(base.hidden).to.be.false;
|
||||
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {
|
||||
clickOnElement(closeButton!);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit sl-hide and sl-after-hide when setting open = false', async () => {
|
||||
const el = await fixture<SlAlert>(html` <sl-alert open>I am an alert</sl-alert> `);
|
||||
const base = el.shadowRoot!.querySelector<HTMLElement>('[part~="base"]')!;
|
||||
const hideHandler = sinon.spy();
|
||||
const afterHideHandler = sinon.spy();
|
||||
describe('toast', () => {
|
||||
const getToastStack = (): HTMLDivElement | null => document.querySelector<HTMLDivElement>('.sl-toast-stack');
|
||||
|
||||
el.addEventListener('sl-hide', hideHandler);
|
||||
el.addEventListener('sl-after-hide', afterHideHandler);
|
||||
el.open = false;
|
||||
const closeRemainingAlerts = async (): Promise<void> => {
|
||||
const toastStack = getToastStack();
|
||||
if (toastStack?.children) {
|
||||
for (const element of toastStack.children) {
|
||||
await (element as SlAlert).hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await waitUntil(() => hideHandler.calledOnce);
|
||||
await waitUntil(() => afterHideHandler.calledOnce);
|
||||
beforeEach(async () => {
|
||||
await closeRemainingAlerts();
|
||||
});
|
||||
|
||||
expect(hideHandler).to.have.been.calledOnce;
|
||||
expect(afterHideHandler).to.have.been.calledOnce;
|
||||
expect(base.hidden).to.be.true;
|
||||
it('can be rendered as a toast', async () => {
|
||||
const alert = await fixture<SlAlert>(html`<sl-alert>I am an alert</sl-alert>`);
|
||||
|
||||
expectShowAndAfterShowToBeEmittedInCorrectOrder(alert, () => alert.toast());
|
||||
const toastStack = getToastStack();
|
||||
expect(toastStack).to.be.visible;
|
||||
expect(toastStack?.firstChild).to.be.equal(alert);
|
||||
});
|
||||
|
||||
it('resolves only after being closed', async () => {
|
||||
const alert = await fixture<SlAlert>(html`<sl-alert closable>I am an alert</sl-alert>`);
|
||||
|
||||
const afterShowEvent = oneEvent(alert, 'sl-after-show');
|
||||
let toastPromiseResolved = false;
|
||||
alert.toast().then(() => (toastPromiseResolved = true));
|
||||
|
||||
await afterShowEvent;
|
||||
expect(toastPromiseResolved).to.be.false;
|
||||
|
||||
const closePromise = oneEvent(alert, 'sl-after-hide');
|
||||
const closeButton = getCloseButton(alert);
|
||||
clickOnElement(closeButton!);
|
||||
|
||||
await closePromise;
|
||||
await aTimeout(0);
|
||||
|
||||
expect(toastPromiseResolved).to.be.true;
|
||||
});
|
||||
|
||||
const expectToastStack = () => {
|
||||
const toastStack = getToastStack();
|
||||
expect(toastStack).not.to.be.null;
|
||||
};
|
||||
|
||||
const expectNoToastStack = () => {
|
||||
const toastStack = getToastStack();
|
||||
expect(toastStack).to.be.null;
|
||||
};
|
||||
|
||||
const openToast = async (alert: SlAlert): Promise<void> => {
|
||||
const openPromise = oneEvent(alert, 'sl-after-show');
|
||||
alert.toast();
|
||||
await openPromise;
|
||||
};
|
||||
|
||||
const closeToast = async (alert: SlAlert): Promise<void> => {
|
||||
const closePromise = oneEvent(alert, 'sl-after-hide');
|
||||
const closeButton = getCloseButton(alert);
|
||||
await clickOnElement(closeButton!);
|
||||
await closePromise;
|
||||
await aTimeout(0);
|
||||
};
|
||||
|
||||
it('deletes the toast stack after the last alert is done', async () => {
|
||||
const container = await fixture<HTMLElement>(html`<div>
|
||||
<sl-alert data-testid="alert1" closable>alert 1</sl-alert>
|
||||
<sl-alert data-testid="alert2" closable>alert 2</sl-alert>
|
||||
</div>`);
|
||||
|
||||
const alert1 = queryByTestId<SlAlert>(container, 'alert1');
|
||||
const alert2 = queryByTestId<SlAlert>(container, 'alert2');
|
||||
|
||||
await openToast(alert1!);
|
||||
|
||||
expectToastStack();
|
||||
|
||||
await openToast(alert2!);
|
||||
|
||||
expectToastStack();
|
||||
|
||||
await closeToast(alert1!);
|
||||
|
||||
expectToastStack();
|
||||
|
||||
await closeToast(alert2!);
|
||||
|
||||
expectNoToastStack();
|
||||
});
|
||||
});
|
||||
|
||||
describe('timer controlled closing', () => {
|
||||
it('closes after a predefined amount of time', async () => {
|
||||
clock = sinon.useFakeTimers();
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert open duration="3000">I am an alert</sl-alert>`);
|
||||
|
||||
expectAlertToBeVisible(alert);
|
||||
|
||||
clock.tick(2999);
|
||||
|
||||
expectAlertToBeVisible(alert);
|
||||
|
||||
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {
|
||||
clock?.tick(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('resets the closing timer after mouse-over', async () => {
|
||||
clock = sinon.useFakeTimers();
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert open duration="3000">I am an alert</sl-alert>`);
|
||||
|
||||
expectAlertToBeVisible(alert);
|
||||
|
||||
clock.tick(1000);
|
||||
|
||||
await moveMouseOnElement(alert);
|
||||
|
||||
clock.tick(2999);
|
||||
|
||||
expectAlertToBeVisible(alert);
|
||||
|
||||
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {
|
||||
clock?.tick(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('resets the closing timer after opening', async () => {
|
||||
clock = sinon.useFakeTimers();
|
||||
const alert = await fixture<SlAlert>(html` <sl-alert duration="3000">I am an alert</sl-alert>`);
|
||||
|
||||
expectAlertToBeInvisible(alert);
|
||||
|
||||
clock.tick(1000);
|
||||
|
||||
const afterShowPromise = oneEvent(alert, 'sl-after-show');
|
||||
alert.show();
|
||||
await afterShowPromise;
|
||||
|
||||
clock.tick(2999);
|
||||
|
||||
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {
|
||||
clock?.tick(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('alert variants', () => {
|
||||
const variants = ['primary', 'success', 'neutral', 'warning', 'danger'];
|
||||
|
||||
variants.forEach(variant => {
|
||||
it(`adapts to the variant: ${variant}`, async () => {
|
||||
const alert = await fixture<SlAlert>(html`<sl-alert variant="${variant}" open>I am an alert</sl-alert>`);
|
||||
|
||||
const alertContainer = getAlertContainer(alert);
|
||||
expect(alertContainer).to.have.class(`alert--${variant}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import { sendMouse } from '@web/test-runner-commands';
|
||||
|
||||
/** A testing utility that measures an element's position and clicks on it. */
|
||||
export async function clickOnElement(
|
||||
/** The element to click */
|
||||
el: Element,
|
||||
/** The location of the element to click */
|
||||
position: 'top' | 'right' | 'bottom' | 'left' | 'center' = 'center',
|
||||
/** The horizontal offset to apply to the position when clicking */
|
||||
offsetX = 0,
|
||||
/** The vertical offset to apply to the position when clicking */
|
||||
offsetY = 0
|
||||
) {
|
||||
function determineMousePosition(el: Element, position: string, offsetX: number, offsetY: number) {
|
||||
const { x, y, width, height } = el.getBoundingClientRect();
|
||||
const centerX = Math.floor(x + window.pageXOffset + width / 2);
|
||||
const centerY = Math.floor(y + window.pageYOffset + height / 2);
|
||||
@@ -41,6 +31,36 @@ export async function clickOnElement(
|
||||
|
||||
clickX += offsetX;
|
||||
clickY += offsetY;
|
||||
return { clickX, clickY };
|
||||
}
|
||||
|
||||
/** A testing utility that measures an element's position and clicks on it. */
|
||||
export async function clickOnElement(
|
||||
/** The element to click */
|
||||
el: Element,
|
||||
/** The location of the element to click */
|
||||
position: 'top' | 'right' | 'bottom' | 'left' | 'center' = 'center',
|
||||
/** The horizontal offset to apply to the position when clicking */
|
||||
offsetX = 0,
|
||||
/** The vertical offset to apply to the position when clicking */
|
||||
offsetY = 0
|
||||
) {
|
||||
const { clickX, clickY } = determineMousePosition(el, position, offsetX, offsetY);
|
||||
|
||||
await sendMouse({ type: 'click', position: [clickX, clickY] });
|
||||
}
|
||||
|
||||
export async function moveMouseOnElement(
|
||||
/** The element to click */
|
||||
el: Element,
|
||||
/** The location of the element to click */
|
||||
position: 'top' | 'right' | 'bottom' | 'left' | 'center' = 'center',
|
||||
/** The horizontal offset to apply to the position when clicking */
|
||||
offsetX = 0,
|
||||
/** The vertical offset to apply to the position when clicking */
|
||||
offsetY = 0
|
||||
) {
|
||||
const { clickX, clickY } = determineMousePosition(el, position, offsetX, offsetY);
|
||||
|
||||
await sendMouse({ type: 'move', position: [clickX, clickY] });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user