From 29cba0a971da0fc9eb7c0e00d67630ed9b9c0c11 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Tue, 27 Feb 2024 12:38:45 +0100 Subject: [PATCH 1/6] Call `nextFetchPolicy` with "variables-changed" even if there is a `fetchPolicy` specified. fixes #11365 --- .changeset/tasty-chairs-dress.md | 5 + src/core/ObservableQuery.ts | 5 +- src/react/hooks/__tests__/useQuery.test.tsx | 133 ++++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 .changeset/tasty-chairs-dress.md diff --git a/.changeset/tasty-chairs-dress.md b/.changeset/tasty-chairs-dress.md new file mode 100644 index 00000000000..459c72bd44b --- /dev/null +++ b/.changeset/tasty-chairs-dress.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Call `nextFetchPolicy` with "variables-changed" even if there is a `fetchPolicy` specified. (fixes #11365) diff --git a/src/core/ObservableQuery.ts b/src/core/ObservableQuery.ts index 2b42f3b044e..d4c74e2953d 100644 --- a/src/core/ObservableQuery.ts +++ b/src/core/ObservableQuery.ts @@ -908,7 +908,10 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`, options.fetchPolicy !== "standby" && // If we're changing the fetchPolicy anyway, don't try to change it here // using applyNextFetchPolicy. The explicit options.fetchPolicy wins. - options.fetchPolicy === oldFetchPolicy + (options.fetchPolicy === oldFetchPolicy || + // A `nextFetchPolicy` function has even higher priority, though, + // so in that case `applyNextFetchPolicy` must be called. + typeof options.nextFetchPolicy === "function") ) { this.applyNextFetchPolicy("variables-changed", options); if (newNetworkStatus === void 0) { diff --git a/src/react/hooks/__tests__/useQuery.test.tsx b/src/react/hooks/__tests__/useQuery.test.tsx index 072c3c9e5cb..0514cb8b1a1 100644 --- a/src/react/hooks/__tests__/useQuery.test.tsx +++ b/src/react/hooks/__tests__/useQuery.test.tsx @@ -11,6 +11,7 @@ import { OperationVariables, TypedDocumentNode, WatchQueryFetchPolicy, + WatchQueryOptions, } from "../../../core"; import { InMemoryCache } from "../../../cache"; import { ApolloProvider } from "../../context"; @@ -6222,6 +6223,138 @@ describe("useQuery Hook", () => { expect(reasons).toEqual(["variables-changed", "after-fetch"]); }); + + it.only("should prioritize a `nextFetchPolicy` function over a `fetchPolicy` option when changing variables", async () => { + const query = gql` + { + hello + } + `; + const link = new MockLink([ + { + request: { query, variables: { id: 1 } }, + result: { data: { hello: "from link" } }, + delay: 10, + }, + { + request: { query, variables: { id: 2 } }, + result: { data: { hello: "from link2" } }, + delay: 10, + }, + ]); + + const client = new ApolloClient({ + cache: new InMemoryCache(), + link, + }); + + const fetchQueryByPolicySpy = jest.spyOn( + client["queryManager"] as any as { + fetchQueryByPolicy( + info: {}, + options: { fetchPolicy: WatchQueryFetchPolicy } + ): unknown; + }, + "fetchQueryByPolicy" + ); + const expectQueryTriggered = ( + nth: number, + fetchPolicy: WatchQueryFetchPolicy + ) => { + expect(fetchQueryByPolicySpy).toHaveBeenCalledTimes(nth); + expect(fetchQueryByPolicySpy).toHaveBeenNthCalledWith( + nth, + expect.anything(), + expect.objectContaining({ fetchPolicy }), + expect.any(Number) + ); + }; + let nextFetchPolicy: WatchQueryOptions< + OperationVariables, + any + >["nextFetchPolicy"] = (_, context) => { + if (context.reason === "variables-changed") { + return "cache-and-network"; + } else if (context.reason === "after-fetch") { + return "cache-only"; + } + throw new Error("should never happen"); + }; + nextFetchPolicy = jest.fn(nextFetchPolicy); + + const { result, rerender } = renderHook< + QueryResult, + { + variables: { id: number }; + } + >( + ({ variables }) => + useQuery(query, { + fetchPolicy: "network-only", + variables, + notifyOnNetworkStatusChange: true, + nextFetchPolicy, + }), + { + initialProps: { + variables: { id: 1 }, + }, + wrapper: ({ children }) => ( + {children} + ), + } + ); + // first network request triggers with initial fetchPolicy + expectQueryTriggered(1, "network-only"); + + await waitFor(() => { + expect(result.current.networkStatus).toBe(NetworkStatus.ready); + }); + + expect(nextFetchPolicy).toHaveBeenCalledTimes(1); + expect(nextFetchPolicy).toHaveBeenNthCalledWith( + 1, + "network-only", + expect.objectContaining({ + reason: "after-fetch", + }) + ); + // `nextFetchPolicy(..., {reason: "after-fetch"})` changed it to + // cache-only + expect(result.current.observable.options.fetchPolicy).toBe("cache-only"); + + rerender({ + variables: { id: 2 }, + }); + + expect(nextFetchPolicy).toHaveBeenNthCalledWith( + 2, + // has been reset to the initial `fetchPolicy` of "network-only" because + // we changed variables, then `nextFetchPolicy` is called + "network-only", + expect.objectContaining({ + reason: "variables-changed", + }) + ); + // the return value of `nextFetchPolicy(..., {reason: "variables-changed"})` + expectQueryTriggered(2, "cache-and-network"); + + await waitFor(() => { + expect(result.current.networkStatus).toBe(NetworkStatus.ready); + }); + + expect(nextFetchPolicy).toHaveBeenCalledTimes(3); + expect(nextFetchPolicy).toHaveBeenNthCalledWith( + 3, + "cache-and-network", + expect.objectContaining({ + reason: "after-fetch", + }) + ); + // `nextFetchPolicy(..., {reason: "after-fetch"})` changed it to + // cache-only + expect(result.current.observable.options.fetchPolicy).toBe("cache-only"); + }); }); describe("Missing Fields", () => { From 15efbff32415e4c39ae642d5c134947a15387a0a Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Tue, 27 Feb 2024 13:27:49 +0100 Subject: [PATCH 2/6] update size-limits --- .size-limits.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.size-limits.json b/.size-limits.json index 8d2f9f6c9d7..c5972945777 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39075, - "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32584 + "dist/apollo-client.min.cjs": 39090, + "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32592 } From 39737d8133a11b587f1831c06b58e9b7346a4d0d Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Tue, 27 Feb 2024 13:36:19 +0100 Subject: [PATCH 3/6] remove `.only` --- src/react/hooks/__tests__/useQuery.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/react/hooks/__tests__/useQuery.test.tsx b/src/react/hooks/__tests__/useQuery.test.tsx index 0514cb8b1a1..11a09c59b1c 100644 --- a/src/react/hooks/__tests__/useQuery.test.tsx +++ b/src/react/hooks/__tests__/useQuery.test.tsx @@ -6224,7 +6224,7 @@ describe("useQuery Hook", () => { expect(reasons).toEqual(["variables-changed", "after-fetch"]); }); - it.only("should prioritize a `nextFetchPolicy` function over a `fetchPolicy` option when changing variables", async () => { + it("should prioritize a `nextFetchPolicy` function over a `fetchPolicy` option when changing variables", async () => { const query = gql` { hello From 15131cdb48479016826902896ce1c0bf603df71d Mon Sep 17 00:00:00 2001 From: phryneas Date: Mon, 8 Jul 2024 08:30:55 +0000 Subject: [PATCH 4/6] Clean up Prettier, Size-limit, and Api-Extractor --- .size-limits.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.size-limits.json b/.size-limits.json index 5cca69e7256..c9a1233d358 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39906, - "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32896 + "dist/apollo-client.min.cjs": 39924, + "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32903 } From bb7aaf37ded10985b8e8ebb42ab5c748c03d1d15 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Mon, 8 Jul 2024 11:19:29 +0200 Subject: [PATCH 5/6] use `mockFetchQuery` helper in test --- src/react/hooks/__tests__/useQuery.test.tsx | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/react/hooks/__tests__/useQuery.test.tsx b/src/react/hooks/__tests__/useQuery.test.tsx index cf3f235c6e4..19a1ba57687 100644 --- a/src/react/hooks/__tests__/useQuery.test.tsx +++ b/src/react/hooks/__tests__/useQuery.test.tsx @@ -37,6 +37,7 @@ import { } from "../../../testing/internal"; import { useApolloClient } from "../useApolloClient"; import { useLazyQuery } from "../useLazyQuery"; +import { mockFetchQuery } from "../../../core/__tests__/ObservableQuery"; const IS_REACT_17 = React.version.startsWith("17"); @@ -7097,21 +7098,14 @@ describe("useQuery Hook", () => { link, }); - const fetchQueryByPolicySpy = jest.spyOn( - client["queryManager"] as any as { - fetchQueryByPolicy( - info: {}, - options: { fetchPolicy: WatchQueryFetchPolicy } - ): unknown; - }, - "fetchQueryByPolicy" - ); + const mocks = mockFetchQuery(client["queryManager"]); + const expectQueryTriggered = ( nth: number, fetchPolicy: WatchQueryFetchPolicy ) => { - expect(fetchQueryByPolicySpy).toHaveBeenCalledTimes(nth); - expect(fetchQueryByPolicySpy).toHaveBeenNthCalledWith( + expect(mocks.fetchQueryByPolicy).toHaveBeenCalledTimes(nth); + expect(mocks.fetchQueryByPolicy).toHaveBeenNthCalledWith( nth, expect.anything(), expect.objectContaining({ fetchPolicy }), From 014d370ec3d76da8f3dd2c720c7ca88ce437b15e Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Mon, 8 Jul 2024 11:19:41 +0200 Subject: [PATCH 6/6] fix detail in test-tsconfig.json --- src/tsconfig.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tsconfig.json b/src/tsconfig.json index efeb2f2da38..d7e90510ecc 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -5,6 +5,8 @@ { "compilerOptions": { "noEmit": true, + "declaration": false, + "declarationMap": false, "lib": ["es2015", "esnext.asynciterable", "ES2021.WeakRef"], "types": ["jest", "node", "./testing/matchers/index.d.ts"] },