Skip to content

Commit

Permalink
Merge pull request #1107 from capricorn86/task/940-cant-render-portal…
Browse files Browse the repository at this point in the history
…s-sometimes

#940@patch: Fixes issue related to ownerDocument being null when exec…
  • Loading branch information
capricorn86 authored Oct 2, 2023
2 parents 0927221 + 7c595e0 commit a4d095d
Show file tree
Hide file tree
Showing 13 changed files with 7,320 additions and 15,205 deletions.
22,325 changes: 7,183 additions & 15,142 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions packages/global-registrator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"access": "public"
},
"scripts": {
"compile": "tsc && tsc --module CommonJS --outDir cjs && npm run change-cjs-file-extension",
"compile": "tsc && tsc --moduleResolution Node --module CommonJS --outDir cjs && npm run change-cjs-file-extension",
"change-cjs-file-extension": "node ../happy-dom/bin/change-file-extension.cjs --dir=./cjs --fromExt=.js --toExt=.cjs",
"watch": "npm run compile && tsc -w --preserveWatchOutput",
"lint": "eslint --ignore-path .gitignore --max-warnings 0 .",
Expand All @@ -90,7 +90,7 @@
"eslint-plugin-turbo": "^0.0.7",
"prettier": "^2.6.0",
"typescript": "^5.0.4",
"react": "^17.0.2",
"react-dom": "^17.0.2"
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
71 changes: 42 additions & 29 deletions packages/global-registrator/test/react/React.test.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,62 @@
import GlobalRegistrator from '../../cjs/GlobalRegistrator.cjs';
import React from 'react';
import ReactDOM from 'react-dom';
import ReactDOM from 'react-dom/client';
import { act } from 'react-dom/test-utils';
import ReactComponent from './ReactComponent.js';

const selfReferringProperties = ['self', 'top', 'parent', 'window'];
async function main(): Promise<void> {
const selfReferringProperties = ['self', 'top', 'parent', 'window'];

// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const originalSetTimeout = global.setTimeout;
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const originalSetTimeout = global.setTimeout;

GlobalRegistrator.register();
GlobalRegistrator.register();

const appElement = document.createElement('app');
document.body.appendChild(appElement);
const appElement = document.createElement('app');
let root;
document.body.appendChild(appElement);

function mountReactComponent(): void {
ReactDOM.render(<ReactComponent />, appElement);
async function mountReactComponent(): Promise<void> {
act(() => {
root = ReactDOM.createRoot(appElement);
root.render(<ReactComponent />);
});

if (appElement.innerHTML !== '<div>Test</div>') {
throw Error('React not rendered correctly.');
await new Promise((resolve) => setTimeout(resolve, 2));

if (appElement.innerHTML !== '<div>Test</div>') {
throw Error('React not rendered correctly.');
}
}
}

function unmountReactComponent(): void {
ReactDOM.unmountComponentAtNode(appElement);
function unmountReactComponent(): void {
act(() => {
root.unmount();
});

if (appElement.innerHTML !== '') {
throw Error('React not unmounted correctly.');
if (appElement.innerHTML !== '') {
throw Error('React not unmounted correctly.');
}
}
}

if (global.setTimeout === originalSetTimeout) {
throw Error('Happy DOM function not registered.');
}
if (global.setTimeout === originalSetTimeout) {
throw Error('Happy DOM function not registered.');
}

for (const property of selfReferringProperties) {
if (global[property] !== global) {
throw Error('Self referring property property was not registered.');
for (const property of selfReferringProperties) {
if (global[property] !== global) {
throw Error('Self referring property property was not registered.');
}
}
}

mountReactComponent();
unmountReactComponent();
await mountReactComponent();
unmountReactComponent();

GlobalRegistrator.unregister();
GlobalRegistrator.unregister();

if (global.setTimeout !== originalSetTimeout) {
throw Error('Global property was not restored.');
if (global.setTimeout !== originalSetTimeout) {
throw Error('Global property was not restored.');
}
}

main();
1 change: 1 addition & 0 deletions packages/global-registrator/test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"rootDir": "../test",
"jsx": "react",
"module": "CommonJS",
"moduleResolution": "Node",
"lib": [
"es2015",
"es2016",
Expand Down
6 changes: 3 additions & 3 deletions packages/global-registrator/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"compilerOptions": {
"outDir": "lib",
"rootDir": "src",
"target": "es2020",
"target": "ES2020",
"declaration": true,
"declarationMap": true,
"module": "es2020",
"moduleResolution": "node16",
"module": "Node16",
"moduleResolution": "Node16",
"esModuleInterop": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"access": "public"
},
"scripts": {
"compile": "tsc && tsc --module CommonJS --outDir cjs && npm run change-cjs-file-extension && npm run build-version-file",
"compile": "tsc && tsc --moduleResolution Node --module CommonJS --outDir cjs && npm run change-cjs-file-extension && npm run build-version-file",
"change-cjs-file-extension": "node ./bin/change-file-extension.cjs --dir=./cjs --fromExt=.js --toExt=.cjs",
"build-version-file": "node ./bin/build-version-file.cjs",
"watch": "tsc -w --preserveWatchOutput",
Expand Down
9 changes: 7 additions & 2 deletions packages/happy-dom/src/window/Window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import SVGGraphicsElement from '../nodes/svg-element/SVGGraphicsElement.js';
import HTMLScriptElement from '../nodes/html-script-element/HTMLScriptElement.js';
import HTMLImageElement from '../nodes/html-image-element/HTMLImageElement.js';
import ImageImplementation from '../nodes/html-image-element/Image.js';
import DocumentFragment from '../nodes/document-fragment/DocumentFragment.js';
import DocumentFragmentImplementation from '../nodes/document-fragment/DocumentFragment.js';
import CharacterData from '../nodes/character-data/CharacterData.js';
import NodeIterator from '../tree-walker/NodeIterator.js';
import TreeWalker from '../tree-walker/TreeWalker.js';
Expand Down Expand Up @@ -259,7 +259,6 @@ export default class Window extends EventTarget implements IWindow {
public readonly ShadowRoot = ShadowRoot;
public readonly ProcessingInstruction = ProcessingInstruction;
public readonly Element = Element;
public readonly DocumentFragment = DocumentFragment;
public readonly CharacterData = CharacterData;
public readonly NodeFilter = NodeFilter;
public readonly NodeIterator = NodeIterator;
Expand Down Expand Up @@ -346,6 +345,7 @@ export default class Window extends EventTarget implements IWindow {
public readonly Range;
public readonly FileReader;
public readonly Image;
public readonly DocumentFragment;
public readonly Audio;

// Events
Expand Down Expand Up @@ -562,6 +562,7 @@ export default class Window extends EventTarget implements IWindow {
ResponseImplementation._ownerDocument = document;
RequestImplementation._ownerDocument = document;
ImageImplementation._ownerDocument = document;
DocumentFragmentImplementation._ownerDocument = document;
FileReaderImplementation._ownerDocument = document;
DOMParserImplementation._ownerDocument = document;
RangeImplementation._ownerDocument = document;
Expand All @@ -577,6 +578,9 @@ export default class Window extends EventTarget implements IWindow {
class Image extends ImageImplementation {
public static _ownerDocument: IDocument = document;
}
class DocumentFragment extends DocumentFragmentImplementation {
public static _ownerDocument: IDocument = document;
}
class FileReader extends FileReaderImplementation {
public static _ownerDocument: IDocument = document;
}
Expand All @@ -597,6 +601,7 @@ export default class Window extends EventTarget implements IWindow {
this.Response = Response;
this.Request = Request;
this.Image = Image;
this.DocumentFragment = DocumentFragment;
this.FileReader = FileReader;
this.DOMParser = DOMParser;
this.XMLHttpRequest = XMLHttpRequest;
Expand Down
6 changes: 3 additions & 3 deletions packages/happy-dom/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"compilerOptions": {
"outDir": "lib",
"rootDir": "src",
"target": "es2020",
"target": "ES2020",
"declaration": true,
"declarationMap": true,
"module": "es2020",
"moduleResolution": "node16",
"module": "Node16",
"moduleResolution": "Node16",
"esModuleInterop": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
Expand Down
13 changes: 7 additions & 6 deletions packages/jest-environment/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@
"@typescript-eslint/eslint-plugin": "^5.16.0",
"@typescript-eslint/parser": "^5.16.0",
"@types/node": "^16.11.7",
"@types/react": "^17.0.2",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^14.4.3",
"@types/react-dom": "^17.0.2",
"@types/react": "^18.2.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
"@types/react-dom": "^18.2.0",
"@types/jest": "29.5.2",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
Expand All @@ -78,8 +78,9 @@
"typescript": "^5.0.4",
"jest": "^29.4.0",
"ts-jest": "^29.1.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@radix-ui/react-select": "^1.2.2",
"vue": "^3.2.31",
"lit": "^2.2.1",
"@lit/reactive-element": "^1.3.1",
Expand Down
68 changes: 61 additions & 7 deletions packages/jest-environment/test/react/React.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import ReactDOM from 'react-dom/client';
import * as ReactTestingLibrary from '@testing-library/react';
import ReactTestingLibraryUserEvent from '@testing-library/user-event';
import { act } from 'react-dom/test-utils';
import { ReactDivComponent, ReactSelectComponent, ReactInputComponent } from './ReactComponents';
import * as Select from '@radix-ui/react-select';

/* eslint-disable @typescript-eslint/consistent-type-assertions */

Expand All @@ -20,19 +22,30 @@ describe('React', () => {
document.body.removeChild(appElement);
});

it('Tests integration.', () => {
ReactDOM.render(<ReactDivComponent />, appElement);
it('Tests integration.', async () => {
act(() => {
ReactDOM.createRoot(appElement).render(<ReactDivComponent />);
});
await new Promise((resolve) => setTimeout(resolve, 2));
expect(appElement.innerHTML).toBe('<div>Test</div>');
});

it('Can unmount a component.', () => {
ReactDOM.render(<ReactDivComponent />, appElement);
ReactDOM.unmountComponentAtNode(appElement);
it('Can unmount a component.', async () => {
const root = ReactDOM.createRoot(appElement);
act(() => {
root.render(<ReactDivComponent />);
});
await new Promise((resolve) => setTimeout(resolve, 2));
act(() => {
root.unmount();
});
expect(appElement.innerHTML).toBe('');
});

it('Handles adding and removing event listeners.', () => {
ReactDOM.render(<ReactSelectComponent onChange={() => {}} />, appElement);
act(() => {
ReactDOM.createRoot(appElement).render(<ReactSelectComponent onChange={() => {}} />);
});
});

it('Testing library handles input', async () => {
Expand All @@ -43,4 +56,45 @@ describe('React', () => {

expect(input.value).toBe('hello');
});

it('Can render Radix UI Select component.', async () => {
act(() => {
ReactDOM.createRoot(appElement).render(
<Select.Root>
<Select.Trigger>
<Select.Value />
<Select.Icon />
</Select.Trigger>

<Select.Portal>
<Select.Content>
<Select.ScrollUpButton />
<Select.Viewport>
<Select.Item value="1">
<Select.ItemText />
<Select.ItemIndicator />
</Select.Item>

<Select.Group>
<Select.Label />
<Select.Item value="2">
<Select.ItemText />
<Select.ItemIndicator />
</Select.Item>
</Select.Group>

<Select.Separator />
</Select.Viewport>
<Select.ScrollDownButton />
<Select.Arrow />
</Select.Content>
</Select.Portal>
</Select.Root>
);
});
await new Promise((resolve) => setTimeout(resolve, 2));
expect(document.body.innerHTML).toBe(
'<app><button type="button" role="combobox" aria-controls="radix-:r0:" aria-expanded="false" aria-autocomplete="none" dir="ltr" data-state="closed" data-placeholder=""><span style="pointer-events: none;"></span><span aria-hidden="true">▼</span></button></app>'
);
});
});
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import UserEvent from '@testing-library/user-event';
import React from 'react';

describe('TestingLibrary', () => {
it('Triggers change event in input when typing.', async () => {
const user = userEvent.setup();
const user = UserEvent.setup();
const onChange = jest.fn();

render(<input onChange={(event) => onChange(event.target.value)} />);
Expand All @@ -14,7 +14,7 @@ describe('TestingLibrary', () => {
});

it('Triggers click and submit event once.', async () => {
const user = userEvent.setup();
const user = UserEvent.setup();
const handleSubmit = jest.fn((ev) => {
ev.preventDefault();
});
Expand All @@ -34,7 +34,7 @@ describe('TestingLibrary', () => {
});

it('Triggers change event once.', async () => {
const user = userEvent.setup();
const user = UserEvent.setup();
const changeHandler = jest.fn();

render(<input type="checkbox" onChange={changeHandler} />);
Expand All @@ -45,7 +45,7 @@ describe('TestingLibrary', () => {
});

it('Finds elements using "screen.getByText()".', async () => {
const user = userEvent.setup();
const user = UserEvent.setup();
const clickHandler = jest.fn();

render(<input type="submit" value="Submit Button" onClick={clickHandler} />);
Expand Down
2 changes: 1 addition & 1 deletion packages/uncaught-exception-observer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"access": "public"
},
"scripts": {
"compile": "tsc && tsc --module CommonJS --outDir cjs && npm run change-cjs-file-extension",
"compile": "tsc && tsc --moduleResolution Node --module CommonJS --outDir cjs && npm run change-cjs-file-extension",
"change-cjs-file-extension": "node ../happy-dom/bin/change-file-extension.cjs --dir=./cjs --fromExt=.js --toExt=.cjs",
"watch": "npm run compile && tsc -w --preserveWatchOutput",
"lint": "eslint --ignore-path .gitignore --max-warnings 0 .",
Expand Down
6 changes: 3 additions & 3 deletions packages/uncaught-exception-observer/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"compilerOptions": {
"outDir": "lib",
"rootDir": "src",
"target": "es2020",
"target": "ES2020",
"declaration": true,
"declarationMap": true,
"module": "es2020",
"moduleResolution": "node16",
"module": "Node16",
"moduleResolution": "Node16",
"esModuleInterop": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
Expand Down

0 comments on commit a4d095d

Please sign in to comment.