import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Alert } from './alert';
import { ALERT_DISMISS_EVENT, type AlertDismissEvent } from './alert.types';
const MARKUP = `
⚠
Heads up
Your trial ends soon.
`;
function setup(markup = MARKUP): HTMLElement {
document.body.innerHTML = markup;
return document.body.firstElementChild as HTMLElement;
}
function dismissBtn(root: HTMLElement): HTMLButtonElement {
return root.querySelector('[data-c42-alert-dismiss]')!;
}
describe('Alert', () => {
beforeEach(() => {
document.body.innerHTML = '';
});
it('reads the variant from data-variant and opens', () => {
const root = setup();
const alert = new Alert(root);
expect(alert.currentVariant).toBe('warning');
expect(root.dataset.state).toBe('open');
});
it('uses role=alert/assertive for warning and error', () => {
const root = setup();
new Alert(root);
expect(root.getAttribute('role')).toBe('alert');
expect(root.getAttribute('aria-live')).toBe('assertive');
});
it('uses role=status/polite for info and success', () => {
const root = setup('');
new Alert(root);
expect(root.getAttribute('role')).toBe('status');
expect(root.getAttribute('aria-live')).toBe('polite');
});
it('falls back to info for an unknown variant', () => {
const root = setup('');
const alert = new Alert(root);
expect(alert.currentVariant).toBe('info');
expect(root.dataset.variant).toBe('info');
});
it('option variant overrides the attribute', () => {
const root = setup();
const alert = new Alert(root, { variant: 'error' });
expect(alert.currentVariant).toBe('error');
expect(root.dataset.variant).toBe('error');
});
it('respects a role override', () => {
const root = setup('');
new Alert(root, { role: 'status' });
expect(root.getAttribute('role')).toBe('status');
expect(root.getAttribute('aria-live')).toBe('polite');
});
it('setVariant updates the variant and ARIA semantics', () => {
const root = setup('');
const alert = new Alert(root);
expect(root.getAttribute('role')).toBe('status');
alert.setVariant('error');
expect(root.dataset.variant).toBe('error');
expect(root.getAttribute('role')).toBe('alert');
});
it('dismisses on dismiss button click and emits the event', () => {
const root = setup();
const alert = new Alert(root);
const spy = vi.fn();
alert.on(ALERT_DISMISS_EVENT, spy);
dismissBtn(root).click();
expect(root.dataset.state).toBe('dismissed');
expect(root.hasAttribute('hidden')).toBe(true);
expect(spy).toHaveBeenCalledOnce();
expect(spy.mock.calls[0][0].detail).toEqual({ variant: 'warning' });
});
it('dismiss is idempotent', () => {
const root = setup();
const alert = new Alert(root);
const spy = vi.fn();
alert.on(ALERT_DISMISS_EVENT, spy);
alert.dismiss();
alert.dismiss();
expect(spy).toHaveBeenCalledOnce();
});
it('show re-opens a dismissed alert', () => {
const root = setup();
const alert = new Alert(root);
alert.dismiss();
alert.show();
expect(root.dataset.state).toBe('open');
expect(root.hasAttribute('hidden')).toBe(false);
});
it('works without a dismiss button', () => {
const root = setup('');
expect(() => new Alert(root)).not.toThrow();
});
it('sets type=button on a bare dismiss button', () => {
const root = setup();
new Alert(root);
expect(dismissBtn(root).type).toBe('button');
});
it('removes listeners on destroy', () => {
const root = setup();
const alert = new Alert(root);
const spy = vi.fn();
alert.on(ALERT_DISMISS_EVENT, spy);
alert.destroy();
dismissBtn(root).click();
expect(spy).not.toHaveBeenCalled();
});
});