From acd1982a59ed66fc44fa9e70b08a31c69dac35a6 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Mon, 15 Apr 2024 18:48:36 -0400 Subject: [PATCH] feat: deep merge resolvers (#11760) --- .changeset/cold-dancers-call.md | 5 + package-lock.json | 1 + package.json | 1 + .../__tests__/persisted-queries.test.ts | 3 +- .../__tests__/createTestSchema.test.tsx | 209 ++++++++++++++---- src/testing/experimental/createTestSchema.ts | 12 +- 6 files changed, 185 insertions(+), 46 deletions(-) create mode 100644 .changeset/cold-dancers-call.md diff --git a/.changeset/cold-dancers-call.md b/.changeset/cold-dancers-call.md new file mode 100644 index 00000000000..08885f636e3 --- /dev/null +++ b/.changeset/cold-dancers-call.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": minor +--- + +`createTestSchema` now uses graphql-tools `mergeResolvers` to merge resolvers instead of a shallow merge. diff --git a/package-lock.json b/package-lock.json index 21f6f038d9e..b8a3a86d396 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "@babel/parser": "7.24.1", "@changesets/changelog-github": "0.5.0", "@changesets/cli": "2.27.1", + "@graphql-tools/merge": "^9.0.3", "@graphql-tools/schema": "10.0.3", "@graphql-tools/utils": "10.0.13", "@microsoft/api-extractor": "7.42.3", diff --git a/package.json b/package.json index bbab1a55392..43676ccc64c 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "@babel/parser": "7.24.1", "@changesets/changelog-github": "0.5.0", "@changesets/cli": "2.27.1", + "@graphql-tools/merge": "^9.0.3", "@graphql-tools/schema": "10.0.3", "@graphql-tools/utils": "10.0.13", "@microsoft/api-extractor": "7.42.3", diff --git a/src/link/persisted-queries/__tests__/persisted-queries.test.ts b/src/link/persisted-queries/__tests__/persisted-queries.test.ts index e970af26d6f..7b4fecaf99f 100644 --- a/src/link/persisted-queries/__tests__/persisted-queries.test.ts +++ b/src/link/persisted-queries/__tests__/persisted-queries.test.ts @@ -544,7 +544,8 @@ describe("failure path", () => { ); it.each([ - ["error message", giveUpResponse], + // TODO(fixme): test flake on CI https://github.com/apollographql/apollo-client/issues/11782 + // ["error message", giveUpResponse], ["error code", giveUpResponseWithCode], ] as const)( "clears the cache when receiving NotSupported error (%s)", diff --git a/src/testing/experimental/__tests__/createTestSchema.test.tsx b/src/testing/experimental/__tests__/createTestSchema.test.tsx index 3d3eab1597b..99b579ddde2 100644 --- a/src/testing/experimental/__tests__/createTestSchema.test.tsx +++ b/src/testing/experimental/__tests__/createTestSchema.test.tsx @@ -11,7 +11,6 @@ import { createProfiler, renderWithClient, spyOnConsole, - useTrackRenders, } from "../../internal/index.js"; import { createTestSchema } from "../createTestSchema.js"; import { GraphQLError, buildSchema } from "graphql"; @@ -109,7 +108,6 @@ function createTrackedErrorComponents( Profiler: Profiler ) { function ErrorFallback({ error }: FallbackProps) { - useTrackRenders({ name: "ErrorFallback" }); Profiler.mergeSnapshot({ error } as Partial); return
Error
; @@ -197,7 +195,6 @@ describe("schema proxy", () => { `; const Fallback = () => { - useTrackRenders(); return
Loading...
; }; @@ -212,16 +209,14 @@ describe("schema proxy", () => { const Child = () => { const result = useSuspenseQuery(query); - useTrackRenders(); - Profiler.mergeSnapshot({ result, - } as Partial<{}>); + }); return
Hello
; }; - const { unmount } = renderWithClient(, { + renderWithClient(, { client, wrapper: Profiler, }); @@ -247,8 +242,6 @@ describe("schema proxy", () => { }, }); } - - unmount(); }); it("allows schema forking with .fork", async () => { @@ -290,7 +283,6 @@ describe("schema proxy", () => { `; const Fallback = () => { - useTrackRenders(); return
Loading...
; }; @@ -305,8 +297,6 @@ describe("schema proxy", () => { const Child = () => { const result = useSuspenseQuery(query); - useTrackRenders(); - Profiler.mergeSnapshot({ result, } as Partial<{}>); @@ -314,7 +304,7 @@ describe("schema proxy", () => { return
Hello
; }; - const { unmount } = renderWithClient(, { + renderWithClient(, { client, wrapper: Profiler, }); @@ -343,13 +333,24 @@ describe("schema proxy", () => { }, }); } - - unmount(); }); - it("does not pollute the original schema", async () => { + it("schema.fork does not pollute the original schema", async () => { const Profiler = createDefaultProfiler(); + schema.fork({ + resolvers: { + Query: { + viewer: () => ({ + book: { + colors: ["red", "blue", "green"], + title: "The Book", + }, + }), + }, + }, + }); + using _fetch = createSchemaFetch(schema).mockGlobal(); const client = new ApolloClient({ @@ -373,7 +374,6 @@ describe("schema proxy", () => { `; const Fallback = () => { - useTrackRenders(); return
Loading...
; }; @@ -388,8 +388,6 @@ describe("schema proxy", () => { const Child = () => { const result = useSuspenseQuery(query); - useTrackRenders(); - Profiler.mergeSnapshot({ result, } as Partial<{}>); @@ -466,7 +464,6 @@ describe("schema proxy", () => { `; const Fallback = () => { - useTrackRenders(); return
Loading...
; }; @@ -481,8 +478,6 @@ describe("schema proxy", () => { const Child = () => { const result = useSuspenseQuery(query); - useTrackRenders(); - Profiler.mergeSnapshot({ result, } as Partial<{}>); @@ -580,7 +575,6 @@ describe("schema proxy", () => { `; const Fallback = () => { - useTrackRenders(); return
Loading...
; }; @@ -596,8 +590,6 @@ describe("schema proxy", () => { const result = useSuspenseQuery(query); const [changeViewerName] = useMutation(mutation); - useTrackRenders(); - Profiler.mergeSnapshot({ result, } as Partial<{}>); @@ -712,7 +704,6 @@ describe("schema proxy", () => { }); const Fallback = () => { - useTrackRenders(); return
Loading...
; }; @@ -729,8 +720,6 @@ describe("schema proxy", () => { const Child = () => { const result = useSuspenseQuery(query); - useTrackRenders(); - Profiler.mergeSnapshot({ result, } as Partial<{}>); @@ -790,7 +779,6 @@ describe("schema proxy", () => { }); const Fallback = () => { - useTrackRenders(); return
Loading...
; }; @@ -807,8 +795,6 @@ describe("schema proxy", () => { const Child = () => { const result = useSuspenseQuery(query); - useTrackRenders(); - Profiler.mergeSnapshot({ result, } as Partial<{}>); @@ -875,10 +861,9 @@ describe("schema proxy", () => { resolvers: { Query: { viewer: () => ({ - name: "Virginia", book: { colors: ["red", "blue", "green"], - title: "The Book", + title: "A New Book", }, }), }, @@ -942,7 +927,6 @@ describe("schema proxy", () => { `; const Fallback = () => { - useTrackRenders(); return
Loading...
; }; @@ -958,8 +942,6 @@ describe("schema proxy", () => { const result = useSuspenseQuery(query); const [changeViewerName] = useMutation(mutation); - useTrackRenders(); - Profiler.mergeSnapshot({ result, } as Partial<{}>); @@ -996,7 +978,7 @@ describe("schema proxy", () => { colors: ["red", "blue", "green"], id: "1", publishedAt: "2024-01-01", - title: "The Book", + title: "A New Book", }, }, }); @@ -1019,7 +1001,155 @@ describe("schema proxy", () => { colors: ["red", "blue", "green"], id: "1", publishedAt: "2024-01-01", - title: "The Book", + title: "A New Book", + }, + }, + }); + } + }); + + it("resets the schema with schema.reset()", async () => { + const resetTestSchema = createTestSchema(schema, { + resolvers: { + Query: { + viewer: () => ({ + book: { + text: "Hello World", + title: "Orlando: A Biography", + }, + }), + }, + Book: { + __resolveType: (obj) => { + if ("text" in obj) { + return "TextBook"; + } + if ("colors" in obj) { + return "ColoringBook"; + } + throw new Error("Could not resolve type"); + }, + }, + }, + }); + const Profiler = createDefaultProfiler(); + + resetTestSchema.add({ + resolvers: { + Query: { + viewer: () => ({ + book: { + text: "Hello World", + title: "The Waves", + }, + }), + }, + }, + }); + + using _fetch = createSchemaFetch(resetTestSchema).mockGlobal(); + + const client = new ApolloClient({ + cache: new InMemoryCache(), + uri, + }); + + const query: TypedDocumentNode = gql` + query { + viewer { + id + name + age + book { + id + title + publishedAt + ... on ColoringBook { + colors + } + } + } + } + `; + + const Fallback = () => { + return
Loading...
; + }; + + const App = () => { + return ( + }> + + + ); + }; + + const Child = () => { + const result = useSuspenseQuery(query); + + Profiler.mergeSnapshot({ + result, + } as Partial<{}>); + + return ( +
+ Hello +
+ ); + }; + + renderWithClient(, { + client, + wrapper: Profiler, + }); + + // initial suspended render + await Profiler.takeRender(); + + { + const { snapshot } = await Profiler.takeRender(); + + expect(snapshot.result?.data).toEqual({ + viewer: { + __typename: "User", + age: 42, + id: "1", + name: "String", + book: { + __typename: "TextBook", + id: "1", + publishedAt: "2024-01-01", + // value set in this test with .add + title: "The Waves", + }, + }, + }); + } + + resetTestSchema.reset(); + + const user = userEvent.setup(); + + await act(() => user.click(screen.getByText("Refetch"))); + + // initial suspended render + await Profiler.takeRender(); + + { + const { snapshot } = await Profiler.takeRender(); + + expect(snapshot.result?.data).toEqual({ + viewer: { + __typename: "User", + age: 42, + id: "1", + name: "String", + book: { + __typename: "TextBook", + id: "1", + publishedAt: "2024-01-01", + // original value + title: "Orlando: A Biography", }, }, }); @@ -1057,7 +1187,6 @@ describe("schema proxy", () => { `; const Fallback = () => { - useTrackRenders(); return
Loading...
; }; @@ -1072,8 +1201,6 @@ describe("schema proxy", () => { const Child = () => { const result = useSuspenseQuery(query); - useTrackRenders(); - Profiler.mergeSnapshot({ result, } as Partial<{}>); diff --git a/src/testing/experimental/createTestSchema.ts b/src/testing/experimental/createTestSchema.ts index 694b821ae1f..5a4002c2902 100644 --- a/src/testing/experimental/createTestSchema.ts +++ b/src/testing/experimental/createTestSchema.ts @@ -1,6 +1,6 @@ -import { addResolversToSchema } from "@graphql-tools/schema"; import type { GraphQLSchema } from "graphql"; - +import { addResolversToSchema } from "@graphql-tools/schema"; +import { mergeResolvers } from "@graphql-tools/merge"; import { createMockSchema } from "./graphql-tools/utils.js"; import type { Resolvers } from "../../core/types.js"; @@ -63,7 +63,9 @@ const createTestSchema = ( const fns: TestSchemaFns = { add: ({ resolvers: newResolvers }) => { - targetResolvers = { ...targetResolvers, ...newResolvers }; + // @ts-ignore TODO(fixme): IResolvers type does not play well with our Resolvers + targetResolvers = mergeResolvers([targetResolvers, newResolvers]); + targetSchema = addResolversToSchema({ schema: targetSchema, resolvers: targetResolvers, @@ -74,7 +76,9 @@ const createTestSchema = ( fork: ({ resolvers: newResolvers } = {}) => { return createTestSchema(targetSchema, { - resolvers: newResolvers ?? targetResolvers, + // @ts-ignore TODO(fixme): IResolvers type does not play well with our Resolvers + resolvers: + mergeResolvers([targetResolvers, newResolvers]) ?? targetResolvers, scalars: options.scalars, }); },