jsdom: [Bug]: structuredClone is not defined

Basic info:

  • Node.js version: 18.0.0
  • jsdom version: 19.0.0

Minimal reproduction case

Running a jest test that calls the structuredClone global using the jsdom environment

structuredClone();

I first opened this issue in the jest repo and was instructed to open it here.

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 85
  • Comments: 22 (1 by maintainers)

Commits related to this issue

Most upvoted comments

How to force Jest/jsdom to use Node.js structuredClone implementation (requires Node.js >= 17.0.0):

// FixJSDOMEnvironment.ts

import JSDOMEnvironment from 'jest-environment-jsdom';

// https://github.com/facebook/jest/blob/v29.4.3/website/versioned_docs/version-29.4/Configuration.md#testenvironment-string
export default class FixJSDOMEnvironment extends JSDOMEnvironment {
  constructor(...args: ConstructorParameters<typeof JSDOMEnvironment>) {
    super(...args);

    // FIXME https://github.com/jsdom/jsdom/issues/3363
    this.global.structuredClone = structuredClone;
  }
}
// jest.config.js

/** @type {import('jest').Config} */
const config = {
  testEnvironment: './FixJSDOMEnvironment.ts',

  ...
}

module.exports = config;

See also https://github.com/jsdom/jsdom/issues/1724#issuecomment-1446858041, https://github.com/jsdom/jsdom/issues/1721#issuecomment-1484202038

global.structuredClone = jest.fn(val => { return JSON.parse(JSON.stringify(val)); });

This method is good for objects that can be converted to JSON. This doesn’t work for regular expressions, for example.


A smarter solution seems to be installing a polyfill.

  1. Installing core-js (it is clear that if it is already installed, you do not need to install it again)
npm install core-js --save-dev

2. In the test file, add the line

import 'core-js/modules/web.structured-clone';
// ...
// tests...
  1. In the test file, add the line
import 'core-js/stable/structured-clone';
// ...
// tests...

This is suitable if you are using the jsdom environment ("testEnvironment": "jsdom"). For other cases, the exact method may be slightly different, but the basic meaning is the same. This is the simplest one I’ve found. You can also try setting up Babel to automate this.

global.structuredClone = val => {
  return JSON.parse(JSON.stringify(val))
}

is what worked for me. But is this going to be fixed?

@Aleksandr-JS-Developer

import 'core-js/modules/web.structured-clone';

Use import 'core-js/stable/structured-clone'; since this module has some dependencies.

I can vouch that this is still an issue using jest-environment-jsdom v28.1.3

How to force Jest/jsdom to use Node.js structuredClone implementation (requires Node.js >= 17.0.0):

// FixJSDOMEnvironment.ts

import JSDOMEnvironment from 'jest-environment-jsdom';

// https://github.com/facebook/jest/blob/v29.4.3/website/versioned_docs/version-29.4/Configuration.md#testenvironment-string
export default class FixJSDOMEnvironment extends JSDOMEnvironment {
  constructor(...args: ConstructorParameters<typeof JSDOMEnvironment>) {
    super(...args);

    // FIXME https://github.com/jsdom/jsdom/issues/3363
    this.global.structuredClone = structuredClone;
  }
}
// jest.config.js

/** @type {import('jest').Config} */
const config = {
  testEnvironment: './FixJSDOMEnvironment.ts',

  ...
}

module.exports = config;

See also #1724 (comment), #1721 (comment)

@tkrotoff Is there a way to do this without ejecting in create-react-app? I have a few React components that are calling structuredClone and they’re erroring out when I try to render them in my unit tests.

As an alternative, you could mock structuredClone in your test file:

global.structuredClone = jest.fn(val => {
    return JSON.parse(JSON.stringify(val));
});

This won’t work for copying functions as values of the keys. For the rest, no problem.

How to force Jest/jsdom to use Node.js structuredClone implementation (requires Node.js >= 17.0.0):

// FixJSDOMEnvironment.ts

import JSDOMEnvironment from 'jest-environment-jsdom';

// https://github.com/facebook/jest/blob/v29.4.3/website/versioned_docs/version-29.4/Configuration.md#testenvironment-string
export default class FixJSDOMEnvironment extends JSDOMEnvironment {
  constructor(...args: ConstructorParameters<typeof JSDOMEnvironment>) {
    super(...args);

    // FIXME https://github.com/jsdom/jsdom/issues/3363
    this.global.structuredClone = structuredClone;
  }
}
// jest.config.js

/** @type {import('jest').Config} */
const config = {
  testEnvironment: './FixJSDOMEnvironment.ts',

  ...
}

module.exports = config;

See also #1724 (comment), #1721 (comment)

In case anybody needs it, I found out how to do the same in vitest:

// environment.ts

import type { Environment } from 'vitest'
import { builtinEnvironments } from 'vitest/environments'

export default <Environment>{
  name: 'custom_jsdom',
  transformMode: 'ssr',
  setup: (global, options) => {
    builtinEnvironments.jsdom.setup(global, options)

    // eslint-disable-next-line no-param-reassign
    global.structuredClone = structuredClone

    return {
      teardown() {
        // called after all tests with this env have been run
      },
    }
  },
}

And in the vitest.config.ts file:

export default mergeConfig(
  viteConfig,
  defineConfig({
    // ...
    test: {
      environment: './path/to/environment.ts',
      // ...
    },
  }),
)

Docs: https://vitest.dev/guide/environment#custom-environment

testEnvironment: './FixJSDOMEnvironment.ts',

were you able to work this out without ejecting?

structuredClone() has polyfills that can be used.

The solution I chose was:

  1. Install ungap/structured-clone npm i -D @ungap/structured-clone @types/ungap__structured-clone

  2. Update jest.config.ts and import the library and make it available as structuredClone:

import * as structuredClone from '@ungap/structured-clone'; // can be written as import { structuredClone } from '@ungap/structured-clone';  if esModuleInterop flag is used


export default {
  ....
  globals: {
    structuredClone: structuredClone.default, // 'structuredClone' is a default export so I access it via .default
  },
};

@Aleksandr-JS-Developer No, this is wrong. The core-js polyfill of the structuredClone() method is incomplete in comparison to the native Node.js implementation.

Instead, Node.js has its own built-in structuredClone() method since version 17.0.0. The method can be reflected in the VM context, as introduced in PR https://github.com/jsdom/jsdom/pull/3459.

any update on this?

This is still not working and it affects React app tests as well.