release: v0.3.0 — 自定义动画真实生效、max超限修复、钩子系统完整化、destroy全量清理
- 自定义动画:animations.register() 注册的动画动态注入 keyframes 真实生效(此前仅存 Map 实际 fallback slide) - max 超限:通过实例注册表真正 close() 最早的 toast(此前仅删内存记录,DOM 残留) - 钩子收敛:beforeClose/beforeUpdate 支持返回 false 拦截;接入 click/hover/dragStart/dragEnd/animationStart/animationEnd/progressStart/progressEnd/configChange/themeChange/localeChange/beforeInit/afterInit/beforeDestroy/afterDestroy;HOOK_NAMES 移除永不触发的声明 - destroy() 全量清理:hooks、实例注册表、全部 metona-toast-* 样式标签;动画注册表真正清空 - applyThemeVariables 重复注入修复;init() 支持插件对象并复活 _destroyed;AnimationConfig 类型放宽 - VERSION 统一到 constants.ts 唯一维护;删除失效 scripts;新增 .gitattributes 统一 LF - docs.html 补 v0.2.1 新 API 文档;CHANGELOG 更新;测试 309 用例全过
This commit is contained in:
+196
-2
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaToast 覆盖率补充测试 — 目标 95%+
|
||||
* @module tests
|
||||
* @version 0.2.1
|
||||
* @version 0.3.0
|
||||
*/
|
||||
|
||||
import MeToast, { Toast, VERSION } from '../src/index';
|
||||
@@ -139,6 +139,70 @@ describe('animations.ts 覆盖率', () => {
|
||||
test('getActiveCount 返回动画数量', () => {
|
||||
expect(MeToast.animations.getActiveCount()).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('ensureAnimationCSS 内置动画不注入', () => {
|
||||
const { ensureAnimationCSS } = require('../src/animations.js');
|
||||
// jsdom 真实 DOM:内置动画由 styles.ts 统一注入,不应产生独立 style 标签
|
||||
ensureAnimationCSS('slide');
|
||||
expect(document.getElementById('metona-toast-anim-styles')).toBeNull();
|
||||
});
|
||||
|
||||
test('ensureAnimationCSS 自定义动画注入样式(幂等)', () => {
|
||||
const { ensureAnimationCSS } = require('../src/animations.js');
|
||||
MeToast.animations.destroy(); // 清理模块注入状态,保证测试独立
|
||||
MeToast.animations.register('my-anim', {
|
||||
enter: { opacity: 0, transform: 'scale(0.5)' },
|
||||
leave: { opacity: 1 },
|
||||
duration: 400,
|
||||
easing: 'ease',
|
||||
});
|
||||
ensureAnimationCSS('my-anim');
|
||||
const styleEl = document.getElementById('metona-toast-anim-styles');
|
||||
expect(styleEl).not.toBeNull();
|
||||
expect(styleEl!.textContent).toContain('@keyframes met-my-anim-in');
|
||||
expect(styleEl!.textContent).toContain('.met-anim-my-anim.met-toast.met-show');
|
||||
// 幂等:二次调用不重复追加
|
||||
const before = styleEl!.textContent!.length;
|
||||
ensureAnimationCSS('my-anim');
|
||||
expect(styleEl!.textContent!.length).toBe(before);
|
||||
MeToast.animations.unregister('my-anim');
|
||||
MeToast.animations.reset();
|
||||
});
|
||||
|
||||
test('ensureAnimationCSS 未注册动画不注入', () => {
|
||||
const { ensureAnimationCSS } = require('../src/animations.js');
|
||||
// 清理 jsdom 中可能残留的动态 style 元素
|
||||
document.getElementById('metona-toast-anim-styles')?.remove();
|
||||
ensureAnimationCSS('not-registered');
|
||||
expect(document.getElementById('metona-toast-anim-styles')).toBeNull();
|
||||
});
|
||||
|
||||
test('hasAnimation 检查内置与自定义动画', () => {
|
||||
const { hasAnimation } = require('../src/animations.js');
|
||||
expect(hasAnimation('slide')).toBe(true);
|
||||
expect(hasAnimation('nonexistent')).toBe(false);
|
||||
MeToast.animations.register('has-anim-test', { enter: {}, leave: {}, duration: 300 });
|
||||
expect(hasAnimation('has-anim-test')).toBe(true);
|
||||
MeToast.animations.unregister('has-anim-test');
|
||||
});
|
||||
|
||||
test('自定义动画注册后 _buildClassName 使用该动画', () => {
|
||||
MeToast.animations.register('custom-anim', {
|
||||
enter: { opacity: 0 },
|
||||
leave: { opacity: 1 },
|
||||
duration: 400,
|
||||
easing: 'ease',
|
||||
});
|
||||
const t = MeToast.info({ message: 'custom', animation: 'custom-anim' });
|
||||
expect(t.el?.className).toContain('met-anim-custom-anim');
|
||||
MeToast.animations.unregister('custom-anim');
|
||||
});
|
||||
|
||||
test('未注册动画名 fallback 到 slide', () => {
|
||||
const t = new Toast({ message: 'test', animation: 'unknown-anim' });
|
||||
t.create();
|
||||
expect(t.el?.className).toContain('met-anim-slide');
|
||||
});
|
||||
});
|
||||
|
||||
// ==================================================================
|
||||
@@ -745,7 +809,7 @@ describe('api.ts 覆盖率', () => {
|
||||
|
||||
test('getStatus 返回完整状态', () => {
|
||||
const status = MeToast.getStatus();
|
||||
expect(status.version).toBe('0.2.1');
|
||||
expect(status.version).toBe('0.3.0');
|
||||
expect(status.toasts).toBeGreaterThanOrEqual(0);
|
||||
expect(status.theme).toBeDefined();
|
||||
expect(status.locale).toBeDefined();
|
||||
@@ -1299,3 +1363,133 @@ describe('最终补漏', () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// ==================================================================
|
||||
// 11. v0.3.0 修复验证
|
||||
// ==================================================================
|
||||
describe('v0.3.0 修复验证', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
MeToast._toasts.clear();
|
||||
MeToast.resetConfig();
|
||||
(MeToast as unknown as { _destroyed?: boolean })._destroyed = false;
|
||||
Toast._hooks.clear();
|
||||
Toast._registry.clear();
|
||||
const { _containerCache } = require('../src/toast.js');
|
||||
_containerCache.clear();
|
||||
// 清理动画模块状态(injected 集合 + 动态 style 引用),再恢复默认动画
|
||||
MeToast.animations.destroy();
|
||||
MeToast.animations.reset();
|
||||
});
|
||||
|
||||
test('_limitToasts 超出 max 时真正关闭最早的 toast', () => {
|
||||
const t1 = MeToast.info('m1');
|
||||
const t2 = MeToast.info('m2');
|
||||
const el = createMockElement() as unknown as HTMLElement;
|
||||
(el as unknown as { querySelectorAll: unknown }).querySelectorAll = jest.fn(() => [
|
||||
{ dataset: { id: t1.id } },
|
||||
{ dataset: { id: t2.id } },
|
||||
]);
|
||||
const newToast = new Toast({ message: 'm3', max: 2 });
|
||||
newToast._limitToasts(el as unknown as HTMLElement);
|
||||
expect(t1.closing).toBe(true);
|
||||
expect(t2.closing).toBe(false);
|
||||
});
|
||||
|
||||
test('beforeClose 钩子返回 false 阻止关闭', () => {
|
||||
const t = MeToast.info('test');
|
||||
const blocker = () => false;
|
||||
Toast.on('beforeClose', blocker);
|
||||
t.close();
|
||||
expect(t.closing).toBe(false);
|
||||
Toast.off('beforeClose', blocker);
|
||||
});
|
||||
|
||||
test('beforeUpdate 钩子返回 false 阻止更新', () => {
|
||||
const t = MeToast.info('原始');
|
||||
const blocker = () => false;
|
||||
Toast.on('beforeUpdate', blocker);
|
||||
t.update({ message: '新消息' });
|
||||
expect(t.message).toBe('原始');
|
||||
Toast.off('beforeUpdate', blocker);
|
||||
});
|
||||
|
||||
test('configure/updateConfig/resetConfig 触发 configChange 钩子', () => {
|
||||
const fn = jest.fn();
|
||||
Toast.on('configChange', fn);
|
||||
MeToast.configure({ duration: 2500 });
|
||||
MeToast.updateConfig({ gap: 8 });
|
||||
MeToast.resetConfig();
|
||||
expect(fn).toHaveBeenCalledTimes(3);
|
||||
Toast.off('configChange', fn);
|
||||
});
|
||||
|
||||
test('init 触发 beforeInit/afterInit 钩子', () => {
|
||||
const calls: string[] = [];
|
||||
const before = jest.fn(() => calls.push('beforeInit'));
|
||||
const after = jest.fn(() => calls.push('afterInit'));
|
||||
Toast.on('beforeInit', before);
|
||||
Toast.on('afterInit', after);
|
||||
MeToast.init();
|
||||
expect(calls).toEqual(['beforeInit', 'afterInit']);
|
||||
Toast.off('beforeInit', before);
|
||||
Toast.off('afterInit', after);
|
||||
});
|
||||
|
||||
test('destroy 触发 beforeDestroy/afterDestroy 钩子', () => {
|
||||
const calls: string[] = [];
|
||||
const before = jest.fn(() => calls.push('beforeDestroy'));
|
||||
const after = jest.fn(() => calls.push('afterDestroy'));
|
||||
Toast.on('beforeDestroy', before);
|
||||
Toast.on('afterDestroy', after);
|
||||
MeToast.destroy();
|
||||
expect(calls).toEqual(['beforeDestroy', 'afterDestroy']);
|
||||
});
|
||||
|
||||
test('destroy 清理全部钩子', () => {
|
||||
const fn = jest.fn();
|
||||
Toast.on('afterShow', fn);
|
||||
MeToast.destroy();
|
||||
MeToast.success('after-destroy');
|
||||
expect(fn).not.toHaveBeenCalled();
|
||||
MeToast.animations.reset();
|
||||
});
|
||||
|
||||
test('destroy 清理实例注册表', () => {
|
||||
MeToast.info('m1');
|
||||
expect(Toast._registry.size).toBe(1);
|
||||
MeToast.destroy();
|
||||
expect(Toast._registry.size).toBe(0);
|
||||
MeToast.animations.reset();
|
||||
});
|
||||
|
||||
test('destroy 后 init 恢复可用', () => {
|
||||
MeToast.destroy();
|
||||
MeToast.init({});
|
||||
const t = MeToast.success('revived');
|
||||
expect(t).toBeDefined();
|
||||
MeToast.animations.reset();
|
||||
});
|
||||
|
||||
test('init plugins 支持插件对象', () => {
|
||||
const install = jest.fn();
|
||||
MeToast.init({ plugins: [{ name: 'obj-plugin', install }] });
|
||||
expect(MeToast.plugins.has('obj-plugin')).toBe(true);
|
||||
expect(install).toHaveBeenCalled();
|
||||
MeToast.plugins.unregister('obj-plugin');
|
||||
});
|
||||
|
||||
test('animate 生命周期钩子 progressStart/progressEnd 触发', (done) => {
|
||||
const start = jest.fn();
|
||||
const end = jest.fn();
|
||||
Toast.on('progressStart', start);
|
||||
Toast.on('progressEnd', end);
|
||||
const t = MeToast.info({ message: 'timed', duration: 30 });
|
||||
setTimeout(() => {
|
||||
expect(start).toHaveBeenCalled();
|
||||
Toast.off('progressStart', start);
|
||||
Toast.off('progressEnd', end);
|
||||
done();
|
||||
}, 120);
|
||||
});
|
||||
});
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaToast 单元测试
|
||||
* @module tests
|
||||
* @version 0.2.1
|
||||
* @version 0.3.0
|
||||
*/
|
||||
|
||||
import MeToast, { Toast, VERSION } from '../src/index';
|
||||
@@ -105,8 +105,8 @@ const mockWindow = {
|
||||
|
||||
describe('版本信息', () => {
|
||||
test('应该有正确的版本号', () => {
|
||||
expect(VERSION).toBe('0.2.1');
|
||||
expect(MeToast.version).toBe('0.2.1');
|
||||
expect(VERSION).toBe('0.3.0');
|
||||
expect(MeToast.version).toBe('0.3.0');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -526,7 +526,7 @@ const mockWindow = {
|
||||
test('应该能够获取状态信息', () => {
|
||||
MeToast.success('消息');
|
||||
const status = MeToast.getStatus();
|
||||
expect(status.version).toBe('0.2.1');
|
||||
expect(status.version).toBe('0.3.0');
|
||||
expect(status.toasts).toBeGreaterThanOrEqual(0);
|
||||
expect(status.theme).toBeDefined();
|
||||
expect(status.locale).toBeDefined();
|
||||
@@ -1213,9 +1213,9 @@ describe('新增功能', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ========== v0.2.1 新增功能测试 ==========
|
||||
// ========== v0.3.0 新增功能测试 ==========
|
||||
|
||||
describe('v0.2.1 新增功能', () => {
|
||||
describe('v0.3.0 新增功能', () => {
|
||||
beforeEach(() => {
|
||||
MeToast._toasts.clear();
|
||||
const { _containerCache } = require('../src/toast.js');
|
||||
|
||||
Reference in New Issue
Block a user