UNPKG

@exodus/test

Version:
246 lines (210 loc) 9.39 kB
// Not using ./engine.js yet, might pass / embed already loaded config instead import assert from 'node:assert/strict' import { specialEnvironments } from './jest.environment.js' const skipPreset = new Set(['ts-jest']) const EXTS = `.?([cm])[jt]s?(x)` // we differ from jest, allowing [cm] before everything const needPreset = ({ preset } = {}) => preset && !skipPreset.has(preset) const normalizeJestConfig = (config) => ({ testMatch: [`**/__tests__/**/*${EXTS}`, `**/?(*.)+(spec|test)${EXTS}`], testEnvironment: 'node', testTimeout: 5000, testPathIgnorePatterns: [], passWithNoTests: false, snapshotSerializers: [], injectGlobals: true, maxConcurrency: 10, // jest has 5, seems too low? maxWorkers: undefined, // jest has 50%, also too low, we default to CPUs - 1 ...config, snapshotFormat: { // jest-snapshot defaults indent: 2, escapeRegex: true, printFunctionName: false, // defaults from https://jestjs.io/docs/configuration#snapshotformat-object escapeString: false, printBasicPrototype: false, // user config ...config?.snapshotFormat, // not overridable per doc compareKeys: undefined, }, }) function verifyJestConfig(c) { assert(!configUsed, 'Can not apply new config as the current one was already used') if (!Object.hasOwn(specialEnvironments, c.testEnvironment)) { assert.equal(c.testEnvironment, 'node', 'Only "node" testEnvironment is supported') } const environmentOptions = c.testEnvironmentOptions || {} assert.deepEqual(environmentOptions, {}, 'Jest config.testEnvironmentOptions is not supported') assert(!c.automock, 'Automocking all modules is not currently supported (config.automock)') // config.moduleNameMapper is ignored if (c.moduleDirectories) { const valid = ['node_modules'] assert.deepEqual(c.moduleDirectories, valid, 'Jest config.moduleDirectories is not supported') } assert(!c.preset || skipPreset.has(c.preset.split('/')[0]), 'Jest config.preset is not supported') // TODO const TODO = ['randomize', 'projects', 'roots'] TODO.push('resolver', 'unmockedModulePathPatterns', 'watchPathIgnorePatterns', 'snapshotResolver') for (const key of TODO) assert.equal(c[key], undefined, `Jest config.${key} is not supported yet`) } let config = normalizeJestConfig({}) let configUsed = false export const jestConfig = () => { configUsed = true return config } // Methods loadJestConfig() and installJestEnvironment() below are for --jest flag // Optimized out in 'bundle' env async function loadConfigParts(rawConfig) { const presetExtension = /\.([cm]?js|json)$/u const suffixes = ['/jest-preset.json', '/jest-preset.js', '/jest-preset.cjs', '/jest-preset.mjs'] const resolveGlobalSetup = (config, req) => { if (config.globalSetup) config.globalSetup = req.resolve(config.globalSetup) // eslint-disable-line @exodus/mutable/no-param-reassign-prop-only if (config.globalTeardown) config.globalTeardown = req.resolve(config.globalTeardown) // eslint-disable-line @exodus/mutable/no-param-reassign-prop-only } assert(rawConfig.rootDir) const { resolve } = await import('node:path') const { createRequire } = await import('node:module') const { pathToFileURL } = await import('node:url') let requireConfig = createRequire(resolve(rawConfig.rootDir, 'package.json')) resolveGlobalSetup(rawConfig, requireConfig) while (needPreset(rawConfig)) { let baseConfig const attemptLoad = async (file) => { try { const resolved = requireConfig.resolve(file) // FIXME: fix linter to allow this // const meta = resolved.toLowerCase().endsWith('.json') ? { with: { type: 'json' } } : undefined // const presetModule = await import(pathToFileURL(resolved), meta) const presetModule = await import(pathToFileURL(resolved)) requireConfig = createRequire(resolved) baseConfig = presetModule.default } catch {} } // Even if it is relative, it could be a path to module for (const suffix of suffixes) { if (!baseConfig) await attemptLoad(`${rawConfig.preset}${suffix}`) } // If it's a path to a file if (!baseConfig && rawConfig.preset[0] === '.' && presetExtension.test(rawConfig.preset)) { const { statSync } = await import('node:fs') if (statSync(rawConfig.preset).isFile()) await attemptLoad(rawConfig.preset) } assert(baseConfig, `Could not load preset: ${rawConfig.preset} `) resolveGlobalSetup(baseConfig, requireConfig) rawConfig = { ...baseConfig, ...rawConfig, preset: baseConfig.preset, setupFiles: [ ...(baseConfig.setupFiles || []).map((file) => requireConfig.resolve(file)), ...(rawConfig.setupFiles || []), ], setupFilesAfterEnv: [ ...(baseConfig.setupFilesAfterEnv || []).map((file) => requireConfig.resolve(file)), ...(rawConfig.setupFilesAfterEnv || []), ], } } return rawConfig } export async function loadJestConfig(...args) { let rawConfig if (process.env.EXODUS_TEST_JEST_CONFIG === undefined) { const { readJestConfig } = await import('./jest.config.fs.js') rawConfig = await readJestConfig(...args) } else { rawConfig = JSON.parse(process.env.EXODUS_TEST_JEST_CONFIG) } const cleanFile = (file) => file.replace(/^<rootDir>\//g, './') // require is already relative to rootDir if (needPreset(rawConfig) || rawConfig?.globalSetup || rawConfig?.globalTeardown) { rawConfig.preset = cleanFile(rawConfig.preset) // relative to root dir only at top level, presets shouldn't use <rootDir> if (process.env.EXODUS_TEST_ENVIRONMENT === 'bundle') { throw new Error('jest preset and globalSetup/Teardown not yet supported in bundles') } else { rawConfig = await loadConfigParts(rawConfig) } } config = normalizeJestConfig(rawConfig) verifyJestConfig(config) config.setupFiles = config.setupFiles?.map((f) => cleanFile(f)) config.setupFilesAfterEnv = config.setupFilesAfterEnv?.map((f) => cleanFile(f)) return config } // Optimized out in 'bundle' env async function makeDynamicImport(rootDir) { const { resolve } = await import('node:path') const { createRequire } = await import('node:module') const { pathToFileURL } = await import('node:url') const require = createRequire(resolve(rootDir, 'package.json')) return (path) => import(pathToFileURL(require.resolve(path))) // does not need json imports } export async function installJestEnvironment(jestGlobals) { const engine = await import('./engine.js') const { beforeEach } = engine const { jest } = jestGlobals const c = config Error.stackTraceLimit = 100 if (c.injectGlobals) Object.assign(globalThis, jestGlobals) if (c.globals) Object.assign(globalThis, config.globals) if (c.fakeTimers?.enableGlobally) jest.useFakeTimers() if (c.clearMocks) beforeEach(() => jest.clearAllMocks()) if (c.resetMocks) beforeEach(() => jest.resetAllMocks()) if (c.restoreMocks) beforeEach(() => jest.restoreAllMocks()) if (c.resetModules) beforeEach(() => jest.resetModules()) let dynamicImport if (process.env.EXODUS_TEST_ENVIRONMENT === 'bundle') { const preloaded = new Map(EXODUS_TEST_PRELOADED) // eslint-disable-line no-undef dynamicImport = async (name) => { if (preloaded.has(name)) return preloaded.get(name)() assert.fail('Requiring non-bundled plugins from bundle is unsupported') } } else if (config.rootDir) { dynamicImport = await makeDynamicImport(config.rootDir) } else { dynamicImport = async () => assert.fail('Unreachable: importing plugins without a rootDir') } const suffixes = haste() if (suffixes.size === 0) { // No action needed } else if (process.env.EXODUS_TEST_ENVIRONMENT === 'bundle') { throw new Error('jest haste not yet supported in bundles') } else { const { createRequire, Module } = await import('node:module') const _require = Module.prototype.require Module.prototype.require = function (...args) { if (args[0] && this.filename) { const pathRequire = createRequire(this.filename) for (const suffix of suffixes) { try { args[0] = pathRequire.resolve(`${args[0]}.${suffix}`) // eslint-disable-line @exodus/mutable/no-param-reassign-prop-only break } catch {} } } return _require.apply(this, args) } } for (const file of c.setupFiles || []) await dynamicImport(file) if (Object.hasOwn(specialEnvironments, c.testEnvironment)) { const { setup } = specialEnvironments[c.testEnvironment] await setup(dynamicImport, engine, jestGlobals, c.testEnvironmentOptions) } for (const file of c.setupFilesAfterEnv || []) await dynamicImport(file) // @jest/globals import auto-mocking is disabled until https://github.com/nodejs/node/issues/53807 is resolved /* import { mock } from 'node:test' try { const resolved = require.resolve('@jest/globals') if (mock.module) mock.module(resolved, { defaultExport: globals, namedExports: globals }) } catch {} */ } export function haste() { configUsed = true const suffixes = new Set() if (config.haste?.defaultPlatform) suffixes.add(config.haste.defaultPlatform) if (config.haste?.platforms) for (const suffix of config.haste.platforms) suffixes.add(suffix) return suffixes }