Files
Notes/tests/unit/event-emitter.test.js
2026-09-12 14:15:26 +08:00

145 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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);
});
});