diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index f71d386840..1d095c0a60 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -1,5 +1,6 @@ import { invariant, newInvariantError } from "../utilities/globals/index.js"; +import { parse } from "graphql"; import type { DocumentNode } from "graphql"; // TODO(brian): A hack until this issue is resolved (https://github.com/graphql/graphql-js/issues/3356) type OperationTypeNode = any; @@ -899,15 +900,15 @@ export class QueryManager { include: InternalRefetchQueriesInclude = "active" ) { const queries = new Map>(); - const queryNamesAndDocs = new Map(); + const queryNamesAndQueryStrings = new Map(); const legacyQueryOptions = new Set(); if (Array.isArray(include)) { include.forEach((desc) => { if (typeof desc === "string") { - queryNamesAndDocs.set(desc, false); + queryNamesAndQueryStrings.set(desc, false); } else if (isDocumentNode(desc)) { - queryNamesAndDocs.set(print(this.transform(desc)), false); + queryNamesAndQueryStrings.set(print(this.transform(desc)), false); } else if (isNonNullObject(desc) && desc.query) { legacyQueryOptions.add(desc); } @@ -935,12 +936,12 @@ export class QueryManager { if ( include === "active" || - (queryName && queryNamesAndDocs.has(queryName)) || - (document && queryNamesAndDocs.has(print(document))) + (queryName && queryNamesAndQueryStrings.has(queryName)) || + (document && queryNamesAndQueryStrings.has(print(document))) ) { queries.set(queryId, oq); - if (queryName) queryNamesAndDocs.set(queryName, true); - if (document) queryNamesAndDocs.set(print(document), true); + if (queryName) queryNamesAndQueryStrings.set(queryName, true); + if (document) queryNamesAndQueryStrings.set(print(document), true); } } }); @@ -969,13 +970,27 @@ export class QueryManager { }); } - if (__DEV__ && queryNamesAndDocs.size) { - queryNamesAndDocs.forEach((included, nameOrDoc) => { + if (__DEV__ && queryNamesAndQueryStrings.size) { + queryNamesAndQueryStrings.forEach((included, nameOrQueryString) => { if (!included) { - invariant.warn( - `Unknown query %s requested in refetchQueries options.include array`, - nameOrDoc - ); + const isQueryString = + nameOrQueryString.startsWith("query ") || + nameOrQueryString.startsWith("{"); // Shorthand anonymous queries + const queryName = + isQueryString ? + getOperationName(parse(nameOrQueryString)) + : nameOrQueryString; + + if (queryName) { + invariant.warn( + `Unknown query named "%s" requested in refetchQueries options.include array`, + queryName + ); + } else { + invariant.warn( + `Unknown query anonymous requested in refetchQueries options.include array` + ); + } } }); } diff --git a/src/core/__tests__/QueryManager/index.ts b/src/core/__tests__/QueryManager/index.ts index a5a13c14ff..075f018f83 100644 --- a/src/core/__tests__/QueryManager/index.ts +++ b/src/core/__tests__/QueryManager/index.ts @@ -46,7 +46,7 @@ import wrap from "../../../testing/core/wrap"; import observableToPromise, { observableToPromiseAndSubscription, } from "../../../testing/core/observableToPromise"; -import { itAsync, wait } from "../../../testing/core"; +import { itAsync } from "../../../testing/core"; import { ApolloClient } from "../../../core"; import { mockFetchQuery } from "../ObservableQuery"; import { Concast, print } from "../../../utilities"; @@ -5075,7 +5075,7 @@ describe("QueryManager", () => { (result) => { expect(result.data).toEqual(secondReqData); expect(consoleWarnSpy).toHaveBeenLastCalledWith( - "Unknown query %s requested in refetchQueries options.include array", + 'Unknown query named "%s" requested in refetchQueries options.include array', "fakeQuery" ); } @@ -5148,7 +5148,7 @@ describe("QueryManager", () => { }) .then(() => { expect(consoleWarnSpy).toHaveBeenLastCalledWith( - "Unknown query %s requested in refetchQueries options.include array", + 'Unknown query named "%s" requested in refetchQueries options.include array', "getAuthors" ); }) @@ -5156,6 +5156,151 @@ describe("QueryManager", () => { } ); + itAsync( + "should ignore (with warning) a document node in refetchQueries that has no active subscriptions", + (resolve, reject) => { + const mutation = gql` + mutation changeAuthorName { + changeAuthorName(newName: "Jack Smith") { + firstName + lastName + } + } + `; + const mutationData = { + changeAuthorName: { + firstName: "Jack", + lastName: "Smith", + }, + }; + const query = gql` + query getAuthors { + author { + firstName + lastName + } + } + `; + const data = { + author: { + firstName: "John", + lastName: "Smith", + }, + }; + const secondReqData = { + author: { + firstName: "Jane", + lastName: "Johnson", + }, + }; + const queryManager = mockQueryManager( + { + request: { query }, + result: { data }, + }, + { + request: { query }, + result: { data: secondReqData }, + }, + { + request: { query: mutation }, + result: { data: mutationData }, + } + ); + + const observable = queryManager.watchQuery({ query }); + return observableToPromise({ observable }, (result) => { + expect(result.data).toEqual(data); + }) + .then(() => { + // The subscription has been stopped already + return queryManager.mutate({ + mutation, + refetchQueries: [query], + }); + }) + .then(() => { + expect(consoleWarnSpy).toHaveBeenLastCalledWith( + 'Unknown query named "%s" requested in refetchQueries options.include array', + "getAuthors" + ); + }) + .then(resolve, reject); + } + ); + + itAsync( + "should ignore (with warning) a document node containing an anonymous query in refetchQueries that has no active subscriptions", + (resolve, reject) => { + const mutation = gql` + mutation changeAuthorName { + changeAuthorName(newName: "Jack Smith") { + firstName + lastName + } + } + `; + const mutationData = { + changeAuthorName: { + firstName: "Jack", + lastName: "Smith", + }, + }; + const query = gql` + query { + author { + firstName + lastName + } + } + `; + const data = { + author: { + firstName: "John", + lastName: "Smith", + }, + }; + const secondReqData = { + author: { + firstName: "Jane", + lastName: "Johnson", + }, + }; + const queryManager = mockQueryManager( + { + request: { query }, + result: { data }, + }, + { + request: { query }, + result: { data: secondReqData }, + }, + { + request: { query: mutation }, + result: { data: mutationData }, + } + ); + + const observable = queryManager.watchQuery({ query }); + return observableToPromise({ observable }, (result) => { + expect(result.data).toEqual(data); + }) + .then(() => { + // The subscription has been stopped already + return queryManager.mutate({ + mutation, + refetchQueries: [query], + }); + }) + .then(() => { + expect(consoleWarnSpy).toHaveBeenLastCalledWith( + "Unknown query anonymous requested in refetchQueries options.include array" + ); + }) + .then(resolve, reject); + } + ); + it("also works with a query document and variables", async () => { const mutation = gql` mutation changeAuthorName($id: ID!) { @@ -5228,12 +5373,6 @@ describe("QueryManager", () => { ); expect(observable.getCurrentResult().data).toEqual(secondReqData); - await wait(10); - - queryManager["queries"].forEach((_, queryId) => { - expect(queryId).not.toContain("legacyOneTimeQuery"); - }); - await expect(stream).not.toEmitAnything(); }); @@ -5309,9 +5448,6 @@ describe("QueryManager", () => { ); expect(observable.getCurrentResult().data).toEqual(secondReqData); - await wait(10); - - await expect(stream).not.toEmitAnything(); }); @@ -5388,7 +5524,6 @@ describe("QueryManager", () => { ); expect(observable.getCurrentResult().data).toEqual(secondReqData); - await expect(stream).not.toEmitAnything(); });