Skip to content

Commit 5d37e60

Browse files
committed
feat(jest-environment): Support configuration of happy-dom with testEnvironmentOptions
1 parent c127bc6 commit 5d37e60

File tree

2 files changed

+160
-5
lines changed

2 files changed

+160
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import type { Config } from '@jest/types';
2+
import {
3+
BrowserErrorCaptureEnum,
4+
BrowserNavigationCrossOriginPolicyEnum,
5+
type IWindow
6+
} from 'happy-dom';
7+
8+
export function parseEnvironmentOptions(
9+
testEnvironmentOptions: Config.ProjectConfig['testEnvironmentOptions'],
10+
happyDOM: IWindow['happyDOM']
11+
): void {
12+
for (const [key, value] of Object.entries(testEnvironmentOptions)) {
13+
switch (key) {
14+
case 'disableJavaScriptEvaluation':
15+
case 'disableJavaScriptFileLoading':
16+
case 'disableCSSFileLoading':
17+
case 'disableComputedStyleRendering': {
18+
setValue('boolean', 'testEnvironmentOptions', happyDOM.settings, key, value);
19+
break;
20+
}
21+
case 'errorCapture': {
22+
const errorCaptureValidOptions: unknown[] = Object.values(BrowserErrorCaptureEnum);
23+
setEnumValue(
24+
errorCaptureValidOptions,
25+
'testEnvironmentOptions',
26+
happyDOM.settings,
27+
key,
28+
value
29+
);
30+
break;
31+
}
32+
case 'navigation': {
33+
parseNavigationOptions(happyDOM, testEnvironmentOptions.navigation);
34+
break;
35+
}
36+
case 'navigator': {
37+
parseNavigatorOptions(happyDOM, testEnvironmentOptions.navigator);
38+
break;
39+
}
40+
case 'device': {
41+
parseDeviceOptions(happyDOM, testEnvironmentOptions.device);
42+
break;
43+
}
44+
case 'url': {
45+
if (typeof value !== 'string') {
46+
throw new Error('testEnvironmentOptions.url must be a string');
47+
}
48+
happyDOM.setURL(value);
49+
}
50+
}
51+
}
52+
}
53+
54+
function parseNavigationOptions(happyDOM: IWindow['happyDOM'], options: unknown): void {
55+
for (const [key, value] of Object.entries(options)) {
56+
switch (key) {
57+
case 'disableMainFrameNavigation':
58+
case 'disableChildFrameNavigation':
59+
case 'disableChildPageNavigation':
60+
case 'disableFallbackToSetURL': {
61+
setValue(
62+
'boolean',
63+
'testEnvironmentOptions.navigation',
64+
happyDOM.settings.navigation,
65+
key,
66+
value
67+
);
68+
break;
69+
}
70+
case 'crossOriginPolicy': {
71+
const browserNavigationCrossOriginPolicyOptions: unknown[] = Object.values(
72+
BrowserNavigationCrossOriginPolicyEnum
73+
);
74+
setEnumValue(
75+
browserNavigationCrossOriginPolicyOptions,
76+
'testEnvironmentOptions.navigation',
77+
happyDOM.settings.navigation,
78+
key,
79+
value
80+
);
81+
break;
82+
}
83+
}
84+
}
85+
}
86+
87+
function parseNavigatorOptions(happyDOM: IWindow['happyDOM'], options: unknown): void {
88+
for (const [key, value] of Object.entries(options)) {
89+
switch (key) {
90+
case 'userAgent': {
91+
setValue(
92+
'string',
93+
'testEnvironmentOptions.navigator',
94+
happyDOM.settings.navigator,
95+
key,
96+
value
97+
);
98+
break;
99+
}
100+
case 'maxTouchPoints': {
101+
setValue(
102+
'number',
103+
'testEnvironmentOptions.navigator',
104+
happyDOM.settings.navigator,
105+
key,
106+
value
107+
);
108+
break;
109+
}
110+
}
111+
}
112+
}
113+
114+
function parseDeviceOptions(happyDOM: IWindow['happyDOM'], options: unknown): void {
115+
for (const [key, value] of Object.entries(options)) {
116+
switch (key) {
117+
case 'prefersColorScheme': {
118+
setValue('string', 'testEnvironmentOptions.device', happyDOM.settings.device, key, value);
119+
break;
120+
}
121+
case 'mediaType': {
122+
setValue('string', 'testEnvironmentOptions.device', happyDOM.settings.device, key, value);
123+
break;
124+
}
125+
}
126+
}
127+
}
128+
129+
function setValue<T, U extends keyof T>(
130+
type: 'string' | 'number' | 'boolean',
131+
path: string,
132+
target: T,
133+
key: U,
134+
value: unknown
135+
): void {
136+
if (typeof value !== type) {
137+
throw new Error(`${path}.${key.toString()} must be a ${type}`);
138+
}
139+
target[key] = <T[U]>value;
140+
}
141+
142+
function setEnumValue<T, U extends keyof T>(
143+
enumValues: unknown[],
144+
path: string,
145+
target: T,
146+
key: U,
147+
value: unknown
148+
): void {
149+
if (!enumValues.includes(value)) {
150+
throw new Error(`${path}.${key.toString()} must be one of ${enumValues.join(', ')}`);
151+
}
152+
target[key] = <T[U]>value;
153+
}

packages/jest-environment/src/index.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import * as JestUtil from 'jest-util';
55
import { ModuleMocker } from 'jest-mock';
66
import { LegacyFakeTimers, ModernFakeTimers } from '@jest/fake-timers';
77
import { JestEnvironment, EnvironmentContext } from '@jest/environment';
8-
import { Window, IWindow } from 'happy-dom';
8+
import { Window, IWindow, BrowserErrorCaptureEnum } from 'happy-dom';
99
import { Script } from 'vm';
1010
import { Global, Config } from '@jest/types';
11+
import { value } from 'happy-dom/lib/PropertySymbol';
12+
import { parseEnvironmentOptions } from './configuration';
1113

1214
/**
1315
* Happy DOM Jest Environment.
@@ -69,10 +71,10 @@ export default class HappyDOMEnvironment implements JestEnvironment {
6971
this.global.window['console'] = options.console;
7072
}
7173

72-
if (projectConfig.testEnvironmentOptions['url']) {
73-
this.window.happyDOM?.setURL(String(projectConfig.testEnvironmentOptions['url']));
74-
} else {
75-
this.window.happyDOM?.setURL('http://localhost/');
74+
// Always set a default URL, override should the option be present in the environment options.
75+
this.window.happyDOM?.setURL('http://localhost/');
76+
if (projectConfig.testEnvironmentOptions && this.window.happyDOM) {
77+
parseEnvironmentOptions(projectConfig.testEnvironmentOptions, this.window.happyDOM);
7678
}
7779

7880
this.fakeTimers = new LegacyFakeTimers({

0 commit comments

Comments
 (0)