diff --git a/CHANGELOG.md b/CHANGELOG.md index eff6af9f3dac..ddfafa992689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - `[jest-circus]` Send test case results for `todo` tests ([#13915](https://github.com/facebook/jest/pull/13915)) - `[jest-circus]` Update message printed on test timeout ([#13830](https://github.com/facebook/jest/pull/13830)) - `[jest-circus]` Avoid creating the word "testfalse" when `takesDoneCallback` is `false` in the message printed on test timeout AND updated timeouts test ([#13954](https://github.com/facebook/jest/pull/13954)) +- `[jest-environment-jsdom]` Stop setting `document` to `null` on teardown ([#13972](https://github.com/facebook/jest/pull/13972)) - `[@jest/test-result]` Allow `TestResultsProcessor` type to return a Promise ([#13950](https://github.com/facebook/jest/pull/13950)) ### Chore & Maintenance diff --git a/packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.ts b/packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.ts index 5b480a1a52f7..138f37e32f8e 100644 --- a/packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.ts +++ b/packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.ts @@ -84,7 +84,7 @@ describe('JSDomEnvironment', () => { /** * When used in conjunction with Custom Elements (part of the WebComponents standard) - * setting the global.document to null too early is problematic because: + * setting the `global` and `global.document` to null too early is problematic because: * * CustomElement's disconnectedCallback method is called when a custom element * is removed from the DOM. The disconnectedCallback could need the document @@ -94,7 +94,7 @@ describe('JSDomEnvironment', () => { * The custom element will be removed from the DOM at this point, therefore disconnectedCallback * will be called, so please make sure the global.document is still available at this point. */ - it('should not set the global.document to null too early', () => { + it('should call CE disconnectedCallback with valid globals on teardown', () => { const env = new JSDomEnvironment( { globalConfig: makeGlobalConfig(), @@ -103,12 +103,46 @@ describe('JSDomEnvironment', () => { {console, docblockPragmas: {}, testPath: __filename}, ); - const originalCloseFn = env.global.close.bind(env.global); - env.global.close = () => { - originalCloseFn(); - expect(env.global.document).not.toBeNull(); - }; + let hasDisconnected = false; + let documentWhenDisconnected = null; - return env.teardown(); + // define a custom element + const {HTMLElement} = env.global; + class MyCustomElement extends HTMLElement { + disconnectedCallback() { + hasDisconnected = true; + documentWhenDisconnected = env.global.document; + } + } + + // append an instance of the custom element + env.global.customElements.define('my-custom-element', MyCustomElement); + const instance = env.global.document.createElement('my-custom-element'); + env.global.document.body.appendChild(instance); + + // teardown will disconnect the custom elements + env.teardown(); + + expect(hasDisconnected).toBe(true); + expect(documentWhenDisconnected).not.toBeNull(); + }); + + it('should not fire load event after the environment was teared down', async () => { + const env = new JSDomEnvironment( + { + globalConfig: makeGlobalConfig(), + projectConfig: makeProjectConfig(), + }, + {console, docblockPragmas: {}, testPath: __filename}, + ); + + const loadHandler = jest.fn(); + env.global.document.addEventListener('load', loadHandler); + env.teardown(); + + // The `load` event is fired in microtasks, wait until the microtask queue is reliably flushed + await new Promise(resolve => setTimeout(resolve, 0)); + + expect(loadHandler).not.toHaveBeenCalled(); }); }); diff --git a/packages/jest-environment-jsdom/src/index.ts b/packages/jest-environment-jsdom/src/index.ts index 96791c2a0869..dec78d95edf4 100644 --- a/packages/jest-environment-jsdom/src/index.ts +++ b/packages/jest-environment-jsdom/src/index.ts @@ -160,13 +160,6 @@ export default class JSDOMEnvironment implements JestEnvironment { this.global.removeEventListener('error', this.errorEventListener); } this.global.close(); - - // Dispose "document" to prevent "load" event from triggering. - - // Note that this.global.close() will trigger the CustomElement::disconnectedCallback - // Do not reset the document before CustomElement disconnectedCallback function has finished running, - // document should be accessible within disconnectedCallback. - Object.defineProperty(this.global, 'document', {value: null}); } this.errorEventListener = null; // @ts-expect-error: this.global not allowed to be `null`