import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { initFlashMessages } from './flash_messages';
describe('initFlashMessages', () => {
let flashContainer;
let alertEl;
beforeEach(() => {
vi.useFakeTimers();
document.body.innerHTML = `
`;
flashContainer = document.querySelector('.flash-messages');
alertEl = document.querySelector('.alert');
});
afterEach(() => {
vi.useRealTimers();
});
it('does not dismiss flash messages automatically before user interaction', () => {
initFlashMessages(3000);
vi.advanceTimersByTime(5000);
expect(document.querySelector('.alert')).not.toBeNull();
});
it('starts 3-second auto-dismiss on window scroll user interaction', () => {
initFlashMessages(3000);
window.dispatchEvent(new Event('scroll'));
vi.advanceTimersByTime(2900);
expect(document.querySelector('.alert')).not.toBeNull();
vi.advanceTimersByTime(300);
// After fallback animation timeout (150ms), element is removed if bootstrap JS object is absent
vi.advanceTimersByTime(200);
expect(document.querySelector('.alert')).toBeNull();
});
it('starts 3-second auto-dismiss on touchstart interaction', () => {
initFlashMessages(3000);
window.dispatchEvent(new Event('touchstart'));
vi.advanceTimersByTime(3500);
expect(document.querySelector('.alert')).toBeNull();
});
it('starts 3-second auto-dismiss on focusin outside the alert', () => {
initFlashMessages(3000);
const outsideInput = document.getElementById('outside-input');
outsideInput.dispatchEvent(new FocusEvent('focusin', { bubbles: true }));
vi.advanceTimersByTime(3500);
expect(document.querySelector('.alert')).toBeNull();
});
it('pauses auto-dismiss timer while mouse hovers over the alert and resumes when unhovered', () => {
initFlashMessages(3000);
// Trigger user interaction
window.dispatchEvent(new Event('scroll'));
vi.advanceTimersByTime(2000);
// Hover over alert
alertEl.dispatchEvent(new MouseEvent('mouseenter'));
// Advance past original 3 second mark
vi.advanceTimersByTime(2000);
expect(document.querySelector('.alert')).not.toBeNull();
// Mouse leaves alert
alertEl.dispatchEvent(new MouseEvent('mouseleave'));
// Advance 2.9 seconds: still there
vi.advanceTimersByTime(2900);
expect(document.querySelector('.alert')).not.toBeNull();
// Advance another 300ms + fallback
vi.advanceTimersByTime(300 + 200);
expect(document.querySelector('.alert')).toBeNull();
});
it('pauses auto-dismiss timer while focus is inside the alert', () => {
initFlashMessages(3000);
// Trigger user interaction
window.dispatchEvent(new Event('scroll'));
vi.advanceTimersByTime(2000);
// Focus on input inside alert
const alertInput = document.getElementById('alert-input');
alertEl.dispatchEvent(new FocusEvent('focusin', { bubbles: true, target: alertInput }));
vi.advanceTimersByTime(3000);
expect(document.querySelector('.alert')).not.toBeNull();
// Focus moves outside alert
const outsideBtn = document.getElementById('outside-btn');
alertEl.dispatchEvent(new FocusEvent('focusout', { bubbles: true, relatedTarget: outsideBtn }));
vi.advanceTimersByTime(3500);
expect(document.querySelector('.alert')).toBeNull();
});
it('uses window.bootstrap.Alert when available', () => {
const closeSpy = vi.fn(() => {
alertEl.remove();
});
window.bootstrap = {
Alert: {
getOrCreateInstance: vi.fn(() => ({
close: closeSpy,
})),
},
};
initFlashMessages(3000);
window.dispatchEvent(new Event('scroll'));
vi.advanceTimersByTime(3100);
expect(window.bootstrap.Alert.getOrCreateInstance).toHaveBeenCalledWith(alertEl);
expect(closeSpy).toHaveBeenCalled();
delete window.bootstrap;
});
});