145 lines
4.6 KiB
JavaScript
145 lines
4.6 KiB
JavaScript
// 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 / event:no-op
|
||
|
||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||
import { EventEmitter } from '../../src/event-emitter.js';
|
||
|
||
describe('EventEmitter 基础语义', () => {
|
||
it('on + emit:handler 被调用,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 不存在的 handler:no-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 多次只占一个 slot,off 一次就够。
|
||
// 这里验证 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);
|
||
});
|
||
}); |