diff --git a/package.json b/package.json index 6cf726f..c2ded26 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "src/dark.cjs", "src/jest.js", "src/jest.config.js", + "src/jest.environment.js", "src/jest.fn.js", "src/jest.mock.js", "src/jest.snapshot.js", diff --git a/src/jest.config.js b/src/jest.config.js index 17159ba..fa9cef9 100644 --- a/src/jest.config.js +++ b/src/jest.config.js @@ -2,6 +2,7 @@ import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import path from 'node:path' import { createRequire } from 'node:module' +import { specialEnvironments } from './jest.environment.js' const files = process.argv.slice(1) const baseDir = files.length === 1 ? path.dirname(path.resolve(files[0])) : undefined @@ -69,32 +70,10 @@ const normalizeJestConfig = (config) => ({ }, }) -const specialEnvs = { - __proto__: null, - - 'setup-polly-jest/jest-environment-node': (require) => { - const { Polly } = require('@pollyjs/core') - const { JestPollyGlobals } = require('setup-polly-jest/lib/common') - const pollyGlobals = new JestPollyGlobals(globalThis) - pollyGlobals.isJestPollyEnvironment = true - - beforeEach((t) => { - if (!pollyGlobals.isPollyActive) return - pollyGlobals.pollyContext.polly = new Polly(t.fullName, pollyGlobals.pollyContext.options) - }) - - afterEach(async () => { - if (!pollyGlobals.pollyContext.polly) return - await pollyGlobals.pollyContext.polly.stop() - pollyGlobals.pollyContext.polly = null - }) - }, -} - function verifyJestConfig(c) { assert(!configUsed, 'Can not apply new config as the current one was already used') - if (!Object.hasOwn(specialEnvs, c.testEnvironment)) { + if (!Object.hasOwn(specialEnvironments, c.testEnvironment)) { assert.equal(c.testEnvironment, 'node', 'Only "node" testEnvironment is supported') } @@ -147,7 +126,10 @@ export async function installJestEnvironment(jestGlobals) { const require = createRequire(config.rootDir) - if (Object.hasOwn(specialEnvs, c.testEnvironment)) specialEnvs[c.testEnvironment](require) + if (Object.hasOwn(specialEnvironments, c.testEnvironment)) { + specialEnvironments[c.testEnvironment](require, jestGlobals, c.testEnvironmentOptions) + } + for (const file of c.setupFiles || []) require(file) for (const file of c.setupFilesAfterEnv || []) require(file) diff --git a/src/jest.environment.js b/src/jest.environment.js new file mode 100644 index 0000000..34ec92b --- /dev/null +++ b/src/jest.environment.js @@ -0,0 +1,93 @@ +// Shoult not import src/ stuff here, as this goes into runner too (to check config) + +function getTestNamePath(t, { require }) { + // No implementation in Node.js yet, will have to PR + if (t.fullName) return t.fullName.split(' > ') + + // We are on Node.js < 22.3.0 where even t.fullName doesn't exist yet, polyfill + const namePath = Symbol('namePath') + try { + if (t[namePath]) return t[namePath] + + // Sigh, ok, whatever + + const { Test } = require('node:internal/test_runner/test') + const TestContextProto = Object.getPrototypeOf(t) + 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) + } + + Object.defineProperty(TestContextProto, namePath, { + get() { + this.diagnostic(usePathName) + const result = this.name + this.diagnostic(restoreName) + return result + }, + }) + + return t[namePath] + } catch {} + + return [t.name] // last resort +} + +export const specialEnvironments = { + __proto__: null, + + // Reproduces setup-polly-jest/jest-environment-node ad hacks into 'setup-polly-jest'.pollyJest + 'setup-polly-jest/jest-environment-node': (require, jestGlobals) => { + const { Polly } = require('@pollyjs/core') + const pollyJest = require('setup-polly-jest') + const { JestPollyGlobals, createPollyContextAccessor } = require('setup-polly-jest/lib/common') + const pollyGlobals = new JestPollyGlobals(globalThis) + pollyGlobals.isJestPollyEnvironment = true + pollyJest.setupPolly = (options) => { + if (!pollyGlobals.isJestPollyEnvironment) return + + jestGlobals.beforeAll(() => { + pollyGlobals.isPollyActive = true + pollyGlobals.pollyContext.options = options + }) + + jestGlobals.afterAll(() => { + pollyGlobals.isPollyActive = false + pollyGlobals.pollyContext.options = null + }) + + return createPollyContextAccessor(pollyGlobals) + } + + jestGlobals.beforeEach((t) => { + if (!pollyGlobals.isPollyActive) return + const name = getTestNamePath(t, { require }).join('/') + pollyGlobals.pollyContext.polly = new Polly(name, pollyGlobals.pollyContext.options) + }) + + jestGlobals.afterEach(async () => { + if (!pollyGlobals.pollyContext.polly) return + await pollyGlobals.pollyContext.polly.stop() + pollyGlobals.pollyContext.polly = null + }) + }, +}