diff --git a/.api-reports/api-report-utilities_globals.md b/.api-reports/api-report-utilities_globals.md index 2383be3ea83..cf51100b464 100644 --- a/.api-reports/api-report-utilities_globals.md +++ b/.api-reports/api-report-utilities_globals.md @@ -44,7 +44,7 @@ type WrappedInvariant = { // Warnings were encountered during analysis: // -// src/utilities/globals/invariantWrappers.ts:58:3 - (ae-forgotten-export) The symbol "LogFunction" needs to be exported by the entry point index.d.ts +// src/utilities/globals/invariantWrappers.ts:62:3 - (ae-forgotten-export) The symbol "LogFunction" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/.changeset/cyan-laws-wave.md b/.changeset/cyan-laws-wave.md new file mode 100644 index 00000000000..fedd425b092 --- /dev/null +++ b/.changeset/cyan-laws-wave.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Fixes argument handling for invariant log messages. diff --git a/.size-limit.cjs b/.size-limit.cjs index b3ed1cc372c..d8da5ff2578 100644 --- a/.size-limit.cjs +++ b/.size-limit.cjs @@ -1,7 +1,7 @@ const checks = [ { path: "dist/apollo-client.min.cjs", - limit: "37914", + limit: "37940", }, { path: "dist/main.cjs", @@ -10,7 +10,7 @@ const checks = [ { path: "dist/index.js", import: "{ ApolloClient, InMemoryCache, HttpLink }", - limit: "31947", + limit: "31970", }, ...[ "ApolloProvider", diff --git a/src/dev/loadErrorMessageHandler.ts b/src/dev/loadErrorMessageHandler.ts index 6dc7743ef2c..199a6477b5c 100644 --- a/src/dev/loadErrorMessageHandler.ts +++ b/src/dev/loadErrorMessageHandler.ts @@ -20,7 +20,7 @@ export function loadErrorMessageHandler(...errorCodes: ErrorCodes[]) { message = definition.message; } return args.reduce( - (msg, arg) => msg.replace("%s", String(arg)), + (msg, arg) => msg.replace(/%[sdfo]/, String(arg)), String(message) ); } diff --git a/src/utilities/globals/__tests__/invariantWrappers.test.ts b/src/utilities/globals/__tests__/invariantWrappers.test.ts new file mode 100644 index 00000000000..f9255c170c8 --- /dev/null +++ b/src/utilities/globals/__tests__/invariantWrappers.test.ts @@ -0,0 +1,101 @@ +import { loadErrorMessageHandler } from "../../../dev"; +import { spyOnConsole, withCleanup } from "../../../testing/internal"; +import { + ApolloErrorMessageHandler, + InvariantError, + invariant, +} from "../invariantWrappers"; + +function disableErrorMessageHandler() { + const original = window[ApolloErrorMessageHandler]; + delete window[ApolloErrorMessageHandler]; + return withCleanup({ original }, ({ original }) => { + window[ApolloErrorMessageHandler] = original; + }); +} + +function mockErrorMessageHandler() { + const original = window[ApolloErrorMessageHandler]; + delete window[ApolloErrorMessageHandler]; + + loadErrorMessageHandler({ + 5: { file: "foo", message: "Replacing %s, %d, %f, %o" }, + }); + + return withCleanup({ original }, ({ original }) => { + window[ApolloErrorMessageHandler] = original; + }); +} + +test("base invariant(false, 5, ...), no handlers", () => { + using _ = disableErrorMessageHandler(); + expect(() => { + invariant(false, 5, "string", 1, 1.1, { a: 1 }); + }).toThrow( + new InvariantError( + "An error occurred! For more details, see the full error text at https://go.apollo.dev/c/err#" + + encodeURIComponent( + JSON.stringify({ + version: "local", + message: 5, + args: [ + "string", + "1", + "1.1", + JSON.stringify({ a: 1 }, undefined, 2), + ], + }) + ) + ) + ); +}); + +test("base invariant(false, 5, ...), handlers in place", () => { + using _ = mockErrorMessageHandler(); + expect(() => { + invariant(false, 5, "string", 1, 1.1, { a: 1 }); + }).toThrow(new InvariantError('Replacing string, 1, 1.1, {\n "a": 1\n}')); +}); + +test("base invariant(false, undefined), no handlers", () => { + using _ = disableErrorMessageHandler(); + expect(() => { + invariant(false); + }).toThrow(new InvariantError("Invariant Violation")); +}); + +test("base invariant(false, undefined), handlers in place", () => { + using _ = mockErrorMessageHandler(); + expect(() => { + invariant(false); + }).toThrow(new InvariantError("Invariant Violation")); +}); + +test("invariant.log(5, ...), no handlers", () => { + using _ = disableErrorMessageHandler(); + using consoleSpy = spyOnConsole("log"); + invariant.log(5, "string", 1, 1.1, { a: 1 }); + expect(consoleSpy.log).toHaveBeenCalledWith( + "An error occurred! For more details, see the full error text at https://go.apollo.dev/c/err#" + + encodeURIComponent( + JSON.stringify({ + version: "local", + message: 5, + args: ["string", "1", "1.1", JSON.stringify({ a: 1 }, undefined, 2)], + }) + ) + ); +}); + +test("invariant.log(5, ...), with handlers", () => { + using _ = mockErrorMessageHandler(); + using consoleSpy = spyOnConsole("log"); + invariant.log(5, "string", 1, 1.1, { a: 1 }); + expect(consoleSpy.log).toHaveBeenCalledWith( + "Replacing %s, %d, %f, %o", + "string", + 1, + 1.1, + { a: 1 } + ); +}); diff --git a/src/utilities/globals/invariantWrappers.ts b/src/utilities/globals/invariantWrappers.ts index 74d83f3bd34..adcbea0ad01 100644 --- a/src/utilities/globals/invariantWrappers.ts +++ b/src/utilities/globals/invariantWrappers.ts @@ -5,12 +5,16 @@ import type { ErrorCodes } from "../../invariantErrorCodes.js"; import { stringifyForDisplay } from "../common/stringifyForDisplay.js"; function wrap(fn: (msg?: string, ...args: any[]) => void) { - return function (message: string | number, ...args: any[]) { + return function (message?: string | number, ...args: any[]) { if (typeof message === "number") { - fn(getErrorMsg(message, args)); - } else { - fn(message, ...args); + const arg0 = message; + message = getHandledErrorMsg(arg0); + if (!message) { + message = getFallbackErrorMsg(arg0, args); + args = []; + } } + fn(...[message].concat(args)); }; } @@ -67,7 +71,10 @@ const invariant: WrappedInvariant = Object.assign( ...args: unknown[] ): asserts condition { if (!condition) { - originalInvariant(condition, getErrorMsg(message, args)); + originalInvariant( + condition, + getHandledErrorMsg(message, args) || getFallbackErrorMsg(message, args) + ); } }, { @@ -92,7 +99,10 @@ function newInvariantError( message?: string | number, ...optionalParams: unknown[] ) { - return new InvariantError(getErrorMsg(message, optionalParams)); + return new InvariantError( + getHandledErrorMsg(message, optionalParams) || + getFallbackErrorMsg(message, optionalParams) + ); } const ApolloErrorMessageHandler = Symbol.for( @@ -106,24 +116,37 @@ declare global { } } -function getErrorMsg(message?: string | number, messageArgs: unknown[] = []) { +function stringify(arg: any) { + return typeof arg == "string" + ? arg + : stringifyForDisplay(arg, 2).slice(0, 1000); +} + +function getHandledErrorMsg( + message?: string | number, + messageArgs: unknown[] = [] +) { if (!message) return; - const args = messageArgs.map((arg) => - typeof arg == "string" ? arg : stringifyForDisplay(arg, 2).slice(0, 1000) - ); return ( - (global[ApolloErrorMessageHandler] && - global[ApolloErrorMessageHandler](message, args)) || - `An error occurred! For more details, see the full error text at https://go.apollo.dev/c/err#${encodeURIComponent( - JSON.stringify({ - version, - message, - args, - }) - )}` + global[ApolloErrorMessageHandler] && + global[ApolloErrorMessageHandler](message, messageArgs.map(stringify)) ); } +function getFallbackErrorMsg( + message?: string | number, + messageArgs: unknown[] = [] +) { + if (!message) return; + return `An error occurred! For more details, see the full error text at https://go.apollo.dev/c/err#${encodeURIComponent( + JSON.stringify({ + version, + message, + args: messageArgs.map(stringify), + }) + )}`; +} + export { invariant, InvariantError,