217 lines
6.1 KiB
TypeScript
217 lines
6.1 KiB
TypeScript
|
|
import { fireEvent, render, screen, within } from "@testing-library/react"
|
||
|
|
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||
|
|
|
||
|
|
import type { SelectionResults } from "../api/selection.types"
|
||
|
|
|
||
|
|
import { SelectionResultsPage } from "./selection-results-page"
|
||
|
|
|
||
|
|
const useSelectionResults = vi.fn()
|
||
|
|
const useSelectionRun = vi.fn()
|
||
|
|
const useTriggerSelectionRun = vi.fn()
|
||
|
|
const mutate = vi.fn()
|
||
|
|
const reset = vi.fn()
|
||
|
|
|
||
|
|
vi.mock("@/features/selection/api/selection.query", () => ({
|
||
|
|
useSelectionResults: (...args: unknown[]) => useSelectionResults(...args),
|
||
|
|
useSelectionRun: (...args: unknown[]) => useSelectionRun(...args),
|
||
|
|
useTriggerSelectionRun: () => useTriggerSelectionRun(),
|
||
|
|
}))
|
||
|
|
|
||
|
|
const selectedResult: SelectionResults = {
|
||
|
|
created_at: "2026-08-08T09:00:00+08:00",
|
||
|
|
coverage: 1,
|
||
|
|
eligible_count: 2,
|
||
|
|
evaluated_count: 2,
|
||
|
|
error_message: null,
|
||
|
|
error_type: null,
|
||
|
|
failed_count: 0,
|
||
|
|
failures: [],
|
||
|
|
finished_at: "2026-08-08T09:02:00+08:00",
|
||
|
|
market_sync_batch_id: "market-run-1",
|
||
|
|
run_id: "run-1",
|
||
|
|
selected_stock_count: 1,
|
||
|
|
signal_count: 2,
|
||
|
|
signals: [
|
||
|
|
{
|
||
|
|
category: "zhixing_b1_original_b1",
|
||
|
|
close: 10.5,
|
||
|
|
details: { j: 12, rsi: 20 },
|
||
|
|
name: "平安银行",
|
||
|
|
strategy: "zhixing_b1",
|
||
|
|
target_trade_date: "2026-08-08",
|
||
|
|
ts_code: "000001.SZ",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
category: "zhixing_b1_pullback_white",
|
||
|
|
close: 10.5,
|
||
|
|
details: { j: 12 },
|
||
|
|
name: "平安银行",
|
||
|
|
strategy: "zhixing_b1",
|
||
|
|
target_trade_date: "2026-08-08",
|
||
|
|
ts_code: "000001.SZ",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
status: "success",
|
||
|
|
strategy: "zhixing_b1",
|
||
|
|
target_count: 2,
|
||
|
|
target_trade_date: "2026-08-08",
|
||
|
|
}
|
||
|
|
|
||
|
|
describe("SelectionResultsPage", () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
useSelectionResults.mockReturnValue({
|
||
|
|
data: selectedResult,
|
||
|
|
isError: false,
|
||
|
|
isPending: false,
|
||
|
|
})
|
||
|
|
useSelectionRun.mockReturnValue({
|
||
|
|
data: undefined,
|
||
|
|
isError: false,
|
||
|
|
isPending: false,
|
||
|
|
})
|
||
|
|
useTriggerSelectionRun.mockReturnValue({
|
||
|
|
isError: false,
|
||
|
|
isPending: false,
|
||
|
|
mutate,
|
||
|
|
reset,
|
||
|
|
})
|
||
|
|
mutate.mockReset()
|
||
|
|
reset.mockReset()
|
||
|
|
})
|
||
|
|
|
||
|
|
it("renders persisted results and keeps multiple categories visible", () => {
|
||
|
|
render(<SelectionResultsPage />)
|
||
|
|
|
||
|
|
expect(
|
||
|
|
screen.getByRole("heading", { name: "选股策略" }),
|
||
|
|
).toBeInTheDocument()
|
||
|
|
expect(screen.getByText("知行 B1 执行结果")).toBeInTheDocument()
|
||
|
|
expect(screen.getByText("原始 B1")).toBeInTheDocument()
|
||
|
|
expect(screen.getByText("回踩白线 B")).toBeInTheDocument()
|
||
|
|
expect(screen.getAllByText("平安银行")).toHaveLength(2)
|
||
|
|
})
|
||
|
|
|
||
|
|
it("requires confirmation before a rerun and does not trigger on cancel", () => {
|
||
|
|
render(<SelectionResultsPage />)
|
||
|
|
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: "重新执行" }))
|
||
|
|
const dialog = screen.getByRole("dialog", { name: "确认重新执行策略?" })
|
||
|
|
expect(within(dialog).getByText(/清空 2026-08-08/)).toBeInTheDocument()
|
||
|
|
|
||
|
|
fireEvent.click(within(dialog).getByRole("button", { name: "取消" }))
|
||
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument()
|
||
|
|
expect(mutate).not.toHaveBeenCalled()
|
||
|
|
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: "重新执行" }))
|
||
|
|
fireEvent.click(
|
||
|
|
within(
|
||
|
|
screen.getByRole("dialog", { name: "确认重新执行策略?" }),
|
||
|
|
).getByRole("button", { name: "确认重新执行" }),
|
||
|
|
)
|
||
|
|
expect(mutate).toHaveBeenCalledWith(
|
||
|
|
{
|
||
|
|
request: {
|
||
|
|
rerun: true,
|
||
|
|
strategy: "zhixing_b1",
|
||
|
|
target_trade_date: "2026-08-08",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
expect.any(Object),
|
||
|
|
)
|
||
|
|
})
|
||
|
|
|
||
|
|
it("uses the same confirmation flow for a failed retry", () => {
|
||
|
|
useSelectionResults.mockReturnValue({
|
||
|
|
data: {
|
||
|
|
...selectedResult,
|
||
|
|
failed_count: 1,
|
||
|
|
failures: [
|
||
|
|
{
|
||
|
|
name: "平安银行",
|
||
|
|
reason: "目标日数据读取失败",
|
||
|
|
status: "data_error",
|
||
|
|
ts_code: "000001.SZ",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
selected_stock_count: 0,
|
||
|
|
signal_count: 0,
|
||
|
|
signals: [],
|
||
|
|
status: "failed",
|
||
|
|
},
|
||
|
|
isError: false,
|
||
|
|
isPending: false,
|
||
|
|
})
|
||
|
|
|
||
|
|
render(<SelectionResultsPage />)
|
||
|
|
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: "重试执行" }))
|
||
|
|
expect(
|
||
|
|
screen.getByRole("dialog", { name: "确认重新执行策略?" }),
|
||
|
|
).toBeInTheDocument()
|
||
|
|
})
|
||
|
|
|
||
|
|
it("renders a no-data state and triggers an initial run after date selection", () => {
|
||
|
|
useSelectionResults.mockReturnValue({
|
||
|
|
data: {
|
||
|
|
...selectedResult,
|
||
|
|
run_id: null,
|
||
|
|
signals: [],
|
||
|
|
status: "no_data",
|
||
|
|
target_trade_date: null,
|
||
|
|
},
|
||
|
|
isError: false,
|
||
|
|
isPending: false,
|
||
|
|
})
|
||
|
|
|
||
|
|
render(<SelectionResultsPage />)
|
||
|
|
|
||
|
|
expect(screen.getByText("暂无策略结果")).toBeInTheDocument()
|
||
|
|
fireEvent.change(screen.getByLabelText("目标交易日"), {
|
||
|
|
target: { value: "2026-08-08" },
|
||
|
|
})
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: "执行策略" }))
|
||
|
|
|
||
|
|
expect(mutate).toHaveBeenCalledWith(
|
||
|
|
{
|
||
|
|
request: {
|
||
|
|
rerun: false,
|
||
|
|
strategy: "zhixing_b1",
|
||
|
|
target_trade_date: "2026-08-08",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
expect.any(Object),
|
||
|
|
)
|
||
|
|
})
|
||
|
|
|
||
|
|
it("continues polling a persisted running run after page reload", () => {
|
||
|
|
useSelectionResults.mockReturnValue({
|
||
|
|
data: { ...selectedResult, signals: [], status: "running" },
|
||
|
|
isError: false,
|
||
|
|
isPending: false,
|
||
|
|
})
|
||
|
|
useSelectionRun.mockReturnValue({
|
||
|
|
data: { ...selectedResult, signals: [], status: "running" },
|
||
|
|
isError: false,
|
||
|
|
isPending: false,
|
||
|
|
})
|
||
|
|
|
||
|
|
render(<SelectionResultsPage />)
|
||
|
|
|
||
|
|
expect(screen.getByText("策略执行中")).toBeInTheDocument()
|
||
|
|
expect(screen.getByRole("button", { name: "执行中" })).toBeDisabled()
|
||
|
|
expect(useSelectionRun).toHaveBeenCalledWith("run-1")
|
||
|
|
})
|
||
|
|
|
||
|
|
it("renders query failures explicitly", () => {
|
||
|
|
useSelectionResults.mockReturnValue({
|
||
|
|
data: undefined,
|
||
|
|
isError: true,
|
||
|
|
isPending: false,
|
||
|
|
})
|
||
|
|
|
||
|
|
render(<SelectionResultsPage />)
|
||
|
|
|
||
|
|
expect(screen.getByText("结果查询失败")).toBeInTheDocument()
|
||
|
|
})
|
||
|
|
})
|