Files
eSIM-Tools/tests/modules/notification-manager.test.js
Abner 093310d25e ♻️ refactor: 优化通知与性能监控模块,加固通知消息 XSS 防护
- 通知服务新增页面可见性监听:标签页隐藏时暂停轮询,回到前台后恢复定时,离开期间超过检查周期则立即补查,节省 Netlify Function 配额
- 性能监控改为批量缓冲写入 sessionStorage,达到 1 秒间隔或 20 条上限自动落盘,并在页面隐藏/卸载时冲刷剩余缓冲,避免逐条读写开销
- 通知管理器改用原生 DOM API 构建节点并以 textContent 渲染消息,移除 innerHTML 与 escapeHtml 方法,从结构上杜绝 XSS 注入
- 新增 notification-service、notification-manager、performance-monitor 三组单元测试,覆盖后台暂停恢复、缓冲落盘、XSS 防护与空消息容错等场景
- 合并共享 Toast 样式至 design-system.css 并支持主题色定制,入场动画改为渐进增强,清理废弃请求日志中间件、SW 预缓存路径等冗余代码
2026-08-09 18:33:34 +08:00

91 lines
3.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* notification-manager 单元测试
* 覆盖通知渲染、消息文本化XSS 防护)、空消息容错、手动关闭、自动消失、类型图标
*/
import notificationManager from '../../src/js/modules/notification-manager.js';
describe('NotificationManager', () => {
beforeEach(() => {
jest.useFakeTimers();
document.body.innerHTML = '';
// 重新初始化容器(单例只建一次,这里清空 body 后重建)
notificationManager.container = null;
notificationManager.notifications.clear();
notificationManager.init();
});
afterEach(() => {
jest.useRealTimers();
notificationManager.clearAll();
document.body.innerHTML = '';
});
it('显示成功通知并渲染消息文本', () => {
notificationManager.success('操作成功');
const notification = document.querySelector('.notification');
expect(notification).toBeTruthy();
expect(notification.classList.contains('notification-success')).toBe(true);
expect(notification.textContent).toContain('操作成功');
});
it('消息作为纯文本渲染,不解析 HTML', () => {
notificationManager.show({
message: '<img src=x onerror=alert(1)>',
type: 'info'
});
const notification = document.querySelector('.notification');
expect(notification.querySelector('img')).toBeNull();
expect(notification.textContent).toContain('<img src=x onerror=alert(1)>');
});
it('null/undefined 消息不应抛错', () => {
expect(() => notificationManager.show({ message: null, type: 'info' })).not.toThrow();
expect(() => notificationManager.show({ message: undefined, type: 'info' })).not.toThrow();
});
it('手动关闭按钮可隐藏通知', () => {
const id = notificationManager.show({ message: '可关闭', type: 'info', closable: true });
const closeBtn = document.querySelector('.notification-close');
expect(closeBtn).toBeTruthy();
closeBtn.click();
// hide 后 300ms 移除元素
jest.advanceTimersByTime(400);
expect(document.querySelector('.notification')).toBeNull();
expect(notificationManager.notifications.has(id)).toBe(false);
});
it('closable=false 时不渲染关闭按钮', () => {
notificationManager.show({ message: '不可关闭', type: 'info', closable: false });
expect(document.querySelector('.notification-close')).toBeNull();
});
it('达到持续时间后自动消失', () => {
notificationManager.success('自动消失', 1000);
expect(document.querySelector('.notification')).toBeTruthy();
jest.advanceTimersByTime(1400);
expect(document.querySelector('.notification')).toBeNull();
});
it('各类型映射正确的图标类', () => {
const cases = [
['success', 'fa-check-circle'],
['warning', 'fa-exclamation-triangle'],
['error', 'fa-times-circle'],
['info', 'fa-info-circle']
];
cases.forEach(([type, iconClass]) => {
notificationManager.show({ message: type, type });
const icon = document.querySelectorAll('.notification-icon')[document.querySelectorAll('.notification').length - 1];
expect(icon.classList.contains(iconClass)).toBe(true);
notificationManager.clearAll();
});
});
});