-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[expect] Protect against undefined matcher
when extending
#12481
Conversation
5287ff0
to
ec0cb2e
Compare
ec0cb2e
to
00976b8
Compare
Thanks! Now the question is, should we throw a descriptive error, or ignore the user input? I'm thinking we should throw, since it could be a genuine bug. E.g. const {
myFirstMatcher,
// notice typo
mySecondMachter,
} = require('./my-matchers');
expect.extend({
FirstMatcher: myFirstMatcher,
SecondMatcher: mySecondMachter,
}); Getting |
Hmm, that's not really any better for my situation, however, since the reason I am submitting this PR is that I cannot remove the offending matcher. In my case, I am dealing with matchers that come from:
I don't understand why (I think it is a cjs vs esm issue), but a If this PR causes a different error to be thrown, then I'm back to square 1. Do you have any ideas what else could be done? Maybe a console warning instead? |
Yeah, it's CJS so you can't really do that safely. Seems like a broken transpilation that gives named exports at all - native ESM gives a single $ node --input-type module --eval "import * as matchers from '@testing-library/jest-dom/matchers.js'; console.log(matchers)"
[Module: null prototype] {
default: {
toBeChecked: [Getter],
toBeDisabled: [Getter],
toBeEmpty: [Getter],
toBeEmptyDOMElement: [Getter],
toBeEnabled: [Getter],
toBeInTheDOM: [Getter],
toBeInTheDocument: [Getter],
toBeInvalid: [Getter],
toBePartiallyChecked: [Getter],
toBeRequired: [Getter],
toBeValid: [Getter],
toBeVisible: [Getter],
toContainElement: [Getter],
toContainHTML: [Getter],
toHaveAccessibleDescription: [Getter],
toHaveAccessibleName: [Getter],
toHaveAttribute: [Getter],
toHaveClass: [Getter],
toHaveDescription: [Getter],
toHaveDisplayValue: [Getter],
toHaveErrorMessage: [Getter],
toHaveFocus: [Getter],
toHaveFormValues: [Getter],
toHaveStyle: [Getter],
toHaveTextContent: [Getter],
toHaveValue: [Getter]
}
} What you want is probably the $ node --input-type module --eval "import matchers from '@testing-library/jest-dom/matchers.js'; console.log(matchers)"
{
toBeChecked: [Getter],
toBeDisabled: [Getter],
toBeEmpty: [Getter],
toBeEmptyDOMElement: [Getter],
toBeEnabled: [Getter],
toBeInTheDOM: [Getter],
toBeInTheDocument: [Getter],
toBeInvalid: [Getter],
toBePartiallyChecked: [Getter],
toBeRequired: [Getter],
toBeValid: [Getter],
toBeVisible: [Getter],
toContainElement: [Getter],
toContainHTML: [Getter],
toHaveAccessibleDescription: [Getter],
toHaveAccessibleName: [Getter],
toHaveAttribute: [Getter],
toHaveClass: [Getter],
toHaveDescription: [Getter],
toHaveDisplayValue: [Getter],
toHaveErrorMessage: [Getter],
toHaveFocus: [Getter],
toHaveFormValues: [Getter],
toHaveStyle: [Getter],
toHaveTextContent: [Getter],
toHaveValue: [Getter]
} Since it works at all, but empty import {createRequire} from 'module';
const require = createRequire(import.meta.url)
const matchers = require('@testing-library/jest-dom/matchers') |
Thanks for the suggestions. My actual situation is that I am using vite and running (a polyfilled version of) Maybe the real solution is for jest-dom to publish an ES module. I can work on a PR for them, but in the meantime I was hoping to get a quick easy win here with a safety check. I can continue to use patch-package to hack it in myself, but it's not a great solution for other vite storybook users. |
You could change to Taking a step back - some layer of ESM source pulling in CJS is wrong though (guessing it's a bundler, as using Providing a better error than |
Something like diff --git i/packages/expect/src/__tests__/extend.test.ts w/packages/expect/src/__tests__/extend.test.ts
index 852dfb139f..f0ff670601 100644
--- i/packages/expect/src/__tests__/extend.test.ts
+++ w/packages/expect/src/__tests__/extend.test.ts
@@ -184,3 +184,27 @@ it('allows overriding existing extension', () => {
jestExpect('foo').toAllowOverridingExistingMatcher();
});
+
+it('throws descriptive errors for invalid matchers', () => {
+ expect(() =>
+ jestExpect.extend({
+ default: undefined,
+ }),
+ ).toThrow(
+ 'expect.extend: `default` is not a valid matcher. Must be function, is "undefined"',
+ );
+ expect(() =>
+ jestExpect.extend({
+ default: 42,
+ }),
+ ).toThrow(
+ 'expect.extend: `default` is not a valid matcher. Must be function, is "number"',
+ );
+ expect(() =>
+ jestExpect.extend({
+ default: 'foobar',
+ }),
+ ).toThrow(
+ 'expect.extend: `default` is not a valid matcher. Must be function, is "string"',
+ );
+});
diff --git i/packages/expect/src/jestMatchersObject.ts w/packages/expect/src/jestMatchersObject.ts
index 90e26e6b77..7aa76977df 100644
--- i/packages/expect/src/jestMatchersObject.ts
+++ w/packages/expect/src/jestMatchersObject.ts
@@ -6,6 +6,7 @@
*
*/
+import {getType} from 'jest-get-type';
import {AsymmetricMatcher} from './asymmetricMatchers';
import type {
Expect,
@@ -56,6 +57,15 @@ export const setMatchers = (
): void => {
Object.keys(matchers).forEach(key => {
const matcher = matchers[key];
+
+ if (typeof matcher !== 'function') {
+ throw new TypeError(
+ `expect.extend: \`${key}\` is not a valid matcher. Must be function, is "${getType(
+ matcher,
+ )}"`,
+ );
+ }
+
Object.defineProperty(matcher, INTERNAL_MATCHER_FLAG, {
value: isInternal,
}); |
FWIW, I think I found the root cause of the issue I'm experiencing: evanw/esbuild#1971 (comment) |
Sounds likely 👍 |
The new validation is in https://github.com/facebook/jest/releases/tag/v28.0.0-alpha.5 |
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Summary
This fixes an issue when
import * as matchers from 'my-matchers';
includes adefault
import whose value isundefined
, as is happening in storybookjs/jest#15. It just adds a bit of safety to be sure that the matcher is defined, before trying to add it.Test plan
I added a unit test which fails without this change.