06a4f9346f
- 合成事件的 reason 改为访问器:有监听方读过就算有人接手;没人读则交回安装前的监听器(含 vitest 失败上报) - 新增 unhandledRejectionBridge.test.ts 钉住两条路径:无人接手会交回、window 读过则不再交回 - 避免桥把无关的未处理拒绝静默吞掉
43 lines
1.7 KiB
TypeScript
43 lines
1.7 KiB
TypeScript
/**
|
||
* 把 Node 的 `unhandledRejection` 桥接成 jsdom `window` 的 `unhandledrejection` 事件。
|
||
*
|
||
* 生产 WebView 里,`void` 掉的 async handler(`onSubmit` / `onClick`)抛出的拒绝会派发
|
||
* `unhandledrejection`,被 `AuthenticatedClient` 的全局 handler 交给错误池。jsdom 既不派发
|
||
* 该事件,vitest 又会把未处理的拒绝当成用例失败;需要观察这条链路的用例用它接管 Node
|
||
* 监听器,再合成一个带 `reason` 的 window 事件。
|
||
*
|
||
* 只抑制**被 window 侧消费**的拒绝:合成事件的 `reason` 是访问器,任何监听方读过它就算
|
||
* 有人接手;没人读说明这条拒绝是意外的,交回安装前的监听器(含 vitest 的失败上报),
|
||
* 不会被静默吞掉。
|
||
*
|
||
* 用法:用例内 `const uninstall = installUnhandledRejectionBridge()`,在 `finally` 或
|
||
* `afterEach` 里 `uninstall()`。
|
||
*/
|
||
export function installUnhandledRejectionBridge() {
|
||
const originalListeners = process.listeners('unhandledRejection');
|
||
process.removeAllListeners('unhandledRejection');
|
||
const handleRejection = (reason: unknown, promise: Promise<unknown>) => {
|
||
let consumed = false;
|
||
const event = new Event('unhandledrejection');
|
||
Object.defineProperty(event, 'reason', {
|
||
get() {
|
||
consumed = true;
|
||
return reason;
|
||
},
|
||
});
|
||
window.dispatchEvent(event);
|
||
if (!consumed) {
|
||
for (const listener of originalListeners) {
|
||
listener(reason, promise);
|
||
}
|
||
}
|
||
};
|
||
process.on('unhandledRejection', handleRejection);
|
||
return () => {
|
||
process.removeListener('unhandledRejection', handleRejection);
|
||
for (const listener of originalListeners) {
|
||
process.on('unhandledRejection', listener);
|
||
}
|
||
};
|
||
}
|