/** * MetonaToast 单元测试 * @module tests * @version 2.0.0 */ import MeToast, { Toast, VERSION } from '../src/index.js'; // 模拟DOM环境 const mockDocument = { createElement: jest.fn(() => ({ className: '', style: {}, setAttribute: jest.fn(), appendChild: jest.fn(), querySelector: jest.fn(), querySelectorAll: jest.fn(() => []), execCommand: jest.fn(() => true), classList: { add: jest.fn(), remove: jest.fn(), contains: jest.fn(), }, addEventListener: jest.fn(), removeEventListener: jest.fn(), setPointerCapture: jest.fn(), releasePointerCapture: jest.fn(), animate: jest.fn(() => ({ onfinish: null, oncancel: null, cancel: jest.fn(), pause: jest.fn(), play: jest.fn(), })), innerHTML: '', textContent: '', dataset: {}, parentNode: { removeChild: jest.fn(), }, })), getElementById: jest.fn(), querySelector: jest.fn(), querySelectorAll: jest.fn(() => []), head: { appendChild: jest.fn(), }, body: { appendChild: jest.fn(), }, readyState: 'complete', addEventListener: jest.fn(), execCommand: jest.fn(() => true), }; const mockWindow = { matchMedia: jest.fn(() => ({ matches: false, addEventListener: jest.fn(), removeEventListener: jest.fn(), })), requestAnimationFrame: jest.fn((cb) => setTimeout(cb, 0)), cancelAnimationFrame: jest.fn(), getComputedStyle: jest.fn(() => ({})), innerWidth: 1024, innerHeight: 768, navigator: { language: 'zh-CN', clipboard: { writeText: jest.fn(), }, connection: { effectiveType: '4g', downlink: 10, rtt: 50, }, onLine: true, }, location: { search: '', href: 'http://localhost', }, history: { pushState: jest.fn(), }, localStorage: { getItem: jest.fn(), setItem: jest.fn(), removeItem: jest.fn(), clear: jest.fn(), }, Audio: jest.fn(() => ({ play: jest.fn(), volume: 0, })), }; // 设置全局变量 global.document = mockDocument; global.window = mockWindow; global.navigator = mockWindow.navigator; global.localStorage = mockWindow.localStorage; global.requestAnimationFrame = mockWindow.requestAnimationFrame; global.cancelAnimationFrame = mockWindow.cancelAnimationFrame; global.matchMedia = mockWindow.matchMedia; global.getComputedStyle = mockWindow.getComputedStyle; global.Audio = mockWindow.Audio; // 测试套件 describe('MetonaToast', () => { beforeEach(() => { jest.clearAllMocks(); MeToast._toasts.clear(); // 清理DOM残留 if (typeof document !== 'undefined') { document.querySelectorAll('.met-container').forEach(c => c.remove()); } // 清理钩子残留 Toast._hooks.clear(); }); describe('版本信息', () => { test('应该有正确的版本号', () => { expect(VERSION).toBe('2.0.0'); expect(MeToast.version).toBe('2.0.0'); }); }); describe('基础功能', () => { test('应该能够显示成功Toast', () => { const toast = MeToast.success('成功消息'); expect(toast).toBeDefined(); expect(toast.id).toBeDefined(); expect(toast.type).toBe('success'); expect(toast.message).toBe('成功消息'); }); test('应该能够显示错误Toast', () => { const toast = MeToast.error('错误消息'); expect(toast).toBeDefined(); expect(toast.type).toBe('error'); expect(toast.message).toBe('错误消息'); }); test('应该能够显示警告Toast', () => { const toast = MeToast.warning('警告消息'); expect(toast).toBeDefined(); expect(toast.type).toBe('warning'); expect(toast.message).toBe('警告消息'); }); test('应该能够显示信息Toast', () => { const toast = MeToast.info('信息消息'); expect(toast).toBeDefined(); expect(toast.type).toBe('info'); expect(toast.message).toBe('信息消息'); }); test('应该能够显示默认Toast', () => { const toast = MeToast.show('默认消息'); expect(toast).toBeDefined(); expect(toast.type).toBe('default'); expect(toast.message).toBe('默认消息'); }); test('应该能够显示带标题的Toast', () => { const toast = MeToast.success({ title: '成功', message: '操作成功', }); expect(toast.title).toBe('成功'); expect(toast.message).toBe('操作成功'); }); test('应该能够显示加载Toast', () => { const loading = MeToast.loading('加载中...'); expect(loading).toBeDefined(); expect(loading.id).toBeDefined(); expect(loading.success).toBeInstanceOf(Function); expect(loading.error).toBeInstanceOf(Function); expect(loading.info).toBeInstanceOf(Function); expect(loading.warning).toBeInstanceOf(Function); expect(loading.update).toBeInstanceOf(Function); expect(loading.dismiss).toBeInstanceOf(Function); }); }); describe('Toast管理', () => { test('应该能够获取所有Toast', () => { MeToast.success('消息1'); MeToast.error('消息2'); const toasts = MeToast.getToasts(); expect(toasts).toHaveLength(2); }); test('应该能够获取Toast数量', () => { MeToast.success('消息1'); MeToast.error('消息2'); MeToast.warning('消息3'); expect(MeToast.count()).toBe(3); }); test('应该能够检查是否有Toast', () => { expect(MeToast.hasToasts()).toBe(false); MeToast.success('消息'); expect(MeToast.hasToasts()).toBe(true); }); test('应该能够查找Toast', () => { const toast = MeToast.success('消息'); const found = MeToast.find(toast.id); expect(found).toBeDefined(); expect(found.id).toBe(toast.id); }); test('应该能够关闭Toast', () => { const toast = MeToast.success('消息'); toast.close(); // 等待关闭动画 setTimeout(() => { expect(MeToast.count()).toBe(0); }, 300); }); test('应该能够关闭所有Toast', () => { MeToast.success('消息1'); MeToast.error('消息2'); MeToast.warning('消息3'); MeToast.dismiss(); // 等待关闭动画 setTimeout(() => { expect(MeToast.count()).toBe(0); }, 300); }); test('应该能够清除所有Toast', () => { MeToast.success('消息1'); MeToast.error('消息2'); MeToast.clear(); // 等待关闭动画 setTimeout(() => { expect(MeToast.count()).toBe(0); }, 300); }); }); describe('配置管理', () => { test('应该能够配置全局选项', () => { MeToast.configure({ position: 'bottom-center', duration: 5000, theme: 'dark', }); const config = MeToast.getConfig(); expect(config.position).toBe('bottom-center'); expect(config.duration).toBe(5000); expect(config.theme).toBe('dark'); }); test('应该能够重置配置', () => { MeToast.configure({ position: 'bottom-center', duration: 5000, }); MeToast.resetConfig(); const config = MeToast.getConfig(); expect(config.position).toBe('top-right'); expect(config.duration).toBe(4000); }); test('应该能够更新配置', () => { MeToast.updateConfig({ position: 'top-left', }); const config = MeToast.getConfig(); expect(config.position).toBe('top-left'); }); }); describe('主题系统', () => { test('应该能够获取当前主题', () => { const theme = MeToast.themes.getCurrentTheme(); expect(theme).toBeDefined(); }); test('应该能够切换主题', () => { MeToast.themes.switchTheme('dark'); expect(MeToast.themes.getCurrentTheme()).toBe('dark'); MeToast.themes.switchTheme('light'); expect(MeToast.themes.getCurrentTheme()).toBe('light'); }); test('应该能够获取主题配置', () => { const config = MeToast.themes.getThemeConfig('light'); expect(config).toBeDefined(); expect(config.bg).toBeDefined(); expect(config.text).toBeDefined(); expect(config.border).toBeDefined(); }); test('应该能够获取所有主题', () => { const themes = MeToast.themes.getAllThemes(); expect(themes).toBeDefined(); expect(themes.light).toBeDefined(); expect(themes.dark).toBeDefined(); }); test('应该能够注册自定义主题', () => { MeToast.themes.registerTheme('custom', { bg: 'rgba(255, 255, 255, 0.96)', text: '#1f2937', border: 'rgba(0, 0, 0, 0.06)', shadow: '0 10px 36px -10px rgba(0, 0, 0, 0.18)', hoverShadow: '0 14px 48px -10px rgba(0, 0, 0, 0.22)', progressBg: 'rgba(0, 0, 0, 0.06)', closeHoverBg: 'rgba(0, 0, 0, 0.06)', }); expect(MeToast.themes.hasTheme('custom')).toBe(true); }); }); describe('国际化系统', () => { test('应该能够获取当前语言', () => { const locale = MeToast.i18n.getCurrentLocale(); expect(locale).toBeDefined(); }); test('应该能够切换语言', () => { MeToast.i18n.switchLocale('en-US'); expect(MeToast.i18n.getCurrentLocale()).toBe('en-US'); MeToast.i18n.switchLocale('zh-CN'); expect(MeToast.i18n.getCurrentLocale()).toBe('zh-CN'); }); test('应该能够获取翻译', () => { const text = MeToast.i18n.t('success'); expect(text).toBeDefined(); }); test('应该能够检查翻译是否存在', () => { expect(MeToast.i18n.hasTranslation('success')).toBe(true); expect(MeToast.i18n.hasTranslation('nonexistent')).toBe(false); }); test('应该能够获取支持的语言列表', () => { const locales = MeToast.i18n.getSupportedLocales(); expect(locales).toContain('zh-CN'); expect(locales).toContain('en-US'); }); test('应该能够获取语言名称', () => { const name = MeToast.i18n.getLocaleName('zh-CN'); expect(name).toBe('简体中文'); }); test('应该能够格式化数字', () => { const formatted = MeToast.i18n.formatNumber(1234567.89); expect(formatted).toBeDefined(); }); test('应该能够格式化货币', () => { const formatted = MeToast.i18n.formatCurrency(99.99, 'USD'); expect(formatted).toBeDefined(); }); test('应该能够格式化日期', () => { const formatted = MeToast.i18n.formatDate(new Date()); expect(formatted).toBeDefined(); }); }); describe('插件系统', () => { test('应该能够注册插件', () => { const plugin = { name: 'test-plugin', version: '1.0.0', install: jest.fn(), }; MeToast.plugins.register('test', plugin); expect(MeToast.plugins.has('test')).toBe(true); }); test('应该能够获取插件', () => { const plugin = { name: 'test-plugin', version: '1.0.0', }; MeToast.plugins.register('test', plugin); const retrieved = MeToast.plugins.get('test'); expect(retrieved).toBeDefined(); expect(retrieved.name).toBe('test-plugin'); }); test('应该能够注销插件', () => { const plugin = { name: 'test-plugin', version: '1.0.0', uninstall: jest.fn(), }; MeToast.plugins.register('test', plugin); MeToast.plugins.unregister('test'); expect(MeToast.plugins.has('test')).toBe(false); }); test('应该能够启用/禁用插件', () => { const plugin = { name: 'test-plugin', version: '1.0.0', }; MeToast.plugins.register('test', plugin); MeToast.plugins.disable('test'); expect(MeToast.plugins.isEnabled('test')).toBe(false); MeToast.plugins.enable('test'); expect(MeToast.plugins.isEnabled('test')).toBe(true); }); test('应该能够获取所有插件', () => { // 清理前面测试遗留的插件 ['test', 'test-plugin', 'custom'].forEach(n => { if (MeToast.plugins.has(n)) MeToast.plugins.unregister(n); }); MeToast.plugins.register('test1', { name: 'test1' }); MeToast.plugins.register('test2', { name: 'test2' }); const plugins = MeToast.plugins.getAll(); expect(plugins).toHaveLength(2); }); test('应该能够获取插件名称', () => { MeToast.plugins.register('test1', { name: 'test1' }); MeToast.plugins.register('test2', { name: 'test2' }); const names = MeToast.plugins.getNames(); expect(names).toContain('test1'); expect(names).toContain('test2'); }); test('应该能够使用预设插件', () => { MeToast.use('keyboard'); expect(MeToast.plugins.has('keyboard')).toBe(true); }); }); describe('动画系统', () => { test('应该能够获取所有动画名称', () => { const names = MeToast.animations.getAnimationNames(); expect(names).toContain('slide'); expect(names).toContain('fade'); expect(names).toContain('scale'); expect(names).toContain('bounce'); }); test('应该能够获取动画配置', () => { const config = MeToast.animations.get('slide'); expect(config).toBeDefined(); expect(config.enter).toBeDefined(); expect(config.leave).toBeDefined(); expect(config.duration).toBeDefined(); expect(config.easing).toBeDefined(); }); test('应该能够注册自定义动画', () => { MeToast.animations.register('custom', { enter: { transform: 'scale(0)', opacity: 0, }, leave: { transform: 'scale(1)', opacity: 1, }, duration: 500, }); expect(MeToast.animations.get('custom')).toBeDefined(); }); test('应该能够注销动画', () => { MeToast.animations.register('custom', { enter: {}, leave: {}, }); MeToast.animations.unregister('custom'); expect(MeToast.animations.get('custom')).toBeNull(); }); }); describe('Promise支持', () => { test('应该能够使用Promise风格', async () => { const promise = Promise.resolve('success'); await MeToast.promise(promise, { loading: '加载中...', success: '成功', error: '失败', }); // 验证Toast被创建 expect(MeToast.count()).toBeGreaterThan(0); }); test('应该能够处理Promise拒绝', async () => { const promise = Promise.reject(new Error('error')); try { await MeToast.promise(promise, { loading: '加载中...', success: '成功', error: '失败', }); } catch (error) { expect(error).toBeDefined(); } }); }); describe('Toast实例', () => { test('应该能够更新Toast', () => { const toast = MeToast.success('原始消息'); toast.update({ title: '新标题', message: '新消息', type: 'info', }); expect(toast.title).toBe('新标题'); expect(toast.message).toBe('新消息'); expect(toast.type).toBe('info'); }); test('应该能够暂停和恢复Toast', () => { const toast = MeToast.success({ message: '消息', duration: 5000, }); toast._pause(); expect(toast.paused).toBe(true); toast._resume(); expect(toast.paused).toBe(false); }); test('应该能够获取Toast配置', () => { const toast = MeToast.success({ message: '消息', position: 'bottom-center', duration: 5000, }); expect(toast.config.position).toBe('bottom-center'); expect(toast.config.duration).toBe(5000); }); }); describe('状态信息', () => { test('应该能够获取状态信息', () => { MeToast.success('消息'); const status = MeToast.getStatus(); expect(status.version).toBe('2.0.0'); expect(status.toasts).toBeGreaterThanOrEqual(0); expect(status.theme).toBeDefined(); expect(status.locale).toBeDefined(); expect(status.plugins).toBeInstanceOf(Array); expect(status.animations).toBeGreaterThanOrEqual(0); }); }); describe('销毁功能', () => { test('应该能够销毁所有Toast', () => { MeToast.success('消息1'); MeToast.error('消息2'); MeToast.destroy(); // 等待销毁完成 setTimeout(() => { expect(MeToast.count()).toBe(0); }, 300); }); }); }); describe('Toast类', () => { test('应该能够创建Toast实例', () => { const toast = new Toast({ type: 'success', title: '成功', message: '操作成功', }); expect(toast).toBeDefined(); expect(toast.type).toBe('success'); expect(toast.title).toBe('成功'); expect(toast.message).toBe('操作成功'); }); test('应该能够生成唯一ID', () => { const toast1 = new Toast({ message: '消息1' }); const toast2 = new Toast({ message: '消息2' }); expect(toast1.id).not.toBe(toast2.id); }); test('应该能够设置默认配置', () => { const toast = new Toast({ message: '消息' }); expect(toast.config).toBeDefined(); expect(toast.config.position).toBe('top-right'); expect(toast.config.duration).toBe(4000); expect(toast.config.max).toBe(6); }); test('应该能够合并配置', () => { const toast = new Toast({ message: '消息', position: 'bottom-center', duration: 5000, }); expect(toast.config.position).toBe('bottom-center'); expect(toast.config.duration).toBe(5000); }); test('应该能够设置类型', () => { const types = ['success', 'error', 'warning', 'info', 'loading', 'default']; types.forEach((type) => { const toast = new Toast({ type, message: '消息' }); expect(toast.type).toBe(type); }); }); test('应该能够设置标题', () => { const toast = new Toast({ title: '标题', message: '消息', }); expect(toast.title).toBe('标题'); }); test('应该能够设置消息', () => { const toast = new Toast({ message: '消息内容', }); expect(toast.message).toBe('消息内容'); }); test('应该能够设置HTML内容', () => { const toast = new Toast({ html: '加粗', }); expect(toast.html).toBe('加粗'); }); test('应该能够设置自定义图标', () => { const toast = new Toast({ iconHTML: '...', }); expect(toast.iconHTML).toBe('...'); }); test('应该能够更新Toast', () => { const toast = new Toast({ message: '原始消息' }); toast.update({ title: '新标题', message: '新消息', type: 'success', }); expect(toast.title).toBe('新标题'); expect(toast.message).toBe('新消息'); expect(toast.type).toBe('success'); }); test('应该能够关闭Toast', (done) => { const toast = new Toast({ message: '消息' }); toast.close(); expect(toast.closing).toBe(true); // _destroy 在 300ms setTimeout 中异步调用 setTimeout(() => { expect(toast.el).toBeNull(); done(); }, 350); }); test('应该能够暂停计时', () => { const toast = new Toast({ message: '消息', duration: 5000, }); toast._pause(); expect(toast.paused).toBe(true); }); test('应该能够恢复计时', () => { const toast = new Toast({ message: '消息', duration: 5000, }); toast._pause(); toast._resume(); expect(toast.paused).toBe(false); }); test('应该能够获取配色方案', () => { const toast = new Toast({ type: 'success' }); const palette = toast._palette(); expect(palette).toBeDefined(); expect(palette.theme).toBeDefined(); expect(palette.c).toBeDefined(); expect(palette.t).toBeDefined(); }); }); describe('工具函数', () => { test('应该能够生成唯一ID', () => { const { generateId } = require('../src/utils.js'); const id1 = generateId(); const id2 = generateId(); expect(id1).not.toBe(id2); expect(id1).toMatch(/^met-/); }); test('应该能够转义HTML', () => { const { escapeHTML } = require('../src/utils.js'); const escaped = escapeHTML(''); expect(escaped).not.toContain('