| 
									
										
										
										
											2025-07-16 22:29:28 +08:00
										 |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | const fs = require("fs"); | 
					
						
							| 
									
										
										
										
											2025-07-14 18:53:09 +08:00
										 |  |  | const { Module } = require("module"); | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | const path = require("path"); | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | const { fileURLToPath, pathToFileURL } = require("url"); | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | const vm = require("vm"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  |  * @param {string} path path | 
					
						
							|  |  |  |  * @returns {string} subPath | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | const getSubPath = (path) => { | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	let subPath = ""; | 
					
						
							|  |  |  | 	const lastSlash = path.lastIndexOf("/"); | 
					
						
							|  |  |  | 	let firstSlash = path.indexOf("/"); | 
					
						
							|  |  |  | 	if (lastSlash !== -1 && firstSlash !== lastSlash) { | 
					
						
							|  |  |  | 		if (firstSlash !== -1) { | 
					
						
							|  |  |  | 			let next = path.indexOf("/", firstSlash + 1); | 
					
						
							|  |  |  | 			let dir = path.slice(firstSlash + 1, next); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			while (dir === ".") { | 
					
						
							|  |  |  | 				firstSlash = next; | 
					
						
							|  |  |  | 				next = path.indexOf("/", firstSlash + 1); | 
					
						
							|  |  |  | 				dir = path.slice(firstSlash + 1, next); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		subPath = path.slice(firstSlash + 1, lastSlash + 1); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return subPath; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  |  * @param {string} path path | 
					
						
							|  |  |  |  * @returns {boolean} whether path is a relative path | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | const isRelativePath = (path) => /^\.\.?\//.test(path);
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  |  * @param {string} url url | 
					
						
							|  |  |  |  * @param {string} outputDirectory outputDirectory | 
					
						
							|  |  |  |  * @returns {string} absolute path | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | const urlToPath = (url, outputDirectory) => { | 
					
						
							|  |  |  | 	if (url.startsWith("https://test.cases/path/")) url = url.slice(24); | 
					
						
							|  |  |  | 	else if (url.startsWith("https://test.cases/")) url = url.slice(19); | 
					
						
							|  |  |  | 	return path.resolve(outputDirectory, `./${url}`); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  |  * @param {string} url url | 
					
						
							|  |  |  |  * @returns {string} relative path | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | const urlToRelativePath = (url) => { | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	if (url.startsWith("https://test.cases/path/")) url = url.slice(24); | 
					
						
							|  |  |  | 	else if (url.startsWith("https://test.cases/")) url = url.slice(19); | 
					
						
							|  |  |  | 	return `./${url}`; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  |  * @typedef {object} TestMeta | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  |  * @property {string} category | 
					
						
							|  |  |  |  * @property {string} name | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  |  * @property {number=} round | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  |  * @typedef {object} TestConfig | 
					
						
							|  |  |  |  * @property {EXPECTED_FUNCTION=} resolveModule | 
					
						
							|  |  |  |  * @property {EXPECTED_FUNCTION=} moduleScope | 
					
						
							|  |  |  |  * @property {EXPECTED_FUNCTION=} nonEsmThis | 
					
						
							|  |  |  |  * @property {boolean=} evaluateScriptOnAttached | 
					
						
							| 
									
										
										
										
											2025-07-10 02:38:53 +08:00
										 |  |  |  * @property {"jsdom"=} env | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  |  * @typedef {object} TestRunnerOptions | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  |  * @property {string|string[]} target | 
					
						
							|  |  |  |  * @property {string} outputDirectory | 
					
						
							|  |  |  |  * @property {TestMeta} testMeta | 
					
						
							|  |  |  |  * @property {TestConfig} testConfig | 
					
						
							|  |  |  |  * @property {EXPECTED_ANY} webpackOptions | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  |  * @typedef {object} ModuleInfo | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  |  * @property {string} subPath | 
					
						
							|  |  |  |  * @property {string} modulePath | 
					
						
							|  |  |  |  * @property {string} content | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  |  * @typedef {object} RequireContext | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  |  * @property {"unlinked"|"evaluated"} esmMode | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  |  * @typedef {object} ModuleRunner | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  |  * @property {(moduleInfo: ModuleInfo, context: RequireContext) => EXPECTED_ANY} cjs | 
					
						
							|  |  |  |  * @property {(moduleInfo: ModuleInfo, context: RequireContext) => Promise<EXPECTED_ANY>} esm | 
					
						
							|  |  |  |  * @property {(moduleInfo: ModuleInfo, context: RequireContext) => EXPECTED_ANY} json | 
					
						
							|  |  |  |  * @property {(moduleInfo: ModuleInfo, context: RequireContext) => EXPECTED_ANY} raw | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TestRunner { | 
					
						
							|  |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 	 * @param {TestRunnerOptions} options test runner options | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	 */ | 
					
						
							|  |  |  | 	constructor({ | 
					
						
							|  |  |  | 		target, | 
					
						
							|  |  |  | 		outputDirectory, | 
					
						
							|  |  |  | 		testMeta, | 
					
						
							|  |  |  | 		testConfig, | 
					
						
							|  |  |  | 		webpackOptions | 
					
						
							|  |  |  | 	}) { | 
					
						
							| 
									
										
										
										
											2025-07-10 02:38:53 +08:00
										 |  |  | 		/** @type {string|string[]} */ | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 		this.target = target; | 
					
						
							| 
									
										
										
										
											2025-07-10 02:38:53 +08:00
										 |  |  | 		/** @type {string} */ | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 		this.outputDirectory = outputDirectory; | 
					
						
							| 
									
										
										
										
											2025-07-10 02:38:53 +08:00
										 |  |  | 		/** @type {TestConfig} */ | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 		this.testConfig = testConfig || {}; | 
					
						
							| 
									
										
										
										
											2025-07-10 02:38:53 +08:00
										 |  |  | 		/** @type {TestMeta} */ | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 		this.testMeta = testMeta || {}; | 
					
						
							| 
									
										
										
										
											2025-07-10 02:38:53 +08:00
										 |  |  | 		/** @type {EXPECTED_ANY} */ | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 		this.webpackOptions = webpackOptions || {}; | 
					
						
							| 
									
										
										
										
											2025-07-10 02:38:53 +08:00
										 |  |  | 		/** @type {boolean} */ | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 		this._runInNewContext = this.isTargetWeb(); | 
					
						
							| 
									
										
										
										
											2025-07-10 02:38:53 +08:00
										 |  |  | 		/** @type {EXPECTED_ANY} */ | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 		this._globalContext = this.createBaseGlobalContext(); | 
					
						
							| 
									
										
										
										
											2025-07-10 02:38:53 +08:00
										 |  |  | 		/** @type {EXPECTED_ANY} */ | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 		this._moduleScope = this.createBaseModuleScope(); | 
					
						
							| 
									
										
										
										
											2025-07-10 02:38:53 +08:00
										 |  |  | 		/** @type {ModuleRunner} */ | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 		this._moduleRunners = this.createModuleRunners(); | 
					
						
							| 
									
										
										
										
											2025-07-10 02:38:53 +08:00
										 |  |  | 		/** @type {EXPECTED_ANY} */ | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 		this._esmContext = this.createBaseEsmContext(); | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 	 * @returns {ModuleRunner} module runners | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	 */ | 
					
						
							|  |  |  | 	createModuleRunners() { | 
					
						
							|  |  |  | 		return { | 
					
						
							|  |  |  | 			cjs: this.createCjsRunner(), | 
					
						
							|  |  |  | 			esm: this.createEsmRunner(), | 
					
						
							|  |  |  | 			json: this.createJSONRunner(), | 
					
						
							|  |  |  | 			raw: this.createRawRunner() | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * @returns {EXPECTED_ANY} globalContext | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	createBaseGlobalContext() { | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 		const base = { console, expect, setTimeout, clearTimeout }; | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 		Object.assign(base, this.setupEnv()); | 
					
						
							|  |  |  | 		return base; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {EXPECTED_ANY} esmContext esm context | 
					
						
							|  |  |  | 	 * @returns {EXPECTED_ANY} esm context | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	mergeEsmContext(esmContext) { | 
					
						
							|  |  |  | 		return Object.assign(this._esmContext, esmContext); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 	 * @returns {boolean} whether target is web | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	 */ | 
					
						
							|  |  |  | 	isTargetWeb() { | 
					
						
							|  |  |  | 		return ( | 
					
						
							|  |  |  | 			this.target === "web" || | 
					
						
							|  |  |  | 			this.target === "webworker" || | 
					
						
							|  |  |  | 			(Array.isArray(this.target) && | 
					
						
							|  |  |  | 				(this.target.includes("web") || this.target.includes("webworker"))) | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 	 * @returns {boolean} whether env is jsdom | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	 */ | 
					
						
							|  |  |  | 	jsDom() { | 
					
						
							| 
									
										
										
										
											2025-07-10 02:38:53 +08:00
										 |  |  | 		return this.testConfig.env === "jsdom" || this.isTargetWeb(); | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * @returns {EXPECTED_ANY} moduleScope | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	createBaseModuleScope() { | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 		const base = { | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 			console, | 
					
						
							|  |  |  | 			expect, | 
					
						
							|  |  |  | 			jest, | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 			nsObj: (m) => { | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 				Object.defineProperty(m, Symbol.toStringTag, { | 
					
						
							|  |  |  | 					value: "Module" | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 				return m; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 		if (this.jsDom()) { | 
					
						
							|  |  |  | 			Object.assign(base, this._globalContext); | 
					
						
							|  |  |  | 			base.window = this._globalContext; | 
					
						
							|  |  |  | 			base.self = this._globalContext; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return base; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @returns {EXPECTED_ANY} esm context | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	createBaseEsmContext() { | 
					
						
							|  |  |  | 		const base = { | 
					
						
							|  |  |  | 			global, | 
					
						
							|  |  |  | 			process, | 
					
						
							|  |  |  | 			setTimeout, | 
					
						
							|  |  |  | 			setImmediate, | 
					
						
							|  |  |  | 			URL, | 
					
						
							|  |  |  | 			Buffer | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 		return base; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 	 * @param {EXPECTED_ANY} globalContext global context | 
					
						
							|  |  |  | 	 * @returns {EXPECTED_ANY} global context | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	 */ | 
					
						
							|  |  |  | 	mergeGlobalContext(globalContext) { | 
					
						
							|  |  |  | 		return Object.assign(this._globalContext, globalContext); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 	 * @param {EXPECTED_ANY} moduleScope module scope | 
					
						
							|  |  |  | 	 * @returns {EXPECTED_ANY} module scope | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	 */ | 
					
						
							|  |  |  | 	mergeModuleScope(moduleScope) { | 
					
						
							|  |  |  | 		return Object.assign(this._moduleScope, moduleScope); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 	 * @param {string} currentDirectory current directory | 
					
						
							|  |  |  | 	 * @param {string|string[]} module module | 
					
						
							|  |  |  | 	 * @returns {ModuleInfo} module info | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	 */ | 
					
						
							|  |  |  | 	_resolveModule(currentDirectory, module) { | 
					
						
							|  |  |  | 		if (Array.isArray(module)) { | 
					
						
							|  |  |  | 			return { | 
					
						
							|  |  |  | 				subPath: "", | 
					
						
							|  |  |  | 				modulePath: path.join(currentDirectory, ".array-require.js"), | 
					
						
							|  |  |  | 				content: `module.exports = (${module | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 					.map((arg) => `require(${JSON.stringify(`./${arg}`)})`) | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 					.join(", ")});`
 | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (isRelativePath(module)) { | 
					
						
							|  |  |  | 			return { | 
					
						
							|  |  |  | 				subPath: getSubPath(module), | 
					
						
							|  |  |  | 				modulePath: path.join(currentDirectory, module), | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 				content: fs.readFileSync(path.join(currentDirectory, module), "utf8") | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 			}; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (path.isAbsolute(module)) { | 
					
						
							|  |  |  | 			return { | 
					
						
							|  |  |  | 				subPath: "", | 
					
						
							|  |  |  | 				modulePath: module, | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 				content: fs.readFileSync(module, "utf8") | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 			}; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2025-06-23 20:22:23 +08:00
										 |  |  | 		if (module.startsWith("https://test.")) { | 
					
						
							|  |  |  | 			const realPath = urlToPath(module, currentDirectory); | 
					
						
							|  |  |  | 			return { | 
					
						
							|  |  |  | 				subPath: "", | 
					
						
							|  |  |  | 				modulePath: realPath, | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 				content: fs.readFileSync(realPath, "utf8") | 
					
						
							| 
									
										
										
										
											2025-06-23 20:22:23 +08:00
										 |  |  | 			}; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 	 * @param {string} currentDirectory current directory | 
					
						
							|  |  |  | 	 * @param {string|string[]} module module | 
					
						
							|  |  |  | 	 * @param {RequireContext=} context context | 
					
						
							|  |  |  | 	 * @returns {EXPECTED_ANY} require result | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	 */ | 
					
						
							|  |  |  | 	require(currentDirectory, module, context = {}) { | 
					
						
							|  |  |  | 		if (this.testConfig.modules && module in this.testConfig.modules) { | 
					
						
							|  |  |  | 			return this.testConfig.modules[module]; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (this.testConfig.resolveModule) { | 
					
						
							|  |  |  | 			module = this.testConfig.resolveModule( | 
					
						
							|  |  |  | 				module, | 
					
						
							|  |  |  | 				this.testMeta.round || 0, | 
					
						
							|  |  |  | 				this.webpackOptions | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 		const moduleInfo = this._resolveModule(currentDirectory, module); | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 		if (!moduleInfo) { | 
					
						
							| 
									
										
										
										
											2025-07-14 18:53:09 +08:00
										 |  |  | 			// node v12.2.0+ has Module.createRequire
 | 
					
						
							|  |  |  | 			const rawRequire = Module.createRequire | 
					
						
							|  |  |  | 				? Module.createRequire(currentDirectory) | 
					
						
							|  |  |  | 				: require; | 
					
						
							|  |  |  | 			return rawRequire(module.startsWith("node:") ? module.slice(5) : module); | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		const { modulePath } = moduleInfo; | 
					
						
							|  |  |  | 		if ( | 
					
						
							|  |  |  | 			modulePath.endsWith(".mjs") && | 
					
						
							|  |  |  | 			this.webpackOptions.experiments && | 
					
						
							|  |  |  | 			this.webpackOptions.experiments.outputModule | 
					
						
							|  |  |  | 		) { | 
					
						
							|  |  |  | 			return this._moduleRunners.esm(moduleInfo, context); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (modulePath.endsWith(".json")) { | 
					
						
							|  |  |  | 			return this._moduleRunners.json(moduleInfo, context); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (["css"].includes(modulePath.split(".").pop())) { | 
					
						
							|  |  |  | 			return this._moduleRunners.raw(moduleInfo, context); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return this._moduleRunners.cjs(moduleInfo, context); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 	 * @returns {(moduleInfo: ModuleInfo, context: RequireContext) => EXPECTED_ANY} cjs runner | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	 */ | 
					
						
							|  |  |  | 	createCjsRunner() { | 
					
						
							|  |  |  | 		const requireCache = Object.create(null); | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 		return (moduleInfo, _context) => { | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 			const { modulePath, subPath, content } = moduleInfo; | 
					
						
							|  |  |  | 			let _content = content; | 
					
						
							|  |  |  | 			if (modulePath in requireCache) { | 
					
						
							|  |  |  | 				return requireCache[modulePath].exports; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			const mod = { | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 				exports: {}, | 
					
						
							|  |  |  | 				webpackTestSuiteModule: true | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 			}; | 
					
						
							|  |  |  | 			requireCache[modulePath] = mod; | 
					
						
							|  |  |  | 			const moduleScope = { | 
					
						
							|  |  |  | 				...this._moduleScope, | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 				require: Object.assign( | 
					
						
							|  |  |  | 					this.require.bind(this, path.dirname(modulePath)), | 
					
						
							|  |  |  | 					this.require | 
					
						
							|  |  |  | 				), | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 				importScripts: (url) => { | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 					expect(url).toMatch(/^https:\/\/test\.cases\/path\//); | 
					
						
							|  |  |  | 					this.require(this.outputDirectory, urlToRelativePath(url)); | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				module: mod, | 
					
						
							|  |  |  | 				exports: mod.exports, | 
					
						
							|  |  |  | 				__dirname: path.dirname(modulePath), | 
					
						
							|  |  |  | 				__filename: modulePath, | 
					
						
							|  |  |  | 				_globalAssign: { expect, it: this._moduleScope.it } | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 			// Call again because some tests rely on `scope.module`
 | 
					
						
							|  |  |  | 			if (this.testConfig.moduleScope) { | 
					
						
							|  |  |  | 				this.testConfig.moduleScope(moduleScope, this.webpackOptions); | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 			if (!this._runInNewContext) { | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 				_content = `Object.assign(global, _globalAssign); ${content}`; | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 			const args = Object.keys(moduleScope); | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 			const argValues = args.map((arg) => moduleScope[arg]); | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 			const code = `(function(${args.join(", ")}) {${_content}\n})`; | 
					
						
							|  |  |  | 			const document = this._moduleScope.document; | 
					
						
							|  |  |  | 			const fn = this._runInNewContext | 
					
						
							|  |  |  | 				? vm.runInNewContext(code, this._globalContext, modulePath) | 
					
						
							|  |  |  | 				: vm.runInThisContext(code, modulePath); | 
					
						
							|  |  |  | 			const call = () => { | 
					
						
							|  |  |  | 				fn.call( | 
					
						
							|  |  |  | 					this.testConfig.nonEsmThis | 
					
						
							|  |  |  | 						? this.testConfig.nonEsmThis(module) | 
					
						
							|  |  |  | 						: mod.exports, | 
					
						
							|  |  |  | 					...argValues | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 			if (document) { | 
					
						
							|  |  |  | 				const CurrentScript = require("../helpers/CurrentScript"); | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 				const oldCurrentScript = document.currentScript; | 
					
						
							|  |  |  | 				document.currentScript = new CurrentScript(subPath); | 
					
						
							|  |  |  | 				try { | 
					
						
							|  |  |  | 					call(); | 
					
						
							|  |  |  | 				} finally { | 
					
						
							|  |  |  | 					document.currentScript = oldCurrentScript; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				call(); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return mod.exports; | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 	 * @returns {(moduleInfo: ModuleInfo, context: RequireContext) => Promise<EXPECTED_ANY>} esm runner | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	 */ | 
					
						
							|  |  |  | 	createEsmRunner() { | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 		const createEsmContext = () => | 
					
						
							|  |  |  | 			vm.createContext( | 
					
						
							|  |  |  | 				{ ...this._moduleScope, ...this._esmContext }, | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					name: "context for esm" | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			); | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 		const esmCache = new Map(); | 
					
						
							|  |  |  | 		const { category, name, round } = this.testMeta; | 
					
						
							|  |  |  | 		const esmIdentifier = `${category.name}-${name}-${round || 0}`; | 
					
						
							|  |  |  | 		let esmContext = null; | 
					
						
							|  |  |  | 		return (moduleInfo, context) => { | 
					
						
							|  |  |  | 			const asModule = require("../helpers/asModule"); | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 			// lazy bind esm context
 | 
					
						
							|  |  |  | 			if (!esmContext) { | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 				esmContext = createEsmContext(); | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 			const { modulePath, content } = moduleInfo; | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 			const { esmMode } = context; | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 			if (!vm.SourceTextModule) { | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 				throw new Error( | 
					
						
							|  |  |  | 					"Running this test requires '--experimental-vm-modules'.\nRun with 'node --experimental-vm-modules node_modules/jest-cli/bin/jest'." | 
					
						
							|  |  |  | 				); | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 			let esm = esmCache.get(modulePath); | 
					
						
							|  |  |  | 			if (!esm) { | 
					
						
							|  |  |  | 				esm = new vm.SourceTextModule(content, { | 
					
						
							|  |  |  | 					identifier: `${esmIdentifier}-${modulePath}`, | 
					
						
							|  |  |  | 					url: `${pathToFileURL(modulePath).href}?${esmIdentifier}`, | 
					
						
							|  |  |  | 					context: esmContext, | 
					
						
							|  |  |  | 					initializeImportMeta: (meta, module) => { | 
					
						
							|  |  |  | 						meta.url = pathToFileURL(modulePath).href; | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 					importModuleDynamically: async (specifier, module) => { | 
					
						
							|  |  |  | 						const normalizedSpecifier = specifier.startsWith("file:") | 
					
						
							|  |  |  | 							? `./${path.relative( | 
					
						
							|  |  |  | 									path.dirname(modulePath), | 
					
						
							|  |  |  | 									fileURLToPath(specifier) | 
					
						
							|  |  |  | 								)}`
 | 
					
						
							|  |  |  | 							: specifier.replace( | 
					
						
							|  |  |  | 									/https:\/\/example.com\/public\/path\//, | 
					
						
							|  |  |  | 									"./" | 
					
						
							|  |  |  | 								); | 
					
						
							|  |  |  | 						const result = await this.require( | 
					
						
							|  |  |  | 							path.dirname(modulePath), | 
					
						
							|  |  |  | 							normalizedSpecifier, | 
					
						
							|  |  |  | 							{ | 
					
						
							|  |  |  | 								esmMode: "evaluated" | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 						); | 
					
						
							|  |  |  | 						return await asModule(result, module.context); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 				esmCache.set(modulePath, esm); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (esmMode === "unlinked") return esm; | 
					
						
							|  |  |  | 			return (async () => { | 
					
						
							|  |  |  | 				if (esmMode === "unlinked") return esm; | 
					
						
							|  |  |  | 				await esm.link( | 
					
						
							|  |  |  | 					async (specifier, referencingModule) => | 
					
						
							|  |  |  | 						await asModule( | 
					
						
							|  |  |  | 							await this.require( | 
					
						
							|  |  |  | 								path.dirname( | 
					
						
							|  |  |  | 									referencingModule.identifier | 
					
						
							|  |  |  | 										? referencingModule.identifier.slice( | 
					
						
							|  |  |  | 												esmIdentifier.length + 1 | 
					
						
							|  |  |  | 											) | 
					
						
							|  |  |  | 										: fileURLToPath(referencingModule.url) | 
					
						
							|  |  |  | 								), | 
					
						
							|  |  |  | 								specifier, | 
					
						
							|  |  |  | 								{ esmMode: "unlinked" } | 
					
						
							|  |  |  | 							), | 
					
						
							|  |  |  | 							referencingModule.context, | 
					
						
							|  |  |  | 							true | 
					
						
							|  |  |  | 						) | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 				// node.js 10 needs instantiate
 | 
					
						
							|  |  |  | 				if (esm.instantiate) esm.instantiate(); | 
					
						
							|  |  |  | 				await esm.evaluate(); | 
					
						
							|  |  |  | 				if (esmMode === "evaluated") return esm; | 
					
						
							|  |  |  | 				const ns = esm.namespace; | 
					
						
							|  |  |  | 				return ns.default && ns.default instanceof Promise ? ns.default : ns; | 
					
						
							|  |  |  | 			})(); | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @returns {(moduleInfo: ModuleInfo, context: RequireContext) => EXPECTED_ANY} json runner | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	createJSONRunner() { | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 		return (moduleInfo) => JSON.parse(moduleInfo.content); | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @returns {(moduleInfo: ModuleInfo, context: RequireContext) => EXPECTED_ANY} raw runner | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	createRawRunner() { | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 		return (moduleInfo) => moduleInfo.content; | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @returns {EXPECTED_ANY} env | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 	setupEnv() { | 
					
						
							|  |  |  | 		if (this.jsDom()) { | 
					
						
							|  |  |  | 			const outputDirectory = this.outputDirectory; | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 			const FakeDocument = require("../helpers/FakeDocument"); | 
					
						
							|  |  |  | 			const createFakeWorker = require("../helpers/createFakeWorker"); | 
					
						
							|  |  |  | 			const EventSource = require("../helpers/EventSourceForNode"); | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 			const document = new FakeDocument(outputDirectory); | 
					
						
							|  |  |  | 			if (this.testConfig.evaluateScriptOnAttached) { | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 				document.onScript = (src) => { | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 					this.require(outputDirectory, urlToRelativePath(src)); | 
					
						
							|  |  |  | 				}; | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 			const fetch = async (url) => { | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 				try { | 
					
						
							|  |  |  | 					const buffer = await new Promise((resolve, reject) => { | 
					
						
							|  |  |  | 						fs.readFile(urlToPath(url, this.outputDirectory), (err, b) => | 
					
						
							|  |  |  | 							err ? reject(err) : resolve(b) | 
					
						
							|  |  |  | 						); | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 					return { | 
					
						
							|  |  |  | 						status: 200, | 
					
						
							|  |  |  | 						ok: true, | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 						json: async () => JSON.parse(buffer.toString("utf8")) | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 					}; | 
					
						
							|  |  |  | 				} catch (err) { | 
					
						
							|  |  |  | 					if (err.code === "ENOENT") { | 
					
						
							|  |  |  | 						return { | 
					
						
							|  |  |  | 							status: 404, | 
					
						
							|  |  |  | 							ok: false | 
					
						
							|  |  |  | 						}; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					throw err; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}; | 
					
						
							| 
									
										
										
										
											2025-07-08 20:52:43 +08:00
										 |  |  | 			const env = { | 
					
						
							| 
									
										
										
										
											2025-06-20 22:08:04 +08:00
										 |  |  | 				setTimeout, | 
					
						
							|  |  |  | 				document, | 
					
						
							|  |  |  | 				location: { | 
					
						
							|  |  |  | 					href: "https://test.cases/path/index.html", | 
					
						
							|  |  |  | 					origin: "https://test.cases", | 
					
						
							|  |  |  | 					toString() { | 
					
						
							|  |  |  | 						return "https://test.cases/path/index.html"; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				getComputedStyle: document.getComputedStyle.bind(document), | 
					
						
							|  |  |  | 				Worker: createFakeWorker({ | 
					
						
							|  |  |  | 					outputDirectory | 
					
						
							|  |  |  | 				}), | 
					
						
							|  |  |  | 				URL, | 
					
						
							|  |  |  | 				EventSource, | 
					
						
							|  |  |  | 				clearTimeout, | 
					
						
							|  |  |  | 				fetch | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 			if (typeof Blob !== "undefined") { | 
					
						
							|  |  |  | 				// node.js >= 18
 | 
					
						
							|  |  |  | 				env.Blob = Blob; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return env; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return {}; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports.TestRunner = TestRunner; |