From d3d0ed9b2218601e2861c7a3f4a98c3d920a0a17 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Fri, 16 Feb 2024 10:25:31 -0500 Subject: [PATCH 01/26] feat: introduce schema proxy, copy graphql-tools/utils fns and license into separate folder --- package-lock.json | 7 +- package.json | 1 + src/testing/core/mocking/mockSchema.ts | 0 src/testing/core/schemaProxy.ts | 36 +++ src/testing/graphql-tools/LICENSE | 21 ++ src/testing/graphql-tools/utils.test.ts | 307 ++++++++++++++++++++++++ src/testing/graphql-tools/utils.ts | 230 ++++++++++++++++++ 7 files changed, 599 insertions(+), 3 deletions(-) create mode 100644 src/testing/core/mocking/mockSchema.ts create mode 100644 src/testing/core/schemaProxy.ts create mode 100644 src/testing/graphql-tools/LICENSE create mode 100644 src/testing/graphql-tools/utils.test.ts create mode 100644 src/testing/graphql-tools/utils.ts diff --git a/package-lock.json b/package-lock.json index edd8e9e7dd0..f2ff820a2bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "@changesets/changelog-github": "0.5.0", "@changesets/cli": "2.27.1", "@graphql-tools/schema": "10.0.3", + "@graphql-tools/utils": "10.0.13", "@microsoft/api-extractor": "7.42.3", "@rollup/plugin-node-resolve": "11.2.1", "@size-limit/esbuild-why": "11.1.1", @@ -1861,9 +1862,9 @@ } }, "node_modules/@graphql-tools/utils": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.1.0.tgz", - "integrity": "sha512-wLPqhgeZ9BZJPRoaQbsDN/CtJDPd/L4qmmtPkjI3NuYJ39x+Eqz1Sh34EAGMuDh+xlOHqBwHczkZUpoK9tvzjw==", + "version": "10.0.13", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.0.13.tgz", + "integrity": "sha512-fMILwGr5Dm2zefNItjQ6C2rauigklv69LIwppccICuGTnGaOp3DspLt/6Lxj72cbg5d9z60Sr+Egco3CJKLsNg==", "dev": true, "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", diff --git a/package.json b/package.json index 3c153eff791..fc2481b7863 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "@changesets/changelog-github": "0.5.0", "@changesets/cli": "2.27.1", "@graphql-tools/schema": "10.0.3", + "@graphql-tools/utils": "10.0.13", "@microsoft/api-extractor": "7.42.3", "@rollup/plugin-node-resolve": "11.2.1", "@size-limit/esbuild-why": "11.1.1", diff --git a/src/testing/core/mocking/mockSchema.ts b/src/testing/core/mocking/mockSchema.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/testing/core/schemaProxy.ts b/src/testing/core/schemaProxy.ts new file mode 100644 index 00000000000..9f7147d7cdc --- /dev/null +++ b/src/testing/core/schemaProxy.ts @@ -0,0 +1,36 @@ +import { addResolversToSchema } from "@graphql-tools/schema"; +import type { GraphQLSchema } from "graphql"; +import type { Resolvers } from "../../core/types.js"; + +const proxiedSchema = ( + schemaWithMocks: GraphQLSchema, + resolvers: Resolvers +) => { + let targetSchema = addResolversToSchema({ + schema: schemaWithMocks, + resolvers, + }); + + const schema = new Proxy(targetSchema, { + get(target, p) { + if (p === "use") { + return (newResolvers: typeof resolvers) => + (targetSchema = addResolversToSchema({ + schema: schemaWithMocks, + resolvers: { + ...resolvers, + ...newResolvers, + }, + })); + } + if (typeof targetSchema[p] === "function") { + return targetSchema[p].bind(targetSchema); + } + return Reflect.get(target, p); + }, + }); + + return schema; +}; + +export { proxiedSchema }; diff --git a/src/testing/graphql-tools/LICENSE b/src/testing/graphql-tools/LICENSE new file mode 100644 index 00000000000..f5940526b77 --- /dev/null +++ b/src/testing/graphql-tools/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 The Guild, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/testing/graphql-tools/utils.test.ts b/src/testing/graphql-tools/utils.test.ts new file mode 100644 index 00000000000..02d23f6ea23 --- /dev/null +++ b/src/testing/graphql-tools/utils.test.ts @@ -0,0 +1,307 @@ +// Originally from @graphql-tools/mock +// https://github.com/ardatan/graphql-tools/blob/4b56b04d69b02919f6c5fa4f97d33da63f36e8c8/packages/mock/tests/addMocksToSchema.spec.ts + +import { buildSchema, graphql } from "graphql"; +import { createMockSchema } from "./utils.js"; + +const mockDate = new Date().toJSON().split("T")[0]; + +const mocks = { + Int: () => 6, + Float: () => 22.1, + String: () => "string", + ID: () => "id", + Date: () => mockDate, +}; + +const typeDefs = /* GraphQL */ ` + type User { + id: ID! + age: Int! + name: String! + image: UserImage! + book: Book! + } + + type Author { + _id: ID! + name: String! + book: Book! + } + + union UserImage = UserImageSolidColor | UserImageURL + + type UserImageSolidColor { + color: String! + } + + type UserImageURL { + url: String! + } + + scalar Date + + interface Book { + id: ID! + title: String + publishedAt: Date + } + + type TextBook implements Book { + id: ID! + title: String + publishedAt: Date + text: String + } + + type ColoringBook implements Book { + id: ID! + title: String + publishedAt: Date + colors: [String] + } + + type Query { + viewer: User! + userById(id: ID!): User! + author: Author! + } + + type Mutation { + changeViewerName(newName: String!): User! + } +`; + +const schema = buildSchema(typeDefs); + +describe("addMocksToSchema", () => { + it("basic", async () => { + const query = /* GraphQL */ ` + query { + viewer { + id + name + age + } + } + `; + + const mockedSchema = createMockSchema(schema, mocks); + + const { data, errors } = await graphql({ + schema: mockedSchema, + source: query, + }); + + expect(errors).not.toBeDefined(); + expect(data).toBeDefined(); + + const viewerData = data?.["viewer"] as any; + expect(typeof viewerData["id"]).toBe("string"); + expect(typeof viewerData["name"]).toBe("string"); + expect(typeof viewerData["age"]).toBe("number"); + + const { data: data2 } = await graphql({ + schema: mockedSchema, + source: query, + }); + + const viewerData2 = data2?.["viewer"] as any; + + expect(viewerData2["id"]).toEqual(viewerData["id"]); + }); + + it("handle _id key field", async () => { + const query = /* GraphQL */ ` + query { + author { + _id + name + } + } + `; + const mockedSchema = createMockSchema(schema, mocks); + const { data, errors } = await graphql({ + schema: mockedSchema, + source: query, + }); + + expect(errors).not.toBeDefined(); + expect(data).toBeDefined(); + const viewerData = data?.["author"] as any; + expect(typeof viewerData["_id"]).toBe("string"); + expect(typeof viewerData["name"]).toBe("string"); + + const { data: data2 } = await graphql({ + schema: mockedSchema, + source: query, + }); + + const viewerData2 = data2?.["author"] as any; + + expect(viewerData2["_id"]).toEqual(viewerData["_id"]); + }); + + it("should handle union type", async () => { + const query = /* GraphQL */ ` + query { + viewer { + image { + __typename + ... on UserImageURL { + url + } + ... on UserImageSolidColor { + color + } + } + } + } + `; + + const mockedSchema = createMockSchema(schema, mocks); + + const { data, errors } = await graphql({ + schema: mockedSchema, + source: query, + }); + + expect(errors).not.toBeDefined(); + expect(data).toBeDefined(); + expect((data!["viewer"] as any)["image"]["__typename"]).toBeDefined(); + }); + + it("should handle interface type", async () => { + const query = /* GraphQL */ ` + query { + viewer { + book { + title + __typename + ... on TextBook { + text + } + ... on ColoringBook { + colors + } + } + } + } + `; + + const mockedSchema = createMockSchema(schema, mocks); + + const { data, errors } = await graphql({ + schema: mockedSchema, + source: query, + }); + + expect(errors).not.toBeDefined(); + expect(data).toBeDefined(); + expect((data!["viewer"] as any)["book"]["__typename"]).toBeDefined(); + }); + + it("should handle custom scalars", async () => { + const query = /* GraphQL */ ` + query { + viewer { + book { + title + publishedAt + } + } + } + `; + + const mockedSchema = createMockSchema(schema, mocks); + + const { data, errors } = await graphql({ + schema: mockedSchema, + source: query, + }); + + expect(errors).not.toBeDefined(); + expect(data).toBeDefined(); + expect((data!["viewer"] as any)["book"]["publishedAt"]).toBe(mockDate); + }); + + it.skip("should handle null fields correctly", async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + foo: Foo + } + type Foo { + field1: String + field2: Int + } + `); + + const mockedSchema = createMockSchema(schema, { + Foo: () => ({ + field1: "text", + field2: null, + }), + }); + + const query = /* GraphQL */ ` + { + foo { + field1 + field2 + } + } + `; + const { data } = await graphql({ + schema: mockedSchema, + source: query, + }); + + const fooData = data?.["foo"] as any; + expect(fooData.field1).toBe("text"); + expect(fooData.field2).toBe(null); + }); + + it.skip("should handle null fields correctly in nested fields", async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + foo: Foo + } + type Foo { + foo_field: String + boo: Boo + } + type Boo { + boo_field: String + } + `); + + const mockedSchema = createMockSchema(schema, { + // TODO: should we allow mocking of concrete types? + Foo: () => ({ + foo_field: "text", + boo: null, + }), + }); + + const query = /* GraphQL */ ` + { + foo { + foo_field + boo { + boo_field + } + } + } + `; + const { data, errors } = await graphql({ + schema: mockedSchema, + source: query, + }); + + expect(errors).toBeFalsy(); + + const fooData = data?.["foo"] as any; + expect(fooData.foo_field).toBe("text"); + expect(fooData.boo).toBe(null); + }); +}); diff --git a/src/testing/graphql-tools/utils.ts b/src/testing/graphql-tools/utils.ts new file mode 100644 index 00000000000..65f6b89d588 --- /dev/null +++ b/src/testing/graphql-tools/utils.ts @@ -0,0 +1,230 @@ +import type { + GraphQLFieldResolver, + GraphQLObjectType, + GraphQLOutputType, + GraphQLSchema, +} from "graphql"; + +import { + GraphQLInterfaceType, + GraphQLString, + GraphQLUnionType, + defaultFieldResolver, + getNullableType, + isAbstractType, + isEnumType, + isInterfaceType, + isListType, + isObjectType, + isScalarType, + isUnionType, +} from "graphql"; + +import { isNonNullObject } from "../../utilities/index.js"; +import { MapperKind, mapSchema, getRootTypeNames } from "@graphql-tools/utils"; + +// Taken from @graphql-tools/mock: +// https://github.com/ardatan/graphql-tools/blob/4b56b04d69b02919f6c5fa4f97d33da63f36e8c8/packages/mock/src/utils.ts#L20 +const takeRandom = (arr: T[]) => arr[Math.floor(Math.random() * arr.length)]; + +const createMockSchema = ( + staticSchema: GraphQLSchema, + mocks: { [key: string]: any } +) => { + // Taken from @graphql-tools/mock: + // https://github.com/ardatan/graphql-tools/blob/5ed60e44f94868f976cd28fe1b6a764fb146bbe9/packages/mock/src/MockStore.ts#L613 + const getType = (typeName: string) => { + const type = staticSchema.getType(typeName); + + if (!type || !(isObjectType(type) || isInterfaceType(type))) { + throw new Error( + `${typeName} does not exist on schema or is not an object or interface` + ); + } + + return type; + }; + + // Taken from @graphql-tools/mock: + // https://github.com/ardatan/graphql-tools/blob/5ed60e44f94868f976cd28fe1b6a764fb146bbe9/packages/mock/src/MockStore.ts#L597 + const getFieldType = (typeName: string, fieldName: string) => { + if (fieldName === "__typename") { + return GraphQLString; + } + + const type = getType(typeName); + + const field = type.getFields()[fieldName]; + + if (!field) { + throw new Error(`${fieldName} does not exist on type ${typeName}`); + } + + return field.type; + }; + + // Taken from @graphql-tools/mock: + // https://github.com/ardatan/graphql-tools/blob/5ed60e44f94868f976cd28fe1b6a764fb146bbe9/packages/mock/src/MockStore.ts#L527 + const generateValueFromType = (fieldType: GraphQLOutputType): unknown => { + const nullableType = getNullableType(fieldType); + + if (isScalarType(nullableType)) { + const mockFn = mocks[nullableType.name]; + + if (typeof mockFn !== "function") { + throw new Error(`No mock defined for type "${nullableType.name}"`); + } + + return mockFn(); + } else if (isEnumType(nullableType)) { + const mockFn = mocks[nullableType.name]; + + if (typeof mockFn === "function") return mockFn(); + + const values = nullableType.getValues().map((v) => v.value); + + return takeRandom(values); + } else if (isObjectType(nullableType)) { + return {}; + } else if (isListType(nullableType)) { + return [...new Array(2)].map(() => + generateValueFromType(nullableType.ofType) + ); + } else if (isAbstractType(nullableType)) { + const mock = mocks[nullableType.name]; + + let typeName: string; + + let values: { [key: string]: unknown } = {}; + + if (!mock) { + typeName = takeRandom( + staticSchema.getPossibleTypes(nullableType).map((t) => t.name) + ); + } else if (typeof mock === "function") { + const mockRes = mock(); + if (mockRes === null) return null; + + if (!isNonNullObject(mockRes)) { + throw new Error( + `Value returned by the mock for ${nullableType.name} is not an object or null` + ); + } + + values = mockRes; + + if (typeof values["__typename"] !== "string") { + throw new Error( + `Please return a __typename in "${nullableType.name}"` + ); + } + + typeName = values["__typename"]; + } else if ( + isNonNullObject(mock) && + typeof mock["__typename"] === "function" + ) { + const mockRes = mock["__typename"](); + + if (typeof mockRes !== "string") { + throw new Error( + `'__typename' returned by the mock for abstract type ${nullableType.name} is not a string` + ); + } + + typeName = mockRes; + } else { + throw new Error(`Please return a __typename in "${nullableType.name}"`); + } + + return typeName; + } else { + throw new Error(`${nullableType} not implemented`); + } + }; + + // Taken from @graphql-tools/mock: + // https://github.com/ardatan/graphql-tools/blob/5ed60e44f94868f976cd28fe1b6a764fb146bbe9/packages/mock/src/utils.ts#L53 + const isRootType = (type: GraphQLObjectType, schema: GraphQLSchema) => { + const rootTypeNames = getRootTypeNames(schema); + + return rootTypeNames.has(type.name); + }; + + // Taken from @graphql-tools/mock: + // https://github.com/ardatan/graphql-tools/blob/5ed60e44f94868f976cd28fe1b6a764fb146bbe9/packages/mock/src/addMocksToSchema.ts#L123 + const mockResolver: GraphQLFieldResolver = ( + source, + args, + contex, + info + ) => { + const defaultResolvedValue = defaultFieldResolver( + source, + args, + contex, + info + ); + + // priority to default resolved value + if (defaultResolvedValue !== undefined) return defaultResolvedValue; + + // we have to handle the root mutation, root query and root subscription types + // differently, because no resolver is called at the root + if (isRootType(info.parentType, info.schema)) { + return { + typeName: info.parentType.name, + key: "ROOT", + fieldName: info.fieldName, + fieldArgs: args, + }; + } + + if (defaultResolvedValue === undefined) { + const fieldType = getFieldType(info.parentType.name, info.fieldName); + + return generateValueFromType(fieldType); + } + + return undefined; + }; + + // Taken from @graphql-tools/mock: + // https://github.com/ardatan/graphql-tools/blob/5ed60e44f94868f976cd28fe1b6a764fb146bbe9/packages/mock/src/addMocksToSchema.ts#L176 + return mapSchema(staticSchema, { + [MapperKind.OBJECT_FIELD]: (fieldConfig) => { + const newFieldConfig = { ...fieldConfig }; + + const oldResolver = fieldConfig.resolve; + + if (!oldResolver) { + newFieldConfig.resolve = mockResolver; + } + return newFieldConfig; + }, + + [MapperKind.ABSTRACT_TYPE]: (type) => { + if (type.resolveType != null && type.resolveType.length) { + return; + } + + const typeResolver = (typename: string) => { + return typename; + }; + + if (isUnionType(type)) { + return new GraphQLUnionType({ + ...type.toConfig(), + resolveType: typeResolver, + }); + } else { + return new GraphQLInterfaceType({ + ...type.toConfig(), + resolveType: typeResolver, + }); + } + }, + }); +}; + +export { createMockSchema }; From 88c3f4fd8ce5e2f59a4315e3434dba0bd8270860 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Fri, 16 Feb 2024 15:25:47 -0500 Subject: [PATCH 02/26] scaffold integration test --- integration-tests/vite-msw/index.html | 13 ++++++++ integration-tests/vite-msw/package.json | 33 +++++++++++++++++++ .../vite-msw/playwright.config.ts | 4 +++ integration-tests/vite-msw/tsconfig.json | 25 ++++++++++++++ integration-tests/vite-msw/tsconfig.node.json | 10 ++++++ integration-tests/vite-msw/vite.config.ts | 7 ++++ 6 files changed, 92 insertions(+) create mode 100644 integration-tests/vite-msw/index.html create mode 100644 integration-tests/vite-msw/package.json create mode 100644 integration-tests/vite-msw/playwright.config.ts create mode 100644 integration-tests/vite-msw/tsconfig.json create mode 100644 integration-tests/vite-msw/tsconfig.node.json create mode 100644 integration-tests/vite-msw/vite.config.ts diff --git a/integration-tests/vite-msw/index.html b/integration-tests/vite-msw/index.html new file mode 100644 index 00000000000..d5279fc3842 --- /dev/null +++ b/integration-tests/vite-msw/index.html @@ -0,0 +1,13 @@ + + + + + + Vite + React + TS + + + +
+ + + diff --git a/integration-tests/vite-msw/package.json b/integration-tests/vite-msw/package.json new file mode 100644 index 00000000000..21e1964163e --- /dev/null +++ b/integration-tests/vite-msw/package.json @@ -0,0 +1,33 @@ +{ + "name": "vite-msw", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port 3000", + "build": "tsc && vite build", + "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview --port 3000", + "serve-app": "npm run preview", + "test": "playwright test" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.37", + "@types/react-dom": "^18.0.11", + "@typescript-eslint/eslint-plugin": "^5.59.0", + "@typescript-eslint/parser": "^5.59.0", + "@vitejs/plugin-react-swc": "^3.0.0", + "eslint": "^8.38.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.3.4", + "typescript": "^5.0.2", + "vite": "^4.3.9", + "shared": "*", + "playwright": "*", + "@playwright/test": "*" + } +} diff --git a/integration-tests/vite-msw/playwright.config.ts b/integration-tests/vite-msw/playwright.config.ts new file mode 100644 index 00000000000..606e059e375 --- /dev/null +++ b/integration-tests/vite-msw/playwright.config.ts @@ -0,0 +1,4 @@ +import { baseConfig } from "shared/playwright.config.ts"; +import { defineConfig } from "@playwright/test"; + +export default defineConfig(baseConfig); diff --git a/integration-tests/vite-msw/tsconfig.json b/integration-tests/vite-msw/tsconfig.json new file mode 100644 index 00000000000..a7fc6fbf23d --- /dev/null +++ b/integration-tests/vite-msw/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/integration-tests/vite-msw/tsconfig.node.json b/integration-tests/vite-msw/tsconfig.node.json new file mode 100644 index 00000000000..42872c59f5b --- /dev/null +++ b/integration-tests/vite-msw/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/integration-tests/vite-msw/vite.config.ts b/integration-tests/vite-msw/vite.config.ts new file mode 100644 index 00000000000..d366e8c8d7c --- /dev/null +++ b/integration-tests/vite-msw/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react-swc"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}); From ea891f231dfd74a245e430b35e4c8463c60d98bc Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Fri, 16 Feb 2024 15:31:15 -0500 Subject: [PATCH 03/26] export createMockSchema and proxiedSchema --- src/__tests__/__snapshots__/exports.ts.snap | 3 +++ src/testing/core/index.ts | 1 + src/testing/core/schemaProxy.ts | 2 ++ src/testing/index.ts | 1 + 4 files changed, 7 insertions(+) diff --git a/src/__tests__/__snapshots__/exports.ts.snap b/src/__tests__/__snapshots__/exports.ts.snap index a2e89a93514..be43348733a 100644 --- a/src/__tests__/__snapshots__/exports.ts.snap +++ b/src/__tests__/__snapshots__/exports.ts.snap @@ -369,9 +369,11 @@ Array [ "MockSubscriptionLink", "MockedProvider", "createMockClient", + "createMockSchema", "itAsync", "mockObservableLink", "mockSingleLink", + "proxiedSchema", "subscribeAndCount", "tick", "wait", @@ -389,6 +391,7 @@ Array [ "itAsync", "mockObservableLink", "mockSingleLink", + "proxiedSchema", "subscribeAndCount", "tick", "wait", diff --git a/src/testing/core/index.ts b/src/testing/core/index.ts index e999590509a..73072db65cb 100644 --- a/src/testing/core/index.ts +++ b/src/testing/core/index.ts @@ -12,4 +12,5 @@ export { createMockClient } from "./mocking/mockClient.js"; export { default as subscribeAndCount } from "./subscribeAndCount.js"; export { itAsync } from "./itAsync.js"; export { wait, tick } from "./wait.js"; +export { proxiedSchema } from "./schemaProxy.js"; export * from "./withConsoleSpy.js"; diff --git a/src/testing/core/schemaProxy.ts b/src/testing/core/schemaProxy.ts index 9f7147d7cdc..56564a7e5e7 100644 --- a/src/testing/core/schemaProxy.ts +++ b/src/testing/core/schemaProxy.ts @@ -23,7 +23,9 @@ const proxiedSchema = ( }, })); } + // @ts-expect-error if (typeof targetSchema[p] === "function") { + // @ts-expect-error return targetSchema[p].bind(targetSchema); } return Reflect.get(target, p); diff --git a/src/testing/index.ts b/src/testing/index.ts index be84a5e57e5..2a499aa8d97 100644 --- a/src/testing/index.ts +++ b/src/testing/index.ts @@ -2,3 +2,4 @@ import "../utilities/globals/index.js"; export type { MockedProviderProps } from "./react/MockedProvider.js"; export { MockedProvider } from "./react/MockedProvider.js"; export * from "./core/index.js"; +export { createMockSchema } from "./graphql-tools/utils.js"; From 9a63ad95b130c431480888fc051c9cd4f12838c1 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Fri, 23 Feb 2024 11:22:40 -0500 Subject: [PATCH 04/26] tests: add schemaProxy tests --- config/jest.config.js | 1 + src/testing/core/mocking/mockSchema.ts | 0 src/testing/core/schemaProxy.test.tsx | 370 +++++++++++++++++++++++++ src/testing/core/schemaProxy.ts | 51 +++- src/testing/graphql-tools/utils.ts | 3 + 5 files changed, 412 insertions(+), 13 deletions(-) delete mode 100644 src/testing/core/mocking/mockSchema.ts create mode 100644 src/testing/core/schemaProxy.test.tsx diff --git a/config/jest.config.js b/config/jest.config.js index 6851e2a6e06..dd71953433d 100644 --- a/config/jest.config.js +++ b/config/jest.config.js @@ -33,6 +33,7 @@ const react17TestFileIgnoreList = [ ignoreTSFiles, // We only support Suspense with React 18, so don't test suspense hooks with // React 17 + "src/testing/core/schemaProxy.test.tsx", "src/react/hooks/__tests__/useSuspenseQuery.test.tsx", "src/react/hooks/__tests__/useBackgroundQuery.test.tsx", "src/react/hooks/__tests__/useLoadableQuery.test.tsx", diff --git a/src/testing/core/mocking/mockSchema.ts b/src/testing/core/mocking/mockSchema.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/testing/core/schemaProxy.test.tsx b/src/testing/core/schemaProxy.test.tsx new file mode 100644 index 00000000000..6ea1848f2f1 --- /dev/null +++ b/src/testing/core/schemaProxy.test.tsx @@ -0,0 +1,370 @@ +import * as React from "react"; +import { ApolloClient, InMemoryCache, gql } from "../../core/index.js"; +import { SchemaLink } from "../../link/schema/index.js"; +import { + createProfiler, + renderWithClient, + useTrackRenders, +} from "../internal/index.js"; +import { proxiedSchema } from "./schemaProxy.js"; +import { buildSchema } from "graphql"; +import { useSuspenseQuery } from "../../react/index.js"; +import { createMockSchema } from "../graphql-tools/utils.js"; + +const typeDefs = /* GraphQL */ ` + type User { + id: ID! + age: Int! + name: String! + image: UserImage! + book: Book! + } + + type Author { + _id: ID! + name: String! + book: Book! + } + + union UserImage = UserImageSolidColor | UserImageURL + + type UserImageSolidColor { + color: String! + } + + type UserImageURL { + url: String! + } + + scalar Date + + interface Book { + id: ID! + title: String + publishedAt: Date + } + + type TextBook implements Book { + id: ID! + title: String + publishedAt: Date + text: String + } + + type ColoringBook implements Book { + id: ID! + title: String + publishedAt: Date + colors: [String] + } + + type Query { + viewer: User! + userById(id: ID!): User! + author: Author! + } + + type Mutation { + changeViewerName(newName: String!): User! + } +`; + +const schemaWithTypeDefs = buildSchema(typeDefs); + +describe("schema proxy", () => { + const _schema = createMockSchema(schemaWithTypeDefs, { + ID: () => "1", + Int: () => 42, + String: () => "String", + Date: () => new Date("January 1, 2024 01:00:00").toJSON().split("T")[0], + }); + + const schema = proxiedSchema(_schema, { + Query: { + viewer: () => ({ + name: "Jane Doe", + book: { + text: "Hello World", + title: "The Book", + }, + }), + }, + Book: { + __resolveType: (obj) => { + if ("text" in obj) { + return "TextBook"; + } + if ("colors" in obj) { + return "ColoringBook"; + } + throw new Error("Could not resolve type"); + }, + }, + }); + + it("should allow adding scalar mocks and resolvers", async () => { + const Profiler = createProfiler({ + initialSnapshot: { + result: null, + }, + }); + + const client = new ApolloClient({ + cache: new InMemoryCache(), + link: new SchemaLink({ + schema, + }), + }); + + const query = gql` + query { + viewer { + id + name + age + book { + id + title + publishedAt + } + } + } + `; + + const Fallback = () => { + useTrackRenders(); + return
Loading...
; + }; + + const App = () => { + return ( + }> + + + ); + }; + + const Child = () => { + const result = useSuspenseQuery(query); + + useTrackRenders(); + + Profiler.mergeSnapshot({ + result, + } as Partial<{}>); + + return
Hello
; + }; + + const { unmount } = 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: "Jane Doe", + book: { + __typename: "TextBook", + id: "1", + publishedAt: "2024-01-01", + title: "The Book", + }, + }, + }); + } + + unmount(); + }); + + it("should allow schema forking with .fork", async () => { + const forkedSchema = schema.fork().withResolvers({ + Query: { + viewer: () => ({ + book: { + colors: ["red", "blue", "green"], + title: "The Book", + }, + }), + }, + }); + + const Profiler = createProfiler({ + initialSnapshot: { + result: null, + }, + }); + + const client = new ApolloClient({ + cache: new InMemoryCache(), + link: new SchemaLink({ + schema: forkedSchema, + }), + }); + + const query = gql` + query { + viewer { + id + name + age + book { + id + title + publishedAt + } + } + } + `; + + const Fallback = () => { + useTrackRenders(); + return
Loading...
; + }; + + const App = () => { + return ( + }> + + + ); + }; + + const Child = () => { + const result = useSuspenseQuery(query); + + useTrackRenders(); + + Profiler.mergeSnapshot({ + result, + } as Partial<{}>); + + return
Hello
; + }; + + const { unmount } = 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", + // In our resolvers defined in this test, we omit name so it uses + // the scalar default mock + name: "String", + book: { + // locally overrode the resolver for the book field + __typename: "ColoringBook", + id: "1", + publishedAt: "2024-01-01", + title: "The Book", + }, + }, + }); + } + + unmount(); + }); + + it("should not pollute the original schema", async () => { + const Profiler = createProfiler({ + initialSnapshot: { + result: null, + }, + }); + + const client = new ApolloClient({ + cache: new InMemoryCache(), + link: new SchemaLink({ + schema, + }), + }); + + const query = gql` + query { + viewer { + id + name + age + book { + id + title + publishedAt + } + } + } + `; + + const Fallback = () => { + useTrackRenders(); + return
Loading...
; + }; + + const App = () => { + return ( + }> + + + ); + }; + + const Child = () => { + const result = useSuspenseQuery(query); + + useTrackRenders(); + + Profiler.mergeSnapshot({ + result, + } as Partial<{}>); + + return
Hello
; + }; + + const { unmount } = 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: "Jane Doe", + book: { + __typename: "TextBook", + id: "1", + publishedAt: "2024-01-01", + title: "The Book", + }, + }, + }); + } + + unmount(); + }); +}); diff --git a/src/testing/core/schemaProxy.ts b/src/testing/core/schemaProxy.ts index 56564a7e5e7..cb361b0d137 100644 --- a/src/testing/core/schemaProxy.ts +++ b/src/testing/core/schemaProxy.ts @@ -11,24 +11,43 @@ const proxiedSchema = ( resolvers, }); + const fns = { + addResolvers: (newResolvers: typeof resolvers) => + (targetSchema = addResolversToSchema({ + schema: targetSchema, + resolvers: { + ...resolvers, + ...newResolvers, + }, + })), + // could also just create a fn that just forks and doesn't take resolvers + withResolvers: (newResolvers: typeof resolvers) => { + return proxiedSchema(targetSchema, newResolvers); + }, + fork: () => { + return proxiedSchema(targetSchema, {}); + }, + reset: () => { + targetSchema = addResolversToSchema({ + schema: schemaWithMocks, + resolvers, + }); + }, + }; + + // Usage notes: + // You'd want to fork aka call withResolvers e.g. in a describe block and + // call addResolvers after/in a single test file, you shouldn't const schema = new Proxy(targetSchema, { - get(target, p) { - if (p === "use") { - return (newResolvers: typeof resolvers) => - (targetSchema = addResolversToSchema({ - schema: schemaWithMocks, - resolvers: { - ...resolvers, - ...newResolvers, - }, - })); + get(_target, p) { + if (p in fns) { + return Reflect.get(fns, p); } - // @ts-expect-error + if (typeof targetSchema[p] === "function") { - // @ts-expect-error return targetSchema[p].bind(targetSchema); } - return Reflect.get(target, p); + return Reflect.get(targetSchema, p); }, }); @@ -36,3 +55,9 @@ const proxiedSchema = ( }; export { proxiedSchema }; + +// const schema = proxiedSchema(schema, { mocks, resolvers }); + +// export { +// schema +// }; diff --git a/src/testing/graphql-tools/utils.ts b/src/testing/graphql-tools/utils.ts index 65f6b89d588..d5a491e7c8f 100644 --- a/src/testing/graphql-tools/utils.ts +++ b/src/testing/graphql-tools/utils.ts @@ -103,6 +103,7 @@ const createMockSchema = ( ); } else if (typeof mock === "function") { const mockRes = mock(); + if (mockRes === null) return null; if (!isNonNullObject(mockRes)) { @@ -209,6 +210,8 @@ const createMockSchema = ( } const typeResolver = (typename: string) => { + console.log({ typename }); + // TODO: throw here if typename is undefined/null with more descriptive error message return typename; }; From 614b8f6c302d0f0fee8700466e6c91df76acce47 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Fri, 23 Feb 2024 12:42:18 -0500 Subject: [PATCH 05/26] commit failing mutation test --- src/testing/core/schemaProxy.test.tsx | 194 +++++++++++++++++++++++++- src/testing/core/schemaProxy.ts | 8 +- src/testing/graphql-tools/utils.ts | 2 - 3 files changed, 193 insertions(+), 11 deletions(-) diff --git a/src/testing/core/schemaProxy.test.tsx b/src/testing/core/schemaProxy.test.tsx index 6ea1848f2f1..f76e2b30d20 100644 --- a/src/testing/core/schemaProxy.test.tsx +++ b/src/testing/core/schemaProxy.test.tsx @@ -7,9 +7,11 @@ import { useTrackRenders, } from "../internal/index.js"; import { proxiedSchema } from "./schemaProxy.js"; -import { buildSchema } from "graphql"; -import { useSuspenseQuery } from "../../react/index.js"; +import { buildSchema, execute } from "graphql"; +import { useMutation, useSuspenseQuery } from "../../react/index.js"; import { createMockSchema } from "../graphql-tools/utils.js"; +import userEvent from "@testing-library/user-event"; +import { act, screen } from "@testing-library/react"; const typeDefs = /* GraphQL */ ` type User { @@ -72,14 +74,14 @@ const typeDefs = /* GraphQL */ ` const schemaWithTypeDefs = buildSchema(typeDefs); describe("schema proxy", () => { - const _schema = createMockSchema(schemaWithTypeDefs, { + const schemaWithMocks = createMockSchema(schemaWithTypeDefs, { ID: () => "1", Int: () => 42, String: () => "String", Date: () => new Date("January 1, 2024 01:00:00").toJSON().split("T")[0], }); - const schema = proxiedSchema(_schema, { + const schema = proxiedSchema(schemaWithMocks, { Query: { viewer: () => ({ name: "Jane Doe", @@ -187,7 +189,7 @@ describe("schema proxy", () => { }); it("should allow schema forking with .fork", async () => { - const forkedSchema = schema.fork().withResolvers({ + const forkedSchema = schema.forkWithResolvers({ Query: { viewer: () => ({ book: { @@ -367,4 +369,186 @@ describe("schema proxy", () => { unmount(); }); + + it.only("should handle mutations", async () => { + const query = gql` + query { + viewer { + id + name + age + book { + id + title + publishedAt + } + } + } + `; + + let name = "Jane Doe"; + + const forkedSchema = schema.forkWithResolvers({ + Query: { + viewer: () => ({ + name: () => name, + book: { + text: "Hello World", + title: "The Book", + }, + }), + }, + Mutation: { + changeViewerName: (_: any, { newName }: { newName: string }) => { + name = newName; + const { data } = execute({ + schema: forkedSchema, + document: query, + }); + data.viewer.name = newName; + console.log(JSON.stringify(data.viewer, null, 2)); + return data.viewer; + }, + }, + }); + + const Profiler = createProfiler({ + initialSnapshot: { + result: null, + }, + }); + + const client = new ApolloClient({ + cache: new InMemoryCache(), + link: new SchemaLink({ + schema: forkedSchema, + }), + }); + + const mutation = gql` + mutation { + changeViewerName(newName: "Alexandre") { + name + } + } + `; + + const Fallback = () => { + useTrackRenders(); + return
Loading...
; + }; + + const App = () => { + return ( + }> + + + ); + }; + + const Child = () => { + const result = useSuspenseQuery(query); + const [changeViewerName, { loading, data }] = useMutation(mutation); + console.log( + JSON.stringify({ data, loading, result: result.data }, null, 2) + ); + + useTrackRenders(); + + Profiler.mergeSnapshot({ + result, + } as Partial<{}>); + + return ( +
+ + Hello +
+ ); + }; + + const user = userEvent.setup(); + + const { unmount } = 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", + // In our resolvers defined in this test, we omit name so it uses + // the scalar default mock + name: "Jane Doe", + book: { + // locally overrode the resolver for the book field + __typename: "TextBook", + id: "1", + publishedAt: "2024-01-01", + title: "The Book", + }, + }, + }); + } + + await act(() => user.click(screen.getByText("Change name"))); + + // initial suspended render + await Profiler.takeRender(); + + { + const { snapshot } = await Profiler.takeRender(); + + expect(snapshot.result.data).toEqual({ + viewer: { + __typename: "User", + age: 42, + id: "1", + name: "Jane Doe", + book: { + __typename: "TextBook", + id: "1", + publishedAt: "2024-01-01", + title: "The Book", + }, + }, + }); + } + + await act(() => user.click(screen.getByText("Change name"))); + + // initial suspended render + await Profiler.takeRender(); + { + const { snapshot } = await Profiler.takeRender(); + + expect(snapshot.result.data).toEqual({ + viewer: { + __typename: "User", + age: 42, + id: "1", + // In our resolvers defined in this test, we omit name so it uses + // the scalar default mock + name: "String", + book: { + // locally overrode the resolver for the book field + __typename: "TextBook", + id: "1", + publishedAt: "2024-01-01", + title: "The Book", + }, + }, + }); + } + + unmount(); + }); }); diff --git a/src/testing/core/schemaProxy.ts b/src/testing/core/schemaProxy.ts index cb361b0d137..f27dc7bf9be 100644 --- a/src/testing/core/schemaProxy.ts +++ b/src/testing/core/schemaProxy.ts @@ -21,12 +21,12 @@ const proxiedSchema = ( }, })), // could also just create a fn that just forks and doesn't take resolvers - withResolvers: (newResolvers: typeof resolvers) => { + forkWithResolvers: (newResolvers: typeof resolvers) => { return proxiedSchema(targetSchema, newResolvers); }, - fork: () => { - return proxiedSchema(targetSchema, {}); - }, + // fork: () => { + // return proxiedSchema(targetSchema, {}); + // }, reset: () => { targetSchema = addResolversToSchema({ schema: schemaWithMocks, diff --git a/src/testing/graphql-tools/utils.ts b/src/testing/graphql-tools/utils.ts index d5a491e7c8f..9822a856ce9 100644 --- a/src/testing/graphql-tools/utils.ts +++ b/src/testing/graphql-tools/utils.ts @@ -210,8 +210,6 @@ const createMockSchema = ( } const typeResolver = (typename: string) => { - console.log({ typename }); - // TODO: throw here if typename is undefined/null with more descriptive error message return typename; }; From a2375e1d0b0d0284d6539a1f1370d036c11217b4 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Wed, 20 Mar 2024 13:21:59 -0400 Subject: [PATCH 06/26] chore: remove integration tests and update mutation test --- integration-tests/vite-msw/index.html | 13 -------- integration-tests/vite-msw/package.json | 33 ------------------- .../vite-msw/playwright.config.ts | 4 --- integration-tests/vite-msw/tsconfig.json | 25 -------------- integration-tests/vite-msw/tsconfig.node.json | 10 ------ integration-tests/vite-msw/vite.config.ts | 7 ---- src/testing/core/schemaProxy.test.tsx | 26 ++++++--------- src/testing/core/schemaProxy.ts | 16 ++++----- 8 files changed, 18 insertions(+), 116 deletions(-) delete mode 100644 integration-tests/vite-msw/index.html delete mode 100644 integration-tests/vite-msw/package.json delete mode 100644 integration-tests/vite-msw/playwright.config.ts delete mode 100644 integration-tests/vite-msw/tsconfig.json delete mode 100644 integration-tests/vite-msw/tsconfig.node.json delete mode 100644 integration-tests/vite-msw/vite.config.ts diff --git a/integration-tests/vite-msw/index.html b/integration-tests/vite-msw/index.html deleted file mode 100644 index d5279fc3842..00000000000 --- a/integration-tests/vite-msw/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Vite + React + TS - - - -
- - - diff --git a/integration-tests/vite-msw/package.json b/integration-tests/vite-msw/package.json deleted file mode 100644 index 21e1964163e..00000000000 --- a/integration-tests/vite-msw/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "vite-msw", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite --port 3000", - "build": "tsc && vite build", - "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview --port 3000", - "serve-app": "npm run preview", - "test": "playwright test" - }, - "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" - }, - "devDependencies": { - "@types/react": "^18.0.37", - "@types/react-dom": "^18.0.11", - "@typescript-eslint/eslint-plugin": "^5.59.0", - "@typescript-eslint/parser": "^5.59.0", - "@vitejs/plugin-react-swc": "^3.0.0", - "eslint": "^8.38.0", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.3.4", - "typescript": "^5.0.2", - "vite": "^4.3.9", - "shared": "*", - "playwright": "*", - "@playwright/test": "*" - } -} diff --git a/integration-tests/vite-msw/playwright.config.ts b/integration-tests/vite-msw/playwright.config.ts deleted file mode 100644 index 606e059e375..00000000000 --- a/integration-tests/vite-msw/playwright.config.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { baseConfig } from "shared/playwright.config.ts"; -import { defineConfig } from "@playwright/test"; - -export default defineConfig(baseConfig); diff --git a/integration-tests/vite-msw/tsconfig.json b/integration-tests/vite-msw/tsconfig.json deleted file mode 100644 index a7fc6fbf23d..00000000000 --- a/integration-tests/vite-msw/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] -} diff --git a/integration-tests/vite-msw/tsconfig.node.json b/integration-tests/vite-msw/tsconfig.node.json deleted file mode 100644 index 42872c59f5b..00000000000 --- a/integration-tests/vite-msw/tsconfig.node.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/integration-tests/vite-msw/vite.config.ts b/integration-tests/vite-msw/vite.config.ts deleted file mode 100644 index d366e8c8d7c..00000000000 --- a/integration-tests/vite-msw/vite.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react-swc"; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], -}); diff --git a/src/testing/core/schemaProxy.test.tsx b/src/testing/core/schemaProxy.test.tsx index f76e2b30d20..6780595fe8c 100644 --- a/src/testing/core/schemaProxy.test.tsx +++ b/src/testing/core/schemaProxy.test.tsx @@ -7,7 +7,7 @@ import { useTrackRenders, } from "../internal/index.js"; import { proxiedSchema } from "./schemaProxy.js"; -import { buildSchema, execute } from "graphql"; +import { buildSchema } from "graphql"; import { useMutation, useSuspenseQuery } from "../../react/index.js"; import { createMockSchema } from "../graphql-tools/utils.js"; import userEvent from "@testing-library/user-event"; @@ -370,7 +370,7 @@ describe("schema proxy", () => { unmount(); }); - it.only("should handle mutations", async () => { + it("should handle mutations", async () => { const query = gql` query { viewer { @@ -391,23 +391,19 @@ describe("schema proxy", () => { const forkedSchema = schema.forkWithResolvers({ Query: { viewer: () => ({ - name: () => name, book: { text: "Hello World", title: "The Book", }, }), }, + User: { + name: () => name, + }, Mutation: { changeViewerName: (_: any, { newName }: { newName: string }) => { name = newName; - const { data } = execute({ - schema: forkedSchema, - document: query, - }); - data.viewer.name = newName; - console.log(JSON.stringify(data.viewer, null, 2)); - return data.viewer; + return {}; }, }, }); @@ -428,6 +424,7 @@ describe("schema proxy", () => { const mutation = gql` mutation { changeViewerName(newName: "Alexandre") { + id name } } @@ -448,10 +445,7 @@ describe("schema proxy", () => { const Child = () => { const result = useSuspenseQuery(query); - const [changeViewerName, { loading, data }] = useMutation(mutation); - console.log( - JSON.stringify({ data, loading, result: result.data }, null, 2) - ); + const [changeViewerName] = useMutation(mutation); useTrackRenders(); @@ -512,7 +506,7 @@ describe("schema proxy", () => { __typename: "User", age: 42, id: "1", - name: "Jane Doe", + name: "Alexandre", book: { __typename: "TextBook", id: "1", @@ -537,7 +531,7 @@ describe("schema proxy", () => { id: "1", // In our resolvers defined in this test, we omit name so it uses // the scalar default mock - name: "String", + name: "Alexandre", book: { // locally overrode the resolver for the book field __typename: "TextBook", diff --git a/src/testing/core/schemaProxy.ts b/src/testing/core/schemaProxy.ts index f27dc7bf9be..0bdbc7e26bd 100644 --- a/src/testing/core/schemaProxy.ts +++ b/src/testing/core/schemaProxy.ts @@ -2,16 +2,22 @@ import { addResolversToSchema } from "@graphql-tools/schema"; import type { GraphQLSchema } from "graphql"; import type { Resolvers } from "../../core/types.js"; +interface ProxiedSchemaFns { + addResolvers: (newResolvers: Resolvers) => GraphQLSchema; + forkWithResolvers: (newResolvers: Resolvers) => GraphQLSchema; + reset: () => void; +} + const proxiedSchema = ( schemaWithMocks: GraphQLSchema, resolvers: Resolvers -) => { +): GraphQLSchema & ProxiedSchemaFns => { let targetSchema = addResolversToSchema({ schema: schemaWithMocks, resolvers, }); - const fns = { + const fns: ProxiedSchemaFns = { addResolvers: (newResolvers: typeof resolvers) => (targetSchema = addResolversToSchema({ schema: targetSchema, @@ -55,9 +61,3 @@ const proxiedSchema = ( }; export { proxiedSchema }; - -// const schema = proxiedSchema(schema, { mocks, resolvers }); - -// export { -// schema -// }; From e92a0afc2c7ca76d3609c4f4917f273f7120735c Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Wed, 20 Mar 2024 13:25:27 -0400 Subject: [PATCH 07/26] chore: add changeset --- .changeset/stupid-bears-cheat.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/stupid-bears-cheat.md diff --git a/.changeset/stupid-bears-cheat.md b/.changeset/stupid-bears-cheat.md new file mode 100644 index 00000000000..636298cae65 --- /dev/null +++ b/.changeset/stupid-bears-cheat.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": minor +--- + +Adds proxiedSchema and createMockSchema testing utilities From e765917ae5db90ca299b5af1773d661bb2cc4dcc Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Thu, 21 Mar 2024 12:30:37 -0400 Subject: [PATCH 08/26] chore: update tests and fix ts errors --- src/__tests__/__snapshots__/exports.ts.snap | 2 + src/link/http/createHttpLink.ts | 5 + src/testing/core/index.ts | 1 + src/testing/core/mockFetchWithSchema.ts | 28 +++++ src/testing/core/schemaProxy.test.tsx | 121 +++++++++----------- src/testing/core/schemaProxy.ts | 8 +- 6 files changed, 92 insertions(+), 73 deletions(-) create mode 100644 src/testing/core/mockFetchWithSchema.ts diff --git a/src/__tests__/__snapshots__/exports.ts.snap b/src/__tests__/__snapshots__/exports.ts.snap index be43348733a..dc06beaef14 100644 --- a/src/__tests__/__snapshots__/exports.ts.snap +++ b/src/__tests__/__snapshots__/exports.ts.snap @@ -369,6 +369,7 @@ Array [ "MockSubscriptionLink", "MockedProvider", "createMockClient", + "createMockFetch", "createMockSchema", "itAsync", "mockObservableLink", @@ -388,6 +389,7 @@ Array [ "MockLink", "MockSubscriptionLink", "createMockClient", + "createMockFetch", "itAsync", "mockObservableLink", "mockSingleLink", diff --git a/src/link/http/createHttpLink.ts b/src/link/http/createHttpLink.ts index 8c51b113a2a..720e773514d 100644 --- a/src/link/http/createHttpLink.ts +++ b/src/link/http/createHttpLink.ts @@ -176,6 +176,11 @@ export const createHttpLink = (linkOptions: HttpOptions = {}) => { } } + if (__DEV__) { + options.context = context; + options.operation = operation; + } + return new Observable((observer) => { // Prefer linkOptions.fetch (preferredFetch) if provided, and otherwise // fall back to the *current* global window.fetch function (see issue diff --git a/src/testing/core/index.ts b/src/testing/core/index.ts index 73072db65cb..45c88fbe89b 100644 --- a/src/testing/core/index.ts +++ b/src/testing/core/index.ts @@ -13,4 +13,5 @@ export { default as subscribeAndCount } from "./subscribeAndCount.js"; export { itAsync } from "./itAsync.js"; export { wait, tick } from "./wait.js"; export { proxiedSchema } from "./schemaProxy.js"; +export { createMockFetch } from "./mockFetchWithSchema.js"; export * from "./withConsoleSpy.js"; diff --git a/src/testing/core/mockFetchWithSchema.ts b/src/testing/core/mockFetchWithSchema.ts new file mode 100644 index 00000000000..27062a49f0c --- /dev/null +++ b/src/testing/core/mockFetchWithSchema.ts @@ -0,0 +1,28 @@ +import { Response as NodeFetchResponse } from "node-fetch"; +import { execute } from "graphql"; + +const createMockFetch = (schema: any) => { + const mockFetch: (uri: any, options: any) => Promise = ( + uri, + options + ) => { + // TODO: validation errors + + return new Promise(async (resolve) => { + const result = await execute({ + schema, + contextValue: options.context, + document: options.operation.query, + variableValues: options.operation.variables, + operationName: options.operation.operationName, + }); + + const stringifiedResult = JSON.stringify(result); + + resolve(new NodeFetchResponse(stringifiedResult) as unknown as Response); + }); + }; + return mockFetch; +}; + +export { createMockFetch }; diff --git a/src/testing/core/schemaProxy.test.tsx b/src/testing/core/schemaProxy.test.tsx index 6780595fe8c..497b433bed4 100644 --- a/src/testing/core/schemaProxy.test.tsx +++ b/src/testing/core/schemaProxy.test.tsx @@ -1,6 +1,11 @@ import * as React from "react"; -import { ApolloClient, InMemoryCache, gql } from "../../core/index.js"; -import { SchemaLink } from "../../link/schema/index.js"; +import { + ApolloClient, + HttpLink, + InMemoryCache, + gql, +} from "../../core/index.js"; +import type { TypedDocumentNode } from "../../core/index.js"; import { createProfiler, renderWithClient, @@ -8,10 +13,12 @@ import { } from "../internal/index.js"; import { proxiedSchema } from "./schemaProxy.js"; import { buildSchema } from "graphql"; +import type { UseSuspenseQueryResult } from "../../react/index.js"; import { useMutation, useSuspenseQuery } from "../../react/index.js"; import { createMockSchema } from "../graphql-tools/utils.js"; import userEvent from "@testing-library/user-event"; import { act, screen } from "@testing-library/react"; +import { createMockFetch } from "./mockFetchWithSchema.js"; const typeDefs = /* GraphQL */ ` type User { @@ -73,6 +80,27 @@ const typeDefs = /* GraphQL */ ` const schemaWithTypeDefs = buildSchema(typeDefs); +function createDefaultProfiler() { + return createProfiler({ + initialSnapshot: { + result: null as UseSuspenseQueryResult | null, + }, + }); +} + +interface ViewerQueryData { + viewer: { + id: string; + name: string; + age: number; + book: { + id: string; + title: string; + publishedAt: string; + }; + }; +} + describe("schema proxy", () => { const schemaWithMocks = createMockSchema(schemaWithTypeDefs, { ID: () => "1", @@ -105,20 +133,14 @@ describe("schema proxy", () => { }); it("should allow adding scalar mocks and resolvers", async () => { - const Profiler = createProfiler({ - initialSnapshot: { - result: null, - }, - }); - + const Profiler = createDefaultProfiler(); + const mockFetch = createMockFetch(schema); const client = new ApolloClient({ cache: new InMemoryCache(), - link: new SchemaLink({ - schema, - }), + link: new HttpLink({ fetch: mockFetch }), }); - const query = gql` + const query: TypedDocumentNode = gql` query { viewer { id @@ -169,7 +191,7 @@ describe("schema proxy", () => { { const { snapshot } = await Profiler.takeRender(); - expect(snapshot.result.data).toEqual({ + expect(snapshot.result?.data).toEqual({ viewer: { __typename: "User", age: 42, @@ -200,20 +222,14 @@ describe("schema proxy", () => { }, }); - const Profiler = createProfiler({ - initialSnapshot: { - result: null, - }, - }); - + const Profiler = createDefaultProfiler(); + const mockFetch = createMockFetch(forkedSchema); const client = new ApolloClient({ cache: new InMemoryCache(), - link: new SchemaLink({ - schema: forkedSchema, - }), + link: new HttpLink({ fetch: mockFetch }), }); - const query = gql` + const query: TypedDocumentNode = gql` query { viewer { id @@ -264,7 +280,7 @@ describe("schema proxy", () => { { const { snapshot } = await Profiler.takeRender(); - expect(snapshot.result.data).toEqual({ + expect(snapshot.result?.data).toEqual({ viewer: { __typename: "User", age: 42, @@ -287,20 +303,15 @@ describe("schema proxy", () => { }); it("should not pollute the original schema", async () => { - const Profiler = createProfiler({ - initialSnapshot: { - result: null, - }, - }); + const Profiler = createDefaultProfiler(); + const mockFetch = createMockFetch(schema); const client = new ApolloClient({ cache: new InMemoryCache(), - link: new SchemaLink({ - schema, - }), + link: new HttpLink({ fetch: mockFetch }), }); - const query = gql` + const query: TypedDocumentNode = gql` query { viewer { id @@ -351,7 +362,7 @@ describe("schema proxy", () => { { const { snapshot } = await Profiler.takeRender(); - expect(snapshot.result.data).toEqual({ + expect(snapshot.result?.data).toEqual({ viewer: { __typename: "User", age: 42, @@ -371,7 +382,7 @@ describe("schema proxy", () => { }); it("should handle mutations", async () => { - const query = gql` + const query: TypedDocumentNode = gql` query { viewer { id @@ -408,17 +419,13 @@ describe("schema proxy", () => { }, }); - const Profiler = createProfiler({ - initialSnapshot: { - result: null, - }, - }); + const Profiler = createDefaultProfiler(); + + const mockFetch = createMockFetch(forkedSchema); const client = new ApolloClient({ cache: new InMemoryCache(), - link: new SchemaLink({ - schema: forkedSchema, - }), + link: new HttpLink({ fetch: mockFetch }), }); const mutation = gql` @@ -474,7 +481,7 @@ describe("schema proxy", () => { { const { snapshot } = await Profiler.takeRender(); - expect(snapshot.result.data).toEqual({ + expect(snapshot.result?.data).toEqual({ viewer: { __typename: "User", age: 42, @@ -495,36 +502,12 @@ describe("schema proxy", () => { await act(() => user.click(screen.getByText("Change name"))); - // initial suspended render - await Profiler.takeRender(); - - { - const { snapshot } = await Profiler.takeRender(); - - expect(snapshot.result.data).toEqual({ - viewer: { - __typename: "User", - age: 42, - id: "1", - name: "Alexandre", - book: { - __typename: "TextBook", - id: "1", - publishedAt: "2024-01-01", - title: "The Book", - }, - }, - }); - } - - await act(() => user.click(screen.getByText("Change name"))); - // initial suspended render await Profiler.takeRender(); { const { snapshot } = await Profiler.takeRender(); - expect(snapshot.result.data).toEqual({ + expect(snapshot.result?.data).toEqual({ viewer: { __typename: "User", age: 42, diff --git a/src/testing/core/schemaProxy.ts b/src/testing/core/schemaProxy.ts index 0bdbc7e26bd..2da1c2e898d 100644 --- a/src/testing/core/schemaProxy.ts +++ b/src/testing/core/schemaProxy.ts @@ -50,14 +50,14 @@ const proxiedSchema = ( return Reflect.get(fns, p); } - if (typeof targetSchema[p] === "function") { - return targetSchema[p].bind(targetSchema); - } + // if (typeof targetSchema[p] === "function") { + // return targetSchema[p].bind(targetSchema); + // } return Reflect.get(targetSchema, p); }, }); - return schema; + return schema as GraphQLSchema & ProxiedSchemaFns; }; export { proxiedSchema }; From 7897611a3e9d97c9234305c755fedeadcaf4d113 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Tue, 26 Mar 2024 14:43:49 -0400 Subject: [PATCH 09/26] update mockFetchWithSchema --- src/link/http/createHttpLink.ts | 5 ----- src/testing/core/mockFetchWithSchema.ts | 11 +++++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/link/http/createHttpLink.ts b/src/link/http/createHttpLink.ts index 720e773514d..8c51b113a2a 100644 --- a/src/link/http/createHttpLink.ts +++ b/src/link/http/createHttpLink.ts @@ -176,11 +176,6 @@ export const createHttpLink = (linkOptions: HttpOptions = {}) => { } } - if (__DEV__) { - options.context = context; - options.operation = operation; - } - return new Observable((observer) => { // Prefer linkOptions.fetch (preferredFetch) if provided, and otherwise // fall back to the *current* global window.fetch function (see issue diff --git a/src/testing/core/mockFetchWithSchema.ts b/src/testing/core/mockFetchWithSchema.ts index 27062a49f0c..d1dbf6f7c3d 100644 --- a/src/testing/core/mockFetchWithSchema.ts +++ b/src/testing/core/mockFetchWithSchema.ts @@ -1,3 +1,4 @@ +import { gql } from "../../core/index.js"; import { Response as NodeFetchResponse } from "node-fetch"; import { execute } from "graphql"; @@ -9,12 +10,14 @@ const createMockFetch = (schema: any) => { // TODO: validation errors return new Promise(async (resolve) => { + const body = JSON.parse(options.body); + const document = gql(body.query); + const result = await execute({ schema, - contextValue: options.context, - document: options.operation.query, - variableValues: options.operation.variables, - operationName: options.operation.operationName, + document, + variableValues: body.variables, + operationName: body.operationName, }); const stringifiedResult = JSON.stringify(result); From d355cd23a480dca585be01c6aade7dc0023e3e02 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Tue, 26 Mar 2024 14:44:55 -0400 Subject: [PATCH 10/26] fix: call .bind and move test file --- .../core/{ => __tests__}/schemaProxy.test.tsx | 23 +++++++++++-------- src/testing/core/schemaProxy.ts | 13 ++++++++--- 2 files changed, 24 insertions(+), 12 deletions(-) rename src/testing/core/{ => __tests__}/schemaProxy.test.tsx (93%) diff --git a/src/testing/core/schemaProxy.test.tsx b/src/testing/core/__tests__/schemaProxy.test.tsx similarity index 93% rename from src/testing/core/schemaProxy.test.tsx rename to src/testing/core/__tests__/schemaProxy.test.tsx index 497b433bed4..9fc3d74f449 100644 --- a/src/testing/core/schemaProxy.test.tsx +++ b/src/testing/core/__tests__/schemaProxy.test.tsx @@ -4,21 +4,21 @@ import { HttpLink, InMemoryCache, gql, -} from "../../core/index.js"; -import type { TypedDocumentNode } from "../../core/index.js"; +} from "../../../core/index.js"; +import type { TypedDocumentNode } from "../../../core/index.js"; import { createProfiler, renderWithClient, useTrackRenders, -} from "../internal/index.js"; -import { proxiedSchema } from "./schemaProxy.js"; +} from "../../internal/index.js"; +import { proxiedSchema } from "../schemaProxy.js"; import { buildSchema } from "graphql"; -import type { UseSuspenseQueryResult } from "../../react/index.js"; -import { useMutation, useSuspenseQuery } from "../../react/index.js"; -import { createMockSchema } from "../graphql-tools/utils.js"; +import type { UseSuspenseQueryResult } from "../../../react/index.js"; +import { useMutation, useSuspenseQuery } from "../../../react/index.js"; +import { createMockSchema } from "../../graphql-tools/utils.js"; import userEvent from "@testing-library/user-event"; import { act, screen } from "@testing-library/react"; -import { createMockFetch } from "./mockFetchWithSchema.js"; +import { createMockFetch } from "../mockFetchWithSchema.js"; const typeDefs = /* GraphQL */ ` type User { @@ -223,6 +223,11 @@ describe("schema proxy", () => { }); const Profiler = createDefaultProfiler(); + // TODO list: + // might need rootValue and context in the future but for now we'll + // leave them out of the options accepted by createMockFetch + // where might we want to inject context? (e.g. for authentication) + // start without (maybe it should be injected via createMockFetch?) const mockFetch = createMockFetch(forkedSchema); const client = new ApolloClient({ cache: new InMemoryCache(), @@ -230,7 +235,7 @@ describe("schema proxy", () => { }); const query: TypedDocumentNode = gql` - query { + query ViewerQuery { viewer { id name diff --git a/src/testing/core/schemaProxy.ts b/src/testing/core/schemaProxy.ts index 2da1c2e898d..b38c093a40f 100644 --- a/src/testing/core/schemaProxy.ts +++ b/src/testing/core/schemaProxy.ts @@ -1,5 +1,6 @@ import { addResolversToSchema } from "@graphql-tools/schema"; import type { GraphQLSchema } from "graphql"; + import type { Resolvers } from "../../core/types.js"; interface ProxiedSchemaFns { @@ -50,9 +51,15 @@ const proxiedSchema = ( return Reflect.get(fns, p); } - // if (typeof targetSchema[p] === "function") { - // return targetSchema[p].bind(targetSchema); - // } + // this binds `this` to the right schema - without it, the new schema + // calls methods but with the wrong `this` context, from the previous + // schema + // @ts-ignore + if (typeof targetSchema[p] === "function") { + // @ts-ignore + return targetSchema[p].bind(targetSchema); + } + return Reflect.get(targetSchema, p); }, }); From 85671a65b845dfbfe614d7dd517b8af4940a45ca Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Tue, 26 Mar 2024 15:46:19 -0400 Subject: [PATCH 11/26] feat: adds schema validation --- config/jest.config.js | 2 +- .../core/__tests__/schemaProxy.test.tsx | 220 +++++++++++++++++- src/testing/core/mockFetchWithSchema.ts | 32 ++- src/testing/core/schemaProxy.ts | 11 +- 4 files changed, 247 insertions(+), 18 deletions(-) diff --git a/config/jest.config.js b/config/jest.config.js index dd71953433d..4cfb5f320e0 100644 --- a/config/jest.config.js +++ b/config/jest.config.js @@ -33,7 +33,7 @@ const react17TestFileIgnoreList = [ ignoreTSFiles, // We only support Suspense with React 18, so don't test suspense hooks with // React 17 - "src/testing/core/schemaProxy.test.tsx", + "src/testing/core/__tests__/schemaProxy.test.tsx", "src/react/hooks/__tests__/useSuspenseQuery.test.tsx", "src/react/hooks/__tests__/useBackgroundQuery.test.tsx", "src/react/hooks/__tests__/useLoadableQuery.test.tsx", diff --git a/src/testing/core/__tests__/schemaProxy.test.tsx b/src/testing/core/__tests__/schemaProxy.test.tsx index 9fc3d74f449..b3c543e83a6 100644 --- a/src/testing/core/__tests__/schemaProxy.test.tsx +++ b/src/testing/core/__tests__/schemaProxy.test.tsx @@ -1,24 +1,31 @@ import * as React from "react"; import { ApolloClient, + ApolloError, HttpLink, InMemoryCache, gql, } from "../../../core/index.js"; import type { TypedDocumentNode } from "../../../core/index.js"; import { + Profiler, createProfiler, renderWithClient, + spyOnConsole, useTrackRenders, } from "../../internal/index.js"; import { proxiedSchema } from "../schemaProxy.js"; -import { buildSchema } from "graphql"; +import { GraphQLError, buildSchema } from "graphql"; import type { UseSuspenseQueryResult } from "../../../react/index.js"; import { useMutation, useSuspenseQuery } from "../../../react/index.js"; import { createMockSchema } from "../../graphql-tools/utils.js"; import userEvent from "@testing-library/user-event"; import { act, screen } from "@testing-library/react"; import { createMockFetch } from "../mockFetchWithSchema.js"; +import { + FallbackProps, + ErrorBoundary as ReactErrorBoundary, +} from "react-error-boundary"; const typeDefs = /* GraphQL */ ` type User { @@ -88,6 +95,36 @@ function createDefaultProfiler() { }); } +function createErrorProfiler() { + return createProfiler({ + initialSnapshot: { + error: null as Error | null, + result: null as UseSuspenseQueryResult | null, + }, + }); +} + +function createTrackedErrorComponents( + Profiler: Profiler +) { + function ErrorFallback({ error }: FallbackProps) { + useTrackRenders({ name: "ErrorFallback" }); + Profiler.mergeSnapshot({ error } as Partial); + + return
Error
; + } + + function ErrorBoundary({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); + } + + return { ErrorBoundary }; +} + interface ViewerQueryData { viewer: { id: string; @@ -132,7 +169,7 @@ describe("schema proxy", () => { }, }); - it("should allow adding scalar mocks and resolvers", async () => { + it("mocks scalars and resolvers", async () => { const Profiler = createDefaultProfiler(); const mockFetch = createMockFetch(schema); const client = new ApolloClient({ @@ -210,7 +247,7 @@ describe("schema proxy", () => { unmount(); }); - it("should allow schema forking with .fork", async () => { + it("allows schema forking with .fork", async () => { const forkedSchema = schema.forkWithResolvers({ Query: { viewer: () => ({ @@ -307,7 +344,7 @@ describe("schema proxy", () => { unmount(); }); - it("should not pollute the original schema", async () => { + it("does not pollute the original schema", async () => { const Profiler = createDefaultProfiler(); const mockFetch = createMockFetch(schema); @@ -386,7 +423,7 @@ describe("schema proxy", () => { unmount(); }); - it("should handle mutations", async () => { + it("handles mutations", async () => { const query: TypedDocumentNode = gql` query { viewer { @@ -533,4 +570,177 @@ describe("schema proxy", () => { unmount(); }); + + it("returns GraphQL errors", async () => { + using _consoleSpy = spyOnConsole("error"); + const query: TypedDocumentNode = gql` + query { + viewer { + id + name + age + book { + id + title + publishedAt + } + } + } + `; + + let name = "Jane Doe"; + + const forkedSchema = schema.forkWithResolvers({ + Query: { + viewer: () => ({ + book: { + // text: "Hello World", <- this will cause a validation error + title: "The Book", + }, + }), + }, + User: { + name: () => name, + }, + }); + + const Profiler = createErrorProfiler(); + + const { ErrorBoundary } = createTrackedErrorComponents(Profiler); + + const mockFetch = createMockFetch(forkedSchema); + + const client = new ApolloClient({ + cache: new InMemoryCache(), + link: new HttpLink({ fetch: mockFetch }), + }); + + const Fallback = () => { + useTrackRenders(); + return
Loading...
; + }; + + const App = () => { + return ( + }> + + + + + ); + }; + + const Child = () => { + const result = useSuspenseQuery(query); + + useTrackRenders(); + + Profiler.mergeSnapshot({ + result, + } as Partial<{}>); + + return
Hello
; + }; + + const { unmount } = renderWithClient(, { + client, + wrapper: Profiler, + }); + + // initial suspended render + await Profiler.takeRender(); + + { + const { snapshot } = await Profiler.takeRender(); + + expect(snapshot.error).toEqual( + new ApolloError({ + graphQLErrors: [new GraphQLError("Could not resolve type")], + }) + ); + } + + unmount(); + }); + + it("validates schema by default and returns validation errors", async () => { + using _consoleSpy = spyOnConsole("error"); + const query: TypedDocumentNode = gql` + query { + viewer { + id + name + age + book { + id + title + publishedAt + } + } + } + `; + + // invalid schema + const forkedSchema = { foo: "bar" }; + + const Profiler = createErrorProfiler(); + + const { ErrorBoundary } = createTrackedErrorComponents(Profiler); + + const mockFetch = createMockFetch(forkedSchema); + + const client = new ApolloClient({ + cache: new InMemoryCache(), + link: new HttpLink({ fetch: mockFetch }), + }); + + const Fallback = () => { + useTrackRenders(); + return
Loading...
; + }; + + const App = () => { + return ( + }> + + + + + ); + }; + + const Child = () => { + const result = useSuspenseQuery(query); + + useTrackRenders(); + + Profiler.mergeSnapshot({ + result, + } as Partial<{}>); + + return
Hello
; + }; + + const { unmount } = renderWithClient(, { + client, + wrapper: Profiler, + }); + + // initial suspended render + await Profiler.takeRender(); + + { + const { snapshot } = await Profiler.takeRender(); + + expect(snapshot.error).toEqual( + new ApolloError({ + graphQLErrors: [ + new GraphQLError('Expected { foo: "bar" } to be a GraphQL schema.'), + ], + }) + ); + } + + unmount(); + }); }); diff --git a/src/testing/core/mockFetchWithSchema.ts b/src/testing/core/mockFetchWithSchema.ts index d1dbf6f7c3d..7a3d1a9a619 100644 --- a/src/testing/core/mockFetchWithSchema.ts +++ b/src/testing/core/mockFetchWithSchema.ts @@ -1,18 +1,40 @@ -import { gql } from "../../core/index.js"; +import { ApolloError, gql } from "../../core/index.js"; import { Response as NodeFetchResponse } from "node-fetch"; -import { execute } from "graphql"; +import { execute, validate } from "graphql"; +import type { GraphQLError } from "graphql"; -const createMockFetch = (schema: any) => { +const createMockFetch = ( + schema: any, + mockFetchOpts: { validate: boolean } = { validate: true } +) => { const mockFetch: (uri: any, options: any) => Promise = ( uri, options ) => { - // TODO: validation errors - return new Promise(async (resolve) => { const body = JSON.parse(options.body); const document = gql(body.query); + if (mockFetchOpts.validate) { + let validationErrors: readonly Error[] = []; + + try { + validationErrors = validate(schema, document); + } catch (e) { + validationErrors = [ + new ApolloError({ graphQLErrors: [e as GraphQLError] }), + ]; + } + + if (validationErrors?.length > 0) { + return resolve( + new NodeFetchResponse( + JSON.stringify({ errors: validationErrors }) + ) as unknown as Response + ); + } + } + const result = await execute({ schema, document, diff --git a/src/testing/core/schemaProxy.ts b/src/testing/core/schemaProxy.ts index b38c093a40f..a374516d973 100644 --- a/src/testing/core/schemaProxy.ts +++ b/src/testing/core/schemaProxy.ts @@ -7,6 +7,7 @@ interface ProxiedSchemaFns { addResolvers: (newResolvers: Resolvers) => GraphQLSchema; forkWithResolvers: (newResolvers: Resolvers) => GraphQLSchema; reset: () => void; + fork: () => GraphQLSchema; } const proxiedSchema = ( @@ -27,13 +28,12 @@ const proxiedSchema = ( ...newResolvers, }, })), - // could also just create a fn that just forks and doesn't take resolvers forkWithResolvers: (newResolvers: typeof resolvers) => { return proxiedSchema(targetSchema, newResolvers); }, - // fork: () => { - // return proxiedSchema(targetSchema, {}); - // }, + fork: () => { + return proxiedSchema(targetSchema, {}); + }, reset: () => { targetSchema = addResolversToSchema({ schema: schemaWithMocks, @@ -42,9 +42,6 @@ const proxiedSchema = ( }, }; - // Usage notes: - // You'd want to fork aka call withResolvers e.g. in a describe block and - // call addResolvers after/in a single test file, you shouldn't const schema = new Proxy(targetSchema, { get(_target, p) { if (p in fns) { From 57ab7e4bddb4bbd40b4f9d7e89510ee52d1520b4 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Tue, 26 Mar 2024 15:55:02 -0400 Subject: [PATCH 12/26] chore: update api reports --- .api-reports/api-report-testing.md | 28 +++++++++++++++++++ .api-reports/api-report-testing_core.md | 23 +++++++++++++++ .../core/__tests__/schemaProxy.test.tsx | 10 +------ 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/.api-reports/api-report-testing.md b/.api-reports/api-report-testing.md index 2c1e6dd0195..46374623734 100644 --- a/.api-reports/api-report-testing.md +++ b/.api-reports/api-report-testing.md @@ -11,6 +11,7 @@ import type { FieldNode } from 'graphql'; import type { FragmentDefinitionNode } from 'graphql'; import type { GraphQLError } from 'graphql'; import type { GraphQLErrorExtensions } from 'graphql'; +import type { GraphQLSchema } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; import * as React_2 from 'react'; @@ -446,6 +447,16 @@ type ConcastSourcesIterable = Iterable>; // @public (undocumented) export function createMockClient(data: TData, query: DocumentNode, variables?: {}): ApolloClient; +// @public (undocumented) +export const createMockFetch: (schema: any, mockFetchOpts?: { + validate: boolean; +}) => (uri: any, options: any) => Promise; + +// @public (undocumented) +export const createMockSchema: (staticSchema: GraphQLSchema, mocks: { + [key: string]: any; +}) => GraphQLSchema; + // @public (undocumented) namespace DataProxy { // (undocumented) @@ -1265,6 +1276,23 @@ type Path = ReadonlyArray; // @public (undocumented) type Primitive = null | undefined | string | number | boolean | symbol | bigint; +// Warning: (ae-forgotten-export) The symbol "ProxiedSchemaFns" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export const proxiedSchema: (schemaWithMocks: GraphQLSchema, resolvers: Resolvers) => GraphQLSchema & ProxiedSchemaFns; + +// @public (undocumented) +interface ProxiedSchemaFns { + // (undocumented) + addResolvers: (newResolvers: Resolvers) => GraphQLSchema; + // (undocumented) + fork: () => GraphQLSchema; + // (undocumented) + forkWithResolvers: (newResolvers: Resolvers) => GraphQLSchema; + // (undocumented) + reset: () => void; +} + // @public (undocumented) class QueryInfo { constructor(queryManager: QueryManager, queryId?: string); diff --git a/.api-reports/api-report-testing_core.md b/.api-reports/api-report-testing_core.md index da8706e0df2..e8f7fd6fe95 100644 --- a/.api-reports/api-report-testing_core.md +++ b/.api-reports/api-report-testing_core.md @@ -11,6 +11,7 @@ import type { FieldNode } from 'graphql'; import type { FragmentDefinitionNode } from 'graphql'; import type { GraphQLError } from 'graphql'; import type { GraphQLErrorExtensions } from 'graphql'; +import type { GraphQLSchema } from 'graphql'; import { Observable } from 'zen-observable-ts'; import type { Observer } from 'zen-observable-ts'; import type { Subscriber } from 'zen-observable-ts'; @@ -445,6 +446,11 @@ type ConcastSourcesIterable = Iterable>; // @public (undocumented) export function createMockClient(data: TData, query: DocumentNode, variables?: {}): ApolloClient; +// @public (undocumented) +export const createMockFetch: (schema: any, mockFetchOpts?: { + validate: boolean; +}) => (uri: any, options: any) => Promise; + // @public (undocumented) namespace DataProxy { // (undocumented) @@ -1220,6 +1226,23 @@ type Path = ReadonlyArray; // @public (undocumented) type Primitive = null | undefined | string | number | boolean | symbol | bigint; +// Warning: (ae-forgotten-export) The symbol "ProxiedSchemaFns" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export const proxiedSchema: (schemaWithMocks: GraphQLSchema, resolvers: Resolvers) => GraphQLSchema & ProxiedSchemaFns; + +// @public (undocumented) +interface ProxiedSchemaFns { + // (undocumented) + addResolvers: (newResolvers: Resolvers) => GraphQLSchema; + // (undocumented) + fork: () => GraphQLSchema; + // (undocumented) + forkWithResolvers: (newResolvers: Resolvers) => GraphQLSchema; + // (undocumented) + reset: () => void; +} + // @public (undocumented) class QueryInfo { constructor(queryManager: QueryManager, queryId?: string); diff --git a/src/testing/core/__tests__/schemaProxy.test.tsx b/src/testing/core/__tests__/schemaProxy.test.tsx index b3c543e83a6..4520d9cf2f3 100644 --- a/src/testing/core/__tests__/schemaProxy.test.tsx +++ b/src/testing/core/__tests__/schemaProxy.test.tsx @@ -260,11 +260,7 @@ describe("schema proxy", () => { }); const Profiler = createDefaultProfiler(); - // TODO list: - // might need rootValue and context in the future but for now we'll - // leave them out of the options accepted by createMockFetch - // where might we want to inject context? (e.g. for authentication) - // start without (maybe it should be injected via createMockFetch?) + const mockFetch = createMockFetch(forkedSchema); const client = new ApolloClient({ cache: new InMemoryCache(), @@ -528,8 +524,6 @@ describe("schema proxy", () => { __typename: "User", age: 42, id: "1", - // In our resolvers defined in this test, we omit name so it uses - // the scalar default mock name: "Jane Doe", book: { // locally overrode the resolver for the book field @@ -554,8 +548,6 @@ describe("schema proxy", () => { __typename: "User", age: 42, id: "1", - // In our resolvers defined in this test, we omit name so it uses - // the scalar default mock name: "Alexandre", book: { // locally overrode the resolver for the book field From 919d66d29669ad0a3bc0864b3e926cd7a6e1a9cc Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Tue, 26 Mar 2024 16:26:34 -0400 Subject: [PATCH 13/26] chore: remove skipped tests --- src/testing/graphql-tools/utils.test.ts | 80 ------------------------- 1 file changed, 80 deletions(-) diff --git a/src/testing/graphql-tools/utils.test.ts b/src/testing/graphql-tools/utils.test.ts index 02d23f6ea23..0d7a9c63fac 100644 --- a/src/testing/graphql-tools/utils.test.ts +++ b/src/testing/graphql-tools/utils.test.ts @@ -224,84 +224,4 @@ describe("addMocksToSchema", () => { expect(data).toBeDefined(); expect((data!["viewer"] as any)["book"]["publishedAt"]).toBe(mockDate); }); - - it.skip("should handle null fields correctly", async () => { - const schema = buildSchema(/* GraphQL */ ` - type Query { - foo: Foo - } - type Foo { - field1: String - field2: Int - } - `); - - const mockedSchema = createMockSchema(schema, { - Foo: () => ({ - field1: "text", - field2: null, - }), - }); - - const query = /* GraphQL */ ` - { - foo { - field1 - field2 - } - } - `; - const { data } = await graphql({ - schema: mockedSchema, - source: query, - }); - - const fooData = data?.["foo"] as any; - expect(fooData.field1).toBe("text"); - expect(fooData.field2).toBe(null); - }); - - it.skip("should handle null fields correctly in nested fields", async () => { - const schema = buildSchema(/* GraphQL */ ` - type Query { - foo: Foo - } - type Foo { - foo_field: String - boo: Boo - } - type Boo { - boo_field: String - } - `); - - const mockedSchema = createMockSchema(schema, { - // TODO: should we allow mocking of concrete types? - Foo: () => ({ - foo_field: "text", - boo: null, - }), - }); - - const query = /* GraphQL */ ` - { - foo { - foo_field - boo { - boo_field - } - } - } - `; - const { data, errors } = await graphql({ - schema: mockedSchema, - source: query, - }); - - expect(errors).toBeFalsy(); - - const fooData = data?.["foo"] as any; - expect(fooData.foo_field).toBe("text"); - expect(fooData.boo).toBe(null); - }); }); From de6aa06b84f896eeb9735f55fd0826f4c692322e Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Thu, 28 Mar 2024 14:24:09 -0400 Subject: [PATCH 14/26] fix: remove typescript ignore comments and improve accuracy of code comment about why we call .bind --- src/testing/core/schemaProxy.ts | 36 ++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/testing/core/schemaProxy.ts b/src/testing/core/schemaProxy.ts index a374516d973..b7dd7a00f67 100644 --- a/src/testing/core/schemaProxy.ts +++ b/src/testing/core/schemaProxy.ts @@ -48,16 +48,34 @@ const proxiedSchema = ( return Reflect.get(fns, p); } - // this binds `this` to the right schema - without it, the new schema - // calls methods but with the wrong `this` context, from the previous - // schema - // @ts-ignore - if (typeof targetSchema[p] === "function") { - // @ts-ignore - return targetSchema[p].bind(targetSchema); - } + // An optimization that eliminates round-trips through the proxy + // on class methods invoked via `this` on a base class instance wrapped by + // the proxy. + // + // For example, consider the following class: + // + // class Base { + // foo(){ + // this.bar() + // } + // bar(){ + // ... + // } + // } + // + // Calling `proxy.foo()` would call `foo` with `this` being the proxy. + // This would result in calling `proxy.bar()` which would again invoke + // the proxy to resolve `bar` and call that method. + // + // Instead, calls to `proxy.foo()` should result in a call to + // `innerObject.foo()` with a `this` of `innerObject`, and that call + // should directly call `innerObject.bar()`. - return Reflect.get(targetSchema, p); + const property = Reflect.get(targetSchema, p); + if (typeof property === "function") { + return property.bind(targetSchema); + } + return property; }, }); From 7d30ccfac444d0cca3ae137e92bf5b6fe6810ca5 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Fri, 29 Mar 2024 11:06:42 -0400 Subject: [PATCH 15/26] chore: update launch.json --- .vscode/launch.json | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 6cdb71bc99d..d40a6ff9315 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,25 +1,22 @@ { "version": "0.2.0", "configurations": [ - { - "name": "Attach to Node.js inspector", - "port": 9229, - "request": "attach", - "skipFiles": ["/**"], - "type": "pwa-node" - }, { "type": "node", "request": "launch", - "name": "Jest Current File", - "program": "${workspaceFolder}/node_modules/.bin/jest", - "args": ["${relativeFile}", "--config", "./config/jest.config.js"], + "name": "Jest Attach Node Inspector for Current File", + "cwd": "${workspaceFolder}", + "runtimeArgs": [ + "--inspect-brk", + "${workspaceRoot}/node_modules/.bin/jest", + "${relativeFile}", + "--config", + "./config/jest.config.js", + "--runInBand", + "--watch" + ], "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - "disableOptimisticBPs": true, - "windows": { - "program": "${workspaceFolder}/node_modules/jest/bin/jest" - } + "internalConsoleOptions": "neverOpen" } ] } From d1f3c19a908397cea9f5433270d1cc49b4a62d90 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Fri, 29 Mar 2024 11:06:59 -0400 Subject: [PATCH 16/26] feat: make fetch automocking/unmocking nicer --- .../core/__tests__/schemaProxy.test.tsx | 31 +++++++++++-------- src/testing/core/mockFetchWithSchema.ts | 16 ++++++++-- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/testing/core/__tests__/schemaProxy.test.tsx b/src/testing/core/__tests__/schemaProxy.test.tsx index 4520d9cf2f3..a353b2b58c3 100644 --- a/src/testing/core/__tests__/schemaProxy.test.tsx +++ b/src/testing/core/__tests__/schemaProxy.test.tsx @@ -2,7 +2,6 @@ import * as React from "react"; import { ApolloClient, ApolloError, - HttpLink, InMemoryCache, gql, } from "../../../core/index.js"; @@ -87,6 +86,8 @@ const typeDefs = /* GraphQL */ ` const schemaWithTypeDefs = buildSchema(typeDefs); +const uri = "https://localhost:3000/graphql"; + function createDefaultProfiler() { return createProfiler({ initialSnapshot: { @@ -171,10 +172,12 @@ describe("schema proxy", () => { it("mocks scalars and resolvers", async () => { const Profiler = createDefaultProfiler(); - const mockFetch = createMockFetch(schema); + + using _fetch = createMockFetch(schema); + const client = new ApolloClient({ cache: new InMemoryCache(), - link: new HttpLink({ fetch: mockFetch }), + uri, }); const query: TypedDocumentNode = gql` @@ -261,10 +264,11 @@ describe("schema proxy", () => { const Profiler = createDefaultProfiler(); - const mockFetch = createMockFetch(forkedSchema); + using _fetch = createMockFetch(forkedSchema); + const client = new ApolloClient({ cache: new InMemoryCache(), - link: new HttpLink({ fetch: mockFetch }), + uri, }); const query: TypedDocumentNode = gql` @@ -342,11 +346,12 @@ describe("schema proxy", () => { it("does not pollute the original schema", async () => { const Profiler = createDefaultProfiler(); - const mockFetch = createMockFetch(schema); + + using _fetch = createMockFetch(schema); const client = new ApolloClient({ cache: new InMemoryCache(), - link: new HttpLink({ fetch: mockFetch }), + uri, }); const query: TypedDocumentNode = gql` @@ -459,11 +464,11 @@ describe("schema proxy", () => { const Profiler = createDefaultProfiler(); - const mockFetch = createMockFetch(forkedSchema); + using _fetch = createMockFetch(forkedSchema); const client = new ApolloClient({ cache: new InMemoryCache(), - link: new HttpLink({ fetch: mockFetch }), + uri, }); const mutation = gql` @@ -600,11 +605,11 @@ describe("schema proxy", () => { const { ErrorBoundary } = createTrackedErrorComponents(Profiler); - const mockFetch = createMockFetch(forkedSchema); + using _fetch = createMockFetch(forkedSchema); const client = new ApolloClient({ cache: new InMemoryCache(), - link: new HttpLink({ fetch: mockFetch }), + uri, }); const Fallback = () => { @@ -679,11 +684,11 @@ describe("schema proxy", () => { const { ErrorBoundary } = createTrackedErrorComponents(Profiler); - const mockFetch = createMockFetch(forkedSchema); + using _fetch = createMockFetch(forkedSchema); const client = new ApolloClient({ cache: new InMemoryCache(), - link: new HttpLink({ fetch: mockFetch }), + uri, }); const Fallback = () => { diff --git a/src/testing/core/mockFetchWithSchema.ts b/src/testing/core/mockFetchWithSchema.ts index 7a3d1a9a619..386dbbafc0b 100644 --- a/src/testing/core/mockFetchWithSchema.ts +++ b/src/testing/core/mockFetchWithSchema.ts @@ -1,14 +1,17 @@ -import { ApolloError, gql } from "../../core/index.js"; import { Response as NodeFetchResponse } from "node-fetch"; import { execute, validate } from "graphql"; import type { GraphQLError } from "graphql"; +import { ApolloError, gql } from "../../core/index.js"; +import { withCleanup } from "../internal/index.js"; const createMockFetch = ( schema: any, mockFetchOpts: { validate: boolean } = { validate: true } ) => { + const prevFetch = window.fetch; + const mockFetch: (uri: any, options: any) => Promise = ( - uri, + _uri, options ) => { return new Promise(async (resolve) => { @@ -47,7 +50,14 @@ const createMockFetch = ( resolve(new NodeFetchResponse(stringifiedResult) as unknown as Response); }); }; - return mockFetch; + + window.fetch = mockFetch; + + const restore = () => { + window.fetch = prevFetch; + }; + + return withCleanup({ mock: mockFetch, restore }, restore); }; export { createMockFetch }; From 4f669384a2f2fc5abd4ea656a8e6b22549a1cd8a Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Fri, 29 Mar 2024 11:42:57 -0400 Subject: [PATCH 17/26] feat: change API to .add and .fork that accept options including resolvers --- .../core/__tests__/schemaProxy.test.tsx | 169 ++++++++++++++---- src/testing/core/schemaProxy.ts | 24 +-- 2 files changed, 147 insertions(+), 46 deletions(-) diff --git a/src/testing/core/__tests__/schemaProxy.test.tsx b/src/testing/core/__tests__/schemaProxy.test.tsx index a353b2b58c3..02545fd72af 100644 --- a/src/testing/core/__tests__/schemaProxy.test.tsx +++ b/src/testing/core/__tests__/schemaProxy.test.tsx @@ -251,14 +251,16 @@ describe("schema proxy", () => { }); it("allows schema forking with .fork", async () => { - const forkedSchema = schema.forkWithResolvers({ - Query: { - viewer: () => ({ - book: { - colors: ["red", "blue", "green"], - title: "The Book", - }, - }), + const forkedSchema = schema.fork({ + resolvers: { + Query: { + viewer: () => ({ + book: { + colors: ["red", "blue", "green"], + title: "The Book", + }, + }), + }, }, }); @@ -424,7 +426,31 @@ describe("schema proxy", () => { unmount(); }); - it("handles mutations", async () => { + it("allows you to call .fork without providing resolvers", async () => { + const forkedSchema = schema.fork(); + + forkedSchema.add({ + resolvers: { + Query: { + viewer: () => ({ + book: { + colors: ["red", "blue", "green"], + title: "The Book", + }, + }), + }, + }, + }); + + const Profiler = createDefaultProfiler(); + + using _fetch = createMockFetch(schema); + + const client = new ApolloClient({ + cache: new InMemoryCache(), + uri, + }); + const query: TypedDocumentNode = gql` query { viewer { @@ -440,24 +466,97 @@ describe("schema proxy", () => { } `; - let name = "Jane Doe"; + const Fallback = () => { + useTrackRenders(); + return
Loading...
; + }; + + const App = () => { + return ( + }> + + + ); + }; - const forkedSchema = schema.forkWithResolvers({ - Query: { - viewer: () => ({ + const Child = () => { + const result = useSuspenseQuery(query); + + useTrackRenders(); + + Profiler.mergeSnapshot({ + result, + } as Partial<{}>); + + return
Hello
; + }; + + const { unmount } = 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: "Jane Doe", book: { - text: "Hello World", + __typename: "TextBook", + id: "1", + publishedAt: "2024-01-01", title: "The Book", }, - }), - }, - User: { - name: () => name, - }, - Mutation: { - changeViewerName: (_: any, { newName }: { newName: string }) => { - name = newName; - return {}; + }, + }); + } + + unmount(); + }); + + it("handles mutations", async () => { + const query: TypedDocumentNode = gql` + query { + viewer { + id + name + age + book { + id + title + publishedAt + } + } + } + `; + + let name = "Jane Doe"; + + const forkedSchema = schema.fork({ + resolvers: { + Query: { + viewer: () => ({ + book: { + text: "Hello World", + title: "The Book", + }, + }), + }, + User: { + name: () => name, + }, + Mutation: { + changeViewerName: (_: any, { newName }: { newName: string }) => { + name = newName; + return {}; + }, }, }, }); @@ -587,17 +686,19 @@ describe("schema proxy", () => { let name = "Jane Doe"; - const forkedSchema = schema.forkWithResolvers({ - Query: { - viewer: () => ({ - book: { - // text: "Hello World", <- this will cause a validation error - title: "The Book", - }, - }), - }, - User: { - name: () => name, + const forkedSchema = schema.fork({ + resolvers: { + Query: { + viewer: () => ({ + book: { + // text: "Hello World", <- this will cause a validation error + title: "The Book", + }, + }), + }, + User: { + name: () => name, + }, }, }); diff --git a/src/testing/core/schemaProxy.ts b/src/testing/core/schemaProxy.ts index b7dd7a00f67..fb22ae80470 100644 --- a/src/testing/core/schemaProxy.ts +++ b/src/testing/core/schemaProxy.ts @@ -3,37 +3,37 @@ import type { GraphQLSchema } from "graphql"; import type { Resolvers } from "../../core/types.js"; +type ProxiedSchema = GraphQLSchema & ProxiedSchemaFns; + interface ProxiedSchemaFns { - addResolvers: (newResolvers: Resolvers) => GraphQLSchema; - forkWithResolvers: (newResolvers: Resolvers) => GraphQLSchema; + add: (addOptions: { resolvers: Resolvers }) => ProxiedSchema; + fork: (forkOptions?: { resolvers: Resolvers }) => ProxiedSchema; reset: () => void; - fork: () => GraphQLSchema; } const proxiedSchema = ( schemaWithMocks: GraphQLSchema, resolvers: Resolvers -): GraphQLSchema & ProxiedSchemaFns => { +): ProxiedSchema => { let targetSchema = addResolversToSchema({ schema: schemaWithMocks, resolvers, }); const fns: ProxiedSchemaFns = { - addResolvers: (newResolvers: typeof resolvers) => + add: ({ resolvers: newResolvers }) => (targetSchema = addResolversToSchema({ schema: targetSchema, resolvers: { ...resolvers, ...newResolvers, }, - })), - forkWithResolvers: (newResolvers: typeof resolvers) => { - return proxiedSchema(targetSchema, newResolvers); - }, - fork: () => { - return proxiedSchema(targetSchema, {}); + })) as ProxiedSchema, + + fork: ({ resolvers } = { resolvers: {} }) => { + return proxiedSchema(targetSchema, resolvers); }, + reset: () => { targetSchema = addResolversToSchema({ schema: schemaWithMocks, @@ -79,7 +79,7 @@ const proxiedSchema = ( }, }); - return schema as GraphQLSchema & ProxiedSchemaFns; + return schema as ProxiedSchema; }; export { proxiedSchema }; From 177790bca4c3d9abd0c38c84a00e211fa90342db Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Mon, 1 Apr 2024 16:08:50 -0400 Subject: [PATCH 18/26] fix: hoist result of shallow merged resolvers to outer scope on calls to .add --- .../core/__tests__/schemaProxy.test.tsx | 192 +++++++++++++++++- src/testing/core/schemaProxy.ts | 24 ++- 2 files changed, 202 insertions(+), 14 deletions(-) diff --git a/src/testing/core/__tests__/schemaProxy.test.tsx b/src/testing/core/__tests__/schemaProxy.test.tsx index 02545fd72af..3dd09999e25 100644 --- a/src/testing/core/__tests__/schemaProxy.test.tsx +++ b/src/testing/core/__tests__/schemaProxy.test.tsx @@ -444,7 +444,7 @@ describe("schema proxy", () => { const Profiler = createDefaultProfiler(); - using _fetch = createMockFetch(schema); + using _fetch = createMockFetch(forkedSchema); const client = new ApolloClient({ cache: new InMemoryCache(), @@ -507,9 +507,12 @@ describe("schema proxy", () => { __typename: "User", age: 42, id: "1", - name: "Jane Doe", + // since we called .add and provided a new `viewer` resolver + // _without_ providing the viewer.name field in the response data, + // it renders with the default scalar mock for String + name: "String", book: { - __typename: "TextBook", + __typename: "ColoringBook", id: "1", publishedAt: "2024-01-01", title: "The Book", @@ -841,4 +844,187 @@ describe("schema proxy", () => { unmount(); }); + + it("preserves resolvers from previous calls to .add on subsequent calls to .fork", async () => { + let name = "Virginia"; + + const schema = proxiedSchema(schemaWithMocks, { + Query: { + viewer: () => ({ + name, + book: { + text: "Hello World", + title: "The Book", + }, + }), + }, + Book: { + __resolveType: (obj) => { + if ("text" in obj) { + return "TextBook"; + } + if ("colors" in obj) { + return "ColoringBook"; + } + throw new Error("Could not resolve type"); + }, + }, + }); + + schema.add({ + resolvers: { + Query: { + viewer: () => ({ + name: "Virginia", + book: { + colors: ["red", "blue", "green"], + title: "The Book", + }, + }), + }, + }, + }); + + schema.add({ + resolvers: { + User: { + name: () => name, + }, + }, + }); + + // should preserve resolvers from previous calls to .add + const forkedSchema = schema.fork({ + resolvers: { + Mutation: { + changeViewerName: (_: any, { newName }: { newName: string }) => { + name = newName; + return {}; + }, + }, + }, + }); + + const Profiler = createDefaultProfiler(); + + using _fetch = createMockFetch(forkedSchema); + + 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 mutation = gql` + mutation { + changeViewerName(newName: "Alexandre") { + id + name + } + } + `; + + const Fallback = () => { + useTrackRenders(); + return
Loading...
; + }; + + const App = () => { + return ( + }> + + + ); + }; + + const Child = () => { + const result = useSuspenseQuery(query); + const [changeViewerName] = useMutation(mutation); + + useTrackRenders(); + + Profiler.mergeSnapshot({ + result, + } as Partial<{}>); + + return ( +
+ + Hello +
+ ); + }; + + const user = userEvent.setup(); + + const { unmount } = 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: "Virginia", + book: { + __typename: "ColoringBook", + colors: ["red", "blue", "green"], + id: "1", + publishedAt: "2024-01-01", + title: "The Book", + }, + }, + }); + } + + await act(() => user.click(screen.getByText("Change name"))); + + await Profiler.takeRender(); + { + const { snapshot } = await Profiler.takeRender(); + + expect(snapshot.result?.data).toEqual({ + viewer: { + __typename: "User", + age: 42, + id: "1", + name: "Alexandre", + book: { + __typename: "ColoringBook", + colors: ["red", "blue", "green"], + id: "1", + publishedAt: "2024-01-01", + title: "The Book", + }, + }, + }); + } + + unmount(); + }); }); diff --git a/src/testing/core/schemaProxy.ts b/src/testing/core/schemaProxy.ts index fb22ae80470..e1bcd90b6b5 100644 --- a/src/testing/core/schemaProxy.ts +++ b/src/testing/core/schemaProxy.ts @@ -7,7 +7,7 @@ type ProxiedSchema = GraphQLSchema & ProxiedSchemaFns; interface ProxiedSchemaFns { add: (addOptions: { resolvers: Resolvers }) => ProxiedSchema; - fork: (forkOptions?: { resolvers: Resolvers }) => ProxiedSchema; + fork: (forkOptions?: { resolvers?: Resolvers }) => ProxiedSchema; reset: () => void; } @@ -15,23 +15,25 @@ const proxiedSchema = ( schemaWithMocks: GraphQLSchema, resolvers: Resolvers ): ProxiedSchema => { + let targetResolvers = { ...resolvers }; let targetSchema = addResolversToSchema({ schema: schemaWithMocks, - resolvers, + resolvers: targetResolvers, }); const fns: ProxiedSchemaFns = { - add: ({ resolvers: newResolvers }) => - (targetSchema = addResolversToSchema({ + add: ({ resolvers: newResolvers }) => { + targetResolvers = { ...targetResolvers, ...newResolvers }; + targetSchema = addResolversToSchema({ schema: targetSchema, - resolvers: { - ...resolvers, - ...newResolvers, - }, - })) as ProxiedSchema, + resolvers: targetResolvers, + }); + + return targetSchema as ProxiedSchema; + }, - fork: ({ resolvers } = { resolvers: {} }) => { - return proxiedSchema(targetSchema, resolvers); + fork: ({ resolvers: newResolvers } = {}) => { + return proxiedSchema(targetSchema, newResolvers ?? targetResolvers); }, reset: () => { From a60a313043ce4ba8195144567b78d239efaff22f Mon Sep 17 00:00:00 2001 From: alessbell Date: Mon, 1 Apr 2024 20:11:29 +0000 Subject: [PATCH 19/26] Clean up Prettier, Size-limit, and Api-Extractor --- .api-reports/api-report-testing.md | 24 ++++++++++++++++++------ .api-reports/api-report-testing_core.md | 24 ++++++++++++++++++------ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/.api-reports/api-report-testing.md b/.api-reports/api-report-testing.md index 46374623734..73103782cdf 100644 --- a/.api-reports/api-report-testing.md +++ b/.api-reports/api-report-testing.md @@ -4,6 +4,8 @@ ```ts +/// + import type { ASTNode } from 'graphql'; import type { DocumentNode } from 'graphql'; import type { ExecutionResult } from 'graphql'; @@ -450,7 +452,10 @@ export function createMockClient(data: TData, query: DocumentNode, variab // @public (undocumented) export const createMockFetch: (schema: any, mockFetchOpts?: { validate: boolean; -}) => (uri: any, options: any) => Promise; +}) => { + mock: (uri: any, options: any) => Promise; + restore: () => void; +} & Disposable; // @public (undocumented) export const createMockSchema: (staticSchema: GraphQLSchema, mocks: { @@ -1279,16 +1284,23 @@ type Primitive = null | undefined | string | number | boolean | symbol | bigint; // Warning: (ae-forgotten-export) The symbol "ProxiedSchemaFns" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export const proxiedSchema: (schemaWithMocks: GraphQLSchema, resolvers: Resolvers) => GraphQLSchema & ProxiedSchemaFns; +type ProxiedSchema = GraphQLSchema & ProxiedSchemaFns; + +// Warning: (ae-forgotten-export) The symbol "ProxiedSchema" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export const proxiedSchema: (schemaWithMocks: GraphQLSchema, resolvers: Resolvers) => ProxiedSchema; // @public (undocumented) interface ProxiedSchemaFns { // (undocumented) - addResolvers: (newResolvers: Resolvers) => GraphQLSchema; - // (undocumented) - fork: () => GraphQLSchema; + add: (addOptions: { + resolvers: Resolvers; + }) => ProxiedSchema; // (undocumented) - forkWithResolvers: (newResolvers: Resolvers) => GraphQLSchema; + fork: (forkOptions?: { + resolvers?: Resolvers; + }) => ProxiedSchema; // (undocumented) reset: () => void; } diff --git a/.api-reports/api-report-testing_core.md b/.api-reports/api-report-testing_core.md index e8f7fd6fe95..7bb584474d6 100644 --- a/.api-reports/api-report-testing_core.md +++ b/.api-reports/api-report-testing_core.md @@ -4,6 +4,8 @@ ```ts +/// + import type { ASTNode } from 'graphql'; import type { DocumentNode } from 'graphql'; import type { ExecutionResult } from 'graphql'; @@ -449,7 +451,10 @@ export function createMockClient(data: TData, query: DocumentNode, variab // @public (undocumented) export const createMockFetch: (schema: any, mockFetchOpts?: { validate: boolean; -}) => (uri: any, options: any) => Promise; +}) => { + mock: (uri: any, options: any) => Promise; + restore: () => void; +} & Disposable; // @public (undocumented) namespace DataProxy { @@ -1229,16 +1234,23 @@ type Primitive = null | undefined | string | number | boolean | symbol | bigint; // Warning: (ae-forgotten-export) The symbol "ProxiedSchemaFns" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export const proxiedSchema: (schemaWithMocks: GraphQLSchema, resolvers: Resolvers) => GraphQLSchema & ProxiedSchemaFns; +type ProxiedSchema = GraphQLSchema & ProxiedSchemaFns; + +// Warning: (ae-forgotten-export) The symbol "ProxiedSchema" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export const proxiedSchema: (schemaWithMocks: GraphQLSchema, resolvers: Resolvers) => ProxiedSchema; // @public (undocumented) interface ProxiedSchemaFns { // (undocumented) - addResolvers: (newResolvers: Resolvers) => GraphQLSchema; - // (undocumented) - fork: () => GraphQLSchema; + add: (addOptions: { + resolvers: Resolvers; + }) => ProxiedSchema; // (undocumented) - forkWithResolvers: (newResolvers: Resolvers) => GraphQLSchema; + fork: (forkOptions?: { + resolvers?: Resolvers; + }) => ProxiedSchema; // (undocumented) reset: () => void; } From 0079451aee8d1737d53ef3aa433c19ea892feedb Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Tue, 2 Apr 2024 15:07:41 -0400 Subject: [PATCH 20/26] fix: remove node-fetch from mockFetchWithSchema --- patches/jest-environment-jsdom+29.7.0.patch | 19 +++++++++++++++++++ .../core/__tests__/schemaProxy.test.tsx | 1 + src/testing/core/mockFetchWithSchema.ts | 11 ++++------- 3 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 patches/jest-environment-jsdom+29.7.0.patch diff --git a/patches/jest-environment-jsdom+29.7.0.patch b/patches/jest-environment-jsdom+29.7.0.patch new file mode 100644 index 00000000000..39b42ec75b6 --- /dev/null +++ b/patches/jest-environment-jsdom+29.7.0.patch @@ -0,0 +1,19 @@ +diff --git a/node_modules/jest-environment-jsdom/build/index.js b/node_modules/jest-environment-jsdom/build/index.js +index 2e6c16c..ce5c77d 100644 +--- a/node_modules/jest-environment-jsdom/build/index.js ++++ b/node_modules/jest-environment-jsdom/build/index.js +@@ -96,6 +96,14 @@ class JSDOMEnvironment { + // TODO: remove this ASAP, but it currently causes tests to run really slow + global.Buffer = Buffer; + ++ // Add mocks for schemaProxy tests that rely on `Response` and `fetch` ++ // being globally available ++ global.fetch = fetch; ++ global.Headers = Headers; ++ global.Request = Request; ++ global.Response = Response; ++ global.AbortController = AbortController; ++ + // Report uncaught errors. + this.errorEventListener = event => { + if (userErrorListenerCount === 0 && event.error != null) { diff --git a/src/testing/core/__tests__/schemaProxy.test.tsx b/src/testing/core/__tests__/schemaProxy.test.tsx index 3dd09999e25..9e798d35d3a 100644 --- a/src/testing/core/__tests__/schemaProxy.test.tsx +++ b/src/testing/core/__tests__/schemaProxy.test.tsx @@ -788,6 +788,7 @@ describe("schema proxy", () => { const { ErrorBoundary } = createTrackedErrorComponents(Profiler); + // @ts-expect-error - we're intentionally passing an invalid schema using _fetch = createMockFetch(forkedSchema); const client = new ApolloClient({ diff --git a/src/testing/core/mockFetchWithSchema.ts b/src/testing/core/mockFetchWithSchema.ts index 386dbbafc0b..c0ddde0f26e 100644 --- a/src/testing/core/mockFetchWithSchema.ts +++ b/src/testing/core/mockFetchWithSchema.ts @@ -1,11 +1,10 @@ -import { Response as NodeFetchResponse } from "node-fetch"; import { execute, validate } from "graphql"; -import type { GraphQLError } from "graphql"; +import type { GraphQLError, GraphQLSchema } from "graphql"; import { ApolloError, gql } from "../../core/index.js"; import { withCleanup } from "../internal/index.js"; const createMockFetch = ( - schema: any, + schema: GraphQLSchema, mockFetchOpts: { validate: boolean } = { validate: true } ) => { const prevFetch = window.fetch; @@ -31,9 +30,7 @@ const createMockFetch = ( if (validationErrors?.length > 0) { return resolve( - new NodeFetchResponse( - JSON.stringify({ errors: validationErrors }) - ) as unknown as Response + new Response(JSON.stringify({ errors: validationErrors })) ); } } @@ -47,7 +44,7 @@ const createMockFetch = ( const stringifiedResult = JSON.stringify(result); - resolve(new NodeFetchResponse(stringifiedResult) as unknown as Response); + resolve(new Response(stringifiedResult)); }); }; From f7c04c91d7950a516d97a406746ddeb762389134 Mon Sep 17 00:00:00 2001 From: alessbell Date: Tue, 2 Apr 2024 19:10:01 +0000 Subject: [PATCH 21/26] Clean up Prettier, Size-limit, and Api-Extractor --- .api-reports/api-report-testing.md | 2 +- .api-reports/api-report-testing_core.md | 2 +- .size-limits.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.api-reports/api-report-testing.md b/.api-reports/api-report-testing.md index 73103782cdf..74c66af4338 100644 --- a/.api-reports/api-report-testing.md +++ b/.api-reports/api-report-testing.md @@ -450,7 +450,7 @@ type ConcastSourcesIterable = Iterable>; export function createMockClient(data: TData, query: DocumentNode, variables?: {}): ApolloClient; // @public (undocumented) -export const createMockFetch: (schema: any, mockFetchOpts?: { +export const createMockFetch: (schema: GraphQLSchema, mockFetchOpts?: { validate: boolean; }) => { mock: (uri: any, options: any) => Promise; diff --git a/.api-reports/api-report-testing_core.md b/.api-reports/api-report-testing_core.md index 7bb584474d6..289937a15db 100644 --- a/.api-reports/api-report-testing_core.md +++ b/.api-reports/api-report-testing_core.md @@ -449,7 +449,7 @@ type ConcastSourcesIterable = Iterable>; export function createMockClient(data: TData, query: DocumentNode, variables?: {}): ApolloClient; // @public (undocumented) -export const createMockFetch: (schema: any, mockFetchOpts?: { +export const createMockFetch: (schema: GraphQLSchema, mockFetchOpts?: { validate: boolean; }) => { mock: (uri: any, options: any) => Promise; diff --git a/.size-limits.json b/.size-limits.json index ca93dc475b9..455469f1ad8 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39512, - "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32801 + "dist/apollo-client.min.cjs": 39506, + "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32793 } From d0a9119419a1670afb6ab3fb844a880250b43da4 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Tue, 2 Apr 2024 15:13:12 -0400 Subject: [PATCH 22/26] chore: adds createMockFetch changeset --- .changeset/chatty-llamas-switch.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/chatty-llamas-switch.md diff --git a/.changeset/chatty-llamas-switch.md b/.changeset/chatty-llamas-switch.md new file mode 100644 index 00000000000..334cb9a020c --- /dev/null +++ b/.changeset/chatty-llamas-switch.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": minor +--- + +Adds `createMockFetch` utility for integration testing that includes the link chain From f72cbba479ee6b86c98ed57b7e1a72f20f1974c7 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Tue, 2 Apr 2024 16:01:36 -0400 Subject: [PATCH 23/26] chore: add doc blocks --- .api-reports/api-report-react.md | 47 ++++++++++--------- .api-reports/api-report-react_hooks.md | 34 +++++++------- .api-reports/api-report-react_internal.md | 28 +++++------ .api-reports/api-report-testing.md | 14 +++--- .api-reports/api-report-testing_core.md | 12 ++--- .api-reports/api-report.md | 47 ++++++++++--------- config/jest.config.js | 2 +- src/__tests__/__snapshots__/exports.ts.snap | 4 +- ....test.tsx => createProxiedSchema.test.tsx} | 8 ++-- ...kFetchWithSchema.ts => createMockFetch.ts} | 27 +++++++++++ ...{schemaProxy.ts => createProxiedSchema.ts} | 38 +++++++++++++-- src/testing/core/index.ts | 4 +- src/testing/graphql-tools/utils.ts | 20 ++++++++ 13 files changed, 183 insertions(+), 102 deletions(-) rename src/testing/core/__tests__/{schemaProxy.test.tsx => createProxiedSchema.test.tsx} (98%) rename src/testing/core/{mockFetchWithSchema.ts => createMockFetch.ts} (58%) rename src/testing/core/{schemaProxy.ts => createProxiedSchema.ts} (65%) diff --git a/.api-reports/api-report-react.md b/.api-reports/api-report-react.md index 2f6a7b58f38..67ea71c6e3e 100644 --- a/.api-reports/api-report-react.md +++ b/.api-reports/api-report-react.md @@ -356,7 +356,7 @@ export interface BackgroundQueryHookOptions = BackgroundQueryHookOptions, NoInfer>; +type BackgroundQueryHookOptionsNoInfer = BackgroundQueryHookOptions, NoInfer_2>; // Warning: (ae-forgotten-export) The symbol "MutationSharedOptions" needs to be exported by the entry point index.d.ts // @@ -1295,7 +1295,8 @@ type NextLink = (operation: Operation) => Observable; type NextResultListener = (method: "next" | "error" | "complete", arg?: any) => any; // @public -export type NoInfer = [T][T extends any ? 0 : never]; +type NoInfer_2 = [T][T extends any ? 0 : never]; +export { NoInfer_2 as NoInfer } // @public (undocumented) class ObservableQuery extends Observable> { @@ -1456,18 +1457,18 @@ export type PreloadQueryFetchPolicy = Extract>(query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg, TOptions>): QueryReference | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables>; - (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + >(query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg, TOptions>): QueryReference | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables>; + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { returnPartialData: true; errorPolicy: "ignore" | "all"; }): QueryReference | undefined, TVariables>; - (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { errorPolicy: "ignore" | "all"; }): QueryReference; - (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { returnPartialData: true; }): QueryReference, TVariables>; - (query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg>): QueryReference; + (query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg>): QueryReference; } // Warning: (ae-forgotten-export) The symbol "VariablesOption" needs to be exported by the entry point index.d.ts @@ -1486,9 +1487,9 @@ export type PreloadQueryOptions = [TVariables] extends [never] ? [ options?: PreloadQueryOptions & TOptions ] : {} extends OnlyRequiredProperties ? [ -options?: PreloadQueryOptions> & Omit +options?: PreloadQueryOptions> & Omit ] : [ -options: PreloadQueryOptions> & Omit +options: PreloadQueryOptions> & Omit ]; // @public (undocumented) @@ -2200,7 +2201,7 @@ export type UseBackgroundQueryResult(options: UseFragmentOptions): UseFragmentResult; // @public (undocumented) -export interface UseFragmentOptions extends Omit, NoInfer>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { +export interface UseFragmentOptions extends Omit, NoInfer_2>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { client?: ApolloClient; // (undocumented) from: StoreObject | Reference | string; @@ -2220,7 +2221,7 @@ export type UseFragmentResult = { }; // @public -export function useLazyQuery(query: DocumentNode | TypedDocumentNode, options?: LazyQueryHookOptions, NoInfer>): LazyQueryResultTuple; +export function useLazyQuery(query: DocumentNode | TypedDocumentNode, options?: LazyQueryHookOptions, NoInfer_2>): LazyQueryResultTuple; // @public (undocumented) export function useLoadableQuery(query: DocumentNode | TypedDocumentNode, options?: LoadableQueryHookOptions & TOptions): UseLoadableQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables>; @@ -2256,10 +2257,10 @@ queryRef: QueryReference | null, ]; // @public -export function useMutation = ApolloCache>(mutation: DocumentNode | TypedDocumentNode, options?: MutationHookOptions, NoInfer, TContext, TCache>): MutationTuple; +export function useMutation = ApolloCache>(mutation: DocumentNode | TypedDocumentNode, options?: MutationHookOptions, NoInfer_2, TContext, TCache>): MutationTuple; // @public -export function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer>): QueryResult; +export function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer_2>): QueryResult; // @public export function useQueryRefHandlers(queryRef: QueryReference): UseQueryRefHandlersResult; @@ -2286,48 +2287,48 @@ export interface UseReadQueryResult { } // @public -export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer>): SubscriptionResult; +export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): SubscriptionResult; // @public (undocumented) -export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; +export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { returnPartialData: true; errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { skip: boolean; returnPartialData: true; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { returnPartialData: true; }): UseSuspenseQueryResult, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { skip: boolean; }): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer_2> & { returnPartialData: true; })): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; // @public (undocumented) export interface UseSuspenseQueryResult { diff --git a/.api-reports/api-report-react_hooks.md b/.api-reports/api-report-react_hooks.md index 30d1621aff3..eef9ae23ef7 100644 --- a/.api-reports/api-report-react_hooks.md +++ b/.api-reports/api-report-react_hooks.md @@ -323,10 +323,10 @@ interface BackgroundQueryHookOptions = BackgroundQueryHookOptions, NoInfer>; +type BackgroundQueryHookOptionsNoInfer = BackgroundQueryHookOptions, NoInfer_2>; // Warning: (ae-forgotten-export) The symbol "MutationSharedOptions" needs to be exported by the entry point index.d.ts // @@ -1240,7 +1240,7 @@ type NextLink = (operation: Operation) => Observable; type NextResultListener = (method: "next" | "error" | "complete", arg?: any) => any; // @public -type NoInfer = [T][T extends any ? 0 : never]; +type NoInfer_2 = [T][T extends any ? 0 : never]; // @public (undocumented) class ObservableQuery extends Observable> { @@ -2036,7 +2036,7 @@ export type UseBackgroundQueryResult(options: UseFragmentOptions): UseFragmentResult; // @public (undocumented) -export interface UseFragmentOptions extends Omit, NoInfer>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { +export interface UseFragmentOptions extends Omit, NoInfer_2>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { client?: ApolloClient; // (undocumented) from: StoreObject | Reference | string; @@ -2058,7 +2058,7 @@ export type UseFragmentResult = { // Warning: (ae-forgotten-export) The symbol "LazyQueryResultTuple" needs to be exported by the entry point index.d.ts // // @public -export function useLazyQuery(query: DocumentNode | TypedDocumentNode, options?: LazyQueryHookOptions, NoInfer>): LazyQueryResultTuple; +export function useLazyQuery(query: DocumentNode | TypedDocumentNode, options?: LazyQueryHookOptions, NoInfer_2>): LazyQueryResultTuple; // Warning: (ae-forgotten-export) The symbol "LoadableQueryHookOptions" needs to be exported by the entry point index.d.ts // @@ -2099,10 +2099,10 @@ queryRef: QueryReference | null, // Warning: (ae-forgotten-export) The symbol "MutationTuple" needs to be exported by the entry point index.d.ts // // @public -export function useMutation = ApolloCache>(mutation: DocumentNode | TypedDocumentNode, options?: MutationHookOptions, NoInfer, TContext, TCache>): MutationTuple; +export function useMutation = ApolloCache>(mutation: DocumentNode | TypedDocumentNode, options?: MutationHookOptions, NoInfer_2, TContext, TCache>): MutationTuple; // @public -export function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer>): QueryResult; +export function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer_2>): QueryResult; // @public export function useQueryRefHandlers(queryRef: QueryReference): UseQueryRefHandlersResult; @@ -2131,50 +2131,50 @@ export interface UseReadQueryResult { // Warning: (ae-forgotten-export) The symbol "SubscriptionHookOptions" needs to be exported by the entry point index.d.ts // // @public -export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer>): SubscriptionResult; +export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): SubscriptionResult; // Warning: (ae-forgotten-export) The symbol "SuspenseQueryHookOptions" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; +export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { returnPartialData: true; errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { skip: boolean; returnPartialData: true; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { returnPartialData: true; }): UseSuspenseQueryResult, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { skip: boolean; }): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer_2> & { returnPartialData: true; })): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; // @public (undocumented) export interface UseSuspenseQueryResult { diff --git a/.api-reports/api-report-react_internal.md b/.api-reports/api-report-react_internal.md index 3d3e1c00d10..55ff6e23ca9 100644 --- a/.api-reports/api-report-react_internal.md +++ b/.api-reports/api-report-react_internal.md @@ -323,10 +323,10 @@ interface BackgroundQueryHookOptions = BackgroundQueryHookOptions, NoInfer>; +type BackgroundQueryHookOptionsNoInfer = BackgroundQueryHookOptions, NoInfer_2>; // Warning: (ae-forgotten-export) The symbol "SharedWatchQueryOptions" needs to be exported by the entry point index.d.ts // @@ -1149,7 +1149,7 @@ type NextLink = (operation: Operation) => Observable; type NextResultListener = (method: "next" | "error" | "complete", arg?: any) => any; // @public -type NoInfer = [T][T extends any ? 0 : never]; +type NoInfer_2 = [T][T extends any ? 0 : never]; // @public (undocumented) class ObservableQuery extends Observable> { @@ -1913,7 +1913,7 @@ type UseBackgroundQueryResult(options: UseFragmentOptions): UseFragmentResult; // @public (undocumented) -interface UseFragmentOptions extends Omit, NoInfer>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { +interface UseFragmentOptions extends Omit, NoInfer_2>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { client?: ApolloClient; // (undocumented) from: StoreObject | Reference | string; @@ -1935,7 +1935,7 @@ type UseFragmentResult = { // Warning: (ae-forgotten-export) The symbol "QueryResult" needs to be exported by the entry point index.d.ts // // @public -function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer>): QueryResult; +function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer_2>): QueryResult; // Warning: (ae-forgotten-export) The symbol "UseReadQueryResult" needs to be exported by the entry point index.d.ts // @@ -1953,45 +1953,45 @@ interface UseReadQueryResult { // Warning: (ae-forgotten-export) The symbol "UseSuspenseQueryResult" needs to be exported by the entry point index.d.ts // // @public (undocumented) -function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; +function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { returnPartialData: true; errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { skip: boolean; returnPartialData: true; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { returnPartialData: true; }): UseSuspenseQueryResult, TVariables>; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { skip: boolean; }): UseSuspenseQueryResult; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer> & { +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer_2> & { returnPartialData: true; })): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; // @public (undocumented) interface UseSuspenseQueryResult { diff --git a/.api-reports/api-report-testing.md b/.api-reports/api-report-testing.md index 74c66af4338..774a8e69cd1 100644 --- a/.api-reports/api-report-testing.md +++ b/.api-reports/api-report-testing.md @@ -449,7 +449,7 @@ type ConcastSourcesIterable = Iterable>; // @public (undocumented) export function createMockClient(data: TData, query: DocumentNode, variables?: {}): ApolloClient; -// @public (undocumented) +// @alpha export const createMockFetch: (schema: GraphQLSchema, mockFetchOpts?: { validate: boolean; }) => { @@ -457,11 +457,16 @@ export const createMockFetch: (schema: GraphQLSchema, mockFetchOpts?: { restore: () => void; } & Disposable; -// @public (undocumented) +// @alpha export const createMockSchema: (staticSchema: GraphQLSchema, mocks: { [key: string]: any; }) => GraphQLSchema; +// Warning: (ae-forgotten-export) The symbol "ProxiedSchema" needs to be exported by the entry point index.d.ts +// +// @alpha +export const createProxiedSchema: (schemaWithMocks: GraphQLSchema, resolvers: Resolvers) => ProxiedSchema; + // @public (undocumented) namespace DataProxy { // (undocumented) @@ -1286,11 +1291,6 @@ type Primitive = null | undefined | string | number | boolean | symbol | bigint; // @public (undocumented) type ProxiedSchema = GraphQLSchema & ProxiedSchemaFns; -// Warning: (ae-forgotten-export) The symbol "ProxiedSchema" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export const proxiedSchema: (schemaWithMocks: GraphQLSchema, resolvers: Resolvers) => ProxiedSchema; - // @public (undocumented) interface ProxiedSchemaFns { // (undocumented) diff --git a/.api-reports/api-report-testing_core.md b/.api-reports/api-report-testing_core.md index 289937a15db..42df7b5c695 100644 --- a/.api-reports/api-report-testing_core.md +++ b/.api-reports/api-report-testing_core.md @@ -448,7 +448,7 @@ type ConcastSourcesIterable = Iterable>; // @public (undocumented) export function createMockClient(data: TData, query: DocumentNode, variables?: {}): ApolloClient; -// @public (undocumented) +// @alpha export const createMockFetch: (schema: GraphQLSchema, mockFetchOpts?: { validate: boolean; }) => { @@ -456,6 +456,11 @@ export const createMockFetch: (schema: GraphQLSchema, mockFetchOpts?: { restore: () => void; } & Disposable; +// Warning: (ae-forgotten-export) The symbol "ProxiedSchema" needs to be exported by the entry point index.d.ts +// +// @alpha +export const createProxiedSchema: (schemaWithMocks: GraphQLSchema, resolvers: Resolvers) => ProxiedSchema; + // @public (undocumented) namespace DataProxy { // (undocumented) @@ -1236,11 +1241,6 @@ type Primitive = null | undefined | string | number | boolean | symbol | bigint; // @public (undocumented) type ProxiedSchema = GraphQLSchema & ProxiedSchemaFns; -// Warning: (ae-forgotten-export) The symbol "ProxiedSchema" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export const proxiedSchema: (schemaWithMocks: GraphQLSchema, resolvers: Resolvers) => ProxiedSchema; - // @public (undocumented) interface ProxiedSchemaFns { // (undocumented) diff --git a/.api-reports/api-report.md b/.api-reports/api-report.md index 75f041a2bd1..23b6b90baaf 100644 --- a/.api-reports/api-report.md +++ b/.api-reports/api-report.md @@ -330,7 +330,7 @@ export interface BackgroundQueryHookOptions = BackgroundQueryHookOptions, NoInfer>; +type BackgroundQueryHookOptionsNoInfer = BackgroundQueryHookOptions, NoInfer_2>; // Warning: (ae-forgotten-export) The symbol "MutationSharedOptions" needs to be exported by the entry point index.d.ts // @@ -1752,7 +1752,8 @@ export type NextLink = (operation: Operation) => Observable; type NextResultListener = (method: "next" | "error" | "complete", arg?: any) => any; // @public -export type NoInfer = [T][T extends any ? 0 : never]; +type NoInfer_2 = [T][T extends any ? 0 : never]; +export { NoInfer_2 as NoInfer } // @public export interface NormalizedCache { @@ -2021,18 +2022,18 @@ export type PreloadQueryFetchPolicy = Extract>(query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg, TOptions>): QueryReference | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables>; - (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + >(query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg, TOptions>): QueryReference | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables>; + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { returnPartialData: true; errorPolicy: "ignore" | "all"; }): QueryReference | undefined, TVariables>; - (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { errorPolicy: "ignore" | "all"; }): QueryReference; - (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { returnPartialData: true; }): QueryReference, TVariables>; - (query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg>): QueryReference; + (query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg>): QueryReference; } // Warning: (ae-forgotten-export) The symbol "VariablesOption" needs to be exported by the entry point index.d.ts @@ -2051,9 +2052,9 @@ export type PreloadQueryOptions = [TVariables] extends [never] ? [ options?: PreloadQueryOptions & TOptions ] : {} extends OnlyRequiredProperties ? [ -options?: PreloadQueryOptions> & Omit +options?: PreloadQueryOptions> & Omit ] : [ -options: PreloadQueryOptions> & Omit +options: PreloadQueryOptions> & Omit ]; // @public (undocumented) @@ -2865,7 +2866,7 @@ export type UseBackgroundQueryResult(options: UseFragmentOptions): UseFragmentResult; // @public (undocumented) -export interface UseFragmentOptions extends Omit, NoInfer>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { +export interface UseFragmentOptions extends Omit, NoInfer_2>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { client?: ApolloClient; // (undocumented) from: StoreObject | Reference | string; @@ -2885,7 +2886,7 @@ export type UseFragmentResult = { }; // @public -export function useLazyQuery(query: DocumentNode | TypedDocumentNode, options?: LazyQueryHookOptions, NoInfer>): LazyQueryResultTuple; +export function useLazyQuery(query: DocumentNode | TypedDocumentNode, options?: LazyQueryHookOptions, NoInfer_2>): LazyQueryResultTuple; // @public (undocumented) export function useLoadableQuery(query: DocumentNode | TypedDocumentNode, options?: LoadableQueryHookOptions & TOptions): UseLoadableQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables>; @@ -2921,10 +2922,10 @@ queryRef: QueryReference | null, ]; // @public -export function useMutation = ApolloCache>(mutation: DocumentNode | TypedDocumentNode, options?: MutationHookOptions, NoInfer, TContext, TCache>): MutationTuple; +export function useMutation = ApolloCache>(mutation: DocumentNode | TypedDocumentNode, options?: MutationHookOptions, NoInfer_2, TContext, TCache>): MutationTuple; // @public -export function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer>): QueryResult; +export function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer_2>): QueryResult; // @public export function useQueryRefHandlers(queryRef: QueryReference): UseQueryRefHandlersResult; @@ -2949,48 +2950,48 @@ export interface UseReadQueryResult { } // @public -export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer>): SubscriptionResult; +export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): SubscriptionResult; // @public (undocumented) -export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; +export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { returnPartialData: true; errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { skip: boolean; returnPartialData: true; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { returnPartialData: true; }): UseSuspenseQueryResult, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { skip: boolean; }): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer_2> & { returnPartialData: true; })): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; // @public (undocumented) export interface UseSuspenseQueryResult { diff --git a/config/jest.config.js b/config/jest.config.js index 4cfb5f320e0..4c045e3fde8 100644 --- a/config/jest.config.js +++ b/config/jest.config.js @@ -33,7 +33,7 @@ const react17TestFileIgnoreList = [ ignoreTSFiles, // We only support Suspense with React 18, so don't test suspense hooks with // React 17 - "src/testing/core/__tests__/schemaProxy.test.tsx", + "src/testing/core/__tests__/createProxiedSchema.test.tsx", "src/react/hooks/__tests__/useSuspenseQuery.test.tsx", "src/react/hooks/__tests__/useBackgroundQuery.test.tsx", "src/react/hooks/__tests__/useLoadableQuery.test.tsx", diff --git a/src/__tests__/__snapshots__/exports.ts.snap b/src/__tests__/__snapshots__/exports.ts.snap index dc06beaef14..5744dc7b332 100644 --- a/src/__tests__/__snapshots__/exports.ts.snap +++ b/src/__tests__/__snapshots__/exports.ts.snap @@ -371,10 +371,10 @@ Array [ "createMockClient", "createMockFetch", "createMockSchema", + "createProxiedSchema", "itAsync", "mockObservableLink", "mockSingleLink", - "proxiedSchema", "subscribeAndCount", "tick", "wait", @@ -390,10 +390,10 @@ Array [ "MockSubscriptionLink", "createMockClient", "createMockFetch", + "createProxiedSchema", "itAsync", "mockObservableLink", "mockSingleLink", - "proxiedSchema", "subscribeAndCount", "tick", "wait", diff --git a/src/testing/core/__tests__/schemaProxy.test.tsx b/src/testing/core/__tests__/createProxiedSchema.test.tsx similarity index 98% rename from src/testing/core/__tests__/schemaProxy.test.tsx rename to src/testing/core/__tests__/createProxiedSchema.test.tsx index 9e798d35d3a..dc5f686f8c7 100644 --- a/src/testing/core/__tests__/schemaProxy.test.tsx +++ b/src/testing/core/__tests__/createProxiedSchema.test.tsx @@ -13,14 +13,14 @@ import { spyOnConsole, useTrackRenders, } from "../../internal/index.js"; -import { proxiedSchema } from "../schemaProxy.js"; +import { createProxiedSchema } from "../createProxiedSchema.js"; import { GraphQLError, buildSchema } from "graphql"; import type { UseSuspenseQueryResult } from "../../../react/index.js"; import { useMutation, useSuspenseQuery } from "../../../react/index.js"; import { createMockSchema } from "../../graphql-tools/utils.js"; import userEvent from "@testing-library/user-event"; import { act, screen } from "@testing-library/react"; -import { createMockFetch } from "../mockFetchWithSchema.js"; +import { createMockFetch } from "../createMockFetch.js"; import { FallbackProps, ErrorBoundary as ReactErrorBoundary, @@ -147,7 +147,7 @@ describe("schema proxy", () => { Date: () => new Date("January 1, 2024 01:00:00").toJSON().split("T")[0], }); - const schema = proxiedSchema(schemaWithMocks, { + const schema = createProxiedSchema(schemaWithMocks, { Query: { viewer: () => ({ name: "Jane Doe", @@ -849,7 +849,7 @@ describe("schema proxy", () => { it("preserves resolvers from previous calls to .add on subsequent calls to .fork", async () => { let name = "Virginia"; - const schema = proxiedSchema(schemaWithMocks, { + const schema = createProxiedSchema(schemaWithMocks, { Query: { viewer: () => ({ name, diff --git a/src/testing/core/mockFetchWithSchema.ts b/src/testing/core/createMockFetch.ts similarity index 58% rename from src/testing/core/mockFetchWithSchema.ts rename to src/testing/core/createMockFetch.ts index c0ddde0f26e..7adb50d10ae 100644 --- a/src/testing/core/mockFetchWithSchema.ts +++ b/src/testing/core/createMockFetch.ts @@ -3,6 +3,33 @@ import type { GraphQLError, GraphQLSchema } from "graphql"; import { ApolloError, gql } from "../../core/index.js"; import { withCleanup } from "../internal/index.js"; +/** + * A function that accepts a static `schema` and a `mockFetchOpts` object and + * returns a disposable object with `mock` and `restore` functions. + * + * The `mock` function is a mock fetch function that is set on the global + * `window` object. This function intercepts any fetch requests and + * returns a response by executing the operation against the provided schema. + * + * The `restore` function is a cleanup function that will restore the previous + * `fetch`. It is automatically called if the function's return value is + * declared with `using`. If your environment does not support the language + * feature `using`, you should manually invoke the `restore` function. + * + * @param schema - A `GraphQLSchema`. + * @param mockFetchOpts - Configuration options. + * @returns An object with both `mock` and `restore` functions. + * + * @example + * ```js + * using _fetch = createMockFetch(schema); // automatically restores fetch after exiting the block + * + * const { restore } = createMockFetch(schema); + * restore(); // manually restore fetch if `using` is not supported + * ``` + * @since 3.10.0 + * @alpha + */ const createMockFetch = ( schema: GraphQLSchema, mockFetchOpts: { validate: boolean } = { validate: true } diff --git a/src/testing/core/schemaProxy.ts b/src/testing/core/createProxiedSchema.ts similarity index 65% rename from src/testing/core/schemaProxy.ts rename to src/testing/core/createProxiedSchema.ts index e1bcd90b6b5..e3ceaec2043 100644 --- a/src/testing/core/schemaProxy.ts +++ b/src/testing/core/createProxiedSchema.ts @@ -11,7 +11,39 @@ interface ProxiedSchemaFns { reset: () => void; } -const proxiedSchema = ( +/** + * A function that creates a [Proxy object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) + * around a given `schema` with `resolvers`. This proxied schema can be used to + * progressively layer resolvers on top of the original schema using the `add` + * method. The `fork` method can be used to create a new proxied schema which + * can be modified independently of the original schema. `reset` will restore + * resolvers to the original proxied schema. + * + * @param schemaWithMocks - A `GraphQLSchema`. + * @param resolvers - `Resolvers` object. + * @returns A `ProxiedSchema` with `add`, `fork` and `reset` methods. + * + * @example + * ```js + * const schemaWithMocks = createMockSchema(schemaWithTypeDefs, { + ID: () => "1", + Int: () => 36, + String: () => "String", + Date: () => new Date("December 10, 1815 01:00:00").toJSON().split("T")[0], + }); + * + * const schema = createProxiedSchema(schemaWithMocks, { + Query: { + writer: () => ({ + name: "Ada Lovelace", + }), + } + }); + * ``` + * @since 3.9.0 + * @alpha + */ +const createProxiedSchema = ( schemaWithMocks: GraphQLSchema, resolvers: Resolvers ): ProxiedSchema => { @@ -33,7 +65,7 @@ const proxiedSchema = ( }, fork: ({ resolvers: newResolvers } = {}) => { - return proxiedSchema(targetSchema, newResolvers ?? targetResolvers); + return createProxiedSchema(targetSchema, newResolvers ?? targetResolvers); }, reset: () => { @@ -84,4 +116,4 @@ const proxiedSchema = ( return schema as ProxiedSchema; }; -export { proxiedSchema }; +export { createProxiedSchema }; diff --git a/src/testing/core/index.ts b/src/testing/core/index.ts index 45c88fbe89b..b9b3065b211 100644 --- a/src/testing/core/index.ts +++ b/src/testing/core/index.ts @@ -12,6 +12,6 @@ export { createMockClient } from "./mocking/mockClient.js"; export { default as subscribeAndCount } from "./subscribeAndCount.js"; export { itAsync } from "./itAsync.js"; export { wait, tick } from "./wait.js"; -export { proxiedSchema } from "./schemaProxy.js"; -export { createMockFetch } from "./mockFetchWithSchema.js"; +export { createProxiedSchema } from "./createProxiedSchema.js"; +export { createMockFetch } from "./createMockFetch.js"; export * from "./withConsoleSpy.js"; diff --git a/src/testing/graphql-tools/utils.ts b/src/testing/graphql-tools/utils.ts index 9822a856ce9..629802eb5bd 100644 --- a/src/testing/graphql-tools/utils.ts +++ b/src/testing/graphql-tools/utils.ts @@ -27,6 +27,26 @@ import { MapperKind, mapSchema, getRootTypeNames } from "@graphql-tools/utils"; // https://github.com/ardatan/graphql-tools/blob/4b56b04d69b02919f6c5fa4f97d33da63f36e8c8/packages/mock/src/utils.ts#L20 const takeRandom = (arr: T[]) => arr[Math.floor(Math.random() * arr.length)]; +/** + * A function that accepts a static `schema` and a `mocks` object for specifying + * default scalar mocks and returns a `GraphQLSchema`. + * + * @param staticSchema - A static `GraphQLSchema`. + * @param mocks - An object containing scalar mocks. + * @returns A `GraphQLSchema` with scalar mocks. + * + * @example + * ```js + * const mockedSchema = createMockSchema(schema, { + ID: () => "1", + Int: () => 42, + String: () => "String", + Date: () => new Date("January 1, 2024 01:00:00").toJSON().split("T")[0], + }); + * ``` + * @since 3.10.0 + * @alpha + */ const createMockSchema = ( staticSchema: GraphQLSchema, mocks: { [key: string]: any } From c86d3065bb6c18cf23be4c0f06bbfc0683224ddf Mon Sep 17 00:00:00 2001 From: alessbell Date: Tue, 2 Apr 2024 20:04:09 +0000 Subject: [PATCH 24/26] Clean up Prettier, Size-limit, and Api-Extractor --- .api-reports/api-report-react.md | 47 +++++++++++------------ .api-reports/api-report-react_hooks.md | 34 ++++++++-------- .api-reports/api-report-react_internal.md | 28 +++++++------- .api-reports/api-report.md | 47 +++++++++++------------ 4 files changed, 77 insertions(+), 79 deletions(-) diff --git a/.api-reports/api-report-react.md b/.api-reports/api-report-react.md index 67ea71c6e3e..2f6a7b58f38 100644 --- a/.api-reports/api-report-react.md +++ b/.api-reports/api-report-react.md @@ -356,7 +356,7 @@ export interface BackgroundQueryHookOptions = BackgroundQueryHookOptions, NoInfer_2>; +type BackgroundQueryHookOptionsNoInfer = BackgroundQueryHookOptions, NoInfer>; // Warning: (ae-forgotten-export) The symbol "MutationSharedOptions" needs to be exported by the entry point index.d.ts // @@ -1295,8 +1295,7 @@ type NextLink = (operation: Operation) => Observable; type NextResultListener = (method: "next" | "error" | "complete", arg?: any) => any; // @public -type NoInfer_2 = [T][T extends any ? 0 : never]; -export { NoInfer_2 as NoInfer } +export type NoInfer = [T][T extends any ? 0 : never]; // @public (undocumented) class ObservableQuery extends Observable> { @@ -1457,18 +1456,18 @@ export type PreloadQueryFetchPolicy = Extract>(query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg, TOptions>): QueryReference | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables>; - (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + >(query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg, TOptions>): QueryReference | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables>; + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { returnPartialData: true; errorPolicy: "ignore" | "all"; }): QueryReference | undefined, TVariables>; - (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { errorPolicy: "ignore" | "all"; }): QueryReference; - (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { returnPartialData: true; }): QueryReference, TVariables>; - (query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg>): QueryReference; + (query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg>): QueryReference; } // Warning: (ae-forgotten-export) The symbol "VariablesOption" needs to be exported by the entry point index.d.ts @@ -1487,9 +1486,9 @@ export type PreloadQueryOptions = [TVariables] extends [never] ? [ options?: PreloadQueryOptions & TOptions ] : {} extends OnlyRequiredProperties ? [ -options?: PreloadQueryOptions> & Omit +options?: PreloadQueryOptions> & Omit ] : [ -options: PreloadQueryOptions> & Omit +options: PreloadQueryOptions> & Omit ]; // @public (undocumented) @@ -2201,7 +2200,7 @@ export type UseBackgroundQueryResult(options: UseFragmentOptions): UseFragmentResult; // @public (undocumented) -export interface UseFragmentOptions extends Omit, NoInfer_2>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { +export interface UseFragmentOptions extends Omit, NoInfer>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { client?: ApolloClient; // (undocumented) from: StoreObject | Reference | string; @@ -2221,7 +2220,7 @@ export type UseFragmentResult = { }; // @public -export function useLazyQuery(query: DocumentNode | TypedDocumentNode, options?: LazyQueryHookOptions, NoInfer_2>): LazyQueryResultTuple; +export function useLazyQuery(query: DocumentNode | TypedDocumentNode, options?: LazyQueryHookOptions, NoInfer>): LazyQueryResultTuple; // @public (undocumented) export function useLoadableQuery(query: DocumentNode | TypedDocumentNode, options?: LoadableQueryHookOptions & TOptions): UseLoadableQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables>; @@ -2257,10 +2256,10 @@ queryRef: QueryReference | null, ]; // @public -export function useMutation = ApolloCache>(mutation: DocumentNode | TypedDocumentNode, options?: MutationHookOptions, NoInfer_2, TContext, TCache>): MutationTuple; +export function useMutation = ApolloCache>(mutation: DocumentNode | TypedDocumentNode, options?: MutationHookOptions, NoInfer, TContext, TCache>): MutationTuple; // @public -export function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer_2>): QueryResult; +export function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer>): QueryResult; // @public export function useQueryRefHandlers(queryRef: QueryReference): UseQueryRefHandlersResult; @@ -2287,48 +2286,48 @@ export interface UseReadQueryResult { } // @public -export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): SubscriptionResult; +export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer>): SubscriptionResult; // @public (undocumented) -export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; +export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { returnPartialData: true; errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { skip: boolean; returnPartialData: true; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { returnPartialData: true; }): UseSuspenseQueryResult, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { skip: boolean; }): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer> & { returnPartialData: true; })): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; // @public (undocumented) export interface UseSuspenseQueryResult { diff --git a/.api-reports/api-report-react_hooks.md b/.api-reports/api-report-react_hooks.md index eef9ae23ef7..30d1621aff3 100644 --- a/.api-reports/api-report-react_hooks.md +++ b/.api-reports/api-report-react_hooks.md @@ -323,10 +323,10 @@ interface BackgroundQueryHookOptions = BackgroundQueryHookOptions, NoInfer_2>; +type BackgroundQueryHookOptionsNoInfer = BackgroundQueryHookOptions, NoInfer>; // Warning: (ae-forgotten-export) The symbol "MutationSharedOptions" needs to be exported by the entry point index.d.ts // @@ -1240,7 +1240,7 @@ type NextLink = (operation: Operation) => Observable; type NextResultListener = (method: "next" | "error" | "complete", arg?: any) => any; // @public -type NoInfer_2 = [T][T extends any ? 0 : never]; +type NoInfer = [T][T extends any ? 0 : never]; // @public (undocumented) class ObservableQuery extends Observable> { @@ -2036,7 +2036,7 @@ export type UseBackgroundQueryResult(options: UseFragmentOptions): UseFragmentResult; // @public (undocumented) -export interface UseFragmentOptions extends Omit, NoInfer_2>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { +export interface UseFragmentOptions extends Omit, NoInfer>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { client?: ApolloClient; // (undocumented) from: StoreObject | Reference | string; @@ -2058,7 +2058,7 @@ export type UseFragmentResult = { // Warning: (ae-forgotten-export) The symbol "LazyQueryResultTuple" needs to be exported by the entry point index.d.ts // // @public -export function useLazyQuery(query: DocumentNode | TypedDocumentNode, options?: LazyQueryHookOptions, NoInfer_2>): LazyQueryResultTuple; +export function useLazyQuery(query: DocumentNode | TypedDocumentNode, options?: LazyQueryHookOptions, NoInfer>): LazyQueryResultTuple; // Warning: (ae-forgotten-export) The symbol "LoadableQueryHookOptions" needs to be exported by the entry point index.d.ts // @@ -2099,10 +2099,10 @@ queryRef: QueryReference | null, // Warning: (ae-forgotten-export) The symbol "MutationTuple" needs to be exported by the entry point index.d.ts // // @public -export function useMutation = ApolloCache>(mutation: DocumentNode | TypedDocumentNode, options?: MutationHookOptions, NoInfer_2, TContext, TCache>): MutationTuple; +export function useMutation = ApolloCache>(mutation: DocumentNode | TypedDocumentNode, options?: MutationHookOptions, NoInfer, TContext, TCache>): MutationTuple; // @public -export function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer_2>): QueryResult; +export function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer>): QueryResult; // @public export function useQueryRefHandlers(queryRef: QueryReference): UseQueryRefHandlersResult; @@ -2131,50 +2131,50 @@ export interface UseReadQueryResult { // Warning: (ae-forgotten-export) The symbol "SubscriptionHookOptions" needs to be exported by the entry point index.d.ts // // @public -export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): SubscriptionResult; +export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer>): SubscriptionResult; // Warning: (ae-forgotten-export) The symbol "SuspenseQueryHookOptions" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; +export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { returnPartialData: true; errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { skip: boolean; returnPartialData: true; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { returnPartialData: true; }): UseSuspenseQueryResult, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { skip: boolean; }): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer> & { returnPartialData: true; })): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; // @public (undocumented) export interface UseSuspenseQueryResult { diff --git a/.api-reports/api-report-react_internal.md b/.api-reports/api-report-react_internal.md index 55ff6e23ca9..3d3e1c00d10 100644 --- a/.api-reports/api-report-react_internal.md +++ b/.api-reports/api-report-react_internal.md @@ -323,10 +323,10 @@ interface BackgroundQueryHookOptions = BackgroundQueryHookOptions, NoInfer_2>; +type BackgroundQueryHookOptionsNoInfer = BackgroundQueryHookOptions, NoInfer>; // Warning: (ae-forgotten-export) The symbol "SharedWatchQueryOptions" needs to be exported by the entry point index.d.ts // @@ -1149,7 +1149,7 @@ type NextLink = (operation: Operation) => Observable; type NextResultListener = (method: "next" | "error" | "complete", arg?: any) => any; // @public -type NoInfer_2 = [T][T extends any ? 0 : never]; +type NoInfer = [T][T extends any ? 0 : never]; // @public (undocumented) class ObservableQuery extends Observable> { @@ -1913,7 +1913,7 @@ type UseBackgroundQueryResult(options: UseFragmentOptions): UseFragmentResult; // @public (undocumented) -interface UseFragmentOptions extends Omit, NoInfer_2>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { +interface UseFragmentOptions extends Omit, NoInfer>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { client?: ApolloClient; // (undocumented) from: StoreObject | Reference | string; @@ -1935,7 +1935,7 @@ type UseFragmentResult = { // Warning: (ae-forgotten-export) The symbol "QueryResult" needs to be exported by the entry point index.d.ts // // @public -function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer_2>): QueryResult; +function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer>): QueryResult; // Warning: (ae-forgotten-export) The symbol "UseReadQueryResult" needs to be exported by the entry point index.d.ts // @@ -1953,45 +1953,45 @@ interface UseReadQueryResult { // Warning: (ae-forgotten-export) The symbol "UseSuspenseQueryResult" needs to be exported by the entry point index.d.ts // // @public (undocumented) -function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; +function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { returnPartialData: true; errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { skip: boolean; returnPartialData: true; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { returnPartialData: true; }): UseSuspenseQueryResult, TVariables>; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { skip: boolean; }): UseSuspenseQueryResult; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer_2> & { +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer> & { returnPartialData: true; })): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; +function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; // @public (undocumented) interface UseSuspenseQueryResult { diff --git a/.api-reports/api-report.md b/.api-reports/api-report.md index 23b6b90baaf..75f041a2bd1 100644 --- a/.api-reports/api-report.md +++ b/.api-reports/api-report.md @@ -330,7 +330,7 @@ export interface BackgroundQueryHookOptions = BackgroundQueryHookOptions, NoInfer_2>; +type BackgroundQueryHookOptionsNoInfer = BackgroundQueryHookOptions, NoInfer>; // Warning: (ae-forgotten-export) The symbol "MutationSharedOptions" needs to be exported by the entry point index.d.ts // @@ -1752,8 +1752,7 @@ export type NextLink = (operation: Operation) => Observable; type NextResultListener = (method: "next" | "error" | "complete", arg?: any) => any; // @public -type NoInfer_2 = [T][T extends any ? 0 : never]; -export { NoInfer_2 as NoInfer } +export type NoInfer = [T][T extends any ? 0 : never]; // @public export interface NormalizedCache { @@ -2022,18 +2021,18 @@ export type PreloadQueryFetchPolicy = Extract>(query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg, TOptions>): QueryReference | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables>; - (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + >(query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg, TOptions>): QueryReference | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables>; + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { returnPartialData: true; errorPolicy: "ignore" | "all"; }): QueryReference | undefined, TVariables>; - (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { errorPolicy: "ignore" | "all"; }): QueryReference; - (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { + (query: DocumentNode | TypedDocumentNode, options: PreloadQueryOptions> & { returnPartialData: true; }): QueryReference, TVariables>; - (query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg>): QueryReference; + (query: DocumentNode | TypedDocumentNode, ...[options]: PreloadQueryOptionsArg>): QueryReference; } // Warning: (ae-forgotten-export) The symbol "VariablesOption" needs to be exported by the entry point index.d.ts @@ -2052,9 +2051,9 @@ export type PreloadQueryOptions = [TVariables] extends [never] ? [ options?: PreloadQueryOptions & TOptions ] : {} extends OnlyRequiredProperties ? [ -options?: PreloadQueryOptions> & Omit +options?: PreloadQueryOptions> & Omit ] : [ -options: PreloadQueryOptions> & Omit +options: PreloadQueryOptions> & Omit ]; // @public (undocumented) @@ -2866,7 +2865,7 @@ export type UseBackgroundQueryResult(options: UseFragmentOptions): UseFragmentResult; // @public (undocumented) -export interface UseFragmentOptions extends Omit, NoInfer_2>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { +export interface UseFragmentOptions extends Omit, NoInfer>, "id" | "query" | "optimistic" | "previousResult" | "returnPartialData">, Omit, "id" | "variables" | "returnPartialData"> { client?: ApolloClient; // (undocumented) from: StoreObject | Reference | string; @@ -2886,7 +2885,7 @@ export type UseFragmentResult = { }; // @public -export function useLazyQuery(query: DocumentNode | TypedDocumentNode, options?: LazyQueryHookOptions, NoInfer_2>): LazyQueryResultTuple; +export function useLazyQuery(query: DocumentNode | TypedDocumentNode, options?: LazyQueryHookOptions, NoInfer>): LazyQueryResultTuple; // @public (undocumented) export function useLoadableQuery(query: DocumentNode | TypedDocumentNode, options?: LoadableQueryHookOptions & TOptions): UseLoadableQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? DeepPartial : TData, TVariables>; @@ -2922,10 +2921,10 @@ queryRef: QueryReference | null, ]; // @public -export function useMutation = ApolloCache>(mutation: DocumentNode | TypedDocumentNode, options?: MutationHookOptions, NoInfer_2, TContext, TCache>): MutationTuple; +export function useMutation = ApolloCache>(mutation: DocumentNode | TypedDocumentNode, options?: MutationHookOptions, NoInfer, TContext, TCache>): MutationTuple; // @public -export function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer_2>): QueryResult; +export function useQuery(query: DocumentNode | TypedDocumentNode, options?: QueryHookOptions, NoInfer>): QueryResult; // @public export function useQueryRefHandlers(queryRef: QueryReference): UseQueryRefHandlersResult; @@ -2950,48 +2949,48 @@ export interface UseReadQueryResult { } // @public -export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): SubscriptionResult; +export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer>): SubscriptionResult; // @public (undocumented) -export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; +export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { returnPartialData: true; errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { errorPolicy: "ignore" | "all"; }): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { skip: boolean; returnPartialData: true; }): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { returnPartialData: true; }): UseSuspenseQueryResult, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SuspenseQueryHookOptions, NoInfer> & { skip: boolean; }): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer_2> & { +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options: SkipToken | (SuspenseQueryHookOptions, NoInfer> & { returnPartialData: true; })): UseSuspenseQueryResult | undefined, TVariables>; // @public (undocumented) -export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer_2>): UseSuspenseQueryResult; +export function useSuspenseQuery(query: DocumentNode | TypedDocumentNode, options?: SkipToken | SuspenseQueryHookOptions, NoInfer>): UseSuspenseQueryResult; // @public (undocumented) export interface UseSuspenseQueryResult { From 225223ee393ec748587e051c3b122b6d6e253dbf Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Tue, 2 Apr 2024 16:23:23 -0400 Subject: [PATCH 25/26] chore: remove unneeded fetch mock from jest-environment-jsdom to fix failing HttpLink test --- patches/jest-environment-jsdom+29.7.0.patch | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/patches/jest-environment-jsdom+29.7.0.patch b/patches/jest-environment-jsdom+29.7.0.patch index 39b42ec75b6..6389addc765 100644 --- a/patches/jest-environment-jsdom+29.7.0.patch +++ b/patches/jest-environment-jsdom+29.7.0.patch @@ -1,14 +1,13 @@ diff --git a/node_modules/jest-environment-jsdom/build/index.js b/node_modules/jest-environment-jsdom/build/index.js -index 2e6c16c..ce5c77d 100644 +index 2e6c16c..5aa9bd5 100644 --- a/node_modules/jest-environment-jsdom/build/index.js +++ b/node_modules/jest-environment-jsdom/build/index.js -@@ -96,6 +96,14 @@ class JSDOMEnvironment { +@@ -96,6 +96,13 @@ class JSDOMEnvironment { // TODO: remove this ASAP, but it currently causes tests to run really slow global.Buffer = Buffer; + // Add mocks for schemaProxy tests that rely on `Response` and `fetch` + // being globally available -+ global.fetch = fetch; + global.Headers = Headers; + global.Request = Request; + global.Response = Response; From 6670ad82b4931ea93f71564765eece95ae442f8f Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Tue, 2 Apr 2024 16:40:08 -0400 Subject: [PATCH 26/26] fix: dont set AbortController in the env, it breaks two PQ tests --- patches/jest-environment-jsdom+29.7.0.patch | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/patches/jest-environment-jsdom+29.7.0.patch b/patches/jest-environment-jsdom+29.7.0.patch index 6389addc765..4f97921d495 100644 --- a/patches/jest-environment-jsdom+29.7.0.patch +++ b/patches/jest-environment-jsdom+29.7.0.patch @@ -1,17 +1,14 @@ diff --git a/node_modules/jest-environment-jsdom/build/index.js b/node_modules/jest-environment-jsdom/build/index.js -index 2e6c16c..5aa9bd5 100644 +index 2e6c16c..174e7a0 100644 --- a/node_modules/jest-environment-jsdom/build/index.js +++ b/node_modules/jest-environment-jsdom/build/index.js -@@ -96,6 +96,13 @@ class JSDOMEnvironment { +@@ -96,6 +96,10 @@ class JSDOMEnvironment { // TODO: remove this ASAP, but it currently causes tests to run really slow global.Buffer = Buffer; + // Add mocks for schemaProxy tests that rely on `Response` and `fetch` + // being globally available -+ global.Headers = Headers; -+ global.Request = Request; + global.Response = Response; -+ global.AbortController = AbortController; + // Report uncaught errors. this.errorEventListener = event => {