feat(sector-radar): 支持排名列表滚动加载

This commit is contained in:
yuxuanhui
2026-09-01 11:16:18 +08:00
parent c8b08add76
commit be31235fc7
4 changed files with 396 additions and 182 deletions
@@ -1,12 +1,13 @@
import { beforeEach, describe, expect, it, vi } from "vitest"
const useQuery = vi.hoisted(() => vi.fn())
const useInfiniteQuery = vi.hoisted(() => vi.fn())
const api = vi.hoisted(() => ({
getSectorRadarDates: vi.fn(),
getSectorRadarRankings: vi.fn(),
}))
vi.mock("@tanstack/react-query", () => ({ useQuery }))
vi.mock("@tanstack/react-query", () => ({ useInfiniteQuery, useQuery }))
vi.mock("./sector-radar.api", () => api)
import {
@@ -32,9 +33,10 @@ describe("sector radar query hooks", () => {
beforeEach(() => {
vi.clearAllMocks()
useQuery.mockImplementation((options) => options)
useInfiniteQuery.mockImplementation((options) => options)
})
it("keeps every server-affecting filter in the ranking query key", () => {
it("keeps filters in one ranking key without splitting cached pages", () => {
expect(sectorRadarDatesQueryKey).toEqual(["sectorRadar", "dates"])
expect(sectorRadarRankingsQueryKey(query)).toEqual([
"sectorRadar",
@@ -46,9 +48,11 @@ describe("sector radar query hooks", () => {
3,
"bottom",
"银行",
2,
50,
])
expect(
sectorRadarRankingsQueryKey({ ...query, page: query.page + 1 }),
).toEqual(sectorRadarRankingsQueryKey(query))
})
it("forwards React Query cancellation signals to both adapters", async () => {
@@ -57,15 +61,98 @@ describe("sector radar query hooks", () => {
const datesOptions = useQuery.mock.calls[0]?.[0] as {
queryFn: (context: { signal: AbortSignal }) => unknown
}
const rankingsOptions = useQuery.mock.calls[1]?.[0] as {
queryFn: (context: { signal: AbortSignal }) => unknown
const rankingsOptions = useInfiniteQuery.mock.calls[0]?.[0] as {
queryFn: (context: { pageParam: number; signal: AbortSignal }) => unknown
}
const signal = new AbortController().signal
await datesOptions.queryFn({ signal })
await rankingsOptions.queryFn({ signal })
await rankingsOptions.queryFn({ pageParam: 3, signal })
expect(api.getSectorRadarDates).toHaveBeenCalledWith(signal)
expect(api.getSectorRadarRankings).toHaveBeenCalledWith(query, signal)
expect(api.getSectorRadarRankings).toHaveBeenCalledWith(
{ ...query, page: 3 },
signal,
)
})
it("starts from page one and stops after loaded rows cover total", () => {
useSectorRadarRankings(query)
const rankingsOptions = useInfiniteQuery.mock.calls[0]?.[0] as {
getNextPageParam: (
lastPage: { page: number; rows: unknown[]; total: number },
pages: { page: number; rows: unknown[]; total: number }[],
lastPageParam: number,
) => number | undefined
initialPageParam: number
}
const firstPage = {
page: 1,
rows: Array(50).fill(null),
status: "success",
total: 75,
}
const secondPage = {
page: 2,
rows: Array(25).fill(null),
status: "success",
total: 75,
}
expect(rankingsOptions.initialPageParam).toBe(1)
expect(rankingsOptions.getNextPageParam(firstPage, [firstPage], 1)).toBe(2)
expect(
rankingsOptions.getNextPageParam(secondPage, [firstPage, secondPage], 2),
).toBeUndefined()
})
it("stops when a page is empty or does not match its requested page param", () => {
useSectorRadarRankings(query)
const rankingsOptions = useInfiniteQuery.mock.calls[0]?.[0] as {
getNextPageParam: (
lastPage: {
page: number
rows: unknown[]
status: "success" | "no_data"
total: number
},
pages: {
page: number
rows: unknown[]
status: "success" | "no_data"
total: number
}[],
lastPageParam: number,
) => number | undefined
}
const firstPage = {
page: 1,
rows: Array(50).fill(null),
status: "success" as const,
total: 75,
}
const emptyPage = {
page: 2,
rows: [],
status: "success" as const,
total: 75,
}
const mismatchedPage = {
page: 1,
rows: Array(25).fill(null),
status: "success" as const,
total: 75,
}
expect(
rankingsOptions.getNextPageParam(emptyPage, [firstPage, emptyPage], 2),
).toBeUndefined()
expect(
rankingsOptions.getNextPageParam(
mismatchedPage,
[firstPage, mismatchedPage],
2,
),
).toBeUndefined()
})
})