diff --git a/packages/jest-environment/src/configuration.ts b/packages/jest-environment/src/configuration.ts new file mode 100644 index 000000000..1f3d705c7 --- /dev/null +++ b/packages/jest-environment/src/configuration.ts @@ -0,0 +1,153 @@ +import type { Config } from '@jest/types'; +import { + BrowserErrorCaptureEnum, + BrowserNavigationCrossOriginPolicyEnum, + type IWindow +} from 'happy-dom'; + +export function parseEnvironmentOptions( + testEnvironmentOptions: Config.ProjectConfig['testEnvironmentOptions'], + happyDOM: IWindow['happyDOM'] +): void { + for (const [key, value] of Object.entries(testEnvironmentOptions)) { + switch (key) { + case 'disableJavaScriptEvaluation': + case 'disableJavaScriptFileLoading': + case 'disableCSSFileLoading': + case 'disableComputedStyleRendering': { + setValue('boolean', 'testEnvironmentOptions', happyDOM.settings, key, value); + break; + } + case 'errorCapture': { + const errorCaptureValidOptions: unknown[] = Object.values(BrowserErrorCaptureEnum); + setEnumValue( + errorCaptureValidOptions, + 'testEnvironmentOptions', + happyDOM.settings, + key, + value + ); + break; + } + case 'navigation': { + parseNavigationOptions(happyDOM, testEnvironmentOptions.navigation); + break; + } + case 'navigator': { + parseNavigatorOptions(happyDOM, testEnvironmentOptions.navigator); + break; + } + case 'device': { + parseDeviceOptions(happyDOM, testEnvironmentOptions.device); + break; + } + case 'url': { + if (typeof value !== 'string') { + throw new Error('testEnvironmentOptions.url must be a string'); + } + happyDOM.setURL(value); + } + } + } +} + +function parseNavigationOptions(happyDOM: IWindow['happyDOM'], options: unknown): void { + for (const [key, value] of Object.entries(options)) { + switch (key) { + case 'disableMainFrameNavigation': + case 'disableChildFrameNavigation': + case 'disableChildPageNavigation': + case 'disableFallbackToSetURL': { + setValue( + 'boolean', + 'testEnvironmentOptions.navigation', + happyDOM.settings.navigation, + key, + value + ); + break; + } + case 'crossOriginPolicy': { + const browserNavigationCrossOriginPolicyOptions: unknown[] = Object.values( + BrowserNavigationCrossOriginPolicyEnum + ); + setEnumValue( + browserNavigationCrossOriginPolicyOptions, + 'testEnvironmentOptions.navigation', + happyDOM.settings.navigation, + key, + value + ); + break; + } + } + } +} + +function parseNavigatorOptions(happyDOM: IWindow['happyDOM'], options: unknown): void { + for (const [key, value] of Object.entries(options)) { + switch (key) { + case 'userAgent': { + setValue( + 'string', + 'testEnvironmentOptions.navigator', + happyDOM.settings.navigator, + key, + value + ); + break; + } + case 'maxTouchPoints': { + setValue( + 'number', + 'testEnvironmentOptions.navigator', + happyDOM.settings.navigator, + key, + value + ); + break; + } + } + } +} + +function parseDeviceOptions(happyDOM: IWindow['happyDOM'], options: unknown): void { + for (const [key, value] of Object.entries(options)) { + switch (key) { + case 'prefersColorScheme': { + setValue('string', 'testEnvironmentOptions.device', happyDOM.settings.device, key, value); + break; + } + case 'mediaType': { + setValue('string', 'testEnvironmentOptions.device', happyDOM.settings.device, key, value); + break; + } + } + } +} + +function setValue( + type: 'string' | 'number' | 'boolean', + path: string, + target: T, + key: U, + value: unknown +): void { + if (typeof value !== type) { + throw new Error(`${path}.${key.toString()} must be a ${type}`); + } + target[key] = value; +} + +function setEnumValue( + enumValues: unknown[], + path: string, + target: T, + key: U, + value: unknown +): void { + if (!enumValues.includes(value)) { + throw new Error(`${path}.${key.toString()} must be one of ${enumValues.join(', ')}`); + } + target[key] = value; +} diff --git a/packages/jest-environment/src/index.ts b/packages/jest-environment/src/index.ts index e142ee549..10269e902 100644 --- a/packages/jest-environment/src/index.ts +++ b/packages/jest-environment/src/index.ts @@ -5,9 +5,11 @@ import * as JestUtil from 'jest-util'; import { ModuleMocker } from 'jest-mock'; import { LegacyFakeTimers, ModernFakeTimers } from '@jest/fake-timers'; import { JestEnvironment, EnvironmentContext } from '@jest/environment'; -import { Window, IWindow } from 'happy-dom'; +import { Window, IWindow, BrowserErrorCaptureEnum } from 'happy-dom'; import { Script } from 'vm'; import { Global, Config } from '@jest/types'; +import { value } from 'happy-dom/lib/PropertySymbol'; +import { parseEnvironmentOptions } from './configuration'; /** * Happy DOM Jest Environment. @@ -69,10 +71,10 @@ export default class HappyDOMEnvironment implements JestEnvironment { this.global.window['console'] = options.console; } - if (projectConfig.testEnvironmentOptions['url']) { - this.window.happyDOM?.setURL(String(projectConfig.testEnvironmentOptions['url'])); - } else { - this.window.happyDOM?.setURL('http://localhost/'); + // Always set a default URL, override should the option be present in the environment options. + this.window.happyDOM?.setURL('http://localhost/'); + if (projectConfig.testEnvironmentOptions && this.window.happyDOM) { + parseEnvironmentOptions(projectConfig.testEnvironmentOptions, this.window.happyDOM); } this.fakeTimers = new LegacyFakeTimers({