UNPKG

@exodus/test

Version:
157 lines (132 loc) 5.5 kB
let locForNextTest const installLocationInNextTest = function (loc) { locForNextTest = loc } // WARNING // Do not refactor, do not wrap // This function has to be called unwrapped directly inside our test() impl let getCallerLocation // This is unoptimal // Ideally, an option for overriding file locations should be added to Node.js, // instead of relying on the call location of the original test() impl // That could be even hardened by a simple option of how many frames up to look // This whole logic is limited only to updating caller locations for reports // We don't do use exposed Node.js internas for anything else function createCallerLocationHook() { if (getCallerLocation) return { installLocationInNextTest, getCallerLocation } getCallerLocation = () => {} if (process.env.EXODUS_TEST_ENGINE === 'node:test') { try { const { Test } = require('node:internal/test_runner/test') const { fileURLToPath } = require('node:url') const mayBeUrlToPath = (str) => (str.startsWith('file://') ? fileURLToPath(str) : str) const locStorage = new Map() Object.defineProperty(Test.prototype, 'loc', { get() { return locStorage.get(this) }, set(val) { locStorage.set(this, val) if (locForNextTest) { const loc = locForNextTest locForNextTest = undefined locStorage.set(this, { line: loc[0], column: loc[1], file: mayBeUrlToPath(loc[2]) }) } }, }) // We can replicate getCallerLocation() with public V8 Error CallSite API, but we won't // need it anyway if we don't have a path for hook into internal Test implementation const { internalBinding } = require('node:internal/test/binding') getCallerLocation = internalBinding('util').getCallerLocation } catch {} } return { installLocationInNextTest, getCallerLocation } } // Optimized out in 'bundle' env function getTestNamePathFromNode(t) { // We are on Node.js < 22.3.0 where even t.fullName doesn't exist yet, polyfill const namePath = Symbol('namePath') const getNamePath = Symbol('getNamePath') try { if (t[namePath]) return t[namePath] // Sigh, ok, whatever const { Test } = require('node:internal/test_runner/test') const usePathName = Symbol('usePathName') const restoreName = Symbol('restoreName') Test.prototype[getNamePath] = function () { if (this === this.root) return [] return [...(this.parent?.[getNamePath]() || []), this.name] } const diagnostic = Test.prototype.diagnostic Test.prototype.diagnostic = function (...args) { if (args[0] === usePathName) { this[restoreName] = this.name this.name = this[getNamePath]() return } if (args[0] === restoreName) { this.name = this[restoreName] delete this[restoreName] return } return diagnostic.apply(this, args) } const TestContextProto = Object.getPrototypeOf(t) Object.defineProperty(TestContextProto, namePath, { get() { this.diagnostic(usePathName) const result = this.name this.diagnostic(restoreName) return result }, }) return t[namePath] } catch {} } // Easy on Node.js >= 22.3.0 || ^20.16.0, but we polyfill for the rest function getTestNamePath(t) { // No implementation in Node.js yet, will have to PR if (t.fullName) return t.fullName.split(' > ') if (process.env.EXODUS_TEST_ENGINE === 'node:test') { const names = getTestNamePathFromNode(t) if (names) return names } return [t.name] // last resort } const execArgv = process.env.EXODUS_TEST_EXECARGV ? JSON.parse(process.env.EXODUS_TEST_EXECARGV) : process.execArgv const esbuildLoaders = ['node_modules/tsx/dist/loader.mjs', '/loader/esbuild.js'] const insideEsbuildStatic = execArgv.some((x) => esbuildLoaders.some((y) => x.endsWith(y))) const insideEsbuild = () => insideEsbuildStatic || globalThis.EXODUS_TEST_INSIDE_ESBUILD function makeEsbuildMockable() { if (!insideEsbuild()) return // Hook into tsx/esbuild transpiled module conversion magic to make loaded modules mockable in runtime // We want all modules to be .configurable = true, so we can override them const defineProperty = Object.defineProperty const obj = Object.create(null) Object.defineProperty = (target, name, options) => { if (options.get && !options.configurable && name !== '__esModule') { if (target.__esModule) { // eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only options.configurable = true } else { const stackTraceLimit = Error.stackTraceLimit Error.stackTraceLimit = 2 Error.captureStackTrace(obj, Object.defineProperty) Error.stackTraceLimit = stackTraceLimit // This is for speed, we don't want to work with text const prepareStackTrace = Error.prepareStackTrace Error.prepareStackTrace = (e, callsites) => callsites.map((site) => site.getFunctionName()) const st = obj.stack Error.prepareStackTrace = prepareStackTrace if (st[0] === '__export' && st[1] === null) { // eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only options.configurable = true } } } return defineProperty(target, name, options) } } module.exports = { createCallerLocationHook, getTestNamePath, insideEsbuild, makeEsbuildMockable }