After upgrading to React 17, there are non-intentional selectionchange event added to document by addEventListener

97 Views Asked by At

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!

1

There are 1 best solutions below

1
ajadhav On

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.