Files
MetonaToast/tests/react.test.ts
T
tianhao 5f8b9d2f75
CI / test (18.x) (push) Successful in 10m8s
CI / test (20.x) (push) Successful in 10m10s
CI / test (22.x) (push) Successful in 10m4s
CI / test (24.x) (push) Successful in 10m2s
chore: 版本回退至 0.5.0 并重建产物(含 CJS 兼容修复)
2026-08-08 15:40:04 +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();
});
});