Skip to content

Commit

Permalink
add some clarifying comments and two more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
phryneas committed Mar 19, 2024
1 parent 12b06eb commit 843adbc
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 29 deletions.
10 changes: 8 additions & 2 deletions src/dev/setErrorMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,22 @@ export type ErrorMessageHandler = {
* In some edge cases, this can already be a string, that can be passed through
* as an error message.
*
* @param args - The placeholders that can be passed into the error message.
* @param args - The placeholders that can be passed into the error message (pre-stringified).
* These relate with the `%s` and `%d` [substitution strings](https://developer.mozilla.org/en-US/docs/Web/API/console#using_string_substitutions)
* in the error message defined in `@apollo/client/invariantErrorCodes.js`.
*
* ⚠️ Note that arguments will only be passed in for error messages.
* For normal log messages, you will get an empty array here and they will directly
* be passed to `console.log` instead, to have the string subsitution done by the
* engine, as that allows for nicer (and in the case of a browser, interactive)
* output.
*
* @returns The error message to be logged or thrown. If it returns `undefined`,
* the mechanism will fall back to the default:
* A link to https://go.apollo.dev/c/err with Apollo Client version,
* the error message number, and the error message arguments encoded into
* the URL hash.
*/ (message: string | number, args: unknown[]): string | undefined;
*/ (message: string | number, args: string[]): string | undefined;
};

/**
Expand Down
106 changes: 80 additions & 26 deletions src/utilities/globals/__tests__/invariantWrappers.test.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,56 @@
import { loadErrorMessageHandler } from "../../../dev";
import { spyOnConsole, withCleanup } from "../../../testing/internal";
import { spyOnConsole } from "../../../testing/internal";
import {
ApolloErrorMessageHandler,
InvariantError,
invariant,
} from "../invariantWrappers";

function withDev() {
const originalErrorMessageHandler = window[ApolloErrorMessageHandler];
window[ApolloErrorMessageHandler] = undefined;
let dev: typeof import("../../../dev");
let restore = () => {};
// we're running the test inside of `jest.isolateModulesAsync` to avoid
// the test overriding the module-level state of the `dev` module
const cleanupFinished = jest.isolateModulesAsync(
() =>
new Promise<void>((resolve) => {
dev = require("../../../dev");
restore = resolve;
})
);
// replicate the code of `src/config/jest/setup.ts`
dev!.loadErrorMessageHandler();
return {
...dev!,
async [Symbol.asyncDispose]() {
restore();
await cleanupFinished;
window[ApolloErrorMessageHandler] = originalErrorMessageHandler;
},
};
}

function disableErrorMessageHandler() {
const original = window[ApolloErrorMessageHandler];
// eslint-disable-next-line local-rules/require-using-disposable
const dev = withDev();
delete window[ApolloErrorMessageHandler];
return withCleanup({ original }, ({ original }) => {
window[ApolloErrorMessageHandler] = original;
});
return dev;
}

function mockErrorMessageHandler() {
const original = window[ApolloErrorMessageHandler];
// eslint-disable-next-line local-rules/require-using-disposable
const dev = withDev();
delete window[ApolloErrorMessageHandler];

loadErrorMessageHandler({
dev.loadErrorMessageHandler({
5: { file: "foo", message: "Replacing %s, %d, %f, %o" },
});

return withCleanup({ original }, ({ original }) => {
window[ApolloErrorMessageHandler] = original;
});
return dev;
}

test("base invariant(false, 5, ...), no handlers", () => {
using _ = disableErrorMessageHandler();
test("base invariant(false, 5, ...), no handlers", async () => {
await using _ = disableErrorMessageHandler();
expect(() => {
invariant(false, 5, "string", 1, 1.1, { a: 1 });
}).toThrow(
Expand All @@ -50,29 +72,47 @@ test("base invariant(false, 5, ...), no handlers", () => {
);
});

test("base invariant(false, 5, ...), handlers in place", () => {
using _ = mockErrorMessageHandler();
test("base invariant(false, 5, ...), handlers in place", async () => {
await 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();
test("base invariant(false, 5, ...), custom handler gets passed arguments", async () => {
await using dev = disableErrorMessageHandler();

const handler = jest.fn(() => "");
dev.setErrorMessageHandler(handler);

try {
invariant(false, 5, "string", 1, 1.1, { a: 1 });
} catch {}

expect(handler).toHaveBeenCalledWith(5, [
"string",
"1",
"1.1",
'{\n "a": 1\n}',
]);
});

test("base invariant(false, undefined), no handlers", async () => {
await using _ = disableErrorMessageHandler();
expect(() => {
invariant(false);
}).toThrow(new InvariantError("Invariant Violation"));
});

test("base invariant(false, undefined), handlers in place", () => {
using _ = mockErrorMessageHandler();
test("base invariant(false, undefined), handlers in place", async () => {
await using _ = mockErrorMessageHandler();
expect(() => {
invariant(false);
}).toThrow(new InvariantError("Invariant Violation"));
});

test("invariant.log(5, ...), no handlers", () => {
using _ = disableErrorMessageHandler();
test("invariant.log(5, ...), no handlers", async () => {
await using _ = disableErrorMessageHandler();
using consoleSpy = spyOnConsole("log");
invariant.log(5, "string", 1, 1.1, { a: 1 });
expect(consoleSpy.log).toHaveBeenCalledWith(
Expand All @@ -87,8 +127,8 @@ test("invariant.log(5, ...), no handlers", () => {
);
});

test("invariant.log(5, ...), with handlers", () => {
using _ = mockErrorMessageHandler();
test("invariant.log(5, ...), with handlers", async () => {
await using _ = mockErrorMessageHandler();
using consoleSpy = spyOnConsole("log");
invariant.log(5, "string", 1, 1.1, { a: 1 });
expect(consoleSpy.log).toHaveBeenCalledWith(
Expand All @@ -100,8 +140,22 @@ test("invariant.log(5, ...), with handlers", () => {
);
});

test("base invariant(false, 6, ...), raises fallback", () => {
using _ = mockErrorMessageHandler();
test("invariant.log(5, ...), custom handler does not get passed arguments", async () => {
await using dev = disableErrorMessageHandler();
using _consoleSpy = spyOnConsole("log");

const handler = jest.fn(() => "");
dev.setErrorMessageHandler(handler);

try {
invariant.log(5, "string", 1, 1.1, { a: 1 });
} catch {}

expect(handler).toHaveBeenCalledWith(5, []);
});

test("base invariant(false, 6, ...), raises fallback", async () => {
await using _ = mockErrorMessageHandler();
expect(() => {
invariant(false, 6, "hello");
}).toThrow(
Expand Down
2 changes: 1 addition & 1 deletion src/utilities/globals/invariantWrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ const ApolloErrorMessageHandler = Symbol.for(
declare global {
interface Window {
[ApolloErrorMessageHandler]?: {
(message: string | number, args: unknown[]): string | undefined;
(message: string | number, args: string[]): string | undefined;
} & ErrorCodes;
}
}
Expand Down

0 comments on commit 843adbc

Please sign in to comment.