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

wp-scripts test-unit-js node debugging #21631

Merged
merged 8 commits into from
Apr 24, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/contributors/testing-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,11 @@ Sometimes we need to mock refs for some stories which use them. Check the follow
- [Using createNodeMock to mock refs](https://github.com/storybookjs/storybook/tree/master/addons/storyshots/storyshots-core#using-createnodemock-to-mock-refs) with StoryShots.

In that case, you might see test failures and `TypeError` reported by Jest in the lines which try to access a property from `ref.current`. If this happens, search for `initStoryshots` method call, which contains all necessary configurations to adjust.

### Debugging Jest unit tests

Running `npm run test-unit:debug` will start the tests in debug mode so a [node inspector client](https://nodejs.org/en/docs/guides/debugging-getting-started/#inspector-clients) can connect to the process and inspect the execution. Instructions for using Google Chrome or Visual Studio Code as an inspector client can be found in the [wp-scripts documentation](/packages/scripts/README.md#debugging-jest-unit-tests).

## Native mobile testing

Part of the unit-tests suite is a set of Jest tests run exercise native-mobile codepaths, developed in React Native. Since those tests run on Node, they can be launched locally on your development machine without the need for specific native Android or iOS dev tools or SDKs. It also means that they can be debugged using typical dev tools. Read on for instructions how to debug.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@
"test-performance": "wp-scripts test-e2e --config packages/e2e-tests/jest.performance.config.js",
"test-php": "npm run lint-php && npm run test-unit-php",
"test-unit": "wp-scripts test-unit-js --config test/unit/jest.config.js",
"test-unit:debug": "wp-scripts --inspect-brk test-unit-js --runInBand --no-cache --verbose --config test/unit/jest.config.js ",
"test-unit:update": "npm run test-unit -- --updateSnapshot",
"test-unit:watch": "npm run test-unit -- --watch",
"test-unit-php": "wp-scripts env test-php",
Expand Down
32 changes: 31 additions & 1 deletion packages/scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,8 @@ _Example:_
"scripts": {
"test:unit": "wp-scripts test-unit-js",
"test:unit:help": "wp-scripts test-unit-js --help",
"test:unit:watch": "wp-scripts test-unit-js --watch"
"test:unit:watch": "wp-scripts test-unit-js --watch",
"test:unit:debug": "wp-scripts --inspect-brk test-unit-js --runInBand --no-cache"
}
}
```
Expand All @@ -435,13 +436,42 @@ This is how you execute those scripts using the presented setup:
* `npm run test:unit` - runs all unit tests.
* `npm run test:unit:help` - prints all available options to configure unit tests runner.
* `npm run test:unit:watch` - runs all unit tests in the watch mode.
* `npm run test:unit:debug` - runs all unit tests with the node debugger enabled.

Jest will look for test files with any of the following popular naming conventions:

- Files with `.js` (or `.ts`) suffix located at any level of depth in `__tests__` folders.
- Files with `.js` (or `.ts`) suffix directly located in `test` folders.
- Files with `.test.js` (or `.test.ts`) suffix.

#### Debugging Jest unit tests
gziolo marked this conversation as resolved.
Show resolved Hide resolved

Tests can be debugged by any [inspector client](https://nodejs.org/en/docs/guides/debugging-getting-started/#inspector-clients) that supports the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/).

Follow the instructions for debugging Node.js with your favorite supported browser or IDE. When the instructions say to use `node --inspect script.js` or `node --inspect-brk script.js`, simply use `wp-scripts --inspect test-unit-js` or `wp-scripts --inspect-brk test-unit-js` instead.

Google Chrome and Visual Studio Code are used as examples below.

##### Debugging in Google Chrome

Place `debugger;` statements in any test and run `npm run test:unit:debug`.

Then open `about:inspect` in Google Chrome and select `inspect` on your process.
gziolo marked this conversation as resolved.
Show resolved Hide resolved

A breakpoint will be set at the first line of the script (this is done to give you time to open the developer tools and to prevent Jest from executing before you have time to do so). Click the resume button in the upper right panel of the dev tools to continue execution. When Jest executes the test that contains the debugger statement, execution will pause and you can examine the current scope and call stack.

##### Debugging in Visual Studio Code
Copy link
Member

Choose a reason for hiding this comment

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

The same question applies, what about other IDEs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Node's inspector clients section lists Visual Studio, JetBrains IDEs, Gitpod, and Eclipse. I expect Gitpod and Visual Studio to be similar to Code since they're all quite similar IDEs, but I haven't used JetBrains or Eclipse for web development, so I can't comment on how different they might be to set up for debugging.

It's probably not reasonable to add all the options, but I figured it would be good to include at least a couple specific graphical options as examples in the README since documentation I've seen elsewhere isn't very great. And debugging these scripts is going to be slightly different than the normal use-case because our script spawns the node process rather than the user running node directly with the options.

Copy link
Member

Choose a reason for hiding this comment

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

I have a similar feeling here. The section could target IDEs in general and could include the details you shared in the comment and we would just have this example for VSC included as is. The truth is that VSC and Chrome are the most popular anyway 😃


Debugging NPM scripts is supported out of the box for Visual Studio Code as of [version 1.23](https://code.visualstudio.com/blogs/2018/07/12/introducing-logpoints-and-auto-attach#_npm-scripts-and-debugging) and can be used to debug Jest unit tests.

First, set a breakpoint in your tests by clicking on a line in the editor's left margin by the line numbers.

Then open NPM Scripts in the Explorer or run `Explorer: Focus on NPM Scripts View` in the command palette to see the NPM scripts. To start the tests, click the debug icon next to `test:unit:debug`.

The tests will start running, and execution will pause on your selected line so you can inspect the current scope and call stack within the editor.

See [Debugging in Visual Studio Code](https://code.visualstudio.com/Docs/editor/debugging) for more details on using the Visual Studio Code debugger.

#### Advanced information

It uses [Jest](https://jestjs.io/) behind the scenes and you are able to use all of its [CLI options](https://jestjs.io/docs/en/cli.html). You can also run `./node_modules/.bin/wp-scripts test:unit --help` or `npm run test:unit:help` (as mentioned above) to view all of the available options. By default, it uses the set of recommended options defined in [@wordpress/jest-preset-default](https://www.npmjs.com/package/@wordpress/jest-preset-default) npm package. You can override them with your own options as described in [Jest documentation](https://jestjs.io/docs/en/configuration). Learn more in the [Advanced Usage](#advanced-usage) section.
Expand Down
6 changes: 3 additions & 3 deletions packages/scripts/bin/wp-scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
/**
* Internal dependencies
*/
const { getArgsFromCLI, spawnScript } = require( '../utils' );
const { getNodeArgsFromCLI, spawnScript } = require( '../utils' );

const [ scriptName, ...nodesArgs ] = getArgsFromCLI();
const { scriptName, scriptArgs, nodeArgs } = getNodeArgsFromCLI();

spawnScript( scriptName, nodesArgs );
spawnScript( scriptName, scriptArgs, nodeArgs );
18 changes: 15 additions & 3 deletions packages/scripts/utils/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const spawn = require( 'cross-spawn' );
/**
* Internal dependencies
*/
const { fromScriptsRoot, hasScriptFile } = require( './file' );
const { fromScriptsRoot, hasScriptFile, getScripts } = require( './file' );
const { exit, getArgsFromCLI } = require( './process' );

const getArgFromCLI = ( arg ) => {
Expand All @@ -23,6 +23,17 @@ const hasArgInCLI = ( arg ) => getArgFromCLI( arg ) !== undefined;

const getFileArgsFromCLI = () => minimist( getArgsFromCLI() )._;

const getNodeArgsFromCLI = () => {
const args = getArgsFromCLI();
const scripts = getScripts();
const scriptIndex = args.findIndex( ( arg ) => scripts.includes( arg ) );
return {
nodeArgs: args.slice( 0, scriptIndex ),
scriptName: args[ scriptIndex ],
scriptArgs: args.slice( scriptIndex + 1 ),
};
};

const hasFileArgInCLI = () => getFileArgsFromCLI().length > 0;

const handleSignal = ( signal ) => {
Expand All @@ -44,7 +55,7 @@ const handleSignal = ( signal ) => {
exit( 1 );
};

const spawnScript = ( scriptName, args = [] ) => {
const spawnScript = ( scriptName, args = [], nodeArgs = [] ) => {
if ( ! scriptName ) {
// eslint-disable-next-line no-console
console.log( 'Script name is missing.' );
Expand All @@ -64,7 +75,7 @@ const spawnScript = ( scriptName, args = [] ) => {

const { signal, status } = spawn.sync(
'node',
[ fromScriptsRoot( scriptName ), ...args ],
[ ...nodeArgs, fromScriptsRoot( scriptName ), ...args ],
{
stdio: 'inherit',
}
Expand All @@ -81,6 +92,7 @@ module.exports = {
getArgFromCLI,
getArgsFromCLI,
getFileArgsFromCLI,
getNodeArgsFromCLI,
hasArgInCLI,
hasFileArgInCLI,
spawnScript,
Expand Down
8 changes: 7 additions & 1 deletion packages/scripts/utils/file.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
const { existsSync } = require( 'fs' );
const { existsSync, readdirSync } = require( 'fs' );
const path = require( 'path' );

/**
Expand All @@ -24,10 +24,16 @@ const fromScriptsRoot = ( scriptName ) =>
const hasScriptFile = ( scriptName ) =>
existsSync( fromScriptsRoot( scriptName ) );

const getScripts = () =>
readdirSync( path.join( path.dirname( __dirname ), 'scripts' ) )
.filter( ( f ) => path.extname( f ) === '.js' )
.map( ( f ) => path.basename( f, '.js' ) );

module.exports = {
fromProjectRoot,
fromConfigRoot,
fromScriptsRoot,
getScripts,
hasProjectFile,
hasScriptFile,
};
2 changes: 2 additions & 0 deletions packages/scripts/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const {
getArgFromCLI,
getArgsFromCLI,
getFileArgsFromCLI,
getNodeArgsFromCLI,
hasArgInCLI,
hasFileArgInCLI,
spawnScript,
Expand Down Expand Up @@ -32,6 +33,7 @@ module.exports = {
getArgFromCLI,
getArgsFromCLI,
getFileArgsFromCLI,
getNodeArgsFromCLI,
getWebpackArgs,
hasBabelConfig,
hasArgInCLI,
Expand Down
26 changes: 26 additions & 0 deletions packages/scripts/utils/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,32 @@ describe( 'utils', () => {
expect( console ).toHaveLogged();
} );

test( 'should pass inspect args to node', () => {
crossSpawnMock.mockReturnValueOnce( { status: 0 } );

expect( () =>
spawnScript( scriptName, [], [ '--inspect-brk' ] )
).toThrow( 'Exit code: 0.' );
expect( crossSpawnMock ).toHaveBeenCalledWith(
'node',
[ '--inspect-brk', expect.stringContaining( scriptName ) ],
{ stdio: 'inherit' }
);
} );

test( 'should pass script args to the script', () => {
crossSpawnMock.mockReturnValueOnce( { status: 0 } );

expect( () =>
spawnScript( scriptName, [ '--runInBand' ] )
).toThrow( 'Exit code: 0.' );
expect( crossSpawnMock ).toHaveBeenCalledWith(
'node',
[ expect.stringContaining( scriptName ), '--runInBand' ],
{ stdio: 'inherit' }
);
} );

test( 'should finish successfully when the script properly executed', () => {
crossSpawnMock.mockReturnValueOnce( { status: 0 } );

Expand Down