Merge branch 'next' of github.com:storybookjs/storybook into next

This commit is contained in:
Michael Shilman 2019-11-25 23:47:21 +08:00
commit c783b30285
19 changed files with 929 additions and 840 deletions

View File

@ -187,7 +187,7 @@ If you've made a change to storybook's codebase and would want this change to be
### Updating Tests
Before any contributes are submitted in a PR, make sure to add or update meaningful tests. A PR that has failing tests will be regarded as a “Work in Progress” and will not be merged until all tests pass.
Before any contributions are submitted in a PR, make sure to add or update meaningful tests. A PR that has failing tests will be regarded as a “Work in Progress” and will not be merged until all tests pass.
When creating new unit test files, the tests should adhere to a particular folder structure and naming convention, as defined below.
```sh
@ -205,7 +205,7 @@ Before you submit a new PR, make sure you run `yarn test`. Do not submit a PR if
### Reviewing PRs
**As a PR submitter**, you should reference the issue if there is one, include a short description of what you contributed and, if it is a code change, instructions for how to manually test out the change. This is informally enforced by our [PR template](https://github.com/storybookjs/storybook/blob/master/.github/PULL_REQUEST_TEMPLATE.md). If your PR is reviewed as only needing trivial changes (e.g. small typos etc), and you have commit access, then you can merge the PR after making those changes.
**As a PR submitter**, you should reference the issue if there is one, include a short description of what you contributed and, if it is a code change, instructions for how to manually test out the change. This is informally enforced by our [PR template](https://github.com/storybookjs/storybook/blob/master/.github/PULL_REQUEST_TEMPLATE.md). If your PR is reviewed as only needing trivial changes (e.g. small typos etc), and you have commit access then you can merge the PR after making those changes.
**As a PR reviewer**, you should read through the changes and comment on any potential problems. If you see something cool, a kind word never hurts either! Additionally, you should follow the testing instructions and manually test the changes. If the instructions are missing, unclear, or overly complex, feel free to request better instructions from the submitter. Unless the PR is tagged with the `do not merge` label, if you approve the review and there is no other required discussion or changes, you should also go ahead and merge the PR.
@ -215,7 +215,7 @@ If you are looking for a way to help the project, triaging issues is a great pla
### Responding to issues
Issues that are tagged `question / support` or `needs reproduction` are great places to help. If you can answer a question, it will help the asker as well as anyone searching. If an issue needs reproduction, you may be able to guide the reporter toward one, or even reproduce it yourself using [this technique](https://github.com/storybookjs/storybook/blob/master/CONTRIBUTING.md#reproductions).
Issues that are tagged `question / support` or `needs reproduction` are great places to help. If you can answer a question, it will help the asker as well as anyone who has a similar question. Also in the future if anyone has that same question they can easily find it by searching. If an issue needs reproduction, you may be able to guide the reporter toward one, or even reproduce it yourself using [this technique](https://github.com/storybookjs/storybook/blob/master/CONTRIBUTING.md#reproductions).
### Triaging issues

View File

@ -35,9 +35,11 @@
"@jest/transform": "^24.9.0",
"@storybook/addons": "5.3.0-beta.6",
"@storybook/client-api": "5.3.0-beta.6",
"@storybook/core": "5.3.0-beta.6",
"@types/glob": "^7.1.1",
"@types/jest": "^24.0.16",
"@types/jest-specific-snapshot": "^0.5.3",
"babel-plugin-require-context-hook": "^1.0.0",
"core-js": "^3.0.1",
"glob": "^7.1.3",
"global": "^4.3.2",

View File

@ -12,11 +12,11 @@ type TestMethod = 'beforeAll' | 'beforeEach' | 'afterEach' | 'afterAll';
const methods: TestMethod[] = ['beforeAll', 'beforeEach', 'afterEach', 'afterAll'];
function callTestMethodGlobals(
testMethod: { [key in TestMethod]?: Function } & { [key in string]: any }
testMethod: { [key in TestMethod]?: Function & { timeout?: number } } & { [key in string]: any }
) {
methods.forEach(method => {
if (typeof testMethod[method] === 'function') {
global[method](testMethod[method]);
global[method](testMethod[method], testMethod[method].timeout);
}
});
}

View File

@ -18,15 +18,19 @@ function snapshotTest({ item, asyncJest, framework, testMethod, testMethodParams
context,
...testMethodParams,
})
)
),
testMethod.timeout
);
} else {
it(name, () =>
testMethod({
story: item,
context,
...testMethodParams,
})
it(
name,
() =>
testMethod({
story: item,
context,
...testMethodParams,
}),
testMethod.timeout
);
}
}

View File

@ -1,9 +1,13 @@
import fs from 'fs';
import path from 'path';
import glob from 'glob';
import { toRequireContext } from '@storybook/core/server';
import registerRequireContextHook from 'babel-plugin-require-context-hook/register';
import global from 'global';
import { ClientApi } from './Loader';
import { StoryshotsOptions } from '../api/StoryshotsOptions';
registerRequireContextHook();
const isFile = (file: string): boolean => {
try {
return fs.lstatSync(file).isFile();
@ -64,9 +68,18 @@ function getConfigPathParts(input: string): Output {
if (main) {
const { stories = [] } = require.requireActual(main);
const result = stories.reduce((acc: string[], i: string) => [...acc, ...glob.sync(i)], []);
output.stories = result;
output.stories = stories.map(
(pattern: string | { path: string; recursive: boolean; match: string }) => {
const { path: basePath, recursive, match } = toRequireContext(pattern);
// eslint-disable-next-line no-underscore-dangle
return global.__requireContext(
configDir,
basePath,
recursive,
new RegExp(match.slice(1, -1))
);
}
);
}
return output;
@ -94,8 +107,7 @@ function configure(
});
if (stories && stories.length) {
// eslint-disable-next-line global-require, import/no-dynamic-require
storybook.configure(() => stories.map(f => require(f)), false);
storybook.configure(stories, false);
}
}

View File

@ -2,3 +2,5 @@ declare module 'global';
declare module 'jest-preset-angular/*';
declare module 'preact-render-to-json';
declare module 'react-test-renderer*';
declare module '@storybook/core/server';
declare module 'babel-plugin-require-context-hook/register';

View File

@ -67,7 +67,7 @@ initStoryshots({
### Specifying options to _jest-image-snapshots_
If you wish to customize [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot), then you can provide a `getMatchOptions` parameter that should return the options config object. Additionally, you can provide `beforeScreenshot` which is called before the screenshot is captured.
If you wish to customize [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot), then you can provide a `getMatchOptions` parameter that should return the options config object. Additionally, you can provide `beforeScreenshot` which is called before the screenshot is captured and a `afterScreenshot` handler which is called after the screenshot and receives the just created image.
```js
import initStoryshots from '@storybook/addon-storyshots';
@ -85,9 +85,16 @@ const beforeScreenshot = (page, { context: { kind, story }, url }) => {
}, 600)
);
};
const afterScreenshot = ({ image, context }) => {
return new Promise(resolve =>
setTimeout(() => {
resolve();
}, 600)
);
};
initStoryshots({
suite: 'Image storyshots',
test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getMatchOptions, beforeScreenshot }),
test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getMatchOptions, beforeScreenshot, afterScreenshot }),
});
```
@ -95,6 +102,8 @@ initStoryshots({
`beforeScreenshot` receives the [Puppeteer page instance](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page) and an object: `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. `beforeScreenshot` is part of the promise chain and is called after the browser navigation is completed but before the screenshot is taken. It allows for triggering events on the page elements and delaying the screenshot and can be used avoid regressions due to mounting animations.
`afterScreenshot` receives the created image from puppeteer.
### Specifying options to _goto()_ (puppeteer API)
You might use `getGotoOptions` to specify options when the storybook is navigating to a story (using the `goto` method). Will be passed to [Puppeteer .goto() fn](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagegotourl-options)
@ -196,6 +205,11 @@ initStoryshots({
});
```
### Specifying setup and tests timeout
By default, `@storybook/addon-storyshots-puppeteer` uses 15 second timeouts for browser setup and test functions.
Those can be customized with `setupTimeout` and `testTimeout` parameters.
### Integrate image storyshots with regular app
You may want to use another Jest project to run your image snapshots as they require more resources: Chrome and Storybook built/served.

View File

@ -11,8 +11,11 @@ export interface ImageSnapshotConfig {
chromeExecutablePath: string;
getMatchOptions: (options: { context: Context; url: string }) => MatchImageSnapshotOptions;
getScreenshotOptions: (options: { context: Context; url: string }) => Base64ScreenShotOptions;
afterScreenshot: (options: { image: string; context: Context }) => void;
beforeScreenshot: (page: Page, options: { context: Context; url: string }) => void;
getGotoOptions: (options: { context: Context; url: string }) => DirectNavigationOptions;
customizePage: (page: Page) => Promise<void>;
getCustomBrowser: () => Promise<Browser>;
setupTimeout: number;
testTimeout: number;
}

View File

@ -1,4 +1,4 @@
import puppeteer, { Browser, Page } from 'puppeteer';
import { Browser, Page } from 'puppeteer';
import { toMatchImageSnapshot } from 'jest-image-snapshot';
import { logger } from '@storybook/node-logger';
import { constructUrl } from './url';
@ -18,9 +18,12 @@ const defaultConfig: ImageSnapshotConfig = {
getMatchOptions: noop,
getScreenshotOptions: defaultScreenshotOptions,
beforeScreenshot: noop,
afterScreenshot: noop,
getGotoOptions: noop,
customizePage: asyncNoop,
getCustomBrowser: undefined,
setupTimeout: 15000,
testTimeout: 15000,
};
export const imageSnapshot = (customConfig: Partial<ImageSnapshotConfig> = {}) => {
@ -30,9 +33,12 @@ export const imageSnapshot = (customConfig: Partial<ImageSnapshotConfig> = {}) =
getMatchOptions,
getScreenshotOptions,
beforeScreenshot,
afterScreenshot,
getGotoOptions,
customizePage,
getCustomBrowser,
setupTimeout,
testTimeout,
} = { ...defaultConfig, ...customConfig };
let browser: Browser; // holds ref to browser. (ie. Chrome)
@ -66,6 +72,7 @@ export const imageSnapshot = (customConfig: Partial<ImageSnapshotConfig> = {}) =
await page.goto(url, getGotoOptions({ context, url }));
await beforeScreenshot(page, { context, url });
image = await page.screenshot(getScreenshotOptions({ context, url }));
await afterScreenshot({ image, context });
} catch (e) {
logger.error(
`Error when connecting to ${url}, did you start or build the storybook first? A storybook instance should be running or a static version should be built when using image snapshot feature.`
@ -75,19 +82,22 @@ export const imageSnapshot = (customConfig: Partial<ImageSnapshotConfig> = {}) =
expect(image).toMatchImageSnapshot(getMatchOptions({ context, url }));
};
testFn.timeout = testTimeout;
testFn.afterAll = () => {
testFn.afterAll = async () => {
if (getCustomBrowser && page) {
return page.close();
await page.close();
} else if (browser) {
await browser.close();
}
return browser.close();
};
testFn.beforeAll = async () => {
const beforeAll = async () => {
if (getCustomBrowser) {
browser = await getCustomBrowser();
} else {
// eslint-disable-next-line global-require
const puppeteer = require('puppeteer');
// add some options "no-sandbox" to make it work properly on some Linux systems as proposed here: https://github.com/Googlechrome/puppeteer/issues/290#issuecomment-322851507
browser = await puppeteer.launch({
args: ['--no-sandbox ', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
@ -97,6 +107,8 @@ export const imageSnapshot = (customConfig: Partial<ImageSnapshotConfig> = {}) =
page = await browser.newPage();
};
beforeAll.timeout = setupTimeout;
testFn.beforeAll = beforeAll;
return testFn;
};

View File

@ -20,7 +20,7 @@ if (!fs.existsSync(pathToStorybookStatic)) {
} else {
initStoryshots({
suite: 'Image snapshots',
storyKindRegex: /^Addons\|Storyshots/,
storyKindRegex: /^Addons\/Storyshots/,
framework: 'react',
configPath: path.join(__dirname, '..'),
test: imageSnapshot({

View File

@ -61,5 +61,8 @@
"ts-loader": "^6.0.0",
"uuid": "^3.3.2",
"webpack": "^4.33.0"
},
"optionalDependencies": {
"puppeteer": "^2.0.0"
}
}

View File

@ -1 +1 @@
export const version = '5.3.0-beta.3';
export const version = '5.3.0-beta.5';

View File

@ -66,7 +66,6 @@
"find-up": "^4.1.0",
"fs-extra": "^8.0.1",
"glob-base": "^0.3.0",
"glob-regex": "^0.3.2",
"global": "^4.3.2",
"html-webpack-plugin": "^4.0.0-beta.2",
"inquirer": "^7.0.0",
@ -74,6 +73,7 @@
"ip": "^1.1.5",
"json5": "^2.1.1",
"lazy-universal-dotenv": "^3.0.1",
"micromatch": "^4.0.2",
"node-fetch": "^2.6.0",
"open": "^7.0.0",
"pnp-webpack-plugin": "1.5.0",
@ -104,6 +104,7 @@
"mock-fs": "^4.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.1",
"babel-loader": "^7.0.0 || ^8.0.0",
"react": "*",
"react-dom": "*"

View File

@ -2,6 +2,7 @@ const defaultWebpackConfig = require('./dist/server/preview/base-webpack.config'
const serverUtils = require('./dist/server/utils/template');
const buildStatic = require('./dist/server/build-static');
const buildDev = require('./dist/server/build-dev');
const toRequireContext = require('./dist/server/preview/to-require-context');
const managerPreset = require.resolve('./dist/server/manager/manager-preset');
@ -11,4 +12,5 @@ module.exports = {
...buildStatic,
...buildDev,
...serverUtils,
...toRequireContext,
};

View File

@ -361,7 +361,7 @@ export default function start(render, { decorateStory } = {}) {
});
} else {
const exported = loadable();
if (Array.isArray(exported) && !exported.find(obj => !obj.default)) {
if (Array.isArray(exported) && exported.every(obj => obj.default != null)) {
currentExports = new Map(exported.map(fileExports => [fileExports, null]));
} else if (exported) {
logger.warn(

View File

@ -9,14 +9,11 @@ import CoreJSUpgradeWebpackPlugin from 'corejs-upgrade-webpack-plugin';
import VirtualModulePlugin from 'webpack-virtual-modules';
import resolveFrom from 'resolve-from';
import toRegex from 'glob-regex';
import globBase from 'glob-base';
import babelLoader from '../common/babel-loader';
import { nodeModulesPaths, loadEnv } from '../config/utils';
import { getPreviewHeadHtml, getPreviewBodyHtml } from '../utils/template';
const isObject = val => val != null && typeof val === 'object' && Array.isArray(val) === false;
import { loadEnv, nodeModulesPaths } from '../config/utils';
import { getPreviewBodyHtml, getPreviewHeadHtml } from '../utils/template';
import { toRequireContextString } from './to-require-context';
const reactPaths = {};
try {
@ -26,27 +23,6 @@ try {
//
}
const toRequireContext = input => {
switch (true) {
case typeof input === 'string': {
const { base, glob } = globBase(input);
const regex = toRegex(glob)
.toString()
.replace('^([^\\/]+)', '');
return `require.context('${base}', true, ${regex})`;
}
case isObject(input): {
const { path: p, recursive: r, match: m } = input;
return `require.context('${p}', ${r}, ${m})`;
}
default: {
throw new Error('the provided input cannot be transformed into a require.context');
}
}
};
export default ({
configDir,
babelOptions,
@ -77,7 +53,7 @@ export default ({
[path.resolve(path.join(configDir, `generated-entry.js`))]: `
import { configure, addDecorator, addParameters } from '@storybook/${framework}';
configure([${stories.map(toRequireContext).join(',')}
configure([${stories.map(toRequireContextString).join(',')}
], module);
`,
})

View File

@ -0,0 +1,30 @@
import globBase from 'glob-base';
import { makeRe } from 'micromatch';
const isObject = val => val != null && typeof val === 'object' && Array.isArray(val) === false;
export const toRequireContext = input => {
switch (true) {
case typeof input === 'string': {
const { base, glob } = globBase(input);
const regex = makeRe(glob)
.toString()
// webpack prepends the relative path with './'
.replace(/^\/\^/, '/^\\.\\/')
.replace(/\?:\^/g, '?:');
return { path: base, recursive: glob.startsWith('**'), match: regex };
}
case isObject(input): {
return input;
}
default: {
throw new Error('the provided input cannot be transformed into a require.context');
}
}
};
export const toRequireContextString = input => {
const { path: p, recursive: r, match: m } = toRequireContext(input);
return `require.context('${p}', ${r}, ${m})`;
};

View File

@ -2,5 +2,5 @@ const path = require('path');
const babelJest = require('babel-jest');
module.exports = babelJest.createTransformer({
configFile: path.resolve('.babelrc'),
configFile: path.resolve(__dirname, '../.babelrc'),
});

1590
yarn.lock

File diff suppressed because it is too large Load Diff