kopia lustrzana https://github.com/shoelace-style/shoelace
309 wiersze
9.8 KiB
TypeScript
309 wiersze
9.8 KiB
TypeScript
import '../../../dist/shoelace.js';
|
|
import { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing';
|
|
import { clickOnElement, moveMouseOnElement } from '../../internal/test.js';
|
|
import { queryByTestId } from '../../internal/test/data-testid-helpers.js';
|
|
import { resetMouse } from '@web/test-runner-commands';
|
|
import sinon from 'sinon';
|
|
import type SlAlert from './alert.js';
|
|
import type SlIconButton from '../icon-button/icon-button.js';
|
|
|
|
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>', () => {
|
|
let clock: sinon.SinonFakeTimers | null = null;
|
|
|
|
afterEach(async () => {
|
|
clock?.restore();
|
|
await resetMouse();
|
|
});
|
|
|
|
it('renders', async () => {
|
|
const alert = await fixture<SlAlert>(html`<sl-alert open>I am an alert</sl-alert>`);
|
|
|
|
expectAlertToBeVisible(alert);
|
|
});
|
|
|
|
it('is accessible', async () => {
|
|
const alert = await fixture<SlAlert>(html`<sl-alert open>I am an alert</sl-alert>`);
|
|
|
|
await expect(alert).to.be.accessible();
|
|
});
|
|
|
|
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>`);
|
|
|
|
expectAlertToBeVisible(alert);
|
|
});
|
|
|
|
it('should not be visible without the open attribute', async () => {
|
|
const alert = await fixture<SlAlert>(html` <sl-alert>I am an alert</sl-alert>`);
|
|
|
|
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;
|
|
});
|
|
});
|
|
});
|
|
|
|
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);
|
|
|
|
expect(closeButton).to.be.visible;
|
|
});
|
|
|
|
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);
|
|
|
|
await expectHideAndAfterHideToBeEmittedInCorrectOrder(alert, () => {
|
|
clickOnElement(closeButton!);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('toast', () => {
|
|
const getToastStack = (): HTMLDivElement | null => document.querySelector<HTMLDivElement>('.sl-toast-stack');
|
|
|
|
const closeRemainingAlerts = async (): Promise<void> => {
|
|
const toastStack = getToastStack();
|
|
if (toastStack?.children) {
|
|
for (const element of toastStack.children) {
|
|
await (element as SlAlert).hide();
|
|
}
|
|
}
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
await closeRemainingAlerts();
|
|
});
|
|
|
|
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}`);
|
|
});
|
|
});
|
|
});
|
|
});
|