From 8e5c653a1ec87c74f86cf53de82a792dcfc99a32 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 20 Mar 2024 11:28:39 -0600 Subject: [PATCH 1/7] Add failing test for default set error policy --- .../hooks/__tests__/useSuspenseQuery.test.tsx | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) diff --git a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx index 4e32f276c72..b1e7d3a932f 100644 --- a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx +++ b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx @@ -10109,6 +10109,225 @@ describe("useSuspenseQuery", () => { await expect(Profiler).not.toRerender(); }); + // https://github.com/apollographql/apollo-client/issues/11708 + it("`fetchMore` works with startTransition when setting errorPolicy as default option in ApolloClient constructor", async () => { + type Variables = { + offset: number; + }; + + interface Todo { + __typename: "Todo"; + id: string; + name: string; + completed: boolean; + } + interface Data { + todos: Todo[]; + } + const user = userEvent.setup(); + + const query: TypedDocumentNode = gql` + query TodosQuery($offset: Int!) { + todos(offset: $offset) { + id + name + completed + } + } + `; + + const mocks: MockedResponse[] = [ + { + request: { query, variables: { offset: 0 } }, + result: { + data: { + todos: [ + { + __typename: "Todo", + id: "1", + name: "Clean room", + completed: false, + }, + ], + }, + }, + delay: 10, + }, + { + request: { query, variables: { offset: 1 } }, + result: { + data: { + todos: [ + { + __typename: "Todo", + id: "2", + name: "Take out trash", + completed: true, + }, + ], + }, + }, + delay: 10, + }, + ]; + + const Profiler = createProfiler({ + initialSnapshot: { + isPending: false, + result: null as Pick< + UseSuspenseQueryResult, + "data" | "error" | "networkStatus" + > | null, + }, + }); + + function SuspenseFallback() { + useTrackRenders(); + + return
Loading...
; + } + + const client = new ApolloClient({ + link: new MockLink(mocks), + cache: new InMemoryCache({ + typePolicies: { + Query: { + fields: { + todos: offsetLimitPagination(), + }, + }, + }, + }), + defaultOptions: { + watchQuery: { + errorPolicy: 'all' + } + } + }); + + function App() { + useTrackRenders(); + const [isPending, startTransition] = React.useTransition(); + const { data, error, networkStatus, fetchMore } = useSuspenseQuery( + query, + { + variables: { offset: 0 }, + } + ); + + Profiler.mergeSnapshot({ + isPending, + result: { data, error, networkStatus }, + }); + + return ( + + ); + } + + render(, { + wrapper: ({ children }) => ( + + + }>{children} + + + ), + }); + + { + const { renderedComponents } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([SuspenseFallback]); + } + + { + const { snapshot } = await Profiler.takeRender(); + + expect(snapshot).toEqual({ + isPending: false, + result: { + data: { + todos: [ + { + __typename: "Todo", + id: "1", + name: "Clean room", + completed: false, + }, + ], + }, + error: undefined, + networkStatus: NetworkStatus.ready, + }, + }); + } + + await act(() => user.click(screen.getByText("Load more"))); + + { + const { snapshot, renderedComponents } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([App]); + expect(snapshot).toEqual({ + isPending: true, + result: { + data: { + todos: [ + { + __typename: "Todo", + id: "1", + name: "Clean room", + completed: false, + }, + ], + }, + error: undefined, + networkStatus: NetworkStatus.ready, + }, + }); + } + + { + const { snapshot, renderedComponents } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([App]); + expect(snapshot).toEqual({ + isPending: false, + result: { + data: { + todos: [ + { + __typename: "Todo", + id: "1", + name: "Clean room", + completed: false, + }, + { + __typename: "Todo", + id: "2", + name: "Take out trash", + completed: true, + }, + ], + }, + error: undefined, + networkStatus: NetworkStatus.ready, + }, + }); + } + + await expect(Profiler).not.toRerender(); + }); + describe.skip("type tests", () => { it("returns unknown when TData cannot be inferred", () => { const query = gql` From 514adcceb35d7986a76256416b3d2e8cd6b5589f Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 20 Mar 2024 11:32:46 -0600 Subject: [PATCH 2/7] Only check for changes to explicitly defined options --- src/react/internal/cache/QueryReference.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/react/internal/cache/QueryReference.ts b/src/react/internal/cache/QueryReference.ts index 83b4f20ac49..95cca795a2d 100644 --- a/src/react/internal/cache/QueryReference.ts +++ b/src/react/internal/cache/QueryReference.ts @@ -253,6 +253,7 @@ export class InternalQueryReference { didChangeOptions(watchQueryOptions: ObservedOptions) { return OBSERVED_CHANGED_OPTIONS.some( (option) => + option in watchQueryOptions && !equal(this.watchQueryOptions[option], watchQueryOptions[option]) ); } From a9871096e63e6933f48604f3ff33d791371187d4 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 20 Mar 2024 11:34:06 -0600 Subject: [PATCH 3/7] Fix formatting --- src/react/hooks/__tests__/useSuspenseQuery.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx index b1e7d3a932f..c6c9463ddb4 100644 --- a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx +++ b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx @@ -10200,9 +10200,9 @@ describe("useSuspenseQuery", () => { }), defaultOptions: { watchQuery: { - errorPolicy: 'all' - } - } + errorPolicy: "all", + }, + }, }); function App() { From 52e101e1d3233265369b42e231d747a0232d49f0 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 20 Mar 2024 11:43:29 -0600 Subject: [PATCH 4/7] Add similar test to useBackgroundQuery --- .../__tests__/useBackgroundQuery.test.tsx | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx index ac7477d51f7..3d2f662598e 100644 --- a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx +++ b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx @@ -5240,6 +5240,210 @@ describe("fetchMore", () => { await expect(Profiler).not.toRerender(); }); + + // https://github.com/apollographql/apollo-client/issues/11708 + it("`fetchMore` works with startTransition when setting errorPolicy as default option in ApolloClient constructor", async () => { + type Variables = { + offset: number; + }; + + interface Todo { + __typename: "Todo"; + id: string; + name: string; + completed: boolean; + } + interface Data { + todos: Todo[]; + } + const user = userEvent.setup(); + + const query: TypedDocumentNode = gql` + query TodosQuery($offset: Int!) { + todos(offset: $offset) { + id + name + completed + } + } + `; + + const mocks: MockedResponse[] = [ + { + request: { query, variables: { offset: 0 } }, + result: { + data: { + todos: [ + { + __typename: "Todo", + id: "1", + name: "Clean room", + completed: false, + }, + ], + }, + }, + delay: 10, + }, + { + request: { query, variables: { offset: 1 } }, + result: { + data: { + todos: [ + { + __typename: "Todo", + id: "2", + name: "Take out trash", + completed: true, + }, + ], + }, + }, + delay: 10, + }, + ]; + + const Profiler = createProfiler({ + initialSnapshot: { + isPending: false, + result: null as UseReadQueryResult | null, + }, + }); + + const { SuspenseFallback, ReadQueryHook } = + createDefaultTrackedComponents(Profiler); + + const client = new ApolloClient({ + link: new MockLink(mocks), + cache: new InMemoryCache({ + typePolicies: { + Query: { + fields: { + todos: offsetLimitPagination(), + }, + }, + }, + }), + defaultOptions: { + watchQuery: { + errorPolicy: "all", + }, + }, + }); + + function App() { + useTrackRenders(); + const [isPending, startTransition] = React.useTransition(); + const [queryRef, { fetchMore }] = useBackgroundQuery(query, { + variables: { offset: 0 }, + }); + + Profiler.mergeSnapshot({ isPending }); + + return ( + <> + + }> + + + + ); + } + + renderWithClient(, { client, wrapper: Profiler }); + + { + const { renderedComponents } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([App, SuspenseFallback]); + } + + { + const { snapshot } = await Profiler.takeRender(); + + expect(snapshot).toEqual({ + isPending: false, + result: { + data: { + todos: [ + { + __typename: "Todo", + id: "1", + name: "Clean room", + completed: false, + }, + ], + }, + error: undefined, + networkStatus: NetworkStatus.ready, + }, + }); + } + + await act(() => user.click(screen.getByText("Load more"))); + + { + const { snapshot, renderedComponents } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([App, ReadQueryHook]); + expect(snapshot).toEqual({ + isPending: true, + result: { + data: { + todos: [ + { + __typename: "Todo", + id: "1", + name: "Clean room", + completed: false, + }, + ], + }, + error: undefined, + networkStatus: NetworkStatus.ready, + }, + }); + } + + { + const { snapshot, renderedComponents } = await Profiler.takeRender(); + + expect(renderedComponents).toStrictEqual([App, ReadQueryHook]); + expect(snapshot).toEqual({ + isPending: false, + result: { + data: { + todos: [ + { + __typename: "Todo", + id: "1", + name: "Clean room", + completed: false, + }, + { + __typename: "Todo", + id: "2", + name: "Take out trash", + completed: true, + }, + ], + }, + error: undefined, + networkStatus: NetworkStatus.ready, + }, + }); + } + + await expect(Profiler).not.toRerender(); + }); }); describe.skip("type tests", () => { From 0c23eec6413021ca8b3822d4639f039196b09377 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 20 Mar 2024 11:43:35 -0600 Subject: [PATCH 5/7] Add changeset --- .changeset/tasty-hotels-press.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tasty-hotels-press.md diff --git a/.changeset/tasty-hotels-press.md b/.changeset/tasty-hotels-press.md new file mode 100644 index 00000000000..c79a5db61d6 --- /dev/null +++ b/.changeset/tasty-hotels-press.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Fix issue where setting a default `watchQuery` option in the `ApolloClient` constructor could break `startTransition` when used with suspense hooks. From 82d51e3774ec456d3c8fee5dddb4813cac485dd8 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 20 Mar 2024 12:00:08 -0600 Subject: [PATCH 6/7] Update size limits --- .size-limits.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limits.json b/.size-limits.json index 6ea6a4c6ebc..b96592bb2ae 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39267, + "dist/apollo-client.min.cjs": 39272, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32630 } From 0827aba26ea4f00d051083b08606836d84e6ca11 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 20 Mar 2024 12:04:20 -0600 Subject: [PATCH 7/7] Update size limits --- .size-limits.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limits.json b/.size-limits.json index 2c7dbd56314..10e91f0ebf7 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39273, + "dist/apollo-client.min.cjs": 39277, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32630 }