I'm using Jest and react-testing-library to test a "modal" component in a "portal" that needs to me mounted to a DOM node. This DOM node is configurable but defaults to body. To get proper test coverage I pass it a bad value too, which should lead to an Error. I'm trying to assert this happens and catch the Error by using the expect().toThrowError() API, this works just fine. However, in the logs I still see an uncaught error printed. I think this maybe caused by the React library printing it before raising the same exception again, but that doesn't mean I can prevent it I guess.
I could just return a fallback error message, in the UI, but I feel like this is not how this should work.
Any suggestions on how to fix this, or even a suggestion to just take an entirely different approach?
// modal.js
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
export interface ModalProps {
children: React.ReactNode;
DOMNodeSelector?: string;
}
export const Modal: React.FC<ModalProps> = ({
children,
DOMNodeSelector = 'body',
}) => {
const wrapper = document.createElement('div');
wrapper.classList.add('modal-wrapper');
// Mount and unmount the modal in the portal
useEffect(() => {
const portal = document.querySelector(DOMNodeSelector);
if (!portal)
throw new Error(`Querying "${DOMNodeSelector}" did not yield a DOM node`);
portal.appendChild(wrapper);
return () => {
portal.removeChild(wrapper);
};
}, []);
return ReactDOM.createPortal(
<div className="modal">{children}</div>,
wrapper,
);
};
//modal.test.js
import React from 'react';
import { render } from '@testing-library/react';
import {Modal} from './modal';
describe('<Modal>', () => {
it(`throws an Error when passed a bad dom node selector`, () => {
expect(() => {
render(
<Modal DOMNodeSelector=".badselector">Hello World!</Modal>,
);
}).toThrowError(/did not yield a dom node/i);
});
});
export default Modal;
console.error
Error: Uncaught [Error: Querying ".badselector" did not yield a DOM node]
at reportException (/workspaces/project/node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:62:24)
at innerInvokeEventListeners (/workspaces/project/node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:333:9)
at invokeEventListeners (/workspaces/project/node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:274:3)
at HTMLUnknownElementImpl._dispatch (/workspaces/project/node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:221:9)
at HTMLUnknownElementImpl.dispatchEvent (/workspaces/project/node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:94:17)
at HTMLUnknownElement.dispatchEvent (/workspaces/project/node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:231:34)
at Object.invokeGuardedCallbackDev (/workspaces/project/node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:3994:16)
at invokeGuardedCallback (/workspaces/project/node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:4056:31)
at flushPassiveEffectsImpl (/workspaces/project/node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:23574:9)
at unstable_runWithPriority (/workspaces/project/node_modules/.pnpm/scheduler@0.20.1/node_modules/scheduler/cjs/scheduler.development.js:646:12) Error: Querying ".badselector" did not yield a DOM node
at /workspaces/project/src/modal.tsx:20:13
at invokePassiveEffectCreate (/workspaces/project/node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:23487:20)
at HTMLUnknownElement.callCallback (/workspaces/project/node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:3945:14)
at HTMLUnknownElement.callTheUserObjectsOperation (/workspaces/project/node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30)
at innerInvokeEventListeners (/workspaces/project/node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:318:25)
at invokeEventListeners (/workspaces/project/node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:274:3)
at HTMLUnknownElementImpl._dispatch (/workspaces/project/node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:221:9)
at HTMLUnknownElementImpl.dispatchEvent (/workspaces/project/node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:94:17)
at HTMLUnknownElement.dispatchEvent (/workspaces/project/node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:231:34)
at Object.invokeGuardedCallbackDev (/workspaces/project/node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:3994:16)
at invokeGuardedCallback (/workspaces/project/node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:4056:31)
at flushPassiveEffectsImpl (/workspaces/project/node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:23574:9)
at unstable_runWithPriority (/workspaces/project/node_modules/.pnpm/scheduler@0.20.1/node_modules/scheduler/cjs/scheduler.development.js:646:12)
at runWithPriority$1 (/workspaces/project/node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:11276:10)
at flushPassiveEffects (/workspaces/project/node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:23447:14)
at Object.<anonymous>.flushWork (/workspaces/project/node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom-test-utils.development.js:992:10)
at act (/workspaces/project/node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom-test-utils.development.js:1107:9)
at render (/workspaces/project/node_modules/.pnpm/@testing-library/react@11.2.5_react-dom@17.0.1+react@17.0.1/node_modules/@testing-library/react/dist/pure.js:95:26)
at /workspaces/project/src/modal.test.tsx:8:7
at Object.<anonymous> (/workspaces/project/node_modules/.pnpm/expect@26.6.2/node_modules/expect/build/toThrowMatchers.js:83:11)
at Object.toThrowError (/workspaces/project/node_modules/.pnpm/expect@26.6.2/node_modules/expect/build/index.js:338:21)
at Object.<anonymous> (/workspaces/project/src/modal.test.tsx:11:8)
at Object.asyncJestTest (/workspaces/project/node_modules/.pnpm/jest-jasmine2@26.6.3/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)
at /workspaces/project/node_modules/.pnpm/jest-jasmine2@26.6.3/node_modules/jest-jasmine2/build/queueRunner.js:45:12
at new Promise (<anonymous>)
at mapper (/workspaces/project/node_modules/.pnpm/jest-jasmine2@26.6.3/node_modules/jest-jasmine2/build/queueRunner.js:28:19)
at /workspaces/project/node_modules/.pnpm/jest-jasmine2@26.6.3/node_modules/jest-jasmine2/build/queueRunner.js:75:41
at processTicksAndRejections (node:internal/process/task_queues:93:5)
at VirtualConsole.<anonymous> (node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/virtual-console.js:29:45)
at reportException (node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:28)
at innerInvokeEventListeners (node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:333:9)
at invokeEventListeners (node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:274:3)
at HTMLUnknownElementImpl._dispatch (node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:221:9)
at HTMLUnknownElementImpl.dispatchEvent (node_modules/.pnpm/jsdom@16.4.0/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:94:17)
console.error
The above error occurred in the <Modal> component:
at Modal (/workspaces/project/src/modal.tsx:10:3)
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
at logCapturedError (node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:20085:23)
at update.callback (node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:20118:5)
at callCallback (node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:12318:12)
at commitUpdateQueue (node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:12339:9)
at commitLifeCycles (node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:20736:11)
at commitLayoutEffects (node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:23426:7)
at HTMLUnknownElement.callCallback (node_modules/.pnpm/react-dom@17.0.1_react@17.0.1/node_modules/react-dom/cjs/react-dom.development.js:3945:14)