认证页面用例抽出unhandledrejection捕获helper

- 两处重复的桥接安装、reason 收集与拆卸收成 captureWindowRejections()
- 顺带修正系统变体用例里过期的载体 message 注释(现在 message 是载荷序列化)
This commit is contained in:
2026-10-02 11:33:53 +08:00
parent 7278023cb7
commit da4645f55e
@@ -95,6 +95,26 @@ async function loginWithCode() {
fireEvent.click(screen.getByRole('button', { name: '登录' }));
}
/**
* 捕获 window 侧 `unhandledrejection`:返回已收到的 `reason` 列表与释放函数,
* 释放时同时摘掉监听和 Node 侧桥接。
*/
function captureWindowRejections() {
const uninstallBridge = installUnhandledRejectionBridge();
const reasons: unknown[] = [];
const onRejection = (event: Event) => {
reasons.push((event as Event & { reason?: unknown }).reason);
};
window.addEventListener('unhandledrejection', onRejection);
return {
reasons,
uninstall() {
window.removeEventListener('unhandledrejection', onRejection);
uninstallBridge();
},
};
}
export function registerAuthTests() {
afterEach(() => {
resetPlatformSessionForTests();
@@ -102,12 +122,7 @@ export function registerAuthTests() {
});
it('throws a startup timeout through to unhandledrejection and leaves the loading state', async () => {
const uninstall = installUnhandledRejectionBridge();
const reasons: unknown[] = [];
const onRejection = (event: Event) => {
reasons.push((event as Event & { reason?: unknown }).reason);
};
window.addEventListener('unhandledrejection', onRejection);
const rejections = captureWindowRejections();
try {
renderAuthSurface(async (command: string) => {
if (command === 'read_client_auth_state') {
@@ -119,7 +134,7 @@ export function registerAuthTests() {
await waitFor(
() =>
expect(
reasons.some(
rejections.reasons.some(
(reason) =>
reason instanceof Error &&
reason.message.includes('检查登录状态超时'),
@@ -138,8 +153,7 @@ export function registerAuthTests() {
).toHaveProperty('disabled', false);
await screen.findByRole('main', { name: '登录' });
} finally {
window.removeEventListener('unhandledrejection', onRejection);
uninstall();
rejections.uninstall();
}
}, 30_000);
@@ -282,12 +296,7 @@ export function registerAuthTests() {
});
it('shows the system variant copy and still throws it through', async () => {
const uninstall = installUnhandledRejectionBridge();
const reasons: unknown[] = [];
const onRejection = (event: Event) => {
reasons.push((event as Event & { reason?: unknown }).reason);
};
window.addEventListener('unhandledrejection', onRejection);
const rejections = captureWindowRejections();
try {
const invoke = vi.fn(async (command: string) => {
if (command === 'read_client_auth_state') {
@@ -302,10 +311,12 @@ export function registerAuthTests() {
await loginWithCode();
// 先等系统变体真的经 unhandledrejection 抛出。
await waitFor(() => expect(reasons).toHaveLength(1));
// 载体自身 message 留空;诊断信息在 error 字段里,由上报链路取。
expect(reasons[0]).toBeInstanceOf(ClientAuthErrorWrapper);
expect((reasons[0] as ClientAuthErrorWrapper).error).toMatchObject({
await waitFor(() => expect(rejections.reasons).toHaveLength(1));
// 载体 message 是载荷序列化(上报链路直接取用);结构化诊断在 error 字段里。
expect(rejections.reasons[0]).toBeInstanceOf(ClientAuthErrorWrapper);
expect(
(rejections.reasons[0] as ClientAuthErrorWrapper).error,
).toMatchObject({
type: 'authNetworkFailure',
reason: 'unreachable',
});
@@ -318,8 +329,7 @@ export function registerAuthTests() {
expect(document.body.textContent).not.toContain('ECONNREFUSED');
await screen.findByRole('main', { name: '登录' });
} finally {
window.removeEventListener('unhandledrejection', onRejection);
uninstall();
rejections.uninstall();
}
});