From 18a3335f4aadee4cc806ee275904934b7340ebec Mon Sep 17 00:00:00 2001 From: Ahn Date: Mon, 19 Apr 2021 22:48:08 +0200 Subject: [PATCH] feat(jest-core): add support for `watchPlugins` written in ESM --- CHANGELOG.md | 1 + e2e/__tests__/watch-plugins.test.ts | 45 +++++++++++++++++++ e2e/watch-plugins/cjs/__tests__/index.js | 11 +++++ e2e/watch-plugins/cjs/my-watch-plugin.cjs | 22 +++++++++ e2e/watch-plugins/cjs/package.json | 5 +++ .../js-type-module/__tests__/index.js | 11 +++++ .../js-type-module/my-watch-plugin.js | 20 +++++++++ e2e/watch-plugins/js-type-module/package.json | 6 +++ e2e/watch-plugins/js/__tests__/index.js | 11 +++++ e2e/watch-plugins/js/my-watch-plugin.js | 20 +++++++++ e2e/watch-plugins/js/package.json | 5 +++ e2e/watch-plugins/mjs/__tests__/index.js | 11 +++++ e2e/watch-plugins/mjs/my-watch-plugin.mjs | 22 +++++++++ e2e/watch-plugins/mjs/package.json | 5 +++ .../jest-core/src/__tests__/watch.test.js | 28 ++++++------ packages/jest-core/src/watch.ts | 13 ++++-- packages/jest-watcher/src/types.ts | 1 + 17 files changed, 220 insertions(+), 17 deletions(-) create mode 100644 e2e/__tests__/watch-plugins.test.ts create mode 100644 e2e/watch-plugins/cjs/__tests__/index.js create mode 100644 e2e/watch-plugins/cjs/my-watch-plugin.cjs create mode 100644 e2e/watch-plugins/cjs/package.json create mode 100644 e2e/watch-plugins/js-type-module/__tests__/index.js create mode 100644 e2e/watch-plugins/js-type-module/my-watch-plugin.js create mode 100644 e2e/watch-plugins/js-type-module/package.json create mode 100644 e2e/watch-plugins/js/__tests__/index.js create mode 100644 e2e/watch-plugins/js/my-watch-plugin.js create mode 100644 e2e/watch-plugins/js/package.json create mode 100644 e2e/watch-plugins/mjs/__tests__/index.js create mode 100644 e2e/watch-plugins/mjs/my-watch-plugin.mjs create mode 100644 e2e/watch-plugins/mjs/package.json diff --git a/CHANGELOG.md b/CHANGELOG.md index d08a3c66ea11..cbaba47a64e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - `[jest-core]` more `TestSequencer` methods can be async ([#10980](https://github.com/facebook/jest/pull/10980)) - `[jest-core]` Add support for `testSequencer` written in ESM ([#11207](https://github.com/facebook/jest/pull/11207)) - `[jest-core]` Add support for `globalSetup` and `globalTeardown` written in ESM ([#11267](https://github.com/facebook/jest/pull/11267)) +- `[jest-core]` Add support for `watchPlugins` written in ESM ([#11315](https://github.com/facebook/jest/pull/11315)) - `[jest-environment-node]` Add AbortController to globals ([#11182](https://github.com/facebook/jest/pull/11182)) - `[@jest/fake-timers]` Update to `@sinonjs/fake-timers` to v7 ([#11198](https://github.com/facebook/jest/pull/11198)) - `[jest-haste-map]` Handle injected scm clocks ([#10966](https://github.com/facebook/jest/pull/10966)) diff --git a/e2e/__tests__/watch-plugins.test.ts b/e2e/__tests__/watch-plugins.test.ts new file mode 100644 index 000000000000..fcbdb1a61d7f --- /dev/null +++ b/e2e/__tests__/watch-plugins.test.ts @@ -0,0 +1,45 @@ +/** + * 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. + */ +import {onNodeVersions} from '@jest/test-utils'; +import {runContinuous} from '../runJest'; + +const testCompletedRE = /Ran all test suites./g; +const numberOfTestRuns = (stderr: string): number => { + const matches = stderr.match(testCompletedRE); + return matches ? matches.length : 0; +}; + +test.each(['js', 'cjs'])('supports %s watch plugins', async watchPluginDir => { + const testRun = runContinuous(`watch-plugins/${watchPluginDir}`, [ + '--watchAll', + '--no-watchman', + ]); + + await testRun.waitUntil(({stderr}) => numberOfTestRuns(stderr) === 1); + + expect(testRun.getCurrentOutput().stdout.trim()).toBe('getUsageInfo'); + + await testRun.end(); +}); + +onNodeVersions('^12.17.0 || >=13.2.0', () => { + test.each(['mjs', 'js-type-module'])( + 'supports %s watch plugins', + async watchPluginDir => { + const testRun = runContinuous(`watch-plugins/${watchPluginDir}`, [ + '--watchAll', + '--no-watchman', + ]); + + await testRun.waitUntil(({stderr}) => numberOfTestRuns(stderr) === 1); + + expect(testRun.getCurrentOutput().stdout.trim()).toBe('getUsageInfo'); + + await testRun.end(); + }, + ); +}); diff --git a/e2e/watch-plugins/cjs/__tests__/index.js b/e2e/watch-plugins/cjs/__tests__/index.js new file mode 100644 index 000000000000..9864bede5030 --- /dev/null +++ b/e2e/watch-plugins/cjs/__tests__/index.js @@ -0,0 +1,11 @@ +/** + * 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'; + +test('load watch plugin cjs', () => { + expect(42).toEqual(42); +}); diff --git a/e2e/watch-plugins/cjs/my-watch-plugin.cjs b/e2e/watch-plugins/cjs/my-watch-plugin.cjs new file mode 100644 index 000000000000..6fa3d51d68d4 --- /dev/null +++ b/e2e/watch-plugins/cjs/my-watch-plugin.cjs @@ -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. + */ +class MyWatchPlugin { + // Add hooks to Jest lifecycle events + apply(jestHooks) { + } + + // Get the prompt information for interactive plugins + getUsageInfo(globalConfig) { + console.log('getUsageInfo'); + } + + // Executed when the key from `getUsageInfo` is input + run(globalConfig, updateConfigAndRun) { + } +} + +module.exports = MyWatchPlugin; diff --git a/e2e/watch-plugins/cjs/package.json b/e2e/watch-plugins/cjs/package.json new file mode 100644 index 000000000000..46ecf4693a8b --- /dev/null +++ b/e2e/watch-plugins/cjs/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "watchPlugins": ["./my-watch-plugin.cjs"] + } +} diff --git a/e2e/watch-plugins/js-type-module/__tests__/index.js b/e2e/watch-plugins/js-type-module/__tests__/index.js new file mode 100644 index 000000000000..b0a3eeff7083 --- /dev/null +++ b/e2e/watch-plugins/js-type-module/__tests__/index.js @@ -0,0 +1,11 @@ +/** + * 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'; + +test('load watch plugin js type module', () => { + expect(42).toEqual(42); +}); diff --git a/e2e/watch-plugins/js-type-module/my-watch-plugin.js b/e2e/watch-plugins/js-type-module/my-watch-plugin.js new file mode 100644 index 000000000000..f313583a7bb4 --- /dev/null +++ b/e2e/watch-plugins/js-type-module/my-watch-plugin.js @@ -0,0 +1,20 @@ +/** + * 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. + */ +class MyWatchPlugin { + // Add hooks to Jest lifecycle events + apply(jestHooks) {} + + // Get the prompt information for interactive plugins + getUsageInfo(globalConfig) { + console.log('getUsageInfo'); + } + + // Executed when the key from `getUsageInfo` is input + run(globalConfig, updateConfigAndRun) {} +} + +export default MyWatchPlugin; diff --git a/e2e/watch-plugins/js-type-module/package.json b/e2e/watch-plugins/js-type-module/package.json new file mode 100644 index 000000000000..394accc14218 --- /dev/null +++ b/e2e/watch-plugins/js-type-module/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "jest": { + "watchPlugins": ["./my-watch-plugin.js"] + } +} diff --git a/e2e/watch-plugins/js/__tests__/index.js b/e2e/watch-plugins/js/__tests__/index.js new file mode 100644 index 000000000000..5dcdcfb0182f --- /dev/null +++ b/e2e/watch-plugins/js/__tests__/index.js @@ -0,0 +1,11 @@ +/** + * 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'; + +test('load watch plugin js', () => { + expect(42).toEqual(42); +}); diff --git a/e2e/watch-plugins/js/my-watch-plugin.js b/e2e/watch-plugins/js/my-watch-plugin.js new file mode 100644 index 000000000000..b68701c9893c --- /dev/null +++ b/e2e/watch-plugins/js/my-watch-plugin.js @@ -0,0 +1,20 @@ +/** + * 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. + */ +class MyWatchPlugin { + // Add hooks to Jest lifecycle events + apply(jestHooks) {} + + // Get the prompt information for interactive plugins + getUsageInfo(globalConfig) { + console.log('getUsageInfo'); + } + + // Executed when the key from `getUsageInfo` is input + run(globalConfig, updateConfigAndRun) {} +} + +module.exports = MyWatchPlugin; diff --git a/e2e/watch-plugins/js/package.json b/e2e/watch-plugins/js/package.json new file mode 100644 index 000000000000..ac767498b92b --- /dev/null +++ b/e2e/watch-plugins/js/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "watchPlugins": ["./my-watch-plugin.js"] + } +} diff --git a/e2e/watch-plugins/mjs/__tests__/index.js b/e2e/watch-plugins/mjs/__tests__/index.js new file mode 100644 index 000000000000..105a44f3080a --- /dev/null +++ b/e2e/watch-plugins/mjs/__tests__/index.js @@ -0,0 +1,11 @@ +/** + * 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'; + +test('load watch plugin mjs', () => { + expect(42).toEqual(42); +}); diff --git a/e2e/watch-plugins/mjs/my-watch-plugin.mjs b/e2e/watch-plugins/mjs/my-watch-plugin.mjs new file mode 100644 index 000000000000..873aaed19d58 --- /dev/null +++ b/e2e/watch-plugins/mjs/my-watch-plugin.mjs @@ -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. + */ +class MyWatchPlugin { + // Add hooks to Jest lifecycle events + apply(jestHooks) { + } + + // Get the prompt information for interactive plugins + getUsageInfo(globalConfig) { + console.log('getUsageInfo'); + } + + // Executed when the key from `getUsageInfo` is input + run(globalConfig, updateConfigAndRun) { + } +} + +export default MyWatchPlugin; diff --git a/e2e/watch-plugins/mjs/package.json b/e2e/watch-plugins/mjs/package.json new file mode 100644 index 000000000000..8ff0caf139e3 --- /dev/null +++ b/e2e/watch-plugins/mjs/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "watchPlugins": ["./my-watch-plugin.mjs"] + } +} diff --git a/packages/jest-core/src/__tests__/watch.test.js b/packages/jest-core/src/__tests__/watch.test.js index 12f487db52f6..dc46da7f1ec7 100644 --- a/packages/jest-core/src/__tests__/watch.test.js +++ b/packages/jest-core/src/__tests__/watch.test.js @@ -205,7 +205,7 @@ describe('Watch mode flows', () => { }); it('shows prompts for WatchPlugins in alphabetical order', async () => { - watch( + await watch( { ...globalConfig, rootDir: __dirname, @@ -377,7 +377,7 @@ describe('Watch mode flows', () => { ${'i'} | ${'UpdateSnapshotsInteractive'} `( 'forbids WatchPlugins overriding reserved internal plugins', - ({key, plugin}) => { + async ({key}) => { const run = jest.fn(() => Promise.resolve()); const pluginPath = `${__dirname}/__fixtures__/plugin_bad_override_${key}`; jest.doMock( @@ -397,7 +397,7 @@ describe('Watch mode flows', () => { {virtual: true}, ); - expect(() => { + await expect( watch( { ...globalConfig, @@ -408,8 +408,8 @@ describe('Watch mode flows', () => { pipe, hasteMapInstances, stdin, - ); - }).toThrowError( + ), + ).rejects.toThrowError( new RegExp( `Watch plugin OffendingWatchPlugin attempted to register key <${key}>,\\s+that is reserved internally for .+\\.\\s+Please change the configuration key for this plugin\\.`, 'm', @@ -426,7 +426,7 @@ describe('Watch mode flows', () => { ${'p'} | ${'TestPathPattern'} `( 'allows WatchPlugins to override non-reserved internal plugins', - ({key, plugin}) => { + ({key}) => { const run = jest.fn(() => Promise.resolve()); const pluginPath = `${__dirname}/__fixtures__/plugin_valid_override_${key}`; jest.doMock( @@ -460,7 +460,7 @@ describe('Watch mode flows', () => { }, ); - it('forbids third-party WatchPlugins overriding each other', () => { + it('forbids third-party WatchPlugins overriding each other', async () => { const pluginPaths = ['Foo', 'Bar'].map(ident => { const run = jest.fn(() => Promise.resolve()); const pluginPath = `${__dirname}/__fixtures__/plugin_bad_override_${ident.toLowerCase()}`; @@ -486,7 +486,7 @@ describe('Watch mode flows', () => { return pluginPath; }); - expect(() => { + await expect( watch( { ...globalConfig, @@ -497,8 +497,8 @@ describe('Watch mode flows', () => { pipe, hasteMapInstances, stdin, - ); - }).toThrowError( + ), + ).rejects.toThrowError( /Watch plugins OffendingFooThirdPartyWatchPlugin and OffendingBarThirdPartyWatchPlugin both attempted to register key \.\s+Please change the key configuration for one of the conflicting plugins to avoid overlap\./m, ); }); @@ -526,7 +526,7 @@ describe('Watch mode flows', () => { {virtual: true}, ); - watch( + await watch( { ...globalConfig, rootDir: __dirname, @@ -560,7 +560,7 @@ describe('Watch mode flows', () => { {virtual: true}, ); - watch( + await watch( { ...globalConfig, rootDir: __dirname, @@ -717,7 +717,7 @@ describe('Watch mode flows', () => { {virtual: true}, ); - watch( + await watch( { ...globalConfig, rootDir: __dirname, @@ -778,7 +778,7 @@ describe('Watch mode flows', () => { {virtual: true}, ); - watch( + await watch( { ...globalConfig, rootDir: __dirname, diff --git a/packages/jest-core/src/watch.ts b/packages/jest-core/src/watch.ts index 1beddd08e82c..7cc38c619c32 100644 --- a/packages/jest-core/src/watch.ts +++ b/packages/jest-core/src/watch.ts @@ -18,7 +18,12 @@ import type { import {formatExecError} from 'jest-message-util'; import Resolver from 'jest-resolve'; import type {Context} from 'jest-runtime'; -import {isInteractive, preRunMessage, specialChars} from 'jest-util'; +import { + isInteractive, + preRunMessage, + requireOrImportModule, + specialChars, +} from 'jest-util'; import {ValidationError} from 'jest-validate'; import { AllowedConfigOptions, @@ -85,7 +90,7 @@ const RESERVED_KEY_PLUGINS = new Map< [QuitPlugin, {forbiddenOverwriteMessage: 'quitting watch mode'}], ]); -export default function watch( +export default async function watch( initialGlobalConfig: Config.GlobalConfig, contexts: Array, outputStream: NodeJS.WriteStream, @@ -187,7 +192,9 @@ export default function watch( for (const pluginWithConfig of globalConfig.watchPlugins) { let plugin: WatchPlugin; try { - const ThirdPartyPlugin = require(pluginWithConfig.path); + const ThirdPartyPlugin = await requireOrImportModule( + pluginWithConfig.path, + ); plugin = new ThirdPartyPlugin({ config: pluginWithConfig.config, stdin, diff --git a/packages/jest-watcher/src/types.ts b/packages/jest-watcher/src/types.ts index 38fc070c1652..12b196dcd066 100644 --- a/packages/jest-watcher/src/types.ts +++ b/packages/jest-watcher/src/types.ts @@ -83,6 +83,7 @@ export interface WatchPlugin { } export interface WatchPluginClass { new (options: { + config: Record; stdin: NodeJS.ReadStream; stdout: NodeJS.WriteStream; }): WatchPlugin;