/**
* 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('