Files
MetonaToast/tests/react.test.ts
T
tianhao 1f718c4ef8 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)
2026-08-08 15:02:51 +08:00

150 lines
5.4 KiB
TypeScript

/**
* MetonaToast React 适配器测试
* @module tests
* @version 0.5.0
*/
import React from 'react';
import { renderToString } from 'react-dom/server';
import { createRoot } from 'react-dom/client';
import { act } from 'react';
import MeToast, { Toast } from '../src/index';
import { createBoundApi, Toast as ToastComponent, useToast } from '../src/react';
import type { ToastInstance } from '../src/types';
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
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();
MeToast.animations.reset();
});
describe('React 适配器 — createBoundApi', () => {
test('跟踪创建的 toast id', () => {
const cleanup = new Set<string>();
const api = createBoundApi(cleanup);
(api.info as (m: string) => ToastInstance)('hello');
expect(cleanup.size).toBe(1);
});
test('success/error/warning/show 均被跟踪', () => {
const cleanup = new Set<string>();
const api = createBoundApi(cleanup);
(api.success as (m: string) => ToastInstance)('a');
(api.error as (m: string) => ToastInstance)('b');
(api.warning as (m: string) => ToastInstance)('c');
(api.show as (m: string) => ToastInstance)('d');
expect(cleanup.size).toBe(4);
});
test('loading 控制对象也被跟踪', () => {
const cleanup = new Set<string>();
const api = createBoundApi(cleanup);
(api.loading as (m: string) => { id: string })('loading...');
expect(cleanup.size).toBe(1);
});
test('dismiss 同步清理集合', () => {
const cleanup = new Set<string>();
const api = createBoundApi(cleanup);
const t = (api.success as (m: string) => ToastInstance)('hi');
expect(cleanup.has(t.id)).toBe(true);
(api.dismiss as (id?: string) => void)(t.id);
expect(cleanup.has(t.id)).toBe(false);
});
test('removeToast 同步清理集合', () => {
const cleanup = new Set<string>();
const api = createBoundApi(cleanup);
const t = (api.info as (m: string) => ToastInstance)('hi');
(api.removeToast as (id: string) => void)(t.id);
expect(cleanup.size).toBe(0);
});
test('未跟踪的方法直接透传', () => {
const cleanup = new Set<string>();
const api = createBoundApi(cleanup);
expect(typeof (api as Record<string, unknown>).count).toBe('function');
expect((api as { count(): number }).count()).toBe(0);
});
});
describe('React 适配器 — Toast 组件', () => {
test('SSR 渲染为空(effect 不执行)', () => {
const html = renderToString(React.createElement(ToastComponent, { message: 'ssr message' }));
expect(html).toBe('');
});
test('SSR 带 type 不抛异常', () => {
const html = renderToString(React.createElement(ToastComponent, { message: 'x', type: 'success' }));
expect(html).toBe('');
});
test('SSR 未知 type fallback 到 show 不抛异常', () => {
const html = renderToString(React.createElement(ToastComponent, { message: 'y', type: 'custom-type' }));
expect(html).toBe('');
});
test('组件挂载创建 toast,卸载自动移除', () => {
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
act(() => { root.render(React.createElement(ToastComponent, { message: 'mounted', type: 'success' })); });
expect(MeToast.count()).toBe(1);
act(() => { root.unmount(); });
expect(MeToast.count()).toBe(0);
host.remove();
});
test('组件 autoClose:false 卸载不移除', () => {
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
act(() => { root.render(React.createElement(ToastComponent, { message: 'keep', type: 'error', autoClose: false })); });
expect(MeToast.count()).toBe(1);
act(() => { root.unmount(); });
expect(MeToast.count()).toBe(1);
MeToast.dismiss();
host.remove();
});
test('组件 message 变化重新创建实例', () => {
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
act(() => { root.render(React.createElement(ToastComponent, { message: 'v1', type: 'info' })); });
act(() => { root.render(React.createElement(ToastComponent, { message: 'v2', type: 'warning' })); });
expect(MeToast.count()).toBe(1);
const t = MeToast.getToasts()[0];
expect(t.message).toBe('v2');
expect(t.type).toBe('warning');
act(() => { root.unmount(); });
expect(MeToast.count()).toBe(0);
host.remove();
});
test('useToast 组件内创建的 toast 在卸载时清理', () => {
const Harness = (): React.ReactElement => {
const toast = useToast() as Record<string, (m: string) => ToastInstance>;
return React.createElement('button', { onClick: () => toast.info('tracked') });
};
const host = document.createElement('div');
document.body.appendChild(host);
const root = createRoot(host);
act(() => { root.render(React.createElement(Harness)); });
const btn = host.querySelector('button')!;
act(() => { btn.dispatchEvent(new MouseEvent('click', { bubbles: true })); });
expect(MeToast.count()).toBe(1);
act(() => { root.unmount(); });
expect(MeToast.count()).toBe(0);
host.remove();
});
});