This commit is contained in:
2026-09-12 14:15:26 +08:00
commit 9c06d3f4be
99 changed files with 41853 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
// src/event-emitter.js
//
// 极简 EventEmitter被 settings-store 等组件复用 —— 没有测试守护的话:
// - emit 不隔离异常 → 一个抛错的 handler 阻断后续 handler 与其它事件订阅者
// - on 返回的 unsubscribe 漏掉 → 测试 / HMR 场景下旧 listener 触发回调
// - off 不存在的 handler 抛错 → emit 路径异常
//
// 覆盖:
// - on / emit 基本语义payload 透传、多 handler
// - on 返回的 unsubscribe 函数(同一 handler 多次 on → 多次 unsub 才彻底移除)
// - emit 异常隔离:一个抛错不影响其它 handler也不影响后续 emit
// - emit 无订阅者no-op
// - off 不存在的 handler / eventno-op
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { EventEmitter } from '../../src/event-emitter.js';
describe('EventEmitter 基础语义', () => {
it('on + emithandler 被调用payload 透传', () => {
const ee = new EventEmitter();
const handler = vi.fn();
ee.on('ping', handler);
ee.emit('ping', { a: 1 });
expect(handler).toHaveBeenCalledTimes(1);
expect(handler).toHaveBeenCalledWith({ a: 1 });
});
it('多个 handler 订阅同一事件 → emit 全部调用', () => {
const ee = new EventEmitter();
const h1 = vi.fn();
const h2 = vi.fn();
ee.on('x', h1);
ee.on('x', h2);
ee.emit('x', 'payload');
expect(h1).toHaveBeenCalledWith('payload');
expect(h2).toHaveBeenCalledWith('payload');
});
it('不同事件互不影响', () => {
const ee = new EventEmitter();
const h1 = vi.fn();
const h2 = vi.fn();
ee.on('a', h1);
ee.on('b', h2);
ee.emit('a', 1);
expect(h1).toHaveBeenCalledWith(1);
expect(h2).not.toHaveBeenCalled();
});
it('emit 无订阅者的事件no-op不抛错', () => {
const ee = new EventEmitter();
expect(() => ee.emit('nothing', 'x')).not.toThrow();
});
});
describe('EventEmitter unsubscribe', () => {
it('on 返回的函数等价于 off可作为 unsubscribe', () => {
const ee = new EventEmitter();
const handler = vi.fn();
const unsub = ee.on('e', handler);
expect(typeof unsub).toBe('function');
unsub();
ee.emit('e', 'x');
expect(handler).not.toHaveBeenCalled();
});
it('off 显式移除 handler', () => {
const ee = new EventEmitter();
const handler = vi.fn();
ee.on('e', handler);
ee.off('e', handler);
ee.emit('e', 'x');
expect(handler).not.toHaveBeenCalled();
});
it('off 不存在的 handlerno-op不抛错', () => {
const ee = new EventEmitter();
const handler = vi.fn();
ee.on('e', handler);
expect(() => ee.off('e', () => {})).not.toThrow();
// 原 handler 仍有效
ee.emit('e', 'x');
expect(handler).toHaveBeenCalledWith('x');
});
it('off 不存在的事件no-op不抛错', () => {
const ee = new EventEmitter();
expect(() => ee.off('no-such-event', () => {})).not.toThrow();
});
it('同一 handler on 多次 → unsubscribe 多次才彻底移除', () => {
// Set 语义:同一个 handler 加入 Set 多次只占一个 slotoff 一次就够。
// 这里验证 unsub/off 不会因为「多订阅一次」就漏移除。
const ee = new EventEmitter();
const handler = vi.fn();
ee.on('e', handler);
ee.on('e', handler); // 重复订阅Set 语义:仍是 1 个 entry
ee.off('e', handler);
ee.emit('e', 'x');
expect(handler).not.toHaveBeenCalled();
});
});
describe('EventEmitter 异常隔离', () => {
let errorSpy;
beforeEach(() => {
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
errorSpy.mockRestore();
});
it('一个 handler 抛错 → 其它 handler 仍执行(隔离)', () => {
const ee = new EventEmitter();
const after = vi.fn();
ee.on('e', () => {
throw new Error('boom');
});
ee.on('e', after);
expect(() => ee.emit('e', 'x')).not.toThrow();
expect(after).toHaveBeenCalledWith('x');
// 抛错被 console.error 记录
expect(errorSpy).toHaveBeenCalled();
// 记录的应该包含事件名 + error
const call = errorSpy.mock.calls[0];
expect(call[0]).toContain('EventEmitter');
expect(call[0]).toContain('e');
});
it('抛错的 handler 不影响后续 emit', () => {
const ee = new EventEmitter();
const good = vi.fn();
ee.on('e', () => {
throw new Error('boom');
});
ee.on('e', good);
ee.emit('e', 1);
// 第二次 emit抛错 handler 仍是同一闭包引用,照样抛、照样隔离
good.mockClear();
ee.emit('e', 2);
expect(good).toHaveBeenCalledWith(2);
});
});