1998 lines
56 KiB
JavaScript
1998 lines
56 KiB
JavaScript
/**
|
|
* 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();
|
|
|
|
// 重置Toast状态
|
|
MeToast._toasts.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: '<b>加粗</b>',
|
|
});
|
|
|
|
expect(toast.html).toBe('<b>加粗</b>');
|
|
});
|
|
|
|
test('应该能够设置自定义图标', () => {
|
|
const toast = new Toast({
|
|
iconHTML: '<svg>...</svg>',
|
|
});
|
|
|
|
expect(toast.iconHTML).toBe('<svg>...</svg>');
|
|
});
|
|
|
|
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('<script>alert("xss")</script>');
|
|
expect(escaped).not.toContain('<script>');
|
|
expect(escaped).toContain('<script>');
|
|
});
|
|
|
|
test('应该能够检测暗色模式偏好', () => {
|
|
const { prefersDark } = require('../src/utils.js');
|
|
|
|
const result = prefersDark();
|
|
expect(typeof result).toBe('boolean');
|
|
});
|
|
|
|
test('应该能够防抖函数', (done) => {
|
|
const { debounce } = require('../src/utils.js');
|
|
|
|
let count = 0;
|
|
const fn = jest.fn(() => count++);
|
|
const debouncedFn = debounce(fn, 100);
|
|
|
|
debouncedFn();
|
|
debouncedFn();
|
|
debouncedFn();
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
|
|
setTimeout(() => {
|
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
done();
|
|
}, 150);
|
|
});
|
|
|
|
test('应该能够节流函数', (done) => {
|
|
const { throttle } = require('../src/utils.js');
|
|
|
|
let count = 0;
|
|
const fn = jest.fn(() => count++);
|
|
const throttledFn = throttle(fn, 100);
|
|
|
|
throttledFn();
|
|
throttledFn();
|
|
throttledFn();
|
|
|
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
|
|
setTimeout(() => {
|
|
throttledFn();
|
|
expect(fn).toHaveBeenCalledTimes(2);
|
|
done();
|
|
}, 150);
|
|
});
|
|
|
|
test('应该能够深度合并对象', () => {
|
|
const { deepMerge } = require('../src/utils.js');
|
|
|
|
const obj1 = { a: 1, b: { c: 2 } };
|
|
const obj2 = { b: { d: 3 }, e: 4 };
|
|
|
|
const merged = deepMerge(obj1, obj2);
|
|
|
|
expect(merged.a).toBe(1);
|
|
expect(merged.b.c).toBe(2);
|
|
expect(merged.b.d).toBe(3);
|
|
expect(merged.e).toBe(4);
|
|
});
|
|
|
|
test('应该能够检查是否为浏览器环境', () => {
|
|
const { isBrowser } = require('../src/utils.js');
|
|
|
|
const result = isBrowser();
|
|
expect(typeof result).toBe('boolean');
|
|
});
|
|
|
|
test('应该能够安全获取嵌套对象属性', () => {
|
|
const { getNestedValue } = require('../src/utils.js');
|
|
|
|
const obj = { a: { b: { c: 1 } } };
|
|
|
|
expect(getNestedValue(obj, 'a.b.c')).toBe(1);
|
|
expect(getNestedValue(obj, 'a.b.d')).toBeUndefined();
|
|
expect(getNestedValue(obj, 'a.b.d', 'default')).toBe('default');
|
|
});
|
|
|
|
test('应该能够格式化文件大小', () => {
|
|
const { formatFileSize } = require('../src/utils.js');
|
|
|
|
expect(formatFileSize(0)).toBe('0 Bytes');
|
|
expect(formatFileSize(1024)).toBe('1 KB');
|
|
expect(formatFileSize(1048576)).toBe('1 MB');
|
|
expect(formatFileSize(1073741824)).toBe('1 GB');
|
|
});
|
|
|
|
test('应该能够格式化日期', () => {
|
|
const { formatDate } = require('../src/utils.js');
|
|
|
|
const date = new Date('2024-01-01 12:00:00');
|
|
const formatted = formatDate(date, 'YYYY-MM-DD HH:mm:ss');
|
|
|
|
expect(formatted).toBe('2024-01-01 12:00:00');
|
|
});
|
|
|
|
test('应该能够生成随机数', () => {
|
|
const { random } = require('../src/utils.js');
|
|
|
|
const result = random(1, 100);
|
|
expect(result).toBeGreaterThanOrEqual(1);
|
|
expect(result).toBeLessThanOrEqual(100);
|
|
});
|
|
|
|
test('应该能够检查元素是否在视口中', () => {
|
|
const { isInViewport } = require('../src/utils.js');
|
|
|
|
const el = {
|
|
getBoundingClientRect: () => ({
|
|
top: 0,
|
|
left: 0,
|
|
bottom: 100,
|
|
right: 100,
|
|
}),
|
|
};
|
|
|
|
expect(isInViewport(el)).toBe(true);
|
|
});
|
|
|
|
test('应该能够复制文本到剪贴板', async () => {
|
|
const { copyToClipboard } = require('../src/utils.js');
|
|
|
|
// Mock clipboard.writeText 返回 resolved promise
|
|
if (!navigator.clipboard) navigator.clipboard = {};
|
|
const origWriteText = navigator.clipboard.writeText;
|
|
navigator.clipboard.writeText = jest.fn(() => Promise.resolve());
|
|
|
|
const result = await copyToClipboard('test');
|
|
expect(result).toBe(true);
|
|
|
|
navigator.clipboard.writeText = origWriteText;
|
|
});
|
|
|
|
test('应该能够检测设备类型', () => {
|
|
const { getDeviceType } = require('../src/utils.js');
|
|
|
|
const result = getDeviceType();
|
|
expect(['desktop', 'mobile', 'tablet']).toContain(result);
|
|
});
|
|
|
|
test('应该能够检测浏览器信息', () => {
|
|
const { getBrowserInfo } = require('../src/utils.js');
|
|
|
|
const result = getBrowserInfo();
|
|
expect(result).toBeDefined();
|
|
expect(result.browser).toBeDefined();
|
|
expect(result.version).toBeDefined();
|
|
});
|
|
|
|
test('应该能够检测操作系统', () => {
|
|
const { getOS } = require('../src/utils.js');
|
|
|
|
const result = getOS();
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
test('应该能够检测网络状态', () => {
|
|
const { getNetworkInfo } = require('../src/utils.js');
|
|
|
|
const result = getNetworkInfo();
|
|
expect(result).toBeDefined();
|
|
expect(result.online).toBeDefined();
|
|
});
|
|
|
|
test('应该能够存储和获取数据', () => {
|
|
const { setStorage, getStorage, removeStorage } = require('../src/utils.js');
|
|
|
|
setStorage('test', 'value');
|
|
expect(getStorage('test')).toBe('value');
|
|
|
|
removeStorage('test');
|
|
expect(getStorage('test')).toBeNull();
|
|
});
|
|
|
|
test('应该能够生成UUID', () => {
|
|
const { generateUUID } = require('../src/utils.js');
|
|
|
|
const uuid = generateUUID();
|
|
expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
|
|
});
|
|
|
|
test('应该能够检查CSS属性支持', () => {
|
|
const { supportsCSSProperty } = require('../src/utils.js');
|
|
|
|
const result = supportsCSSProperty('transform');
|
|
expect(typeof result).toBe('boolean');
|
|
});
|
|
|
|
test('应该能够检查JavaScript API支持', () => {
|
|
const { supportsAPI } = require('../src/utils.js');
|
|
|
|
const result = supportsAPI('fetch');
|
|
expect(typeof result).toBe('boolean');
|
|
});
|
|
|
|
test('应该能够获取URL参数', () => {
|
|
const { getURLParam } = require('../src/utils.js');
|
|
|
|
// Mock URLSearchParams 来绕过 jsdom location.search 限制
|
|
const origSearchParams = global.URLSearchParams;
|
|
global.URLSearchParams = jest.fn(() => ({
|
|
get: jest.fn((name) => name === 'test' ? 'value' : null),
|
|
}));
|
|
|
|
const result = getURLParam('test');
|
|
expect(result).toBe('value');
|
|
|
|
global.URLSearchParams = origSearchParams;
|
|
});
|
|
|
|
test('应该能够格式化数字', () => {
|
|
const { formatNumber } = require('../src/utils.js');
|
|
|
|
const result = formatNumber(1234567.89, 2);
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
test('应该能够格式化货币', () => {
|
|
const { formatCurrency } = require('../src/utils.js');
|
|
|
|
const result = formatCurrency(99.99, 'USD');
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
test('应该能够格式化百分比', () => {
|
|
const { formatPercent } = require('../src/utils.js');
|
|
|
|
const result = formatPercent(75.5, 2);
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
test('应该能够检查是否为空值', () => {
|
|
const { isEmpty } = require('../src/utils.js');
|
|
|
|
expect(isEmpty(null)).toBe(true);
|
|
expect(isEmpty(undefined)).toBe(true);
|
|
expect(isEmpty('')).toBe(true);
|
|
expect(isEmpty(' ')).toBe(true);
|
|
expect(isEmpty([])).toBe(true);
|
|
expect(isEmpty({})).toBe(true);
|
|
expect(isEmpty('text')).toBe(false);
|
|
expect(isEmpty([1])).toBe(false);
|
|
expect(isEmpty({ a: 1 })).toBe(false);
|
|
});
|
|
|
|
test('应该能够深拷贝对象', () => {
|
|
const { deepClone } = require('../src/utils.js');
|
|
|
|
const obj = { a: 1, b: { c: 2 }, d: [1, 2, 3] };
|
|
const cloned = deepClone(obj);
|
|
|
|
expect(cloned).toEqual(obj);
|
|
expect(cloned).not.toBe(obj);
|
|
expect(cloned.b).not.toBe(obj.b);
|
|
expect(cloned.d).not.toBe(obj.d);
|
|
});
|
|
|
|
test('应该能够比较对象是否相等', () => {
|
|
const { isEqual } = require('../src/utils.js');
|
|
|
|
expect(isEqual({ a: 1 }, { a: 1 })).toBe(true);
|
|
expect(isEqual({ a: 1 }, { a: 2 })).toBe(false);
|
|
expect(isEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false);
|
|
expect(isEqual([1, 2, 3], [1, 2, 3])).toBe(true);
|
|
expect(isEqual([1, 2, 3], [1, 2])).toBe(false);
|
|
});
|
|
|
|
test('应该能够验证邮箱格式', () => {
|
|
const { isValidEmail } = require('../src/utils.js');
|
|
|
|
expect(isValidEmail('test@example.com')).toBe(true);
|
|
expect(isValidEmail('invalid-email')).toBe(false);
|
|
expect(isValidEmail('test@')).toBe(false);
|
|
expect(isValidEmail('@example.com')).toBe(false);
|
|
});
|
|
|
|
test('应该能够验证URL格式', () => {
|
|
const { isValidURL } = require('../src/utils.js');
|
|
|
|
expect(isValidURL('https://example.com')).toBe(true);
|
|
expect(isValidURL('http://example.com')).toBe(true);
|
|
expect(isValidURL('invalid-url')).toBe(false);
|
|
});
|
|
|
|
test('应该能够验证手机号格式', () => {
|
|
const { isValidPhone } = require('../src/utils.js');
|
|
|
|
expect(isValidPhone('13800138000')).toBe(true);
|
|
expect(isValidPhone('12345678901')).toBe(false);
|
|
expect(isValidPhone('1380013800')).toBe(false);
|
|
});
|
|
|
|
test('应该能够验证身份证号格式', () => {
|
|
const { isValidIDCard } = require('../src/utils.js');
|
|
|
|
expect(isValidIDCard('110101199003077777')).toBe(true);
|
|
expect(isValidIDCard('123456789012345678')).toBe(false);
|
|
});
|
|
|
|
test('应该能够生成随机颜色', () => {
|
|
const { randomColor } = require('../src/utils.js');
|
|
|
|
const color = randomColor();
|
|
expect(color).toMatch(/^#[0-9a-f]{6}$/);
|
|
});
|
|
|
|
test('应该能够颜色转RGBA', () => {
|
|
const { colorToRGBA } = require('../src/utils.js');
|
|
|
|
const rgba = colorToRGBA('#ff0000', 0.5);
|
|
expect(rgba).toBe('rgba(255, 0, 0, 0.5)');
|
|
});
|
|
|
|
test('应该能够获取颜色亮度', () => {
|
|
const { getColorBrightness } = require('../src/utils.js');
|
|
|
|
const brightness = getColorBrightness('#ffffff');
|
|
expect(brightness).toBe(255);
|
|
});
|
|
|
|
test('应该能够判断是否为浅色', () => {
|
|
const { isLightColor } = require('../src/utils.js');
|
|
|
|
expect(isLightColor('#ffffff')).toBe(true);
|
|
expect(isLightColor('#000000')).toBe(false);
|
|
});
|
|
|
|
test('应该能够获取对比色', () => {
|
|
const { getContrastColor } = require('../src/utils.js');
|
|
|
|
expect(getContrastColor('#ffffff')).toBe('#000000');
|
|
expect(getContrastColor('#000000')).toBe('#FFFFFF');
|
|
});
|
|
|
|
test('应该能够生成渐变色', () => {
|
|
const { generateGradient } = require('../src/utils.js');
|
|
|
|
const gradient = generateGradient('#ff0000', '#0000ff', 5);
|
|
expect(gradient).toHaveLength(5);
|
|
expect(gradient[0]).toBe('#ff0000');
|
|
expect(gradient[4]).toBe('#0000ff');
|
|
});
|
|
|
|
test('应该能够获取字符长度', () => {
|
|
const { getStringLength } = require('../src/utils.js');
|
|
|
|
expect(getStringLength('abc')).toBe(3);
|
|
expect(getStringLength('中文')).toBe(4);
|
|
expect(getStringLength('abc中文')).toBe(7);
|
|
});
|
|
|
|
test('应该能够截取字符串', () => {
|
|
const { truncateString } = require('../src/utils.js');
|
|
|
|
expect(truncateString('abcdefghij', 5)).toBe('abcde...');
|
|
expect(truncateString('中文字符串测试', 8)).toBe('中文字符...');
|
|
expect(truncateString('abc', 5)).toBe('abc');
|
|
});
|
|
|
|
test('应该能够驼峰转换', () => {
|
|
const { toCamelCase } = require('../src/utils.js');
|
|
|
|
expect(toCamelCase('hello-world')).toBe('helloWorld');
|
|
expect(toCamelCase('hello_world')).toBe('helloWorld');
|
|
expect(toCamelCase('hello world')).toBe('helloWorld');
|
|
});
|
|
|
|
test('应该能够短横线转换', () => {
|
|
const { toKebabCase } = require('../src/utils.js');
|
|
|
|
expect(toKebabCase('helloWorld')).toBe('hello-world');
|
|
expect(toKebabCase('hello_world')).toBe('hello-world');
|
|
expect(toKebabCase('hello world')).toBe('hello-world');
|
|
});
|
|
|
|
test('应该能够下划线转换', () => {
|
|
const { toSnakeCase } = require('../src/utils.js');
|
|
|
|
expect(toSnakeCase('helloWorld')).toBe('hello_world');
|
|
expect(toSnakeCase('hello-world')).toBe('hello_world');
|
|
expect(toSnakeCase('hello world')).toBe('hello_world');
|
|
});
|
|
|
|
test('应该能够首字母大写', () => {
|
|
const { capitalize } = require('../src/utils.js');
|
|
|
|
expect(capitalize('hello')).toBe('Hello');
|
|
expect(capitalize('HELLO')).toBe('HELLO');
|
|
});
|
|
|
|
test('应该能够每个单词首字母大写', () => {
|
|
const { capitalizeWords } = require('../src/utils.js');
|
|
|
|
expect(capitalizeWords('hello world')).toBe('Hello World');
|
|
});
|
|
|
|
test('应该能够移除HTML标签', () => {
|
|
const { stripHTML } = require('../src/utils.js');
|
|
|
|
expect(stripHTML('<b>bold</b>')).toBe('bold');
|
|
expect(stripHTML('<p>paragraph</p>')).toBe('paragraph');
|
|
});
|
|
|
|
test('应该能够转义正则表达式', () => {
|
|
const { escapeRegExp } = require('../src/utils.js');
|
|
|
|
expect(escapeRegExp('[test]')).toBe('\\[test\\]');
|
|
expect(escapeRegExp('hello.world')).toBe('hello\\.world');
|
|
});
|
|
|
|
test('应该能够生成随机字符串', () => {
|
|
const { randomString } = require('../src/utils.js');
|
|
|
|
const str = randomString(10);
|
|
expect(str).toHaveLength(10);
|
|
});
|
|
|
|
test('应该能够延迟执行', async () => {
|
|
const { sleep } = require('../src/utils.js');
|
|
|
|
const start = Date.now();
|
|
await sleep(100);
|
|
const end = Date.now();
|
|
|
|
expect(end - start).toBeGreaterThanOrEqual(90);
|
|
});
|
|
|
|
test('应该能够重试函数', async () => {
|
|
const { retry } = require('../src/utils.js');
|
|
|
|
let attempts = 0;
|
|
const fn = jest.fn(() => {
|
|
attempts++;
|
|
if (attempts < 3) {
|
|
throw new Error('fail');
|
|
}
|
|
return 'success';
|
|
});
|
|
|
|
const result = await retry(fn, 3, 10);
|
|
expect(result).toBe('success');
|
|
expect(fn).toHaveBeenCalledTimes(3);
|
|
});
|
|
|
|
test('应该能够超时控制', async () => {
|
|
const { timeout } = require('../src/utils.js');
|
|
|
|
const promise = new Promise((resolve) => setTimeout(resolve, 200));
|
|
|
|
await expect(timeout(promise, 100)).rejects.toThrow('Timeout');
|
|
});
|
|
|
|
test('应该能够并发控制', async () => {
|
|
const { parallel } = require('../src/utils.js');
|
|
|
|
const tasks = [
|
|
() => Promise.resolve(1),
|
|
() => Promise.resolve(2),
|
|
() => Promise.resolve(3),
|
|
];
|
|
|
|
const results = await parallel(tasks, 2);
|
|
expect(results).toEqual([1, 2, 3]);
|
|
});
|
|
|
|
test('应该能够序列执行', async () => {
|
|
const { sequence } = require('../src/utils.js');
|
|
|
|
const tasks = [
|
|
() => Promise.resolve(1),
|
|
() => Promise.resolve(2),
|
|
() => Promise.resolve(3),
|
|
];
|
|
|
|
const results = await sequence(tasks);
|
|
expect(results).toEqual([1, 2, 3]);
|
|
});
|
|
|
|
test('应该能够缓存函数', () => {
|
|
const { memoize } = require('../src/utils.js');
|
|
|
|
let count = 0;
|
|
const fn = jest.fn((x) => {
|
|
count++;
|
|
return x * 2;
|
|
});
|
|
|
|
const memoized = memoize(fn);
|
|
|
|
expect(memoized(2)).toBe(4);
|
|
expect(memoized(2)).toBe(4);
|
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('应该能够单例模式', () => {
|
|
const { singleton } = require('../src/utils.js');
|
|
|
|
let count = 0;
|
|
const fn = jest.fn(() => {
|
|
count++;
|
|
return { id: count };
|
|
});
|
|
|
|
const singletonFn = singleton(fn);
|
|
|
|
const obj1 = singletonFn();
|
|
const obj2 = singletonFn();
|
|
|
|
expect(obj1).toBe(obj2);
|
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('应该能够创建观察者', () => {
|
|
const { createObserver } = require('../src/utils.js');
|
|
|
|
const observer = createObserver();
|
|
const callback = jest.fn();
|
|
|
|
observer.on('test', callback);
|
|
observer.emit('test', 'data');
|
|
|
|
expect(callback).toHaveBeenCalledWith('data');
|
|
});
|
|
|
|
test('应该能够创建状态机', () => {
|
|
const { createStateMachine } = require('../src/utils.js');
|
|
|
|
const machine = createStateMachine({
|
|
initial: 'idle',
|
|
transitions: {
|
|
idle: { start: 'running' },
|
|
running: { stop: 'idle' },
|
|
},
|
|
});
|
|
|
|
expect(machine.state).toBe('idle');
|
|
|
|
machine.transition('start');
|
|
expect(machine.state).toBe('running');
|
|
|
|
machine.transition('stop');
|
|
expect(machine.state).toBe('idle');
|
|
});
|
|
});
|
|
|
|
describe('常量', () => {
|
|
test('应该有默认配置', () => {
|
|
const { DEFAULTS } = require('../src/constants.js');
|
|
|
|
expect(DEFAULTS).toBeDefined();
|
|
expect(DEFAULTS.position).toBe('top-right');
|
|
expect(DEFAULTS.duration).toBe(4000);
|
|
expect(DEFAULTS.max).toBe(6);
|
|
});
|
|
|
|
test('应该有图标', () => {
|
|
const { ICONS } = require('../src/constants.js');
|
|
|
|
expect(ICONS).toBeDefined();
|
|
expect(ICONS.success).toBeDefined();
|
|
expect(ICONS.error).toBeDefined();
|
|
expect(ICONS.warning).toBeDefined();
|
|
expect(ICONS.info).toBeDefined();
|
|
expect(ICONS.loading).toBeDefined();
|
|
});
|
|
|
|
test('应该有类型颜色', () => {
|
|
const { TYPE_COLORS } = require('../src/constants.js');
|
|
|
|
expect(TYPE_COLORS).toBeDefined();
|
|
expect(TYPE_COLORS.success).toBeDefined();
|
|
expect(TYPE_COLORS.error).toBeDefined();
|
|
expect(TYPE_COLORS.warning).toBeDefined();
|
|
expect(TYPE_COLORS.info).toBeDefined();
|
|
expect(TYPE_COLORS.loading).toBeDefined();
|
|
});
|
|
|
|
test('应该有动画配置', () => {
|
|
const { ANIMATIONS } = require('../src/constants.js');
|
|
|
|
expect(ANIMATIONS).toBeDefined();
|
|
expect(ANIMATIONS.slide).toBeDefined();
|
|
expect(ANIMATIONS.fade).toBeDefined();
|
|
expect(ANIMATIONS.scale).toBeDefined();
|
|
expect(ANIMATIONS.bounce).toBeDefined();
|
|
});
|
|
|
|
test('应该有主题配置', () => {
|
|
const { THEMES } = require('../src/constants.js');
|
|
|
|
expect(THEMES).toBeDefined();
|
|
expect(THEMES.light).toBeDefined();
|
|
expect(THEMES.dark).toBeDefined();
|
|
});
|
|
|
|
test('应该有位置配置', () => {
|
|
const { POSITIONS } = require('../src/constants.js');
|
|
|
|
expect(POSITIONS).toContain('top-left');
|
|
expect(POSITIONS).toContain('top-center');
|
|
expect(POSITIONS).toContain('top-right');
|
|
expect(POSITIONS).toContain('bottom-left');
|
|
expect(POSITIONS).toContain('bottom-center');
|
|
expect(POSITIONS).toContain('bottom-right');
|
|
});
|
|
|
|
test('应该有类型配置', () => {
|
|
const { TYPES } = require('../src/constants.js');
|
|
|
|
expect(TYPES).toContain('default');
|
|
expect(TYPES).toContain('success');
|
|
expect(TYPES).toContain('error');
|
|
expect(TYPES).toContain('warning');
|
|
expect(TYPES).toContain('info');
|
|
expect(TYPES).toContain('loading');
|
|
});
|
|
|
|
test('应该有国际化配置', () => {
|
|
const { LOCALES } = require('../src/constants.js');
|
|
|
|
expect(LOCALES).toBeDefined();
|
|
expect(LOCALES['zh-CN']).toBeDefined();
|
|
expect(LOCALES['en-US']).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('动画管理器', () => {
|
|
test('应该能够创建动画管理器', () => {
|
|
const { createAnimationManager } = require('../src/animations.js');
|
|
|
|
const manager = createAnimationManager();
|
|
expect(manager).toBeDefined();
|
|
expect(manager.register).toBeInstanceOf(Function);
|
|
expect(manager.apply).toBeInstanceOf(Function);
|
|
});
|
|
|
|
test('应该能够注册动画', () => {
|
|
const { createAnimationManager } = require('../src/animations.js');
|
|
|
|
const manager = createAnimationManager();
|
|
|
|
manager.register('test', {
|
|
enter: { opacity: 0 },
|
|
leave: { opacity: 1 },
|
|
duration: 300,
|
|
});
|
|
|
|
expect(manager.get('test')).toBeDefined();
|
|
});
|
|
|
|
test('应该能够应用动画', async () => {
|
|
const { createAnimationManager } = require('../src/animations.js');
|
|
|
|
const manager = createAnimationManager();
|
|
const element = document.createElement('div');
|
|
// 确保 animate 方法存在并在设置 onfinish 后自动调用
|
|
element.animate = jest.fn(() => {
|
|
const anim = {
|
|
onfinish: null,
|
|
oncancel: null,
|
|
cancel: jest.fn(),
|
|
pause: jest.fn(),
|
|
play: jest.fn(),
|
|
};
|
|
// 下一个微任务自动触发 onfinish,让 Promise resolve
|
|
Promise.resolve().then(() => {
|
|
if (anim.onfinish) anim.onfinish();
|
|
});
|
|
return anim;
|
|
});
|
|
|
|
manager.register('test', {
|
|
enter: { opacity: 0 },
|
|
leave: { opacity: 1 },
|
|
duration: 100,
|
|
});
|
|
|
|
await manager.apply(element, 'test');
|
|
|
|
expect(element.animate).toHaveBeenCalled();
|
|
});
|
|
|
|
test('应该能够取消动画', () => {
|
|
const { createAnimationManager } = require('../src/animations.js');
|
|
|
|
const manager = createAnimationManager();
|
|
|
|
manager.cancelAll();
|
|
expect(manager.getActiveCount()).toBe(0);
|
|
});
|
|
|
|
test('应该能够获取活动动画数量', () => {
|
|
const { createAnimationManager } = require('../src/animations.js');
|
|
|
|
const manager = createAnimationManager();
|
|
expect(manager.getActiveCount()).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('主题管理器', () => {
|
|
test('应该能够获取系统主题', () => {
|
|
const { getSystemTheme } = require('../src/themes.js');
|
|
|
|
const theme = getSystemTheme();
|
|
expect(['light', 'dark']).toContain(theme);
|
|
});
|
|
|
|
test('应该能够解析主题', () => {
|
|
const { resolveTheme } = require('../src/themes.js');
|
|
|
|
expect(resolveTheme('light')).toBe('light');
|
|
expect(resolveTheme('dark')).toBe('dark');
|
|
expect(resolveTheme('auto')).toBeDefined();
|
|
});
|
|
|
|
test('应该能够获取主题配置', () => {
|
|
const { getThemeConfig } = require('../src/themes.js');
|
|
|
|
const config = getThemeConfig('light');
|
|
expect(config).toBeDefined();
|
|
expect(config.bg).toBeDefined();
|
|
expect(config.text).toBeDefined();
|
|
});
|
|
|
|
test('应该能够应用主题', () => {
|
|
const { applyTheme } = require('../src/themes.js');
|
|
|
|
applyTheme('light');
|
|
applyTheme('dark');
|
|
applyTheme('auto');
|
|
});
|
|
|
|
test('应该能够获取当前主题', () => {
|
|
const { getCurrentTheme } = require('../src/themes.js');
|
|
|
|
const theme = getCurrentTheme();
|
|
expect(theme).toBeDefined();
|
|
});
|
|
|
|
test('应该能够切换主题', () => {
|
|
const { switchTheme } = require('../src/themes.js');
|
|
|
|
switchTheme('light');
|
|
switchTheme('dark');
|
|
switchTheme('auto');
|
|
});
|
|
|
|
test('应该能够获取所有主题', () => {
|
|
const { getAllThemes } = require('../src/themes.js');
|
|
|
|
const themes = getAllThemes();
|
|
expect(themes).toBeDefined();
|
|
expect(themes.light).toBeDefined();
|
|
expect(themes.dark).toBeDefined();
|
|
});
|
|
|
|
test('应该能够获取主题名称列表', () => {
|
|
const { getThemeNames } = require('../src/themes.js');
|
|
|
|
const names = getThemeNames();
|
|
expect(names).toContain('light');
|
|
expect(names).toContain('dark');
|
|
});
|
|
|
|
test('应该能够检查主题是否存在', () => {
|
|
const { hasTheme } = require('../src/themes.js');
|
|
|
|
expect(hasTheme('light')).toBe(true);
|
|
expect(hasTheme('dark')).toBe(true);
|
|
expect(hasTheme('nonexistent')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('国际化管理器', () => {
|
|
test('应该能够获取当前语言', () => {
|
|
const { getCurrentLocale } = require('../src/i18n.js');
|
|
|
|
const locale = getCurrentLocale();
|
|
expect(locale).toBeDefined();
|
|
});
|
|
|
|
test('应该能够设置当前语言', () => {
|
|
const { setCurrentLocale } = require('../src/i18n.js');
|
|
|
|
setCurrentLocale('en-US');
|
|
setCurrentLocale('zh-CN');
|
|
});
|
|
|
|
test('应该能够获取翻译', () => {
|
|
const { t } = require('../src/i18n.js');
|
|
|
|
const text = t('success');
|
|
expect(text).toBeDefined();
|
|
});
|
|
|
|
test('应该能够检查翻译是否存在', () => {
|
|
const { hasTranslation } = require('../src/i18n.js');
|
|
|
|
expect(hasTranslation('success')).toBe(true);
|
|
expect(hasTranslation('nonexistent')).toBe(false);
|
|
});
|
|
|
|
test('应该能够获取支持的语言列表', () => {
|
|
const { getSupportedLocales } = require('../src/i18n.js');
|
|
|
|
const locales = getSupportedLocales();
|
|
expect(locales).toContain('zh-CN');
|
|
expect(locales).toContain('en-US');
|
|
});
|
|
|
|
test('应该能够检查语言是否支持', () => {
|
|
const { isLocaleSupported } = require('../src/i18n.js');
|
|
|
|
expect(isLocaleSupported('zh-CN')).toBe(true);
|
|
expect(isLocaleSupported('en-US')).toBe(true);
|
|
expect(isLocaleSupported('nonexistent')).toBe(false);
|
|
});
|
|
|
|
test('应该能够获取语言名称', () => {
|
|
const { getLocaleName } = require('../src/i18n.js');
|
|
|
|
expect(getLocaleName('zh-CN')).toBe('简体中文');
|
|
expect(getLocaleName('en-US')).toBe('English (US)');
|
|
});
|
|
|
|
test('应该能够获取语言方向', () => {
|
|
const { getLocaleDirection } = require('../src/i18n.js');
|
|
|
|
expect(getLocaleDirection('zh-CN')).toBe('ltr');
|
|
expect(getLocaleDirection('ar')).toBe('rtl');
|
|
});
|
|
|
|
test('应该能够格式化数字', () => {
|
|
const { formatNumber } = require('../src/i18n.js');
|
|
|
|
const formatted = formatNumber(1234567.89);
|
|
expect(formatted).toBeDefined();
|
|
});
|
|
|
|
test('应该能够格式化货币', () => {
|
|
const { formatCurrency } = require('../src/i18n.js');
|
|
|
|
const formatted = formatCurrency(99.99, 'USD');
|
|
expect(formatted).toBeDefined();
|
|
});
|
|
|
|
test('应该能够格式化日期', () => {
|
|
const { formatDate } = require('../src/i18n.js');
|
|
|
|
const formatted = formatDate(new Date());
|
|
expect(formatted).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('插件管理器', () => {
|
|
test('应该能够创建插件管理器', () => {
|
|
const { PluginManager } = require('../src/plugins.js');
|
|
|
|
const manager = new PluginManager();
|
|
expect(manager).toBeDefined();
|
|
expect(manager.register).toBeInstanceOf(Function);
|
|
expect(manager.unregister).toBeInstanceOf(Function);
|
|
});
|
|
|
|
test('应该能够注册插件', () => {
|
|
const { PluginManager } = require('../src/plugins.js');
|
|
|
|
const manager = new PluginManager();
|
|
|
|
manager.register('test', {
|
|
name: 'test',
|
|
version: '1.0.0',
|
|
});
|
|
|
|
expect(manager.has('test')).toBe(true);
|
|
});
|
|
|
|
test('应该能够获取插件', () => {
|
|
const { PluginManager } = require('../src/plugins.js');
|
|
|
|
const manager = new PluginManager();
|
|
|
|
manager.register('test', {
|
|
name: 'test',
|
|
version: '1.0.0',
|
|
});
|
|
|
|
const plugin = manager.get('test');
|
|
expect(plugin).toBeDefined();
|
|
expect(plugin.name).toBe('test');
|
|
});
|
|
|
|
test('应该能够注销插件', () => {
|
|
const { PluginManager } = require('../src/plugins.js');
|
|
|
|
const manager = new PluginManager();
|
|
|
|
manager.register('test', {
|
|
name: 'test',
|
|
version: '1.0.0',
|
|
uninstall: jest.fn(),
|
|
});
|
|
|
|
manager.unregister('test');
|
|
expect(manager.has('test')).toBe(false);
|
|
});
|
|
|
|
test('应该能够启用/禁用插件', () => {
|
|
const { PluginManager } = require('../src/plugins.js');
|
|
|
|
const manager = new PluginManager();
|
|
|
|
manager.register('test', {
|
|
name: 'test',
|
|
version: '1.0.0',
|
|
});
|
|
|
|
manager.disable('test');
|
|
expect(manager.isEnabled('test')).toBe(false);
|
|
|
|
manager.enable('test');
|
|
expect(manager.isEnabled('test')).toBe(true);
|
|
});
|
|
|
|
test('应该能够获取所有插件', () => {
|
|
const { PluginManager } = require('../src/plugins.js');
|
|
|
|
const manager = new PluginManager();
|
|
|
|
manager.register('test1', { name: 'test1' });
|
|
manager.register('test2', { name: 'test2' });
|
|
|
|
const plugins = manager.getAll();
|
|
expect(plugins).toHaveLength(2);
|
|
});
|
|
|
|
test('应该能够获取插件名称', () => {
|
|
const { PluginManager } = require('../src/plugins.js');
|
|
|
|
const manager = new PluginManager();
|
|
|
|
manager.register('test1', { name: 'test1' });
|
|
manager.register('test2', { name: 'test2' });
|
|
|
|
const names = manager.getNames();
|
|
expect(names).toContain('test1');
|
|
expect(names).toContain('test2');
|
|
});
|
|
|
|
test('应该能够注册和注销插件', () => {
|
|
const { PluginManager } = require('../src/plugins.js');
|
|
const manager = new PluginManager();
|
|
manager.register('p1', { name: 'p1' });
|
|
manager.register('p2', { name: 'p2' });
|
|
expect(manager.getAll()).toHaveLength(2);
|
|
manager.unregister('p1');
|
|
expect(manager.getAll()).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
// ========== 新增测试:高级功能 & 钩子系统 & 边界情况 ==========
|
|
|
|
describe('高级功能', () => {
|
|
test('confirm 返回 Promise<boolean>', async () => {
|
|
// confirm 创建 toast 并等待按钮点击,这里只验证 Promise 类型
|
|
const result = MeToast.confirm('确认?');
|
|
expect(result).toBeInstanceOf(Promise);
|
|
// 立即清理:点击取消按钮(如果有)
|
|
setTimeout(() => {
|
|
const btn = document.querySelector('.met-cancel-btn');
|
|
if (btn) btn.click();
|
|
}, 10);
|
|
const ok = await result;
|
|
expect(typeof ok).toBe('boolean');
|
|
});
|
|
|
|
test('confirm 非字符串参数返回 false', async () => {
|
|
const result = await MeToast.confirm(123);
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
test('prompt 返回 Promise<string|null>', async () => {
|
|
const result = MeToast.prompt('输入');
|
|
expect(result).toBeInstanceOf(Promise);
|
|
setTimeout(() => {
|
|
const btn = document.querySelector('.met-cancel-btn');
|
|
if (btn) btn.click();
|
|
}, 10);
|
|
const val = await result;
|
|
expect(val === null || typeof val === 'string').toBe(true);
|
|
});
|
|
|
|
test('prompt 非字符串参数返回 null', async () => {
|
|
const result = await MeToast.prompt(123);
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
test('progress 返回控制对象', () => {
|
|
const p = MeToast.progress('上传中...');
|
|
expect(p).toBeDefined();
|
|
expect(p.id).toBeDefined();
|
|
expect(p.setProgress).toBeInstanceOf(Function);
|
|
expect(p.complete).toBeInstanceOf(Function);
|
|
expect(p.error).toBeInstanceOf(Function);
|
|
expect(p.dismiss).toBeInstanceOf(Function);
|
|
});
|
|
|
|
test('countdown 返回控制对象', () => {
|
|
const c = MeToast.countdown('{seconds} 秒后执行', 3);
|
|
expect(c).toBeDefined();
|
|
expect(c.id).toBeDefined();
|
|
expect(c.cancel).toBeInstanceOf(Function);
|
|
expect(c.pause).toBeInstanceOf(Function);
|
|
expect(c.resume).toBeInstanceOf(Function);
|
|
// 清理
|
|
c.cancel();
|
|
});
|
|
|
|
test('countdown 非字符串参数返回空控制对象', () => {
|
|
const c = MeToast.countdown(123, 5);
|
|
expect(c.cancel).toBeInstanceOf(Function);
|
|
expect(c.pause).toBeInstanceOf(Function);
|
|
});
|
|
|
|
test('queue 返回 Promise', () => {
|
|
const result = MeToast.queue(['a', 'b', 'c'], { delay: 10, duration: 10 });
|
|
expect(result).toBeInstanceOf(Promise);
|
|
});
|
|
|
|
test('queue 非数组返回 resolved Promise', async () => {
|
|
const result = await MeToast.queue('not-an-array');
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
test('stack 不抛出异常', () => {
|
|
expect(() => MeToast.stack(['a', 'b', 'c'], { stagger: 10 })).not.toThrow();
|
|
});
|
|
|
|
test('stack 非数组不影响', () => {
|
|
expect(() => MeToast.stack('not-an-array')).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('Toast钩子系统', () => {
|
|
beforeEach(() => {
|
|
MeToast._toasts.clear();
|
|
});
|
|
|
|
test('Toast.on 注册钩子并返回取消函数', () => {
|
|
const handler = jest.fn();
|
|
const unsubscribe = Toast.on('afterShow', handler);
|
|
expect(typeof unsubscribe).toBe('function');
|
|
Toast.off('afterShow', handler);
|
|
});
|
|
|
|
test('Toast.trigger 触发已注册钩子', () => {
|
|
const handler = jest.fn();
|
|
Toast.on('afterShow', handler);
|
|
const toast = MeToast.success('test');
|
|
Toast.trigger('afterShow', toast);
|
|
expect(handler).toHaveBeenCalledWith(toast);
|
|
Toast.off('afterShow', handler);
|
|
});
|
|
|
|
test('Toast.off 移除钩子后不再触发', () => {
|
|
const handler = jest.fn();
|
|
Toast.on('beforeShow', handler);
|
|
Toast.off('beforeShow', handler);
|
|
const toast = MeToast.success('test');
|
|
Toast.trigger('beforeShow', toast);
|
|
expect(handler).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('钩子错误不中断其他钩子', () => {
|
|
const goodHandler = jest.fn();
|
|
const badHandler = jest.fn(() => { throw new Error('hook error'); });
|
|
Toast.on('afterClose', badHandler);
|
|
Toast.on('afterClose', goodHandler);
|
|
const toast = MeToast.success('test');
|
|
Toast.trigger('afterClose', toast);
|
|
expect(badHandler).toHaveBeenCalled();
|
|
expect(goodHandler).toHaveBeenCalled();
|
|
Toast.off('afterClose', badHandler);
|
|
Toast.off('afterClose', goodHandler);
|
|
});
|
|
|
|
test('完整生命周期钩子触发顺序', () => {
|
|
const calls = [];
|
|
const record = (name) => jest.fn(() => calls.push(name));
|
|
|
|
const beforeShow = record('beforeShow');
|
|
const afterShow = record('afterShow');
|
|
|
|
Toast.on('beforeShow', beforeShow);
|
|
Toast.on('afterShow', afterShow);
|
|
|
|
const toast = MeToast.success('test');
|
|
Toast.trigger('beforeShow', toast);
|
|
Toast.trigger('afterShow', toast);
|
|
|
|
expect(calls).toEqual(['beforeShow', 'afterShow']);
|
|
|
|
Toast.off('beforeShow', beforeShow);
|
|
Toast.off('afterShow', afterShow);
|
|
});
|
|
});
|
|
|
|
describe('边界情况', () => {
|
|
test('空消息 toast 不抛异常', () => {
|
|
expect(() => MeToast.success('')).not.toThrow();
|
|
expect(() => MeToast.show()).not.toThrow();
|
|
});
|
|
|
|
test('configure 空参数不抛异常', () => {
|
|
expect(() => MeToast.configure()).not.toThrow();
|
|
expect(() => MeToast.configure(null)).not.toThrow();
|
|
});
|
|
|
|
test('max 限制 toast 数量', () => {
|
|
MeToast.configure({ max: 3 });
|
|
for (let i = 0; i < 10; i++) {
|
|
MeToast.info('msg ' + i);
|
|
}
|
|
// max=3 意味着最多3条
|
|
expect(MeToast.count()).toBeLessThanOrEqual(3);
|
|
MeToast.resetConfig();
|
|
});
|
|
|
|
test('dismiss 指定 id 关闭单个 toast', () => {
|
|
const t1 = MeToast.success('msg1');
|
|
const t2 = MeToast.error('msg2');
|
|
expect(MeToast.count()).toBe(2);
|
|
MeToast.dismiss(t1.id);
|
|
// 关闭是异步动画,直接检查关闭状态
|
|
expect(t1.closing).toBe(true);
|
|
});
|
|
|
|
test('find 不存在的 id 返回 undefined', () => {
|
|
expect(MeToast.find('nonexistent-id')).toBeUndefined();
|
|
});
|
|
|
|
test('findByType 按类型过滤', () => {
|
|
MeToast.success('s1');
|
|
MeToast.success('s2');
|
|
MeToast.error('e1');
|
|
const successToasts = MeToast.findByType('success');
|
|
expect(successToasts.length).toBe(2);
|
|
const errorToasts = MeToast.findByType('error');
|
|
expect(errorToasts.length).toBe(1);
|
|
});
|
|
|
|
test('promise 非 Promise 参数返回 rejected Promise', async () => {
|
|
try {
|
|
await MeToast.promise('not-a-promise', {});
|
|
} catch (e) {
|
|
expect(e).toBeDefined();
|
|
}
|
|
});
|
|
|
|
test('getToast 不存在的 id 返回 null', () => {
|
|
expect(MeToast.getToast('nonexistent')).toBeNull();
|
|
expect(MeToast.getToast(null)).toBeNull();
|
|
expect(MeToast.getToast()).toBeNull();
|
|
});
|
|
});
|
|
|
|
// ========== 新增功能测试 ==========
|
|
|
|
describe('新增功能', () => {
|
|
beforeEach(() => {
|
|
MeToast._toasts.clear();
|
|
});
|
|
|
|
test('resetTimerOnUpdate — update后重置计时器', () => {
|
|
const t = MeToast.info('test', { duration: 5000, resetTimerOnUpdate: true });
|
|
const oldRemaining = t.remaining;
|
|
t.update({ message: 'updated' });
|
|
expect(t.remaining).toBe(5000);
|
|
expect(t.paused).toBe(false);
|
|
});
|
|
|
|
test('onUpdate 回调触发', () => {
|
|
const fn = jest.fn();
|
|
const t = MeToast.success('test', { onUpdate: fn });
|
|
t.update({ message: 'changed' });
|
|
expect(fn).toHaveBeenCalledWith(t);
|
|
});
|
|
|
|
test('action 返回控制对象', () => {
|
|
const a = MeToast.action('test', [{ text: 'OK', onClick: () => {} }]);
|
|
expect(a).toBeDefined();
|
|
expect(a.id).toBeDefined();
|
|
expect(a.toast).toBeDefined();
|
|
expect(a.dismiss).toBeInstanceOf(Function);
|
|
});
|
|
|
|
test('queue 返回可取消对象', () => {
|
|
const q = MeToast.queue(['a', 'b'], { delay: 10, duration: 10 });
|
|
expect(q.then).toBeInstanceOf(Function);
|
|
expect(q.catch).toBeInstanceOf(Function);
|
|
expect(q.cancel).toBeInstanceOf(Function);
|
|
q.cancel();
|
|
});
|
|
|
|
test('group 创建分组并正常工作', () => {
|
|
const g = MeToast.group('test-group');
|
|
expect(g._group).toBe('test-group');
|
|
expect(g.dismiss).toBeInstanceOf(Function);
|
|
expect(g.count).toBeInstanceOf(Function);
|
|
const t = g.info('grouped message');
|
|
expect(t.group).toBe('test-group');
|
|
});
|
|
|
|
test('dismissGroup 按组关闭', () => {
|
|
const g = MeToast.group('batch');
|
|
g.success('m1');
|
|
g.error('m2');
|
|
expect(MeToast._groupCount('batch')).toBe(2);
|
|
MeToast.dismissGroup('batch');
|
|
});
|
|
|
|
test('自定义渲染函数', () => {
|
|
const t = MeToast.show({
|
|
render: (toast) => `<b>${toast.message}</b>`,
|
|
message: 'rendered',
|
|
});
|
|
expect(t.config.render).toBeInstanceOf(Function);
|
|
});
|
|
});
|