1

I'm testing my React component with Jest and React testing library.The component uses d3 code like this:

useEffect(() => {
  svg.transition()
    .duration(500)
    .call(zoom.transform, newTransform);
});

In the test script, I render my component, and then trying to wait 500ms until transition is done:

const sleep = async (timeout: number) => await new Promise((r) => setTimeout(r, timeout));
...
const {container} = render(<MyComponent width={...} height={...} data={...} />);
await sleep(500);

Adding sleep causes an error in the test:

 console.error
      Error: Uncaught [TypeError: Cannot read properties of undefined (reading 'baseVal')]
          at reportException (C:\...\node_modules\jsdom\lib\jsdom\living\helpers\runtime-script-errors.js:66:24)
          at runAnimationFrameCallbacks (C:\...\node_modules\jsdom\lib\jsdom\browser\Window.js:605:13)
          at Timeout.<anonymous> (C:\...\node_modules\jsdom\lib\jsdom\browser\Window.js:581:11)
          at listOnTimeout (node:internal/timers:569:17)
          at processTimers (node:internal/timers:512:7) {

Without sleep, the component's code does not execute transition as expected.

My project configuration:

  "dependencies": {
    "d3": "^7.8.5",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.13.0",
    "react-scripts": "^5.0.1",
    "typescript": "^4.2.3"
  },
  "devDependencies": {
    "@jest/globals": "29.1.1",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^14.0.0",
    "@types/d3": "^7.4.0",
    "@types/jest": "^29.5.3",
    "@types/react": "^18.0.26",
    "@types/react-dom": "^18.0.10",
    "@types/react-router-dom": "^5.3.3",
    "jest": "29.1.1",
    "jest-environment-jsdom": "29.1.1",
    "ts-jest": "29.1.1"
  }

Any suggestions how to solve the problem?

Michael Rovinsky
  • 6,807
  • 7
  • 15
  • 30
  • 1
    I don't use React but you would have a similar problem in Svelte due to the top level `await`. The solution there is proper use of `Promise.then`, but the placement can be tricky. It looks like there's a discussion of how that works in React in [this SO post](https://stackoverflow.com/questions/40029867/trying-to-implement-a-simple-promise-in-reactjs). – Mark McClure Jul 17 '23 at 15:11

1 Answers1

1

May be you can try Jest's fakeTimers to control the timers in your tests. This will allow you to manually advance time without having to use setTimeout. Using jest.useFakeTimers() and act() with jest.advanceTimersByTime(), you can control the timers in your test environment

jest.useFakeTimers(); // Use Jest's fake timers

test('Test MyComponent transition', () => {
  const { container } = render(<MyComponent width={...} height={...} data={...} />)

  // Optionally, you can wait for the transition to finish by manually advancing the timers
  act(() => {
    jest.advanceTimersByTime(500)
  });

  // Continue with your assertions
  ...
  
  jest.useRealTimers() // Restore real timers (optional, depending on other tests)
})

Also, may be the useEffect hook could be wrapped with a dependency array if it isn't already, to prevent unnecessary re-renders?

Nazrul Chowdhury
  • 1,483
  • 1
  • 5
  • 11