coming together
This commit is contained in:
133
static/js/__tests__/alerts.ui.test.js
Normal file
133
static/js/__tests__/alerts.ui.test.js
Normal file
@@ -0,0 +1,133 @@
|
||||
/** @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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user