Skip to content

Commit

Permalink
Merge pull request #3678 from storybooks/tmeasday/refactor-jest-addon
Browse files Browse the repository at this point in the history
Refactor addon-jest to use a parameter-based pattern
  • Loading branch information
Tom Coleman authored Jul 4, 2018
2 parents 759d741 + 1e3d925 commit 5f279c1
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 72 deletions.
96 changes: 59 additions & 37 deletions addons/jest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,19 @@ When running **Jest**, be sure to save the results in a json file:
```

You may want to add it the result file to `.gitignore`, since it's a generated file:

```
jest-test-results.json
```

But much like lockfiles and snapshots checking-in generated files can have certain advantages as well. It's up to you.
We recommend to **do** check in the test results file so starting storybook from an clean git clone doesn't require running all tests first,
but this can mean you'll experience merge conflicts on this file in the future. (*re-generating this file is super easy though, just like lockfiles and snapshots*)
We recommend to **do** check in the test results file so starting storybook from an clean git clone doesn't require running all tests first,
but this can mean you'll experience merge conflicts on this file in the future. (_re-generating this file is super easy though, just like lockfiles and snapshots_)

## Generating the test results

You need to make sure the generated test-results file exists before you start storybook.
During development you will likely start jest in watch-mode
You need to make sure the generated test-restuls file exists before you start storybook.
During development you will likely start jest in watch-mode
and so the json file will be re-generated every time code or tests change.

```sh
Expand All @@ -50,9 +52,10 @@ npm run test:generate-output -- --watch

This change will then be HMR (hot module reloaded) using webpack and displayed by this addon.

If you want to pre-run jest automaticly during development or a static build,
If you want to pre-run jest automaticly during development or a static build,
you may need to consider that if your tests fail, the script receives a non-0 exit code and will exit.
You could create a `prebuild:storybook` npm script, which will never fail by appending `|| true`:

```json
"scripts": {
"test:generate-output": "jest --json --outputFile=.jest-test-results.json || true",
Expand Down Expand Up @@ -83,48 +86,65 @@ import results from '../.jest-test-results.json';
import { withTests } from '@storybook/addon-jest';

storiesOf('MyComponent', module)
.addDecorator(withTests({ results })('MyComponent', 'MyOtherComponent'))
.add('This story shows test results from MyComponent.test.js and MyOtherComponent.test.js', () => (
<div>Jest results in storybook</div>
));
.addDecorator(withTests({ results }))
.add(
'This story shows test results from MyComponent.test.js and MyOtherComponent.test.js',
() => <div>Jest results in storybook</div>,
{
jest: ['MyComponent.test.js', 'MyOtherComponent.test.js'],
}
);
```

Or in order to avoid importing `.jest-test-results.json` in each story, you can create a simple file `withTests.js`:
Or in order to avoid importing `.jest-test-results.json` in each story, simply add the decorator in your `.storybook/config.js` and results will display for stories that you have set the `jest` parameter on:

```js
import results from '../.jest-test-results.json';
import { addDecorator } from '@storybook/react'; // <- or your view layer
import { withTests } from '@storybook/addon-jest';

export default withTests({
results,
});
import results from '../.jest-test-results.json';

addDecorator(
withTests({
results,
})
);
```

Then in your story:

```js
// import your file
import withTests from '.withTests';

storiesOf('MyComponent', module)
.addDecorator(withTests('MyComponent', 'MyOtherComponent'))
.add('This story shows test results from MyComponent.test.js and MyOtherComponent.test.js', () => (
<div>Jest results in storybook</div>
));
// Use .addParameters if you want the same tests displayed for all stories of the component
.addParameters({ jest: ['MyComponent', 'MyOtherComponent'] })
.add(
'This story shows test results from MyComponent.test.js and MyOtherComponent.test.js',
() => <div>Jest results in storybook</div>
);
```

### Disabling

You can disable the addon for a single story by setting the `jest` parameter to `{disabled: true}`:

```js
storiesOf('MyComponent', module).add('Story', () => <div>Jest results disabled herek</div>, {
jest: disabled,
});
```

### withTests(options)

- **options.results**: OBJECT jest output results. *mandatory*
- **filesExt**: STRING test file extention. *optional*. This allow you to write "MyComponent" and not "MyComponent.test.js". It will be used as regex to find your file results. Default value is `((\\.specs?)|(\\.tests?))?(\\.js)?$`. That mean it will match: MyComponent.js, MyComponent.test.js, MyComponent.tests.js, MyComponent.spec.js, MyComponent.specs.js...
- **options.results**: OBJECT jest output results. _mandatory_
- **filesExt**: STRING test file extention. _optional_. This allow you to write "MyComponent" and not "MyComponent.test.js". It will be used as regex to find your file results. Default value is `((\\.specs?)|(\\.tests?))?(\\.js)?$`. That mean it will match: MyComponent.js, MyComponent.test.js, MyComponent.tests.js, MyComponent.spec.js, MyComponent.specs.js...

## Usage with Angular

Assuming that you have created a test files `my.component.spec.ts` and `my-other.comonent.spec.ts`

Configure Jest with [jest-preset-angular](https://www.npmjs.com/package/jest-preset-angular)

In project`s `typings.d.ts` add
In project`s`typings.d.ts` add

```ts
declare module '*.json' {
Expand All @@ -133,29 +153,31 @@ declare module '*.json' {
}
```

Create a simple file `withTests.ts`:
In your `.storybook/config.ts`:

```ts
import * as results from '../.jest-test-results.json';
import { addDecorator } from '@storybook/angular';
import { withTests } from '@storybook/addon-jest';

export const wTests = withTests({
results,
filesExt: '((\\.specs?)|(\\.tests?))?(\\.ts)?$'
});
import * as results from '../.jest-test-results.json';

addDecorator(
withTests({
results,
filesExt: '((\\.specs?)|(\\.tests?))?(\\.ts)?$',
})
);
```

Then in your story:

```js
// import your file
import wTests from '.withTests';

storiesOf('MyComponent', module)
.addDecorator(wTests('my.component', 'my-other.component'))
.add('This story shows test results from my.component.spec.ts and my-other.component.spec.ts', () => (
<div>Jest results in storybook</div>
));
.addParameters({ jest: ['my.component', 'my-other.component'] })
.add(
'This story shows test results from my.component.spec.ts and my-other.component.spec.ts',
() => <div>Jest results in storybook</div>
);
```

##### Example [here](https://github.com/storybooks/storybook/tree/master/examples/angular-cli)
Expand Down
3 changes: 2 additions & 1 deletion addons/jest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"babel-runtime": "^6.26.0",
"global": "^4.3.2",
"prop-types": "^15.6.1",
"react-emotion": "^9.1.3"
"react-emotion": "^9.1.3",
"util-deprecate": "^1.0.2"
},
"peerDependencies": {
"react": "*"
Expand Down
27 changes: 23 additions & 4 deletions addons/jest/src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import addons from '@storybook/addons';
import deprecate from 'util-deprecate';

const findTestResults = (testFiles, jestTestResults, jestTestFilesExt) =>
testFiles.map(name => {
Array.from(testFiles).map(name => {
if (jestTestResults && jestTestResults.testResults) {
return {
name,
Expand All @@ -27,9 +28,27 @@ export const withTests = userOptions => {
};
const options = Object.assign({}, defaultOptions, userOptions);

return (...testFiles) => (storyFn, { kind, story }) => {
emitAddTests({ kind, story, testFiles, options });
return (...args) => {
if (typeof args[0] === 'string') {
return deprecate((story, { kind }) => {
emitAddTests({ kind, story, testFiles: args, options });

return storyFn();
return story();
}, 'Passing component filenames to the `@storybook/addon-jest` via `withTests` is deprecated. Instead, use the `jest` story parameter');
}

const [
story,
{
kind,
parameters: { jest: testFiles },
},
] = args;

if (testFiles && !testFiles.disable) {
emitAddTests({ kind, story, testFiles, options });
}

return story();
};
};
7 changes: 0 additions & 7 deletions examples/angular-cli/.storybook/withTests.ts

This file was deleted.

102 changes: 101 additions & 1 deletion examples/angular-cli/addon-jest.testresults.json
Original file line number Diff line number Diff line change
@@ -1 +1,101 @@
{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":1,"numPassedTests":3,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTotalTestSuites":1,"numTotalTests":3,"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeys":[],"unmatched":0,"updated":0},"startTime":1530186110081,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should create the app","location":null,"status":"passed","title":"should create the app"},{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should have as title 'app'","location":null,"status":"passed","title":"should have as title 'app'"},{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should render title in a h1 tag","location":null,"status":"passed","title":"should render title in a h1 tag"}],"endTime":1530186112997,"message":"","name":"/Users/jetbrains/IdeaProjects/storybook/examples/angular-cli/src/app/app.component.spec.ts","startTime":1530186110782,"status":"passed","summary":""}],"wasInterrupted":false}
{
"numFailedTestSuites": 0,
"numFailedTests": 0,
"numPassedTestSuites": 2,
"numPassedTests": 6,
"numPendingTestSuites": 0,
"numPendingTests": 0,
"numRuntimeErrorTestSuites": 0,
"numTotalTestSuites": 2,
"numTotalTests": 6,
"snapshot": {
"added": 0,
"didUpdate": false,
"failure": false,
"filesAdded": 0,
"filesRemoved": 0,
"filesUnmatched": 0,
"filesUpdated": 0,
"matched": 0,
"total": 0,
"unchecked": 0,
"uncheckedKeys": [],
"unmatched": 0,
"updated": 0
},
"startTime": 1527637782576,
"success": true,
"testResults": [
{
"assertionResults": [
{
"ancestorTitles": ["AppComponent"],
"failureMessages": [],
"fullName": "AppComponent should create the app",
"location": null,
"status": "passed",
"title": "should create the app"
},
{
"ancestorTitles": ["AppComponent"],
"failureMessages": [],
"fullName": "AppComponent should have as title 'app'",
"location": null,
"status": "passed",
"title": "should have as title 'app'"
},
{
"ancestorTitles": ["AppComponent"],
"failureMessages": [],
"fullName": "AppComponent should render title in a h1 tag",
"location": null,
"status": "passed",
"title": "should render title in a h1 tag"
}
],
"endTime": 1527637787074,
"message": "",
"name":
"/Users/jetbrains/IdeaProjects/storybook/examples/angular-cli/dist/app/app.component.spec.ts",
"startTime": 1527637783974,
"status": "passed",
"summary": ""
},
{
"assertionResults": [
{
"ancestorTitles": ["AppComponent"],
"failureMessages": [],
"fullName": "AppComponent should create the app",
"location": null,
"status": "passed",
"title": "should create the app"
},
{
"ancestorTitles": ["AppComponent"],
"failureMessages": [],
"fullName": "AppComponent should have as title 'app'",
"location": null,
"status": "passed",
"title": "should have as title 'app'"
},
{
"ancestorTitles": ["AppComponent"],
"failureMessages": [],
"fullName": "AppComponent should render title in a h1 tag",
"location": null,
"status": "passed",
"title": "should render title in a h1 tag"
}
],
"endTime": 1527637787196,
"message": "",
"name":
"/Users/jetbrains/IdeaProjects/storybook/examples/angular-cli/src/app/app.component.spec.ts",
"startTime": 1527637783968,
"status": "passed",
"summary": ""
}
],
"wasInterrupted": false
}
25 changes: 19 additions & 6 deletions examples/angular-cli/src/stories/addon-jest.stories.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { storiesOf } from '@storybook/angular';
import { withTests } from '@storybook/addon-jest';

import { AppComponent } from '../app/app.component';
import { wTests } from '../../.storybook/withTests';
import * as results from '../../addon-jest.testresults.json';

storiesOf('Addon|Jest', module)
.addDecorator(wTests('app.component'))
.add('app.component with jest tests', () => ({
component: AppComponent,
props: {},
}));
.addDecorator(
withTests({
results,
filesExt: '((\\.specs?)|(\\.tests?))?(\\.ts)?$',
})
)
.add(
'app.component with jest tests',
() => ({
component: AppComponent,
props: {},
}),
{
jest: 'app.component',
}
);
8 changes: 2 additions & 6 deletions examples/html-kitchen-sink/stories/addon-jest.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import { storiesOf } from '@storybook/html';
import { withTests } from '@storybook/addon-jest';
import results from './addon-jest.testresults.json';

const withTestsFiles = withTests({
results,
});

storiesOf('Addons|jest', module)
.addDecorator(withTestsFiles('addon-jest'))
.add('withTests', () => 'This story shows test results');
.addDecorator(withTests({ results }))
.add('withTests', () => 'This story shows test results', { jest: 'addon-jest' });
20 changes: 10 additions & 10 deletions examples/official-storybook/stories/addon-jest.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { storiesOf } from '@storybook/react';
import { withTests } from '@storybook/addon-jest';
import results from './addon-jest.testresults.json';

const withTestsFiles = withTests({
results,
});

storiesOf('Addons|jest', module)
.addDecorator(withTestsFiles('addon-jest'))
.add('withTests', () => (
<div>
<p>Hello</p>
</div>
));
.addDecorator(withTests({ results }))
.add(
'withTests',
() => (
<div>
<p>Hello</p>
</div>
),
{ jest: 'addon-jest' }
);

0 comments on commit 5f279c1

Please sign in to comment.