I am using jest v27 and @testing-library/react v12, I upgraded to React 17, there are non-intentional selectionchange event added to document by addEventListener. I am not using this event at all.
Minimum reproducible example:
// App.test.tsx
import { render } from "@testing-library/react";
import App from "./App";
test("App", () => {
it("test", async () => {
const addSpyDoc = jest.spyOn(window.document, "addEventListener");
render(<App />);
// this expect would fail and show that it is called with
// 1: "selectionchange", [Function bound dispatchDiscreteEvent], false
// 2: "selectionchange", [Function bound dispatchDiscreteEvent], true
expect(addSpyDoc).not.toHaveBeenCalled();
});
});
// App.tsx
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
</div>
);
}
// index.tsx
import { render } from "react-dom";
import App from "./App";
const rootElement = document.getElementById("root");
render(<App />, rootElement);
The following are config files and package.json
// tsconfig.json
{
"include": ["*"],
"compilerOptions": {
"baseUrl": ".",
"lib": ["dom", "es2015"],
"jsx": "react-jsx",
"isolatedModules": true,
"moduleResolution": "node",
"module": "ESNext",
"target": "ES6",
"resolveJsonModule": true
}
}
// .babelrc.json
{
"sourceType": "unambiguous",
"presets": [
"@babel/preset-env",
"@babel/preset-typescript",
"@babel/preset-react"
],
"plugins": []
}
// jest.config.ts
import type { Config } from "@jest/types";
export const sharedConfig: Config.InitialOptions = {
verbose: true,
preset: "ts-jest",
testEnvironment: "jsdom",
};
// package.json
"dependencies": {
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
"ts-node": "^10.9.1",
"ts-jest": "^27.1.3",
"@babel/core": "^7.17.9",
"@babel/preset-env": "^7.23.3",
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.23.3",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "12.1.5",
"@types/jest": "^27.4.1",
"@types/node": "^17.0.25",
"@types/react": "17.0.55",
"@types/react-dom": "17.0.23",
"babel-loader": "^8.2.4",
"jest": "27.5.1",
"typescript": "4.5.3"
},
"scripts": {
"test": "jest test --env=jsdom"
},
To make it easier to run, here are two sandboxes with React 17 and 16. In v16, there are no extra events added to document.
https://codesandbox.io/s/jest-react-testing-library-forked-8cygdn?file=/src/App.test.tsx React 17
https://codesandbox.io/s/jest-react-testing-library-forked-r4k6zh?file=/src/App.test.tsx React 16
Is this behaviour expected? What is the reason for this non-intentional selectionchange event being added?
Thank you so much for your help!
I ran into a similar issue, and stepped through the debugger to see that ReactDOM's implementation of listenToNativeEvent has a special case where it adds an event bubble listener. The case is for events of 'selectionchange' and when the rootContainerElement is not the document node.
The selectionchange event is a browser event that fires when the user makes a selection within a document, and I believe it does not bubble up the DOM tree (browsers fire it on the document directly). So this is necessary for React components to correctly listen for selectionchange events even when the root container is not the document node.
I haven't tested with react18 to see if the behavior is different but it seems like it is intended, and I'm unsure if we are just supposed to account for this behavior in our tests while using react17 and mocking the addEventListenerCall.