134 lines
4.9 KiB
JavaScript
134 lines
4.9 KiB
JavaScript
/** @jest-environment jsdom */
|
|
|
|
const path = require('path');
|
|
|
|
// Load sanitizer and alerts modules (IIFE attaches to window)
|
|
require(path.join(__dirname, '..', 'sanitizer.js'));
|
|
require(path.join(__dirname, '..', 'alerts.js'));
|
|
|
|
// Polyfill requestAnimationFrame for jsdom
|
|
beforeAll(() => {
|
|
if (!global.requestAnimationFrame) {
|
|
global.requestAnimationFrame = (cb) => setTimeout(cb, 0);
|
|
}
|
|
});
|
|
|
|
beforeEach(() => {
|
|
document.body.innerHTML = '';
|
|
});
|
|
|
|
describe('alerts.show UI behavior', () => {
|
|
it('creates a container and renders a notification', () => {
|
|
const wrapper = window.alerts.show('Hello world', 'info', { duration: 0 });
|
|
const container = document.getElementById('notification-container');
|
|
expect(container).toBeTruthy();
|
|
expect(container.contains(wrapper)).toBe(true);
|
|
expect(wrapper.className).toMatch(/alert-notification/);
|
|
});
|
|
|
|
it('applies styling based on type and aliases', () => {
|
|
const s = window.alerts.show('ok', 'success', { duration: 0 });
|
|
const e = window.alerts.show('bad', 'error', { duration: 0 }); // alias to danger
|
|
const w = window.alerts.show('warn', 'warning', { duration: 0 });
|
|
const i = window.alerts.show('info', 'info', { duration: 0 });
|
|
|
|
expect(s.className).toContain('bg-green-50');
|
|
expect(e.className).toContain('bg-red-50');
|
|
expect(w.className).toContain('bg-yellow-50');
|
|
expect(i.className).toContain('bg-blue-50');
|
|
});
|
|
|
|
it('renders title when provided', () => {
|
|
const wrapper = window.alerts.show('Body', 'info', { title: 'My Title', duration: 0 });
|
|
const titleEl = wrapper.querySelector('p.text-sm.font-bold');
|
|
expect(titleEl).toBeTruthy();
|
|
expect(titleEl.textContent).toBe('My Title');
|
|
});
|
|
|
|
it('sanitizes HTML when html option is true', () => {
|
|
const wrapper = window.alerts.show('<img src=x onerror=alert(1)><script>evil()</script><p>hi</p>', 'info', { html: true, duration: 0 });
|
|
const textEl = wrapper.querySelector('.text-sm.mt-1.font-semibold');
|
|
expect(textEl).toBeTruthy();
|
|
const html = textEl.innerHTML;
|
|
expect(html).toContain('<img');
|
|
expect(html).toContain('<p>hi</p>');
|
|
expect(html).not.toMatch(/<script/i);
|
|
expect(html).not.toMatch(/onerror/i);
|
|
});
|
|
|
|
it('supports Node message content without sanitization', () => {
|
|
const span = document.createElement('span');
|
|
span.textContent = 'node message';
|
|
const wrapper = window.alerts.show(span, 'info', { duration: 0 });
|
|
const textEl = wrapper.querySelector('.text-sm.mt-1.font-semibold');
|
|
expect(textEl.textContent).toContain('node message');
|
|
});
|
|
|
|
it('is dismissible by default and calls onClose', () => {
|
|
const onClose = jest.fn();
|
|
const wrapper = window.alerts.show('dismiss me', 'info', { onClose, duration: 0 });
|
|
const btn = wrapper.querySelector('button[aria-label="Close"]');
|
|
expect(btn).toBeTruthy();
|
|
btn.click();
|
|
expect(document.body.contains(wrapper)).toBe(false);
|
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('can be non-dismissible', () => {
|
|
const wrapper = window.alerts.show('stay', 'info', { dismissible: false, duration: 0 });
|
|
const btn = wrapper.querySelector('button[aria-label="Close"]');
|
|
expect(btn).toBeNull();
|
|
});
|
|
|
|
it('auto-closes after duration', () => {
|
|
jest.useFakeTimers();
|
|
const wrapper = window.alerts.show('timeout', 'info', { duration: 50 });
|
|
expect(document.body.contains(wrapper)).toBe(true);
|
|
jest.advanceTimersByTime(400);
|
|
expect(document.body.contains(wrapper)).toBe(false);
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
it('renders actions and handles clicks (autoClose true by default)', () => {
|
|
const onClick = jest.fn();
|
|
const wrapper = window.alerts.show('with action', 'info', {
|
|
duration: 0,
|
|
actions: [
|
|
{ label: 'Retry', onClick }
|
|
]
|
|
});
|
|
const buttons = Array.from(wrapper.querySelectorAll('button'));
|
|
const retryBtn = buttons.find((b) => b.textContent === 'Retry');
|
|
expect(retryBtn).toBeTruthy();
|
|
retryBtn.click();
|
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
expect(document.body.contains(wrapper)).toBe(false);
|
|
});
|
|
|
|
it('respects action.autoClose = false', () => {
|
|
const onClick = jest.fn();
|
|
const wrapper = window.alerts.show('stay open', 'info', {
|
|
duration: 0,
|
|
actions: [
|
|
{ label: 'Stay', onClick, autoClose: false }
|
|
]
|
|
});
|
|
const buttons = Array.from(wrapper.querySelectorAll('button'));
|
|
const stayBtn = buttons.find((b) => b.textContent === 'Stay');
|
|
expect(stayBtn).toBeTruthy();
|
|
stayBtn.click();
|
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
expect(document.body.contains(wrapper)).toBe(true);
|
|
});
|
|
|
|
it('supports custom containerId and element id', () => {
|
|
const wrapper = window.alerts.show('custom', 'info', { duration: 0, containerId: 'alt-container', id: 'toast-1' });
|
|
const container = document.getElementById('alt-container');
|
|
expect(container).toBeTruthy();
|
|
expect(wrapper.id).toBe('toast-1');
|
|
expect(container.contains(wrapper)).toBe(true);
|
|
});
|
|
});
|
|
|
|
|