Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ test("returns null when no drops", () => {
allItemsSelected={false}
onToggleSelectAll={jest.fn()}
removeSelected={jest.fn()}
setPausePolling={jest.fn()}
onResettingChange={jest.fn()}
/>
</ReactQueryWrapperContext.Provider>
</AuthContext.Provider>
Expand All @@ -67,7 +67,7 @@ test("shows available votes when provided", () => {
allItemsSelected={false}
onToggleSelectAll={jest.fn()}
removeSelected={jest.fn()}
setPausePolling={jest.fn()}
onResettingChange={jest.fn()}
/>
</ReactQueryWrapperContext.Provider>
</AuthContext.Provider>
Expand All @@ -87,7 +87,7 @@ test("hides available votes when missing", () => {
allItemsSelected={false}
onToggleSelectAll={jest.fn()}
removeSelected={jest.fn()}
setPausePolling={jest.fn()}
onResettingChange={jest.fn()}
/>
</ReactQueryWrapperContext.Provider>
</AuthContext.Provider>
Expand All @@ -98,7 +98,7 @@ test("hides available votes when missing", () => {

test("resets votes for selected drops", async () => {
const removeSelected = jest.fn();
const setPausePolling = jest.fn();
const onResettingChange = jest.fn();
const selected = new Set(["a", "b"]);
render(
<AuthContext.Provider value={auth}>
Expand All @@ -110,15 +110,16 @@ test("resets votes for selected drops", async () => {
allItemsSelected={false}
onToggleSelectAll={jest.fn()}
removeSelected={removeSelected}
setPausePolling={setPausePolling}
onResettingChange={onResettingChange}
/>
</ReactQueryWrapperContext.Provider>
</AuthContext.Provider>
);
await act(async () => {
fireEvent.click(screen.getAllByRole("button")[1]);
});
expect(setPausePolling).toHaveBeenCalledWith(true);
expect(onResettingChange).toHaveBeenNthCalledWith(1, true);
expect(onResettingChange).toHaveBeenNthCalledWith(2, false);
expect(removeSelected).toHaveBeenCalledTimes(2);
// onDropRateChange is handled by React Query elsewhere, not directly by this component
});
35 changes: 21 additions & 14 deletions __tests__/hooks/useWaveDropsLeaderboard.extra.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
} from "@/hooks/useWaveDropsLeaderboard";
import {
useInfiniteQuery,
useQueryClient,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import { commonApiFetch } from "@/services/api/common-api";

Expand All @@ -16,9 +16,7 @@ jest.mock("@tanstack/react-query", () => ({
useQueryClient: jest.fn(),
keepPreviousData: {},
}));
jest.mock("react-use", () => ({ useDebounce: jest.fn() }));
jest.mock("@/services/api/common-api", () => ({ commonApiFetch: jest.fn() }));
jest.mock("@/hooks/useCapacitor", () => () => ({ isCapacitor: false }));
jest.mock("@/helpers/waves/wave-drops.helpers", () => ({
generateUniqueKeys: jest.fn((a: any) => a),
mapToExtendedDrops: jest.fn((pages: any) =>
Expand Down Expand Up @@ -52,6 +50,12 @@ beforeEach(() => {
});
});

const getMainQueryOptions = () => {
const firstCall = (useInfiniteQuery as jest.Mock).mock.calls[0];
expect(firstCall).toBeDefined();
return firstCall![0];
};

describe("useWaveDropsLeaderboard extra", () => {
it("maps pages to drops", async () => {
(useInfiniteQuery as jest.Mock).mockReturnValue({
Expand All @@ -70,18 +74,24 @@ describe("useWaveDropsLeaderboard extra", () => {
expect(result.current.isFetching).toBe(false);
});

it("prefetches with correct sort", () => {
it("uses the correct sort in the main query key", () => {
renderHook(() =>
useWaveDropsLeaderboard({
waveId: "2",
sort: WaveDropsLeaderboardSort.CREATED_AT,
})
);
const call = (queryClientMock.prefetchInfiniteQuery as jest.Mock).mock
.calls[0][0];
const call = getMainQueryOptions();
expect(call.queryKey[1].sort).toBe(WaveDropsLeaderboardSort.CREATED_AT);
});

it("does not prefetch or start a polling query on mount", () => {
renderHook(() => useWaveDropsLeaderboard({ waveId: "2" }));

expect(queryClientMock.prefetchInfiniteQuery).not.toHaveBeenCalled();
expect(useQuery).not.toHaveBeenCalled();
});

it("includes curation and price params in query key and request params", async () => {
renderHook(() =>
useWaveDropsLeaderboard({
Expand All @@ -94,9 +104,8 @@ describe("useWaveDropsLeaderboard extra", () => {
})
);

const call = (queryClientMock.prefetchInfiniteQuery as jest.Mock).mock
.calls[0][0];
expect(call.queryKey[1].curated_by_group).toBe("curation-group-1");
const call = getMainQueryOptions();
expect(call.queryKey[1].curation_id).toBe("curation-group-1");
expect(call.queryKey[1].min_price).toBe("0.5");
expect(call.queryKey[1].max_price).toBe("2.75");
expect(call.queryKey[1].price_currency).toBe("ETH");
Expand All @@ -108,7 +117,7 @@ describe("useWaveDropsLeaderboard extra", () => {
endpoint: "waves/2/leaderboard",
params: expect.objectContaining({
sort: WaveDropsLeaderboardSort.PRICE,
curated_by_group: "curation-group-1",
curation_id: "curation-group-1",
min_price: "0.5",
max_price: "2.75",
price_currency: "ETH",
Expand All @@ -128,8 +137,7 @@ describe("useWaveDropsLeaderboard extra", () => {
})
);

const call = (queryClientMock.prefetchInfiniteQuery as jest.Mock).mock
.calls[0][0];
const call = getMainQueryOptions();
expect(call.queryKey[1].min_price).toBe("0.5");
expect(call.queryKey[1].max_price).toBe("2.75");

Expand Down Expand Up @@ -159,8 +167,7 @@ describe("useWaveDropsLeaderboard extra", () => {
})
);

const call = (queryClientMock.prefetchInfiniteQuery as jest.Mock).mock
.calls[0][0];
const call = getMainQueryOptions();
expect(call.queryKey[1].price_currency).toBeNull();

await call.queryFn({ pageParam: null });
Expand Down
97 changes: 63 additions & 34 deletions __tests__/hooks/useWaveDropsLeaderboard.test.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,58 @@
import { renderHook, act } from "@testing-library/react";
import { useWaveDropsLeaderboard } from "@/hooks/useWaveDropsLeaderboard";
import {
useInfiniteQuery,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";

jest.mock("@tanstack/react-query", () => ({
useInfiniteQuery: jest.fn(),
useQuery: jest.fn(),
useQueryClient: jest.fn(),
keepPreviousData: {},
}));
jest.mock("react-use", () => ({ useDebounce: jest.fn() }));
jest.mock("@/services/api/common-api", () => ({ commonApiFetch: jest.fn() }));
jest.mock("@/hooks/useCapacitor", () => () => ({ isCapacitor: false }));
jest.mock("@/helpers/waves/wave-drops.helpers", () => ({
generateUniqueKeys: jest.fn((a) => a),
mapToExtendedDrops: jest.fn((pages) => pages.flatMap((p: any) => p.drops)),
}));

const queryClientMock = {
prefetchInfiniteQuery: jest.fn(),
removeQueries: jest.fn(),
};
(useQueryClient as jest.Mock).mockReturnValue(queryClientMock);
(useInfiniteQuery as jest.Mock).mockReturnValue({
data: { pages: [] },
fetchNextPage: jest.fn(),
hasNextPage: true,
isFetching: false,
isFetchingNextPage: false,
refetch: jest.fn(),
});
(useQuery as jest.Mock).mockReturnValue({});

const mockInfiniteQueryReturn = ({
data = { pages: [] },
fetchNextPage = jest.fn(),
hasNextPage = true,
}: {
readonly data?: unknown;
readonly fetchNextPage?: jest.Mock;
readonly hasNextPage?: boolean;
} = {}) => {
(useInfiniteQuery as jest.Mock).mockReturnValue({
data,
fetchNextPage,
hasNextPage,
isFetching: false,
isFetchingNextPage: false,
refetch: jest.fn(),
});
};

const getLatestInfiniteQueryOptions = () => {
const calls = (useInfiniteQuery as jest.Mock).mock.calls;
const latestCall = calls[calls.length - 1];
expect(latestCall).toBeDefined();
return latestCall![0];
};

describe("useWaveDropsLeaderboard", () => {
beforeEach(() => {
jest.clearAllMocks();
mockInfiniteQueryReturn();
});

it("calls fetchNextPage via manualFetch when more pages", async () => {
const fetchNext = jest.fn();
(useInfiniteQuery as jest.Mock).mockReturnValue({
data: { pages: [] },
fetchNextPage: fetchNext,
hasNextPage: true,
isFetching: false,
isFetchingNextPage: false,
refetch: jest.fn(),
});
mockInfiniteQueryReturn({ fetchNextPage: fetchNext });
const { result } = renderHook(() =>
useWaveDropsLeaderboard({ waveId: "1" })
);
Expand All @@ -67,14 +74,7 @@ describe("useWaveDropsLeaderboard", () => {

it("does not fetch next page when none left", async () => {
const fetchNext = jest.fn();
(useInfiniteQuery as jest.Mock).mockReturnValue({
data: { pages: [] },
fetchNextPage: fetchNext,
hasNextPage: false,
isFetching: false,
isFetchingNextPage: false,
refetch: jest.fn(),
});
mockInfiniteQueryReturn({ fetchNextPage: fetchNext, hasNextPage: false });
const { result } = renderHook(() =>
useWaveDropsLeaderboard({ waveId: "3" })
);
Expand All @@ -83,4 +83,33 @@ describe("useWaveDropsLeaderboard", () => {
});
expect(fetchNext).not.toHaveBeenCalled();
});

it("enables the main query only when enabled and waveId are truthy", () => {
const { rerender } = renderHook(
({ enabled, waveId }: { enabled: boolean; waveId: string }) =>
useWaveDropsLeaderboard({ waveId, enabled }),
{
initialProps: { enabled: true, waveId: "1" },
}
);

expect(getLatestInfiniteQueryOptions().enabled).toBe(true);

rerender({ enabled: false, waveId: "1" });
expect(getLatestInfiniteQueryOptions().enabled).toBe(false);

rerender({ enabled: true, waveId: "" });
expect(getLatestInfiniteQueryOptions().enabled).toBe(false);
});

it("does not report fetching while disabled before data initializes", () => {
mockInfiniteQueryReturn({ data: null });

const { result } = renderHook(() =>
useWaveDropsLeaderboard({ waveId: "1", enabled: false })
);

expect(getLatestInfiniteQueryOptions().enabled).toBe(false);
expect(result.current.isFetching).toBe(false);
});
});
8 changes: 4 additions & 4 deletions components/brain/my-stream/votes/MyStreamWaveMyVotes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ const MyStreamWaveMyVotes: React.FC<MyStreamWaveMyVotesProps> = ({
wave,
onDropClick,
}) => {
const [pausePolling, setPausePolling] = useState(false);
const [isResettingVotes, setIsResettingVotes] = useState(false);
const { drops, fetchNextPage, hasNextPage, isFetching, isFetchingNextPage } =
useWaveDropsLeaderboard({
waveId: wave.id,
sort: WaveDropsLeaderboardSort.MY_REALTIME_VOTE,
pausePolling,
enabled: !isResettingVotes,
});
Comment thread
simo6529 marked this conversation as resolved.

const { myVotesViewStyle } = useLayout();
Expand Down Expand Up @@ -111,7 +111,7 @@ const MyStreamWaveMyVotes: React.FC<MyStreamWaveMyVotesProps> = ({
onToggleSelectAll={handleToggleSelectAll}
allItemsSelected={allItemsSelected}
removeSelected={removeSelected}
setPausePolling={setPausePolling}
onResettingChange={setIsResettingVotes}
/>
<div className="tw-space-y-2">
{drops.map((drop) => (
Expand All @@ -121,7 +121,7 @@ const MyStreamWaveMyVotes: React.FC<MyStreamWaveMyVotesProps> = ({
onDropClick={onDropClick}
isChecked={checkedDrops.has(drop.id)}
onToggleCheck={handleToggleCheck}
isResetting={pausePolling}
isResetting={isResettingVotes}
/>
))}
{isFetchingNextPage && <WaveLeaderboardLoadingBar />}
Expand Down
8 changes: 4 additions & 4 deletions components/brain/my-stream/votes/MyStreamWaveMyVotesReset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface MyStreamWaveMyVotesResetProps {
readonly allItemsSelected: boolean;
readonly onToggleSelectAll: () => void;
readonly removeSelected: (dropId: string) => void;
readonly setPausePolling: (pausePolling: boolean) => void;
readonly onResettingChange: (isResetting: boolean) => void;
}

const DEFAULT_DROP_RATE_CATEGORY = "Rep";
Expand All @@ -29,7 +29,7 @@ const MyStreamWaveMyVotesReset: React.FC<MyStreamWaveMyVotesResetProps> = ({
allItemsSelected,
onToggleSelectAll,
removeSelected,
setPausePolling,
onResettingChange,
}) => {
const { setToast } = useContext(AuthContext);
// State for reset progress
Expand Down Expand Up @@ -67,7 +67,7 @@ const MyStreamWaveMyVotesReset: React.FC<MyStreamWaveMyVotesResetProps> = ({
const handleReset = async () => {
if (!selectedCount || isResetting) return;
setTotalCount(selectedCount);
setPausePolling(true);
onResettingChange(true);
setIsResetting(true);
setResetProgress(0);

Expand All @@ -81,7 +81,7 @@ const MyStreamWaveMyVotesReset: React.FC<MyStreamWaveMyVotesResetProps> = ({

setIsResetting(false);
setResetProgress(0);
setPausePolling(false);
onResettingChange(false);
setTotalCount(0);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function NextMintLeadingSection() {
const { drops, isFetching: isLeaderboardFetching } = useWaveDropsLeaderboard({
waveId: waveId ?? "",
sort: WaveDropsLeaderboardSort.RATING_PREDICTION,
pausePolling: !waveId,
enabled: !!waveId,
});

// Compare with nowMinting name (case-insensitive, trimmed)
Expand Down
Loading
Loading