-
Notifications
You must be signed in to change notification settings - Fork 7
Stop the feed new-posts poll running in background tabs #1563
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| import React from "react"; | ||
| import { render, act } from "@testing-library/react"; | ||
| import "@testing-library/jest-dom"; | ||
| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | ||
| import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; | ||
| import type { Entry } from "@/entities"; | ||
|
|
||
| const fetchSpy = vi.hoisted(() => vi.fn(async () => [] as Entry[])); | ||
|
|
||
| vi.mock("@/utils", async () => ({ | ||
| ...(await vi.importActual<typeof import("@/utils")>("@/utils")), | ||
| random: vi.fn(), | ||
| getAccessToken: vi.fn(() => "mock-token") | ||
| })); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| vi.mock("@ecency/sdk", async () => ({ | ||
| ...(await vi.importActual<Record<string, unknown>>("@ecency/sdk")), | ||
| getPostsRankedQueryOptions: vi.fn(() => ({ | ||
| queryKey: ["posts", "ranked-page", "poll"], | ||
| queryFn: fetchSpy | ||
|
qodo-code-review[bot] marked this conversation as resolved.
Outdated
|
||
| })) | ||
| })); | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| vi.mock("@/api/queries", async () => ({ | ||
| ...(await vi.importActual<Record<string, unknown>>("@/api/queries")), | ||
| usePostsFeedQuery: () => ({ data: undefined, isFetching: false }) | ||
| })); | ||
| vi.mock("@/features/shared/entry-list-content", () => ({ EntryListContent: () => null })); | ||
| vi.mock("@/features/shared/linear-progress", () => ({ LinearProgress: () => null })); | ||
| vi.mock("@/features/shared/user-avatar", () => ({ UserAvatar: () => null })); | ||
|
|
||
| import { FeedLayout } from "@/app/(dynamicPages)/feed/_components/feed-layout"; | ||
|
|
||
| function setVisibility(state: "visible" | "hidden") { | ||
| Object.defineProperty(document, "visibilityState", { value: state, configurable: true }); | ||
| document.dispatchEvent(new Event("visibilitychange")); | ||
|
qodo-code-review[bot] marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| function renderFeed() { | ||
| const client = new QueryClient({ | ||
| defaultOptions: { queries: { retry: false, staleTime: 0, gcTime: 0 } } | ||
| }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. Singleton cache leaks across tests The spec provides a new QueryClient, but FeedLayout polls through the separate global client returned by getQueryClient(), whose 60-second cache is never cleared. Cached poll data can therefore suppress fetchSpy calls in reordered, repeated, or watch-mode runs, making the visibility assertions order-dependent. Agent Prompt
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Valid, and worse than stated: Fixed in 2a7f43a: the global client is cleared before and after every test. The gate itself was already verified by removing it and watching the hidden and catch-up tests fail, so the assertions were testing the right thing, but they could have stopped doing so silently. |
||
| return render( | ||
| <QueryClientProvider client={client}> | ||
|
qodo-code-review[bot] marked this conversation as resolved.
Outdated
|
||
| <FeedLayout tag="" filter="trending" observer="ecency"> | ||
| <div /> | ||
| </FeedLayout> | ||
| </QueryClientProvider> | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * The poll fetches a full 20-post ranked page, 257 KB gzipped on /trending, and | ||
| * it runs for anonymous readers too. A background tab has nobody to show the | ||
| * "new posts" chip to, so it should not be paying for one. | ||
| */ | ||
| describe("feed poll", () => { | ||
| beforeEach(() => { | ||
| vi.useFakeTimers(); | ||
| fetchSpy.mockClear(); | ||
| setVisibility("visible"); | ||
| }); | ||
| afterEach(() => { | ||
| vi.useRealTimers(); | ||
| vi.clearAllMocks(); | ||
| }); | ||
|
|
||
| it("polls while the tab is visible", async () => { | ||
| renderFeed(); | ||
| await act(async () => { | ||
| await vi.advanceTimersByTimeAsync(31_000); | ||
| }); | ||
| expect(fetchSpy).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("does not poll while the tab is hidden", async () => { | ||
| renderFeed(); | ||
| setVisibility("hidden"); | ||
| fetchSpy.mockClear(); | ||
|
|
||
| await act(async () => { | ||
| await vi.advanceTimersByTimeAsync(5 * 60_000); | ||
| }); | ||
|
|
||
| expect(fetchSpy).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("catches up as soon as the reader comes back", async () => { | ||
| renderFeed(); | ||
| setVisibility("hidden"); | ||
| await act(async () => { | ||
| await vi.advanceTimersByTimeAsync(5 * 60_000); | ||
| }); | ||
| fetchSpy.mockClear(); | ||
|
|
||
| await act(async () => { | ||
| setVisibility("visible"); | ||
| await vi.advanceTimersByTimeAsync(0); | ||
| }); | ||
|
|
||
| // No waiting out the rest of an interval for a chip that is already stale. | ||
| expect(fetchSpy).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("stops listening once the feed unmounts", async () => { | ||
| const { unmount } = renderFeed(); | ||
| unmount(); | ||
| fetchSpy.mockClear(); | ||
|
|
||
| await act(async () => { | ||
| setVisibility("visible"); | ||
| await vi.advanceTimersByTimeAsync(2 * 60_000); | ||
| }); | ||
|
|
||
| expect(fetchSpy).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.