9a289e1e1f
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 4s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 5s
CI/CD Pipeline / Check push changed paths (push) Successful in 16s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 12s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 13s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 15s
CI/CD Pipeline / Build Staging API Image (push) Successful in 21s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 22s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 23s
CI/CD Pipeline / CI Gate (pull_request) Successful in 9s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 2m7s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m23s
CI/CD Pipeline / Validate - Style (push) Successful in 2m50s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m37s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m6s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m36s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m31s
CI/CD Pipeline / Validate - Security (push) Successful in 5m21s
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 241h44m7s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 241h46m52s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 241h46m53s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 241h47m15s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 241h47m22s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 241h47m25s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 241h47m26s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 241h47m27s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 241h47m38s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 241h47m27s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 241h47m39s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 241h47m41s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 241h47m39s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 241h47m42s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 241h48m8s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 241h48m9s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 241h48m21s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 241h48m22s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 241h48m22s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Failing after 241h48m23s
CI/CD Pipeline / Validate - Security (pull_request) Failing after 241h48m24s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 241h48m24s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 241h48m25s
CI/CD Pipeline / PR Build Web Image (push) Failing after 241h48m25s
CI/CD Pipeline / Frontend Lint (push) Failing after 241h48m27s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 241h48m41s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 241h48m43s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 242h21m52s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 242h22m16s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 242h22m43s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 242h22m57s
CI/CD Pipeline / PR Build API Image (push) Failing after 242h23m2s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
80 lines
2.9 KiB
TypeScript
80 lines
2.9 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"
|
||
import { render, screen, fireEvent } from "@testing-library/react"
|
||
import { Button } from "antd"
|
||
import { useState } from "react"
|
||
import ChunkErrorBoundary from "@/components/common/ChunkErrorBoundary"
|
||
import * as chunkUtils from "@/utils/chunkLoadError"
|
||
|
||
// reload 函数 mock 掉(jsdom 不支持真实 window.location.reload)
|
||
vi.mock("@/utils/chunkLoadError", async (importOriginal) => {
|
||
const actual = await importOriginal<typeof import("@/utils/chunkLoadError")>()
|
||
return {
|
||
...actual,
|
||
reloadForChunkError: vi.fn(),
|
||
goHomeRecover: vi.fn(),
|
||
}
|
||
})
|
||
const { reloadForChunkError, goHomeRecover } = vi.mocked(chunkUtils)
|
||
|
||
/** 渲染时直接抛错的子组件 */
|
||
const Boom: React.FC<{ error: Error }> = ({ error }) => {
|
||
throw error
|
||
}
|
||
|
||
/** 点击按钮后才抛 chunk 错误的子组件 */
|
||
const ChunkBoomButton: React.FC = () => {
|
||
const [boom, setBoom] = useState(false)
|
||
if (boom) {
|
||
throw new TypeError("Failed to fetch dynamically imported module: /assets/x.js")
|
||
}
|
||
return <Button onClick={() => setBoom(true)}>boom</Button>
|
||
}
|
||
|
||
const renderBoundary = (ui: React.ReactNode) =>
|
||
render(<ChunkErrorBoundary>{ui}</ChunkErrorBoundary>)
|
||
|
||
beforeEach(() => {
|
||
sessionStorage.clear()
|
||
vi.clearAllMocks()
|
||
// error boundary 捕获后 React 会打 error log,静默掉
|
||
vi.spyOn(console, "error").mockImplementation(() => {})
|
||
})
|
||
|
||
afterEach(() => {
|
||
vi.restoreAllMocks()
|
||
sessionStorage.clear()
|
||
})
|
||
|
||
describe("ChunkErrorBoundary", () => {
|
||
it("正常渲染 children", () => {
|
||
renderBoundary(<div>hello-child</div>)
|
||
expect(screen.getByText("hello-child")).toBeInTheDocument()
|
||
})
|
||
|
||
it("首次捕获 chunk 错误 → 自动刷新(reloadForChunkError)并显示自动刷新提示", () => {
|
||
renderBoundary(<ChunkBoomButton />)
|
||
fireEvent.click(screen.getByText("boom"))
|
||
expect(reloadForChunkError).toHaveBeenCalledTimes(1)
|
||
expect(screen.getByText(/正在自动刷新/)).toBeInTheDocument()
|
||
})
|
||
|
||
it("已刷新过仍失败 → 不再自动刷新,显示手动兜底按钮", () => {
|
||
// 模拟"本会话已经自动刷新过一次"
|
||
sessionStorage.setItem("chunk_error_reloaded_at", String(Date.now()))
|
||
renderBoundary(
|
||
<Boom error={new TypeError("Failed to fetch dynamically imported module: /assets/y.js")} />,
|
||
)
|
||
expect(reloadForChunkError).not.toHaveBeenCalled()
|
||
expect(screen.getByText("系统已更新")).toBeInTheDocument()
|
||
// 点击兜底按钮 → goHomeRecover(跳首页,不刷新当前 URL)
|
||
fireEvent.click(screen.getByText("刷新并返回首页"))
|
||
expect(goHomeRecover).toHaveBeenCalledTimes(1)
|
||
})
|
||
|
||
it("非 chunk 错误 → 显示通用错误页,不触发 chunk 自动刷新", () => {
|
||
renderBoundary(<Boom error={new Error("普通业务报错")} />)
|
||
expect(reloadForChunkError).not.toHaveBeenCalled()
|
||
expect(screen.getByText("页面出现异常")).toBeInTheDocument()
|
||
})
|
||
})
|