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 routerNavigate = vi.hoisted(() => vi.fn()) 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(), })) vi.mock("@tanstack/react-router", () => ({ useNavigate: () => routerNavigate, useSearch: () => ({ page: 1, pageSize: 5, search: "", category: "all" }), })) 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", page: 1, page_size: 5, run_id: "run-1", selected_stock_count: 1, signal_count: 2, signals_total: 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() routerNavigate.mockReset() }) it("renders persisted results and keeps multiple categories visible", () => { render() expect(screen.getByText("执行完成")).toBeInTheDocument() const table = screen.getByRole("table") expect(table).toBeInTheDocument() expect(within(table).getByText("原始 B1")).toBeInTheDocument() expect(within(table).getByText("回踩白线 B")).toBeInTheDocument() expect(within(table).getAllByText("平安银行")).toHaveLength(2) }) it("filters signals, updates the detail panel, and expands mobile details", () => { const extraSignal = { ...selectedResult.signals[0], category: "zhixing_b1_oversold_volume", details: { macd: 3.2 }, name: "招商银行", ts_code: "600036.SH", } useSelectionResults.mockReturnValue({ data: { ...selectedResult, signal_count: 3, signals: [...selectedResult.signals, extraSignal], }, isError: false, isPending: false, }) render() fireEvent.change(screen.getByLabelText("搜索命中信号"), { target: { value: "招商" }, }) const table = screen.getByRole("table") expect(within(table).getByText("招商银行")).toBeInTheDocument() fireEvent.click(within(table).getByText("招商银行")) expect( within( screen.getByRole("complementary", { name: "当前股票详情" }), ).getByText("招商银行"), ).toBeInTheDocument() const expandButton = screen.getAllByRole("button", { name: "查看详情" })[0] fireEvent.click(expandButton) expect(expandButton).toHaveAttribute("aria-expanded", "true") expect(screen.getAllByText("macd: 3.2").length).toBeGreaterThan(1) }) it("navigates to another page when the result list is paginated", () => { const signals = Array.from({ length: 6 }, (_, index) => ({ ...selectedResult.signals[0], name: `股票${index + 1}`, ts_code: `00000${index + 1}.SZ`, })) useSelectionResults.mockReturnValue({ data: { ...selectedResult, signal_count: signals.length, signals, signals_total: signals.length, }, isError: false, isPending: false, }) render() expect(screen.getByText("第 1–5 条,共 6 条")).toBeInTheDocument() fireEvent.click(screen.getByRole("button", { name: "第 2 页" })) expect(routerNavigate).toHaveBeenCalled() }) it("requires confirmation before a rerun and does not trigger on cancel", () => { render() 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() 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() 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() expect(screen.getByText("策略执行中")).toBeInTheDocument() expect(screen.getByRole("button", { name: "执行中" })).toBeDisabled() expect(useSelectionRun).toHaveBeenCalledWith("run-1", { page: 1, pageSize: 5, }) }) it("renders query failures explicitly", () => { useSelectionResults.mockReturnValue({ data: undefined, isError: true, isPending: false, }) render() expect(screen.getByText("结果查询失败")).toBeInTheDocument() }) })