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

Refactor addon-jest to use a parameter-based pattern #3678

Merged
merged 6 commits into from
Jul 4, 2018
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
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' }
);