155 lines
4.4 KiB
TypeScript
155 lines
4.4 KiB
TypeScript
|
|
import { fireEvent, render, screen, within } from "@testing-library/react"
|
||
|
|
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||
|
|
|
||
|
|
import { HomePage } from "./home-page"
|
||
|
|
|
||
|
|
const useHomeOverview = vi.fn()
|
||
|
|
|
||
|
|
vi.mock("@/features/home/api/home.query", () => ({
|
||
|
|
useHomeOverview: () => useHomeOverview(),
|
||
|
|
}))
|
||
|
|
|
||
|
|
const overview = {
|
||
|
|
status: "partial_success" as const,
|
||
|
|
stock_pool_count: 3,
|
||
|
|
latest_update: {
|
||
|
|
trade_date: "2026-08-06",
|
||
|
|
completed_at: "2026-08-07T08:30:00Z",
|
||
|
|
status: "partial_success" as const,
|
||
|
|
target_count: 3,
|
||
|
|
successful_count: 1,
|
||
|
|
failed_count: 2,
|
||
|
|
valid_count: 1,
|
||
|
|
coverage_rate: 1 / 3,
|
||
|
|
failures: [
|
||
|
|
{
|
||
|
|
ts_code: "000001.SZ",
|
||
|
|
name: "平安银行",
|
||
|
|
failure_type: "missing_daily_bar",
|
||
|
|
reason: "目标交易日缺少日线行情",
|
||
|
|
trade_date: "2026-08-06",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
ts_code: "600000.SH",
|
||
|
|
name: "浦发银行",
|
||
|
|
failure_type: "recorded_stock_failure",
|
||
|
|
reason: "供应商请求失败",
|
||
|
|
trade_date: "2026-08-06",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
batch_errors: [],
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
describe("HomePage", () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
useHomeOverview.mockReturnValue({
|
||
|
|
data: overview,
|
||
|
|
isError: false,
|
||
|
|
isPending: false,
|
||
|
|
refetch: vi.fn(),
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
it("renders the management shell and partial overview", () => {
|
||
|
|
render(<HomePage />)
|
||
|
|
|
||
|
|
expect(screen.getByRole("heading", { name: "首页" })).toBeInTheDocument()
|
||
|
|
expect(screen.getByText("市场数据概览")).toBeInTheDocument()
|
||
|
|
expect(screen.getByText("部分更新")).toBeInTheDocument()
|
||
|
|
expect(screen.getByText("当前目标股票池")).toBeInTheDocument()
|
||
|
|
expect(screen.getByText("33.33%")).toBeInTheDocument()
|
||
|
|
expect(screen.getByRole("button", { name: "行情数据" })).toBeDisabled()
|
||
|
|
expect(screen.getByRole("button", { name: "同步任务" })).toBeDisabled()
|
||
|
|
expect(screen.getByRole("button", { name: "选股策略" })).toBeDisabled()
|
||
|
|
})
|
||
|
|
|
||
|
|
it("opens and closes the accessible failed-stock dialog", () => {
|
||
|
|
render(<HomePage />)
|
||
|
|
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: "查看失败股票" }))
|
||
|
|
|
||
|
|
const dialog = screen.getByRole("dialog", { name: "失败股票详情" })
|
||
|
|
expect(dialog).toBeInTheDocument()
|
||
|
|
expect(within(dialog).getByText("平安银行")).toBeInTheDocument()
|
||
|
|
expect(within(dialog).getByText("缺少日线行情")).toBeInTheDocument()
|
||
|
|
expect(within(dialog).getAllByText("目标交易日:2026-08-06")).toHaveLength(
|
||
|
|
2,
|
||
|
|
)
|
||
|
|
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: "关闭" }))
|
||
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument()
|
||
|
|
})
|
||
|
|
|
||
|
|
it("renders a loading placeholder", () => {
|
||
|
|
useHomeOverview.mockReturnValue({
|
||
|
|
data: undefined,
|
||
|
|
isError: false,
|
||
|
|
isPending: true,
|
||
|
|
})
|
||
|
|
|
||
|
|
render(<HomePage />)
|
||
|
|
|
||
|
|
expect(screen.getByLabelText("正在加载市场数据概览")).toBeInTheDocument()
|
||
|
|
})
|
||
|
|
|
||
|
|
it("offers retry when the overview request fails", () => {
|
||
|
|
const refetch = vi.fn()
|
||
|
|
useHomeOverview.mockReturnValue({
|
||
|
|
data: undefined,
|
||
|
|
isError: true,
|
||
|
|
isPending: false,
|
||
|
|
refetch,
|
||
|
|
})
|
||
|
|
|
||
|
|
render(<HomePage />)
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: "重试" }))
|
||
|
|
|
||
|
|
expect(refetch).toHaveBeenCalledOnce()
|
||
|
|
})
|
||
|
|
|
||
|
|
it.each([
|
||
|
|
["updating", "更新中"],
|
||
|
|
["success", "已更新"],
|
||
|
|
["failed", "未更新"],
|
||
|
|
] as const)("renders the %s overview status", (status, label) => {
|
||
|
|
useHomeOverview.mockReturnValue({
|
||
|
|
data: {
|
||
|
|
...overview,
|
||
|
|
status,
|
||
|
|
latest_update: { ...overview.latest_update, status },
|
||
|
|
},
|
||
|
|
isError: false,
|
||
|
|
isPending: false,
|
||
|
|
})
|
||
|
|
|
||
|
|
render(<HomePage />)
|
||
|
|
|
||
|
|
expect(screen.getByText(label)).toBeInTheDocument()
|
||
|
|
})
|
||
|
|
|
||
|
|
it("closes the failed-stock dialog with Escape", () => {
|
||
|
|
render(<HomePage />)
|
||
|
|
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: "查看失败股票" }))
|
||
|
|
const dialog = screen.getByRole("dialog", { name: "失败股票详情" })
|
||
|
|
|
||
|
|
fireEvent.keyDown(dialog, { key: "Escape" })
|
||
|
|
|
||
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument()
|
||
|
|
})
|
||
|
|
|
||
|
|
it("keeps the shell and explains the no-data state", () => {
|
||
|
|
useHomeOverview.mockReturnValue({
|
||
|
|
data: { status: "no_data", stock_pool_count: 7, latest_update: null },
|
||
|
|
isError: false,
|
||
|
|
isPending: false,
|
||
|
|
})
|
||
|
|
|
||
|
|
render(<HomePage />)
|
||
|
|
|
||
|
|
expect(screen.getByText("暂无市场数据")).toBeInTheDocument()
|
||
|
|
expect(screen.getByText(/当前目标股票池:/)).toBeInTheDocument()
|
||
|
|
})
|
||
|
|
})
|