Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jest-core): add support for watchPlugins written in ESM #11315

Merged
merged 1 commit into from Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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))
Expand Down
45 changes: 45 additions & 0 deletions 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();
},
);
});
11 changes: 11 additions & 0 deletions 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);
});
22 changes: 22 additions & 0 deletions 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;
5 changes: 5 additions & 0 deletions e2e/watch-plugins/cjs/package.json
@@ -0,0 +1,5 @@
{
"jest": {
"watchPlugins": ["./my-watch-plugin.cjs"]
}
}
11 changes: 11 additions & 0 deletions 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);
});
20 changes: 20 additions & 0 deletions 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;
6 changes: 6 additions & 0 deletions e2e/watch-plugins/js-type-module/package.json
@@ -0,0 +1,6 @@
{
"type": "module",
"jest": {
"watchPlugins": ["./my-watch-plugin.js"]
}
}
11 changes: 11 additions & 0 deletions 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);
});
20 changes: 20 additions & 0 deletions 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;
5 changes: 5 additions & 0 deletions e2e/watch-plugins/js/package.json
@@ -0,0 +1,5 @@
{
"jest": {
"watchPlugins": ["./my-watch-plugin.js"]
}
}
11 changes: 11 additions & 0 deletions 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);
});
22 changes: 22 additions & 0 deletions 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;
5 changes: 5 additions & 0 deletions e2e/watch-plugins/mjs/package.json
@@ -0,0 +1,5 @@
{
"jest": {
"watchPlugins": ["./my-watch-plugin.mjs"]
}
}
28 changes: 14 additions & 14 deletions packages/jest-core/src/__tests__/watch.test.js
Expand Up @@ -205,7 +205,7 @@ describe('Watch mode flows', () => {
});

it('shows prompts for WatchPlugins in alphabetical order', async () => {
watch(
await watch(
{
...globalConfig,
rootDir: __dirname,
Expand Down Expand Up @@ -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(
Expand All @@ -397,7 +397,7 @@ describe('Watch mode flows', () => {
{virtual: true},
);

expect(() => {
await expect(
watch(
{
...globalConfig,
Expand All @@ -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',
Expand All @@ -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(
Expand Down Expand Up @@ -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()}`;
Expand All @@ -486,7 +486,7 @@ describe('Watch mode flows', () => {
return pluginPath;
});

expect(() => {
await expect(
watch(
{
...globalConfig,
Expand All @@ -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,
);
});
Expand Down Expand Up @@ -526,7 +526,7 @@ describe('Watch mode flows', () => {
{virtual: true},
);

watch(
await watch(
{
...globalConfig,
rootDir: __dirname,
Expand Down Expand Up @@ -560,7 +560,7 @@ describe('Watch mode flows', () => {
{virtual: true},
);

watch(
await watch(
{
...globalConfig,
rootDir: __dirname,
Expand Down Expand Up @@ -717,7 +717,7 @@ describe('Watch mode flows', () => {
{virtual: true},
);

watch(
await watch(
{
...globalConfig,
rootDir: __dirname,
Expand Down Expand Up @@ -778,7 +778,7 @@ describe('Watch mode flows', () => {
{virtual: true},
);

watch(
await watch(
{
...globalConfig,
rootDir: __dirname,
Expand Down
13 changes: 10 additions & 3 deletions packages/jest-core/src/watch.ts
Expand Up @@ -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,
Expand Down Expand Up @@ -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<Context>,
outputStream: NodeJS.WriteStream,
Expand Down Expand Up @@ -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<WatchPluginClass>(
pluginWithConfig.path,
);
plugin = new ThirdPartyPlugin({
config: pluginWithConfig.config,
stdin,
Expand Down
1 change: 1 addition & 0 deletions packages/jest-watcher/src/types.ts
Expand Up @@ -83,6 +83,7 @@ export interface WatchPlugin {
}
export interface WatchPluginClass {
new (options: {
config: Record<string, unknown>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which config is this? can we type it better?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aha, it's the options provided directly to the plugin, gotcha 👍

stdin: NodeJS.ReadStream;
stdout: NodeJS.WriteStream;
}): WatchPlugin;
Expand Down