Skip to content

Commit

Permalink
Merge pull request capricorn86#1232 from capricorn86/task/1105-add-su…
Browse files Browse the repository at this point in the history
…pport-for-sending-in-options-to-globalregistrator

capricorn86#1105@minor: Adds support for sending in Window options to GlobalRegi…
  • Loading branch information
capricorn86 committed Jan 24, 2024
2 parents 1a85ba1 + 0b95343 commit a302efc
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 15 deletions.
81 changes: 67 additions & 14 deletions packages/global-registrator/src/GlobalRegistrator.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,87 @@
import { GlobalWindow } from 'happy-dom';
import { GlobalWindow, Window, EventTarget } from 'happy-dom';
import type { IOptionalBrowserSettings } from 'happy-dom';

const IGNORE_LIST = ['undefined', 'NaN', 'global', 'globalThis'];
const IGNORE_LIST = ['constructor', 'undefined', 'NaN', 'global', 'globalThis'];
const SELF_REFERRING = ['self', 'top', 'parent', 'window'];

/**
*
*/
export default class GlobalRegistrator {
private static registered: { [key: string]: string } | null = null;
private static registered: { [key: string]: PropertyDescriptor } | null = null;

/**
* Registers Happy DOM globally.
*
* @param [options] Options.
* @param [options.width] Window width. Defaults to "1024".
* @param [options.height] Window height. Defaults to "768".
* @param [options.url] URL.
* @param [options.settings] Settings.
*/
public static register(): void {
public static register(options?: {
width?: number;
height?: number;
url?: string;
settings?: IOptionalBrowserSettings;
}): void {
if (this.registered !== null) {
throw new Error('Failed to register. Happy DOM has already been globally registered.');
}

const window = new GlobalWindow();
const window = new GlobalWindow({ ...options, console: global.console });

this.registered = {};

for (const key of Object.keys(window)) {
if (global[key] !== window[key] && !IGNORE_LIST.includes(key)) {
this.registered[key] =
global[key] !== window[key] && global[key] !== undefined ? global[key] : null;
global[key] =
typeof window[key] === 'function' && !window[key].toString().startsWith('class ')
? window[key].bind(global)
: window[key];
const propertyDescriptors = Object.getOwnPropertyDescriptors(window);

for (const key of Object.keys(propertyDescriptors)) {
if (!IGNORE_LIST.includes(key)) {
const windowPropertyDescriptor = propertyDescriptors[key];
const globalPropertyDescriptor = Object.getOwnPropertyDescriptor(global, key);

if (
windowPropertyDescriptor.value !== undefined &&
(!globalPropertyDescriptor ||
windowPropertyDescriptor.value !== globalPropertyDescriptor.value)
) {
this.registered[key] = globalPropertyDescriptor || null;

if (
typeof windowPropertyDescriptor.value === 'function' &&
!windowPropertyDescriptor.value.toString().startsWith('class ')
) {
Object.defineProperty(global, key, {
...windowPropertyDescriptor,
value: windowPropertyDescriptor.value.bind(global)
});
} else {
Object.defineProperty(global, key, windowPropertyDescriptor);
}
}
}
}

for (const windowClass of [GlobalWindow, Window, EventTarget]) {
const propertyDescriptors = Object.getOwnPropertyDescriptors(
Reflect.getPrototypeOf(windowClass.prototype)
);
for (const key of Object.keys(propertyDescriptors)) {
if (!IGNORE_LIST.includes(key) && !this.registered[key]) {
const windowPropertyDescriptor = propertyDescriptors[key];
if (windowPropertyDescriptor.get || windowPropertyDescriptor.set) {
const globalPropertyDescriptor = Object.getOwnPropertyDescriptor(global, key);

this.registered[key] = globalPropertyDescriptor || null;

Object.defineProperty(global, key, {
configurable: true,
enumerable: windowPropertyDescriptor.enumerable,
get: windowPropertyDescriptor.get?.bind(window),
set: windowPropertyDescriptor.set?.bind(window)
});
}
}
}
}

Expand All @@ -50,7 +103,7 @@ export default class GlobalRegistrator {

for (const key of Object.keys(this.registered)) {
if (this.registered[key] !== null) {
global[key] = this.registered[key];
Object.defineProperty(global, key, this.registered[key]);
} else {
delete global[key];
}
Expand Down
29 changes: 29 additions & 0 deletions packages/global-registrator/test/react/React.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,35 @@ async function main(): Promise<void> {
if (global.setTimeout !== originalSetTimeout) {
throw Error('Global property was not restored.');
}

GlobalRegistrator.register({
url: 'https://example.com/',
width: 1920,
height: 1080,
settings: {
navigator: {
userAgent: 'Custom User Agent'
}
}
});

if (globalThis.location.href !== 'https://example.com/') {
throw Error('The option "url" has no affect.');
}

if (globalThis.innerWidth !== 1920) {
throw Error('The option "width" has no affect.');
}

if (globalThis.innerHeight !== 1080) {
throw Error('The option "height" has no affect.');
}

if (globalThis.navigator.userAgent !== 'Custom User Agent') {
throw Error('The option "settings.userAgent" has no affect.');
}

GlobalRegistrator.unregister();
}

main();
6 changes: 5 additions & 1 deletion packages/happy-dom/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ import type IBrowserContext from './browser/types/IBrowserContext.js';
import type IBrowserFrame from './browser/types/IBrowserFrame.js';
import type IBrowserPage from './browser/types/IBrowserPage.js';
import type ICrossOriginBrowserWindow from './window/ICrossOriginBrowserWindow.js';
import type IOptionalBrowserSettings from './browser/types/IOptionalBrowserSettings.js';
import type IBrowserSettings from './browser/types/IBrowserSettings.js';

export type {
IAnimationEventInit,
Expand Down Expand Up @@ -248,7 +250,9 @@ export type {
IText,
IUIEventInit,
IWheelEventInit,
IWindow
IWindow,
IBrowserSettings,
IOptionalBrowserSettings
};

export {
Expand Down

0 comments on commit a302efc

Please sign in to comment.