update
This commit is contained in:
145
tests/unit/event-emitter.test.js
Normal file
145
tests/unit/event-emitter.test.js
Normal 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 / 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user