feat(sector-radar): 支持排名列表滚动加载
This commit is contained in:
@@ -14,6 +14,7 @@ const useSectorRadarDates = vi.fn()
|
||||
const useSectorRadarRankings = vi.fn()
|
||||
const refetchDates = vi.fn()
|
||||
const refetchRankings = vi.fn()
|
||||
const fetchNextPage = vi.fn()
|
||||
|
||||
let routeSearch: SectorRadarRouteSearch
|
||||
|
||||
@@ -114,6 +115,21 @@ const rankingsResponse: RadarRankingsResponse = {
|
||||
],
|
||||
}
|
||||
|
||||
function rankingQueryResult(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
data: { pageParams: [1], pages: [rankingsResponse] },
|
||||
fetchNextPage,
|
||||
hasNextPage: false,
|
||||
isError: false,
|
||||
isFetchNextPageError: false,
|
||||
isFetching: false,
|
||||
isFetchingNextPage: false,
|
||||
isPending: false,
|
||||
refetch: refetchRankings,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
describe("SectorRadarPage", () => {
|
||||
beforeEach(() => {
|
||||
routeSearch = {
|
||||
@@ -134,36 +150,43 @@ describe("SectorRadarPage", () => {
|
||||
isPending: false,
|
||||
refetch: refetchDates,
|
||||
})
|
||||
useSectorRadarRankings.mockReturnValue({
|
||||
data: rankingsResponse,
|
||||
isError: false,
|
||||
isFetching: false,
|
||||
isPending: false,
|
||||
refetch: refetchRankings,
|
||||
})
|
||||
useSectorRadarRankings.mockReturnValue(rankingQueryResult())
|
||||
navigate.mockReset()
|
||||
refetchDates.mockReset()
|
||||
refetchRankings.mockReset()
|
||||
fetchNextPage.mockReset()
|
||||
fetchNextPage.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
it("renders independent metric provenance, units, quality, and ranking rows", () => {
|
||||
it("renders ranking rows without the description card or pagination", () => {
|
||||
render(<SectorRadarPage />)
|
||||
|
||||
expect(screen.getAllByText("知行独立实现").length).toBeGreaterThan(0)
|
||||
expect(screen.getByText("zhixing_amount_net_bn_v1")).toBeInTheDocument()
|
||||
expect(screen.getByText("12.5 亿元")).toBeInTheDocument()
|
||||
expect(screen.getByText("机器人")).toBeInTheDocument()
|
||||
expect(screen.getByText("样本有限")).toBeInTheDocument()
|
||||
expect(screen.getByText("95%")).toBeInTheDocument()
|
||||
expect(screen.queryByText("收盘后板块资金排名")).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText("知行独立实现,非 OneChartLab 原站公式。"),
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText("zhixing_amount_net_bn_v1"),
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "第 1 页" }),
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole("combobox", { name: "每页数量" }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("stores search and pagination changes in router search state", () => {
|
||||
it("resets filters to the first batch in router search state", () => {
|
||||
routeSearch = { ...routeSearch, page: 3 }
|
||||
render(<SectorRadarPage />)
|
||||
|
||||
fireEvent.change(screen.getByRole("searchbox", { name: "搜索板块" }), {
|
||||
target: { value: "机器人" },
|
||||
})
|
||||
fireEvent.click(screen.getByRole("button", { name: "第 1 页" }))
|
||||
|
||||
const searchCall = navigate.mock.calls[0]?.[0].search as (
|
||||
previous: SectorRadarRouteSearch,
|
||||
@@ -174,24 +197,112 @@ describe("SectorRadarPage", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("loads the next page once when repeated scroll events reach the bottom", () => {
|
||||
fetchNextPage.mockReturnValue(new Promise(() => undefined))
|
||||
useSectorRadarRankings.mockReturnValue(
|
||||
rankingQueryResult({ hasNextPage: true }),
|
||||
)
|
||||
render(<SectorRadarPage />)
|
||||
const scrollRegion = screen.getByRole("region", {
|
||||
name: "板块资金排名滚动区域",
|
||||
})
|
||||
Object.defineProperties(scrollRegion, {
|
||||
clientHeight: { configurable: true, value: 300 },
|
||||
scrollHeight: { configurable: true, value: 1_000 },
|
||||
scrollTop: { configurable: true, value: 550 },
|
||||
})
|
||||
|
||||
fireEvent.scroll(scrollRegion)
|
||||
fireEvent.scroll(scrollRegion)
|
||||
|
||||
expect(fetchNextPage).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it("announces that the next page is loading without hiding loaded rows", () => {
|
||||
useSectorRadarRankings.mockReturnValue(
|
||||
rankingQueryResult({
|
||||
hasNextPage: true,
|
||||
isFetching: true,
|
||||
isFetchingNextPage: true,
|
||||
}),
|
||||
)
|
||||
|
||||
render(<SectorRadarPage />)
|
||||
|
||||
expect(screen.getByRole("status")).toHaveTextContent("正在加载更多板块")
|
||||
expect(screen.getByText("机器人")).toBeInTheDocument()
|
||||
expect(screen.queryByText("雷达数据正在刷新")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("merges rows from every loaded page", () => {
|
||||
const firstPage = { ...rankingsResponse, total: 3 }
|
||||
const nextPage = {
|
||||
...rankingsResponse,
|
||||
page: 2,
|
||||
total: 3,
|
||||
rows: [
|
||||
{
|
||||
...rankingsResponse.rows[0],
|
||||
sector_code: "BK0003.DC",
|
||||
sector_name: "商业航天",
|
||||
rank_position: 3,
|
||||
},
|
||||
],
|
||||
}
|
||||
useSectorRadarRankings.mockReturnValue(
|
||||
rankingQueryResult({
|
||||
data: { pageParams: [1, 2], pages: [firstPage, nextPage] },
|
||||
}),
|
||||
)
|
||||
|
||||
render(<SectorRadarPage />)
|
||||
|
||||
expect(screen.getByText("机器人")).toBeInTheDocument()
|
||||
expect(screen.getByText("低空经济")).toBeInTheDocument()
|
||||
expect(screen.getByText("商业航天")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("keeps loaded rows and offers retry when loading more fails", () => {
|
||||
useSectorRadarRankings.mockReturnValue(
|
||||
rankingQueryResult({
|
||||
hasNextPage: true,
|
||||
isError: true,
|
||||
isFetchNextPageError: true,
|
||||
}),
|
||||
)
|
||||
|
||||
render(<SectorRadarPage />)
|
||||
|
||||
expect(screen.getByText("机器人")).toBeInTheDocument()
|
||||
expect(screen.getByRole("alert")).toHaveTextContent(
|
||||
"加载更多板块失败,已加载结果仍保留",
|
||||
)
|
||||
expect(screen.queryByText("雷达数据刷新失败")).not.toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole("button", { name: "重试加载更多" }))
|
||||
expect(fetchNextPage).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it("shows rank changes without inventing missing history", () => {
|
||||
routeSearch = { ...routeSearch, view: "rank_change", rankChangeDays: 5 }
|
||||
useSectorRadarRankings.mockReturnValue({
|
||||
data: {
|
||||
...rankingsResponse,
|
||||
view: "rank_change",
|
||||
rank_change_days: 5,
|
||||
rows: rankingsResponse.rows.map((row, index) => ({
|
||||
...row,
|
||||
rank_change_days: 5,
|
||||
rank_change: index === 0 ? 3 : null,
|
||||
})),
|
||||
},
|
||||
isError: false,
|
||||
isFetching: false,
|
||||
isPending: false,
|
||||
refetch: refetchRankings,
|
||||
})
|
||||
useSectorRadarRankings.mockReturnValue(
|
||||
rankingQueryResult({
|
||||
data: {
|
||||
pageParams: [1],
|
||||
pages: [
|
||||
{
|
||||
...rankingsResponse,
|
||||
view: "rank_change",
|
||||
rank_change_days: 5,
|
||||
rows: rankingsResponse.rows.map((row, index) => ({
|
||||
...row,
|
||||
rank_change_days: 5,
|
||||
rank_change: index === 0 ? 3 : null,
|
||||
})),
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
render(<SectorRadarPage />)
|
||||
|
||||
@@ -230,13 +341,9 @@ describe("SectorRadarPage", () => {
|
||||
})
|
||||
|
||||
it("keeps the last response visible while React Query refreshes it", () => {
|
||||
useSectorRadarRankings.mockReturnValue({
|
||||
data: rankingsResponse,
|
||||
isError: false,
|
||||
isFetching: true,
|
||||
isPending: false,
|
||||
refetch: refetchRankings,
|
||||
})
|
||||
useSectorRadarRankings.mockReturnValue(
|
||||
rankingQueryResult({ isFetching: true }),
|
||||
)
|
||||
|
||||
render(<SectorRadarPage />)
|
||||
|
||||
@@ -247,13 +354,9 @@ describe("SectorRadarPage", () => {
|
||||
})
|
||||
|
||||
it("keeps stale data visible when a background refresh fails", () => {
|
||||
useSectorRadarRankings.mockReturnValue({
|
||||
data: rankingsResponse,
|
||||
isError: true,
|
||||
isFetching: false,
|
||||
isPending: false,
|
||||
refetch: refetchRankings,
|
||||
})
|
||||
useSectorRadarRankings.mockReturnValue(
|
||||
rankingQueryResult({ isError: true }),
|
||||
)
|
||||
|
||||
render(<SectorRadarPage />)
|
||||
|
||||
@@ -272,8 +375,12 @@ describe("SectorRadarPage", () => {
|
||||
})
|
||||
useSectorRadarRankings.mockReturnValue({
|
||||
data: undefined,
|
||||
fetchNextPage,
|
||||
hasNextPage: false,
|
||||
isError: false,
|
||||
isFetchNextPageError: false,
|
||||
isFetching: true,
|
||||
isFetchingNextPage: false,
|
||||
isPending: true,
|
||||
refetch: refetchRankings,
|
||||
})
|
||||
@@ -289,8 +396,12 @@ describe("SectorRadarPage", () => {
|
||||
})
|
||||
useSectorRadarRankings.mockReturnValue({
|
||||
data: undefined,
|
||||
fetchNextPage,
|
||||
hasNextPage: false,
|
||||
isError: true,
|
||||
isFetchNextPageError: false,
|
||||
isFetching: false,
|
||||
isFetchingNextPage: false,
|
||||
isPending: false,
|
||||
refetch: refetchRankings,
|
||||
})
|
||||
@@ -311,19 +422,22 @@ describe("SectorRadarPage", () => {
|
||||
isPending: false,
|
||||
refetch: refetchDates,
|
||||
})
|
||||
useSectorRadarRankings.mockReturnValue({
|
||||
data: {
|
||||
...rankingsResponse,
|
||||
status: "no_data",
|
||||
publication: null,
|
||||
rows: [],
|
||||
total: 0,
|
||||
},
|
||||
isError: false,
|
||||
isFetching: false,
|
||||
isPending: false,
|
||||
refetch: refetchRankings,
|
||||
})
|
||||
useSectorRadarRankings.mockReturnValue(
|
||||
rankingQueryResult({
|
||||
data: {
|
||||
pageParams: [1],
|
||||
pages: [
|
||||
{
|
||||
...rankingsResponse,
|
||||
status: "no_data",
|
||||
publication: null,
|
||||
rows: [],
|
||||
total: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
rerender(<SectorRadarPage />)
|
||||
expect(screen.getByText("暂无板块资金发布")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user