From b0d71226b781f70df72f7761e84aaeb6e39fdbd8 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Wed, 30 Jan 2019 01:26:32 +0100 Subject: [PATCH] fix: load transformers before installing require hooks (#7752) --- CHANGELOG.md | 1 + e2e/__tests__/globalSetup.test.js | 12 +++++++++ .../__tests__/test.js | 25 +++++++++++++++++ e2e/global-setup-custom-transform/index.js | 3 +++ .../package.json | 9 +++++++ e2e/global-setup-custom-transform/setup.js | 22 +++++++++++++++ .../transformer.js | 15 +++++++++++ packages/jest-cli/src/runGlobalHook.js | 4 +++ .../jest-runtime/src/ScriptTransformer.js | 6 +++++ .../src/__tests__/script_transformer.test.js | 27 +++++++++++++++---- 10 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 e2e/global-setup-custom-transform/__tests__/test.js create mode 100644 e2e/global-setup-custom-transform/index.js create mode 100644 e2e/global-setup-custom-transform/package.json create mode 100644 e2e/global-setup-custom-transform/setup.js create mode 100644 e2e/global-setup-custom-transform/transformer.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 99d277321399..bbf5d5c30136 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - `[jest-config]` Do not use a uuid as `name` since that breaks caching ([#7746](https://github.com/facebook/jest/pull/7746)) - `[jest-config]` Make sure `normalize` can consume `Defaults` without warnings ([#7742](https://github.com/facebook/jest/pull/7742)) - `[jest-config]` Allow `moduleFileExtensions` without 'js' for custom runners ([#7751](https://github.com/facebook/jest/pull/7751)) +- `[jest-cli]` Load transformers before installing require hooks ([#7752](https://github.com/facebook/jest/pull/7752) ### Chore & Maintenance diff --git a/e2e/__tests__/globalSetup.test.js b/e2e/__tests__/globalSetup.test.js index 69ccad35636b..0c5d82198932 100644 --- a/e2e/__tests__/globalSetup.test.js +++ b/e2e/__tests__/globalSetup.test.js @@ -16,16 +16,22 @@ import {cleanup} from '../Utils'; const DIR = path.join(os.tmpdir(), 'jest-global-setup'); const project1DIR = path.join(os.tmpdir(), 'jest-global-setup-project-1'); const project2DIR = path.join(os.tmpdir(), 'jest-global-setup-project-2'); +const customTransformDIR = path.join( + os.tmpdir(), + 'jest-global-setup-custom-transform', +); beforeEach(() => { cleanup(DIR); cleanup(project1DIR); cleanup(project2DIR); + cleanup(customTransformDIR); }); afterAll(() => { cleanup(DIR); cleanup(project1DIR); cleanup(project2DIR); + cleanup(customTransformDIR); }); test('globalSetup is triggered once before all test suites', () => { @@ -155,3 +161,9 @@ test('globalSetup throws with named export', () => { `TypeError: globalSetup file must export a function at ${setupPath}`, ); }); + +test('should not transpile the transformer', () => { + const {status} = runJest('global-setup-custom-transform', [`--no-cache`]); + + expect(status).toBe(0); +}); diff --git a/e2e/global-setup-custom-transform/__tests__/test.js b/e2e/global-setup-custom-transform/__tests__/test.js new file mode 100644 index 000000000000..a25f56109c69 --- /dev/null +++ b/e2e/global-setup-custom-transform/__tests__/test.js @@ -0,0 +1,25 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const greeting = require('../'); + +const DIR = path.join(os.tmpdir(), 'jest-global-setup-custom-transform'); + +test('should exist setup file', () => { + const files = fs.readdirSync(DIR); + expect(files).toHaveLength(1); + const setup = fs.readFileSync(path.join(DIR, files[0]), 'utf8'); + expect(setup).toBe('setup'); +}); + +test('should transform imported file', () => { + expect(greeting).toEqual('hello, world!'); +}); diff --git a/e2e/global-setup-custom-transform/index.js b/e2e/global-setup-custom-transform/index.js new file mode 100644 index 000000000000..f45215d7460d --- /dev/null +++ b/e2e/global-setup-custom-transform/index.js @@ -0,0 +1,3 @@ +// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + +module.exports = 'hello!'; diff --git a/e2e/global-setup-custom-transform/package.json b/e2e/global-setup-custom-transform/package.json new file mode 100644 index 000000000000..508172a45c33 --- /dev/null +++ b/e2e/global-setup-custom-transform/package.json @@ -0,0 +1,9 @@ +{ + "jest": { + "testEnvironment": "node", + "globalSetup": "/setup.js", + "transform": { + "^.+\\.js$": "/transformer.js" + } + } +} diff --git a/e2e/global-setup-custom-transform/setup.js b/e2e/global-setup-custom-transform/setup.js new file mode 100644 index 000000000000..975d615e401b --- /dev/null +++ b/e2e/global-setup-custom-transform/setup.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +const crypto = require('crypto'); +const fs = require('fs'); +const {createDirectory} = require('jest-util'); +const os = require('os'); +const path = require('path'); + +const DIR = path.join(os.tmpdir(), 'jest-global-setup-custom-transform'); + +module.exports = function() { + return new Promise(resolve => { + createDirectory(DIR); + const fileId = crypto.randomBytes(20).toString('hex'); + fs.writeFileSync(path.join(DIR, fileId), 'setup'); + resolve(); + }); +}; diff --git a/e2e/global-setup-custom-transform/transformer.js b/e2e/global-setup-custom-transform/transformer.js new file mode 100644 index 000000000000..ba92f92952f0 --- /dev/null +++ b/e2e/global-setup-custom-transform/transformer.js @@ -0,0 +1,15 @@ +// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + +'use strict'; + +const fileToTransform = require.resolve('./index.js'); + +module.exports = { + process(src, filename) { + if (filename === fileToTransform) { + return src.replace('hello', 'hello, world'); + } + + return src; + }, +}; diff --git a/packages/jest-cli/src/runGlobalHook.js b/packages/jest-cli/src/runGlobalHook.js index ca4448cfd623..855d21df57e3 100644 --- a/packages/jest-cli/src/runGlobalHook.js +++ b/packages/jest-cli/src/runGlobalHook.js @@ -54,6 +54,10 @@ export default ({ const transformer = new Runtime.ScriptTransformer(projectConfig); + // Load the transformer to avoid a cycle where we need to load a + // transformer in order to transform it in the require hooks + transformer.preloadTransformer(modulePath); + const revertHook = addHook( (code, filename) => transformer.transformSource(filename, code, false).code || code, diff --git a/packages/jest-runtime/src/ScriptTransformer.js b/packages/jest-runtime/src/ScriptTransformer.js index aef3d85022b9..92d0cb46714b 100644 --- a/packages/jest-runtime/src/ScriptTransformer.js +++ b/packages/jest-runtime/src/ScriptTransformer.js @@ -204,6 +204,12 @@ export default class ScriptTransformer { } } + // We don't want to expose transformers to the outside - this function is just + // to warm up `this._transformCache` + preloadTransformer(filepath: Path): void { + this._getTransformer(filepath); + } + transformSource(filepath: Path, content: string, instrument: boolean) { const filename = this._getRealPath(filepath); const transform = this._getTransformer(filename); diff --git a/packages/jest-runtime/src/__tests__/script_transformer.test.js b/packages/jest-runtime/src/__tests__/script_transformer.test.js index 63dcf0f420fb..7058e0f22f31 100644 --- a/packages/jest-runtime/src/__tests__/script_transformer.test.js +++ b/packages/jest-runtime/src/__tests__/script_transformer.test.js @@ -210,7 +210,7 @@ describe('ScriptTransformer', () => { expect(vm.Script.mock.calls[0][0]).toMatchSnapshot(); // no-cache case - expect(fs.readFileSync.mock.calls.length).toBe(1); + expect(fs.readFileSync).toHaveBeenCalledTimes(1); expect(fs.readFileSync).toBeCalledWith('/fruits/banana.js', 'utf8'); // in-memory cache @@ -258,7 +258,7 @@ describe('ScriptTransformer', () => { expect(vm.Script.mock.calls[0][0]).toContain(fsSourceCode); // Native files should never be transformed. - expect(shouldInstrument.mock.calls.length).toBe(0); + expect(shouldInstrument).toHaveBeenCalledTimes(0); }); it( @@ -512,7 +512,7 @@ describe('ScriptTransformer', () => { scriptTransformer = new ScriptTransformer(transformConfig); scriptTransformer.transform('/fruits/banana.js', {}); - expect(fs.readFileSync.mock.calls.length).toBe(2); + expect(fs.readFileSync).toHaveBeenCalledTimes(2); expect(fs.readFileSync).toBeCalledWith('/fruits/banana.js', 'utf8'); expect(fs.readFileSync).toBeCalledWith(cachePath, 'utf8'); expect(writeFileAtomic.sync).not.toBeCalled(); @@ -525,7 +525,7 @@ describe('ScriptTransformer', () => { scriptTransformer = new ScriptTransformer(transformConfig); scriptTransformer.transform('/fruits/banana.js', {}); - expect(fs.readFileSync.mock.calls.length).toBe(1); + expect(fs.readFileSync).toHaveBeenCalledTimes(1); expect(fs.readFileSync).toBeCalledWith('/fruits/banana.js', 'utf8'); expect(fs.readFileSync).not.toBeCalledWith(cachePath, 'utf8'); expect(writeFileAtomic.sync).toBeCalled(); @@ -546,7 +546,24 @@ describe('ScriptTransformer', () => { anotherScriptTransformer.transform('/fruits/banana.js', {}); - expect(fs.readFileSync.mock.calls.length).toBe(2); + expect(fs.readFileSync).toHaveBeenCalledTimes(2); expect(fs.readFileSync).toBeCalledWith('/fruits/banana.js', 'utf8'); }); + + it('preload transformer when using `preloadTransformer`', () => { + const scriptTransformer = new ScriptTransformer({ + ...config, + transform: [['^.+\\.js$', 'test_preprocessor']], + }); + + expect(Array.from(scriptTransformer._transformCache.entries())).toEqual([]); + + expect( + scriptTransformer.preloadTransformer('/fruits/banana.js'), + ).toBeUndefined(); + + expect(Array.from(scriptTransformer._transformCache.entries())).toEqual([ + ['test_preprocessor', expect.any(Object)], + ]); + }); });