From 08ef1058175ba0cf3ad6749442fe88ac45524845 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Wed, 5 Apr 2023 10:54:20 +0300 Subject: [PATCH] fix(jest-config): handle frozen config object (#14054) * fix(jest-config): handle frozen config object * change log entry --- CHANGELOG.md | 1 + .../readConfigFileAndSetRootDir.test.ts | 169 ++++++++++++++++++ .../src/readConfigFileAndSetRootDir.ts | 13 +- 3 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 packages/jest-config/src/__tests__/readConfigFileAndSetRootDir.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index feea55330ed2..2ad8312f260d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixes +- `[jest-config]` Handle frozen config object ([#14054](https://github.com/facebook/jest/pull/14054)) - `[jest-environment-jsdom, jest-environment-node]` Fix assignment of `customExportConditions` via `testEnvironmentOptions` when custom env subclass defines a default value ([#13989](https://github.com/facebook/jest/pull/13989)) - `[jest-matcher-utils]` Fix copying value of inherited getters ([#14007](https://github.com/facebook/jest/pull/14007)) - `[jest-snapshot]` Fix a potential bug when not using prettier and improve performance ([#14036](https://github.com/facebook/jest/pull/14036)) diff --git a/packages/jest-config/src/__tests__/readConfigFileAndSetRootDir.test.ts b/packages/jest-config/src/__tests__/readConfigFileAndSetRootDir.test.ts new file mode 100644 index 000000000000..de5cd2878fb4 --- /dev/null +++ b/packages/jest-config/src/__tests__/readConfigFileAndSetRootDir.test.ts @@ -0,0 +1,169 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path = require('path'); +import * as fs from 'graceful-fs'; +import {requireOrImportModule} from 'jest-util'; +import readConfigFileAndSetRootDir from '../readConfigFileAndSetRootDir'; + +jest.mock('graceful-fs').mock('jest-util'); + +describe('readConfigFileAndSetRootDir', () => { + describe('JavaScript file', () => { + test('reads config and sets `rootDir`', async () => { + jest.mocked(requireOrImportModule).mockResolvedValueOnce({notify: true}); + + const rootDir = path.resolve('some', 'path', 'to'); + const config = await readConfigFileAndSetRootDir( + path.join(rootDir, 'jest.config.js'), + ); + + expect(config).toEqual({notify: true, rootDir}); + }); + + test('handles exported function', async () => { + jest + .mocked(requireOrImportModule) + .mockResolvedValueOnce(() => ({bail: 1})); + + const rootDir = path.resolve('some', 'path', 'to'); + const config = await readConfigFileAndSetRootDir( + path.join(rootDir, 'jest.config.js'), + ); + + expect(config).toEqual({bail: 1, rootDir}); + }); + + test('handles exported async function', async () => { + jest + .mocked(requireOrImportModule) + .mockResolvedValueOnce(async () => ({testTimeout: 10000})); + + const rootDir = path.resolve('some', 'path', 'to'); + const config = await readConfigFileAndSetRootDir( + path.join(rootDir, 'jest.config.js'), + ); + + expect(config).toEqual({rootDir, testTimeout: 10000}); + }); + }); + + describe('JSON file', () => { + test('reads config and sets `rootDir`', async () => { + jest.mocked(fs.readFileSync).mockReturnValueOnce('{ "verbose": true }'); + + const rootDir = path.resolve('some', 'path', 'to'); + const config = await readConfigFileAndSetRootDir( + path.join(rootDir, 'jest.config.json'), + ); + + expect(config).toEqual({rootDir, verbose: true}); + }); + + test('supports comments in JSON', async () => { + jest + .mocked(fs.readFileSync) + .mockReturnValueOnce('{ // test comment\n "bail": true }'); + + const rootDir = path.resolve('some', 'path', 'to'); + const config = await readConfigFileAndSetRootDir( + path.join(rootDir, 'jest.config.json'), + ); + + expect(config).toEqual({bail: true, rootDir}); + }); + }); + + describe('package.json file', () => { + test('reads config from "jest" key and sets `rootDir`', async () => { + jest + .mocked(fs.readFileSync) + .mockReturnValueOnce('{ "jest": { "coverage": true } }'); + + const rootDir = path.resolve('some', 'path', 'to'); + const config = await readConfigFileAndSetRootDir( + path.join(rootDir, 'package.json'), + ); + + expect(config).toEqual({coverage: true, rootDir}); + }); + + test('sets rootDir if "jest" is absent', async () => { + jest.mocked(fs.readFileSync).mockReturnValueOnce('{ "name": "test" }'); + + const rootDir = path.resolve('some', 'path', 'to'); + const config = await readConfigFileAndSetRootDir( + path.join(rootDir, 'package.json'), + ); + + expect(config).toEqual({rootDir}); + }); + }); + + describe('sets `rootDir`', () => { + test('handles frozen config object', async () => { + jest + .mocked(requireOrImportModule) + .mockResolvedValueOnce(Object.freeze({preset: 'some-preset'})); + + const rootDir = path.resolve('some', 'path', 'to'); + const config = await readConfigFileAndSetRootDir( + path.join(rootDir, 'jest.config.js'), + ); + + expect(config).toEqual({preset: 'some-preset', rootDir}); + }); + + test('keeps the path if it is absolute', async () => { + const rootDir = path.resolve('some', 'path', 'to'); + jest.mocked(requireOrImportModule).mockResolvedValueOnce({ + rootDir, + testEnvironment: 'node', + }); + + const config = await readConfigFileAndSetRootDir( + path.join(path.resolve('other', 'path', 'to'), 'jest.config.js'), + ); + + expect(config).toEqual({rootDir, testEnvironment: 'node'}); + }); + + test('resolves the path relative to dirname of the config file', async () => { + jest.mocked(requireOrImportModule).mockResolvedValueOnce({ + restoreMocks: true, + rootDir: path.join('path', 'to'), + }); + + const config = await readConfigFileAndSetRootDir( + path.join(path.resolve('some'), 'jest.config.js'), + ); + + expect(config).toEqual({ + restoreMocks: true, + rootDir: path.resolve('some', 'path', 'to'), + }); + }); + + test('resolves relative path when the read config object if frozen', async () => { + jest.mocked(requireOrImportModule).mockResolvedValueOnce( + Object.freeze({ + resetModules: true, + rootDir: path.join('path', 'to'), + }), + ); + + const config = await readConfigFileAndSetRootDir( + path.join(path.resolve('some'), 'jest.config.js'), + ); + + expect(config).toEqual({ + resetModules: true, + rootDir: path.resolve('some', 'path', 'to'), + }); + }); + }); +}); diff --git a/packages/jest-config/src/readConfigFileAndSetRootDir.ts b/packages/jest-config/src/readConfigFileAndSetRootDir.ts index b48443f83455..fef76c2c7ab6 100644 --- a/packages/jest-config/src/readConfigFileAndSetRootDir.ts +++ b/packages/jest-config/src/readConfigFileAndSetRootDir.ts @@ -64,14 +64,17 @@ export default async function readConfigFileAndSetRootDir( // We don't touch it if it has an absolute path specified if (!path.isAbsolute(configObject.rootDir)) { // otherwise, we'll resolve it relative to the file's __dirname - configObject.rootDir = path.resolve( - path.dirname(configPath), - configObject.rootDir, - ); + configObject = { + ...configObject, + rootDir: path.resolve(path.dirname(configPath), configObject.rootDir), + }; } } else { // If rootDir is not there, we'll set it to this file's __dirname - configObject.rootDir = path.dirname(configPath); + configObject = { + ...configObject, + rootDir: path.dirname(configPath), + }; } return configObject;