Skip to content
Closed
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
265 changes: 262 additions & 3 deletions __tests__/components/waves/drops/WaveDropsAll.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,13 @@ const mockFetchNextPage = jest.fn();
const mockWaitAndRevealDrop = jest.fn();
const mockRemoveNotifications = jest.fn();
const mockCommonApiPost = jest.fn();
const mockSetToast = jest.fn();
const mockAddress = "0xAAA";
const mockJwt = "test-jwt";
const mockJwtExp = 4102444800;
const SERIAL_FIND_FAILURE_TOAST = "Could not find that drop in history.";
const SERIAL_JUMP_FAILURE_TOAST =
"Could not jump to that drop. Please try again.";

const useVirtualizedWaveDropsMock =
useVirtualizedWaveDrops as jest.MockedFunction<
Expand Down Expand Up @@ -292,6 +296,7 @@ function setupMocks(options: MockSetupOptions = {}) {
require("@/components/auth/Auth").useAuth.mockReturnValue({
connectedProfile: options.auth?.connectedProfile ?? null,
activeProfileProxy: null,
setToast: mockSetToast,
});

require("@/components/auth/SeizeConnectContext").useSeizeConnectContext.mockReturnValue(
Expand Down Expand Up @@ -371,6 +376,12 @@ function renderComponent(options: RenderOptions = {}) {
};
}

async function advanceTimersByTime(ms: number) {
await act(async () => {
await jest.advanceTimersByTimeAsync(ms);
});
}

describe("WaveDropsAll", () => {
beforeEach(() => {
// Mock setTimeout for tests that need it
Expand Down Expand Up @@ -917,23 +928,181 @@ describe("WaveDropsAll", () => {

await waitFor(() => {
expect(mockFetchNextPage).toHaveBeenCalledWith(
{
expect.objectContaining({
waveId: "test-wave-1",
type: "LIGHT",
targetSerialNo: 10,
},
onSerialScrollFailure: expect.any(Function),
}),
null
);
});

await waitFor(() => {
expect(dropsProps.suspendLightDropHydration).toBe(false);
});
await waitFor(() => {
expect(mockSetToast).toHaveBeenCalledWith({
message: SERIAL_JUMP_FAILURE_TOAST,
type: "error",
});
});
expect(mockWaitAndRevealDrop).not.toHaveBeenCalled();
consoleWarn.mockRestore();
});

it("releases light drop hydration when target reveal fails", async () => {
it("reveals locally and does not toast when target fetching returns null", async () => {
const consoleWarn = jest
.spyOn(console, "warn")
.mockImplementation(() => {});

setupMocks({
waveMessages: {
drops: [createMockDrop({ id: "drop-50", serial_no: 50 })],
hasNextPage: true,
isLoading: false,
isLoadingNextPage: false,
},
});

mockFetchNextPage.mockResolvedValueOnce(null);
mockWaitAndRevealDrop.mockResolvedValueOnce(true);

renderComponent({ initialDrop: 10 });

const targetElement = document.createElement("div");
targetElement.scrollIntoView = jest.fn();
Object.defineProperty(targetElement, "getBoundingClientRect", {
value: () => ({ top: 0, bottom: 1 }),
});
dropsProps.targetDropRef.current = targetElement;

expect(dropsProps.suspendLightDropHydration).toBe(true);

await waitFor(() => {
expect(mockFetchNextPage).toHaveBeenCalledWith(
expect.objectContaining({
waveId: "test-wave-1",
type: "LIGHT",
targetSerialNo: 10,
onSerialScrollFailure: expect.any(Function),
}),
null
);
});

await waitFor(() => {
expect(mockWaitAndRevealDrop).toHaveBeenCalledWith(10);
});
await waitFor(() => {
expect(targetElement.scrollIntoView).toHaveBeenCalled();
});
act(() => {
jest.advanceTimersByTime(600);
});
await waitFor(() => {
expect(dropsProps.suspendLightDropHydration).toBe(false);
});
expect(screen.getByTestId("scrolling-overlay").style.display).toBe(
"none"
);
expect(mockSetToast).not.toHaveBeenCalled();
consoleWarn.mockRestore();
});

it("shows jump toast when target fetching returns null and local reveal fails", async () => {
const consoleWarn = jest
.spyOn(console, "warn")
.mockImplementation(() => {});

setupMocks({
waveMessages: {
drops: [createMockDrop({ id: "drop-50", serial_no: 50 })],
hasNextPage: true,
isLoading: false,
isLoadingNextPage: false,
},
});

mockFetchNextPage.mockResolvedValueOnce(null);
mockWaitAndRevealDrop.mockResolvedValueOnce(false);

renderComponent({ initialDrop: 10 });

expect(dropsProps.suspendLightDropHydration).toBe(true);

await waitFor(() => {
expect(mockWaitAndRevealDrop).toHaveBeenCalledWith(10);
});

await waitFor(() => {
expect(dropsProps.suspendLightDropHydration).toBe(false);
});
expect(mockSetToast).toHaveBeenCalledWith({
message: SERIAL_JUMP_FAILURE_TOAST,
type: "error",
});
consoleWarn.mockRestore();
});

it("does not toast when fetch reports target missing but local reveal succeeds", async () => {
const consoleWarn = jest
.spyOn(console, "warn")
.mockImplementation(() => {});

setupMocks({
waveMessages: {
drops: [createMockDrop({ id: "drop-50", serial_no: 50 })],
hasNextPage: true,
isLoading: false,
isLoadingNextPage: false,
},
});

mockFetchNextPage.mockImplementationOnce((params) => {
params.onSerialScrollFailure({
reason: "target_not_found",
waveId: "test-wave-1",
targetSerialNo: 10,
details: { requestsMade: 1 },
});
return Promise.resolve(null);
});
mockWaitAndRevealDrop.mockResolvedValueOnce(true);

renderComponent({ initialDrop: 10 });

const targetElement = document.createElement("div");
targetElement.scrollIntoView = jest.fn();
Object.defineProperty(targetElement, "getBoundingClientRect", {
value: () => ({ top: 0, bottom: 1 }),
});
dropsProps.targetDropRef.current = targetElement;

await waitFor(() => {
expect(mockWaitAndRevealDrop).toHaveBeenCalledWith(10);
});
await waitFor(() => {
expect(targetElement.scrollIntoView).toHaveBeenCalled();
});
act(() => {
jest.advanceTimersByTime(600);
});
await waitFor(() => {
expect(dropsProps.suspendLightDropHydration).toBe(false);
});
expect(screen.getByTestId("scrolling-overlay").style.display).toBe(
"none"
);
expect(mockSetToast).not.toHaveBeenCalled();
consoleWarn.mockRestore();
});

it("releases light drop hydration and shows find toast when target reveal fails", async () => {
const consoleWarn = jest
.spyOn(console, "warn")
.mockImplementation(() => {});

setupMocks({
waveMessages: {
drops: [createMockDrop({ id: "drop-50", serial_no: 50 })],
Expand All @@ -957,9 +1126,55 @@ describe("WaveDropsAll", () => {
await waitFor(() => {
expect(dropsProps.suspendLightDropHydration).toBe(false);
});
expect(mockSetToast).toHaveBeenCalledWith({
message: SERIAL_FIND_FAILURE_TOAST,
type: "error",
});
consoleWarn.mockRestore();
});

it("shows jump toast when revealed target never reaches the DOM", async () => {
const consoleWarn = jest
.spyOn(console, "warn")
.mockImplementation(() => {});

setupMocks({
waveMessages: {
drops: [createMockDrop({ id: "drop-50", serial_no: 50 })],
hasNextPage: true,
isLoading: false,
isLoadingNextPage: false,
},
});

mockFetchNextPage.mockResolvedValueOnce([]);
mockWaitAndRevealDrop.mockResolvedValueOnce(true);

renderComponent({ initialDrop: 10 });

await waitFor(() => {
expect(mockWaitAndRevealDrop).toHaveBeenCalledWith(10);
});

await advanceTimersByTime(3000);

await waitFor(() => {
expect(mockSetToast).toHaveBeenCalledWith({
message: SERIAL_JUMP_FAILURE_TOAST,
type: "error",
});
});
await waitFor(() => {
expect(dropsProps.suspendLightDropHydration).toBe(false);
});
consoleWarn.mockRestore();
});

it("keeps light drop hydration suspended until successful target scroll settles", async () => {
const consoleWarn = jest
.spyOn(console, "warn")
.mockImplementation(() => {});

setupMocks({
waveMessages: {
drops: [createMockDrop({ id: "drop-50", serial_no: 50 })],
Expand Down Expand Up @@ -999,6 +1214,50 @@ describe("WaveDropsAll", () => {
await waitFor(() => {
expect(dropsProps.suspendLightDropHydration).toBe(false);
});
expect(mockSetToast).not.toHaveBeenCalled();
consoleWarn.mockRestore();
});

it("clears overlay and shows jump toast when scroll operation watchdog expires", async () => {
const consoleWarn = jest
.spyOn(console, "warn")
.mockImplementation(() => {});

setupMocks({
waveMessages: {
drops: [createMockDrop({ id: "drop-50", serial_no: 50 })],
hasNextPage: true,
isLoading: false,
isLoadingNextPage: false,
},
});

const neverSettles = new Promise<never>(() => {
// Keep the operation active until the watchdog timeout fires.
});
mockFetchNextPage.mockReturnValueOnce(neverSettles);

renderComponent({ initialDrop: 10 });

await waitFor(() => {
expect(screen.getByTestId("scrolling-overlay").style.display).toBe(
"block"
);
});

await advanceTimersByTime(10000);

await waitFor(() => {
expect(screen.getByTestId("scrolling-overlay").style.display).toBe(
"none"
);
});
expect(mockSetToast).toHaveBeenCalledWith({
message: SERIAL_JUMP_FAILURE_TOAST,
type: "error",
});
expect(dropsProps.suspendLightDropHydration).toBe(false);
consoleWarn.mockRestore();
});
});

Expand Down
Loading
Loading