release: v0.5.0 — 覆盖率96.42%达标、lint零警告、覆盖率门禁、CI强化、发布自动化

- 覆盖率 96.42%(行):新增 59 测试共 385 个,覆盖 DOM 交互事件、对话框按钮交互、Intl/localStorage 异常路径、React 渲染生命周期
- 测试环境重构:SSR 分支用 defineProperty 真正覆盖 document/window;新增 tests/dom.test.ts、tests/setup.ts
- jest 覆盖率门禁 lines >= 95%;CI lint 改必过;新增 publish.yml(v* tag 自动发布到 Gitea registry)
- 修复:Toast.off 空数组残留、Toast.on 取消函数 this 绑定、action close:false 冒泡关闭、use() 重复注册钩子、i18n catch 引用错误
- 删除死代码 autoApplySystemTheme;lint 零警告(no-console 策略 + caughtErrorsIgnorePattern)
This commit is contained in:
tianhao
2026-08-08 15:02:51 +08:00
parent bbff631f4f
commit 1f718c4ef8
41 changed files with 1146 additions and 281 deletions
+22 -13
View File
@@ -1,12 +1,11 @@
/**
* MetonaToast API — meToast 核心 API 对象
* @module api
* @version 0.4.0
* @version 0.5.0
*/
import { Toast, _containerCache } from './toast.js';
import { DEFAULTS, VERSION } from './constants.js';
import { escapeHTML } from './utils.js';
import { t, setCurrentLocale } from './i18n.js';
import { applyTheme } from './themes.js';
import { confirmHTML, promptHTML, progressHTML, actionHTML } from './templates.js';
@@ -395,8 +394,10 @@ const meToast: MeToast = {
actions.forEach((a, i) => {
const btn = toast.el?.querySelector(`.met-action-btn-${i}`) as HTMLElement | null;
if (btn && typeof a.onClick === 'function') {
btn.addEventListener('click', () => {
try { a.onClick(toast); } catch (e) { console.error('Action onClick error:', e); }
btn.addEventListener('click', (e: Event) => {
// 阻止冒泡到 toast 的 closeOnClick 处理器,由 close 选项控制是否关闭
e.stopPropagation();
try { a.onClick(toast); } catch (err) { console.error('Action onClick error:', err); }
if (a.close !== false) toast.close();
});
}
@@ -658,22 +659,30 @@ const meToast: MeToast = {
}
this.plugins.register(plugin, { ...preset, ...options });
// 连接插件钩子
// 连接插件钩子(重复 use 前先卸载旧钩子,防止重复注册)
// 注意:off 引用存在已注册的插件对象上(stored),uninstall 时 this 即该对象
const stored = this.plugins.get(plugin) as (Record<string, unknown> & Plugin) | null;
if (plugin === 'accessibility') {
Toast.on('afterShow', (toast: ToastInstance) => {
const acc = preset as Record<string, (t: ToastInstance) => void>;
if (typeof acc.announce === 'function') acc.announce(toast);
});
if (stored && typeof stored._off === 'function') { (stored._off as () => void)(); }
const acc = preset as Record<string, (t: ToastInstance) => void>;
if (stored) {
stored._off = Toast.on('afterShow', (toast: ToastInstance) => {
if (typeof acc.announce === 'function') acc.announce(toast);
});
}
}
if (plugin === 'persistence') {
if (stored && typeof stored._saveOff === 'function') { (stored._saveOff as () => void)(); }
const saved = typeof preset.install === 'function'
? preset.install(this.plugins as unknown as import('./types.js').PluginManager)
: null;
if (saved) this.configure(saved as Partial<ToastConfig>);
Toast.on('afterClose', () => {
const p = preset as Record<string, (c: unknown) => void>;
if (typeof p.save === 'function') p.save(this.getConfig());
});
const p = preset as Record<string, (c: unknown) => void>;
if (stored) {
stored._saveOff = Toast.on('afterClose', () => {
if (typeof p.save === 'function') p.save(this.getConfig());
});
}
}
} else if (plugin && typeof plugin === 'object') {
this.plugins.register(plugin.name || 'custom', { ...plugin, ...options } as Plugin);