diff --git a/.editorconfig b/.editorconfig index 22d8918a3089..86c7d17c7883 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,6 +3,6 @@ root = true [*] end_of_line = lf -[*.{js,json,ts,html}] +[*.{js,json,ts,vue,html}] indent_style = space indent_size = 2 diff --git a/addons/storyshots/README.md b/addons/storyshots/README.md index b75b0ec4c495..b440594d0ac4 100644 --- a/addons/storyshots/README.md +++ b/addons/storyshots/README.md @@ -14,6 +14,8 @@ StoryShots adds automatic Jest Snapshot Testing for [Storybook](https://storyboo This addon works with Storybook for: - [React](https://github.com/storybooks/storybook/tree/master/app/react) - [React Native](https://github.com/storybooks/storybook/tree/master/app/react-native) +- [Angular](https://github.com/storybooks/storybook/tree/master/app/angular) +- [Vue](https://github.com/storybooks/storybook/tree/master/app/vue) ![StoryShots In Action](docs/storyshots-fail.png) @@ -36,6 +38,69 @@ Usually, you might already have completed this step. If not, here are some resou > Note: If you use React 16, you'll need to follow [these additional instructions](https://github.com/facebook/react/issues/9102#issuecomment-283873039). +### Configure Jest for React +StoryShots addon for React is dependent on [react-test-renderer](https://github.com/facebook/react/tree/master/packages/react-test-renderer), but +[doesn't](#deps-issue) install it, so you need to install it separately. + +```sh +npm install --save-dev react-test-renderer +``` + +### Configure Jest for Angular +StoryShots addon for Angular is dependent on [jest-preset-angular](https://github.com/thymikee/jest-preset-angular), but +[doesn't](#deps-issue) install it, so you need to install it separately. + +```sh +npm install --save-dev jest-preset-angular +``` + +If you already use Jest for testing your angular app - probably you already have the needed jest configuration. +Anyway you can add these lines to your jest config: +```js +module.exports = { + globals: { + __TRANSFORM_HTML__: true, + }, + transform: { + '^.+\\.jsx?$': 'babel-jest', + '^.+\\.(ts|html)$': '/node_modules/jest-preset-angular/preprocessor.js', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node', '.html'], +}; +``` +### Configure Jest for Vue +StoryShots addon for Vue is dependent on [jest-vue-preprocessor](https://github.com/vire/jest-vue-preprocessor), but +[doesn't](#deps-issue) install it, so you need yo install it separately. + + ```sh + npm install --save-dev jest-vue-preprocessor + ``` + +If you already use Jest for testing your vue app - probably you already have the needed jest configuration. +Anyway you can add these lines to your jest config: +```js +module.exports = { + transform: { + '^.+\\.jsx?$': 'babel-jest', + '.*\\.(vue)$': '/node_modules/jest-vue-preprocessor', + }, + moduleFileExtensions: ['vue', 'js', 'jsx', 'json', 'node'], +}; +``` + +### Why don't we install dependencies of each framework ? +Storyshots addon is currently supporting React, Angular and Vue. Each framework needs its own packages to be integrated with Jest. We don't want people that use only React will need to bring other dependencies that do not make sense for them. + +`dependencies` - will installed an exact version of the particular dep - Storyshots can work with different versions of the same framework (let's say React v16 and React v15), that have to be compatible with a version of its plugin (react-test-renderer). + +`optionalDependencies` - behaves like a regular dependency, but do not fail the installation in case there is a problem to bring the dep. + +`peerDependencies` - listing all the deps in peer will trigger warnings during the installation - we don't want users to install unneeded deps by hand. + +`optionalPeerDependencies` - unfortunately there is nothing like this =( + +For more information read npm [docs](https://docs.npmjs.com/files/package.json#dependencies) + ## Configure Storyshots for HTML snapshots Create a new test file with the name `Storyshots.test.js`. (Or whatever the name you prefer, as long as it matches Jest's config [`testMatch`](http://facebook.github.io/jest/docs/en/configuration.html#testmatch-array-string)). diff --git a/addons/storyshots/package.json b/addons/storyshots/package.json index d1ae8f5bf113..b2d1c986e9fa 100644 --- a/addons/storyshots/package.json +++ b/addons/storyshots/package.json @@ -11,7 +11,7 @@ }, "scripts": { "build-storybook": "build-storybook", - "prepare": "babel ./src --out-dir ./dist", + "prepare": "node ../../scripts/prepare.js", "storybook": "start-storybook -p 6006", "example": "jest storyshot.test" }, @@ -43,8 +43,6 @@ }, "peerDependencies": { "@storybook/addons": "^3.4.0-alpha.4", - "babel-core": "^6.26.0 || ^7.0.0-0", - "react": "*", - "react-test-renderer": "*" + "babel-core": "^6.26.0 || ^7.0.0-0" } } diff --git a/addons/storyshots/src/angular/app.component.ts b/addons/storyshots/src/angular/app.component.ts new file mode 100644 index 000000000000..297624ac8b79 --- /dev/null +++ b/addons/storyshots/src/angular/app.component.ts @@ -0,0 +1,92 @@ +// We could use NgComponentOutlet here but there's currently no easy way +// to provide @Inputs and subscribe to @Outputs, see +// https://github.com/angular/angular/issues/15360 +// For the time being, the ViewContainerRef approach works pretty well. +import { + Component, + Inject, + OnInit, + ViewChild, + ViewContainerRef, + ComponentFactoryResolver, + OnDestroy, + EventEmitter, + SimpleChanges, + SimpleChange, +} from '@angular/core'; +import { STORY } from './app.token'; +import { NgStory, ICollection } from './types'; + +@Component({ + selector: 'storybook-dynamic-app-root', + template: '', +}) +export class AppComponent implements OnInit, OnDestroy { + @ViewChild('target', { read: ViewContainerRef }) + target: ViewContainerRef; + constructor(private cfr: ComponentFactoryResolver, @Inject(STORY) private data: NgStory) {} + + ngOnInit(): void { + this.putInMyHtml(); + } + + ngOnDestroy(): void { + this.target.clear(); + } + + private putInMyHtml(): void { + this.target.clear(); + const compFactory = this.cfr.resolveComponentFactory(this.data.component); + const instance = this.target.createComponent(compFactory).instance; + + this.setProps(instance, this.data); + } + + /** + * Set inputs and outputs + */ + private setProps(instance: any, { props = {} }: NgStory): void { + const changes: SimpleChanges = {}; + const hasNgOnChangesHook = !!instance['ngOnChanges']; + + Object.keys(props).map((key: string) => { + const value = props[key]; + const instanceProperty = instance[key]; + + if (!(instanceProperty instanceof EventEmitter) && !!value) { + instance[key] = value; + if (hasNgOnChangesHook) { + changes[key] = new SimpleChange(undefined, value, instanceProperty === undefined); + } + } else if (typeof value === 'function' && key !== 'ngModelChange') { + instanceProperty.subscribe(value); + } + }); + + this.callNgOnChangesHook(instance, changes); + this.setNgModel(instance, props); + } + + /** + * Manually call 'ngOnChanges' hook because angular doesn't do that for dynamic components + * Issue: [https://github.com/angular/angular/issues/8903] + */ + private callNgOnChangesHook(instance: any, changes: SimpleChanges): void { + if (!!Object.keys(changes).length) { + instance.ngOnChanges(changes); + } + } + + /** + * If component implements ControlValueAccessor interface try to set ngModel + */ + private setNgModel(instance: any, props: ICollection): void { + if (!!props['ngModel']) { + instance.writeValue(props.ngModel); + } + + if (typeof props.ngModelChange === 'function') { + instance.registerOnChange(props.ngModelChange); + } + } +} diff --git a/addons/storyshots/src/angular/app.token.ts b/addons/storyshots/src/angular/app.token.ts new file mode 100644 index 000000000000..3e4753b21c11 --- /dev/null +++ b/addons/storyshots/src/angular/app.token.ts @@ -0,0 +1,4 @@ +import { InjectionToken } from '@angular/core'; +import { NgStory } from './types'; + +export const STORY = new InjectionToken('story'); diff --git a/addons/storyshots/src/angular/helpers.ts b/addons/storyshots/src/angular/helpers.ts new file mode 100644 index 000000000000..d1d42e746434 --- /dev/null +++ b/addons/storyshots/src/angular/helpers.ts @@ -0,0 +1,64 @@ +import { Component, Type } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { AppComponent } from './app.component'; +import { STORY } from './app.token'; +import { NgStory } from './types'; + +const getModuleMeta = ( + declarations: Array | any[]>, + entryComponents: Array | any[]>, + bootstrap: Array | any[]>, + data: NgStory, + moduleMetadata: any +) => { + return { + declarations: [...declarations, ...(moduleMetadata.declarations || [])], + imports: [BrowserModule, FormsModule, ...(moduleMetadata.imports || [])], + providers: [ + { provide: STORY, useValue: Object.assign({}, data) }, + ...(moduleMetadata.providers || []), + ], + entryComponents: [...entryComponents, ...(moduleMetadata.entryComponents || [])], + schemas: [...(moduleMetadata.schemas || [])], + bootstrap: [...bootstrap], + }; +}; + +const createComponentFromTemplate = (template: string): Function => { + const componentClass = class DynamicComponent {}; + + return Component({ + template: template, + })(componentClass); +}; + +export const initModuleData = (storyObj: NgStory): any => { + const { component, template, props, moduleMetadata = {} } = storyObj; + + let AnnotatedComponent; + + if (template) { + AnnotatedComponent = createComponentFromTemplate(template); + } else { + AnnotatedComponent = component; + } + + const story = { + component: AnnotatedComponent, + props, + }; + + const moduleMeta = getModuleMeta( + [AppComponent, AnnotatedComponent], + [AnnotatedComponent], + [AppComponent], + story, + moduleMetadata + ); + + return { + AppComponent, + moduleMeta, + }; +}; diff --git a/addons/storyshots/src/angular/loader.js b/addons/storyshots/src/angular/loader.js new file mode 100644 index 000000000000..aae9bb4a3cf8 --- /dev/null +++ b/addons/storyshots/src/angular/loader.js @@ -0,0 +1,43 @@ +import runWithRequireContext from '../require_context'; +import hasDependency from '../hasDependency'; +import loadConfig from '../config-loader'; + +function setupAngularJestPreset() { + // Angular + Jest + Storyshots = Crazy Shit: + // We need to require 'jest-preset-angular/setupJest' before any storybook code + // is running inside jest - one of the things that `jest-preset-angular/setupJest` does is + // extending the `window.Reflect` with all the needed metadata functions, that are required + // for emission of the TS decorations like 'design:paramtypes' + require.requireActual('jest-preset-angular/setupJest'); +} + +function test(options) { + return ( + options.framework === 'angular' || (!options.framework && hasDependency('@storybook/angular')) + ); +} + +function load(options) { + setupAngularJestPreset(); + + const { content, contextOpts } = loadConfig({ + configDirPath: options.configPath, + babelConfigPath: '@storybook/angular/dist/server/babel_config', + }); + + runWithRequireContext(content, contextOpts); + + return { + framework: 'angular', + renderTree: require.requireActual('./renderTree').default, + renderShallowTree: () => { + throw new Error('Shallow renderer is not supported for angular'); + }, + storybook: require.requireActual('@storybook/angular'), + }; +} + +export default { + load, + test, +}; diff --git a/addons/storyshots/src/angular/renderTree.js b/addons/storyshots/src/angular/renderTree.js new file mode 100644 index 000000000000..ebe9f0e8f0a0 --- /dev/null +++ b/addons/storyshots/src/angular/renderTree.js @@ -0,0 +1,44 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import AngularSnapshotSerializer from 'jest-preset-angular/AngularSnapshotSerializer'; +// eslint-disable-next-line import/no-extraneous-dependencies +import HTMLCommentSerializer from 'jest-preset-angular/HTMLCommentSerializer'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { TestBed } from '@angular/core/testing'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { addSerializer } from 'jest-specific-snapshot'; +import { initModuleData } from './helpers.ts'; + +addSerializer(HTMLCommentSerializer); +addSerializer(AngularSnapshotSerializer); + +function getRenderedTree(story, context) { + const currentStory = story.render(context); + + const { moduleMeta, AppComponent } = initModuleData(currentStory); + + TestBed.configureTestingModule({ + imports: [...moduleMeta.imports], + declarations: [...moduleMeta.declarations], + providers: [...moduleMeta.providers], + schemas: [NO_ERRORS_SCHEMA, ...moduleMeta.schemas], + bootstrap: [...moduleMeta.bootstrap], + }); + + TestBed.overrideModule(BrowserDynamicTestingModule, { + set: { + entryComponents: [...moduleMeta.entryComponents], + }, + }); + + return TestBed.compileComponents().then(() => { + const tree = TestBed.createComponent(AppComponent); + tree.detectChanges(); + + return tree; + }); +} + +export default getRenderedTree; diff --git a/addons/storyshots/src/angular/types.ts b/addons/storyshots/src/angular/types.ts new file mode 100644 index 000000000000..191d2ed44c07 --- /dev/null +++ b/addons/storyshots/src/angular/types.ts @@ -0,0 +1,19 @@ +export interface NgModuleMetadata { + declarations?: Array; + entryComponents?: Array; + imports?: Array; + schemas?: Array; + providers?: Array; +} + +export interface ICollection { + [p: string]: any; +} + +export interface NgStory { + component?: any; + props: ICollection; + propsMeta?: ICollection; + moduleMetadata?: NgModuleMetadata; + template?: string; +} diff --git a/addons/storyshots/src/config-loader.js b/addons/storyshots/src/config-loader.js new file mode 100644 index 000000000000..a5d6b36b5214 --- /dev/null +++ b/addons/storyshots/src/config-loader.js @@ -0,0 +1,24 @@ +import path from 'path'; + +const babel = require('babel-core'); + +function getConfigContent({ resolvedConfigDirPath, configPath, babelConfigPath }) { + const loadBabelConfig = require.requireActual(babelConfigPath).default; + const babelConfig = loadBabelConfig(resolvedConfigDirPath); + return babel.transformFileSync(configPath, babelConfig).code; +} + +function load({ configDirPath, babelConfigPath }) { + const resolvedConfigDirPath = path.resolve(configDirPath || '.storybook'); + const configPath = path.join(resolvedConfigDirPath, 'config.js'); + + const content = getConfigContent({ resolvedConfigDirPath, configPath, babelConfigPath }); + const contextOpts = { filename: configPath, dirname: resolvedConfigDirPath }; + + return { + content, + contextOpts, + }; +} + +export default load; diff --git a/addons/storyshots/src/frameworkLoader.js b/addons/storyshots/src/frameworkLoader.js new file mode 100644 index 000000000000..047759ec3a98 --- /dev/null +++ b/addons/storyshots/src/frameworkLoader.js @@ -0,0 +1,18 @@ +import loaderReact from './react/loader'; +import loaderRn from './rn/loader'; +import loaderAngular from './angular/loader'; +import loaderVue from './vue/loader'; + +const loaders = [loaderReact, loaderAngular, loaderRn, loaderVue]; + +function loadFramework(options) { + const loader = loaders.find(frameworkLoader => frameworkLoader.test(options)); + + if (!loader) { + throw new Error('storyshots is intended only to be used with storybook'); + } + + return loader.load(options); +} + +export default loadFramework; diff --git a/addons/storyshots/src/hasDependency.js b/addons/storyshots/src/hasDependency.js new file mode 100644 index 000000000000..673ff614221e --- /dev/null +++ b/addons/storyshots/src/hasDependency.js @@ -0,0 +1,13 @@ +import fs from 'fs'; +import path from 'path'; +import readPkgUp from 'read-pkg-up'; + +const { pkg } = readPkgUp.sync(); + +export default function hasDependency(name) { + return ( + (pkg.devDependencies && pkg.devDependencies[name]) || + (pkg.dependencies && pkg.dependencies[name]) || + fs.existsSync(path.join('node_modules', name, 'package.json')) + ); +} diff --git a/addons/storyshots/src/index.js b/addons/storyshots/src/index.js index 1f94fd13a45b..b50b4c7351b6 100644 --- a/addons/storyshots/src/index.js +++ b/addons/storyshots/src/index.js @@ -1,96 +1,58 @@ -import path from 'path'; +/* eslint-disable no-loop-func */ import fs from 'fs'; import glob from 'glob'; import global, { describe, it, beforeEach, afterEach } from 'global'; -import readPkgUp from 'read-pkg-up'; import addons from '@storybook/addons'; - -import runWithRequireContext from './require_context'; +import loadFramework from './frameworkLoader'; import createChannel from './storybook-channel-mock'; -import { snapshotWithOptions } from './test-bodies'; import { getPossibleStoriesFiles, getSnapshotFileName } from './utils'; +import { imageSnapshot } from './test-body-image-snapshot'; -export { - snapshot, +import { multiSnapshotWithOptions, snapshotWithOptions, + snapshot, shallowSnapshot, renderOnly, } from './test-bodies'; -export { imageSnapshot } from './test-body-image-snapshot'; - -export { getSnapshotFileName }; - -let storybook; -let configPath; global.STORYBOOK_REACT_CLASSES = global.STORYBOOK_REACT_CLASSES || {}; -const babel = require('babel-core'); - -const { pkg } = readPkgUp.sync(); - -const hasDependency = name => - (pkg.devDependencies && pkg.devDependencies[name]) || - (pkg.dependencies && pkg.dependencies[name]) || - fs.existsSync(path.join('node_modules', name, 'package.json')); +export { + getSnapshotFileName, + snapshot, + multiSnapshotWithOptions, + snapshotWithOptions, + shallowSnapshot, + renderOnly, + imageSnapshot, +}; export default function testStorySnapshots(options = {}) { - addons.setChannel(createChannel()); - - const isStorybook = - options.framework === 'react' || (!options.framework && hasDependency('@storybook/react')); - const isRNStorybook = - options.framework === 'react-native' || - (!options.framework && hasDependency('@storybook/react-native')); - - if (isStorybook) { - storybook = require.requireActual('@storybook/react'); - // eslint-disable-next-line - const loadBabelConfig = require('@storybook/react/dist/server/babel_config') - .default; - const configDirPath = path.resolve(options.configPath || '.storybook'); - configPath = path.join(configDirPath, 'config.js'); - - const babelConfig = loadBabelConfig(configDirPath); - const content = babel.transformFileSync(configPath, babelConfig).code; - const contextOpts = { - filename: configPath, - dirname: configDirPath, - }; - - runWithRequireContext(content, contextOpts); - } else if (isRNStorybook) { - storybook = require.requireActual('@storybook/react-native'); - - configPath = path.resolve(options.configPath || 'storybook'); - require.requireActual(configPath); - } else { - throw new Error('storyshots is intended only to be used with storybook'); - } - if (typeof describe !== 'function') { throw new Error('testStorySnapshots is intended only to be used inside jest'); } - // NOTE: keep `suit` typo for backwards compatibility - const suite = options.suite || options.suit || 'Storyshots'; + addons.setChannel(createChannel()); + + const { storybook, framework, renderTree, renderShallowTree } = loadFramework(options); const stories = storybook.getStorybook(); if (stories.length === 0) { throw new Error('storyshots found 0 stories'); } - // Added not to break existing storyshots configs (can be removed in a future major release) - // eslint-disable-next-line - options.storyNameRegex = options.storyNameRegex || options.storyRegex; + // NOTE: keep `suit` typo for backwards compatibility + const suite = options.suite || options.suit || 'Storyshots'; + // NOTE: Added not to break existing storyshots configs (can be removed in a future major release) + const storyNameRegex = options.storyNameRegex || options.storyRegex; + const snapshotOptions = { renderer: options.renderer, serializer: options.serializer, }; - // eslint-disable-next-line - options.test = - options.test || snapshotWithOptions({ options: snapshotOptions }); + + const testMethod = options.test || snapshotWithOptions({ options: snapshotOptions }); // eslint-disable-next-line for (const group of stories) { @@ -103,15 +65,15 @@ export default function testStorySnapshots(options = {}) { describe(suite, () => { beforeEach(() => { - if (typeof options.test.beforeEach === 'function') { - return options.test.beforeEach(); + if (typeof testMethod.beforeEach === 'function') { + return testMethod.beforeEach(); } return Promise.resolve(); }); afterEach(() => { - if (typeof options.test.afterEach === 'function') { - return options.test.afterEach(); + if (typeof testMethod.afterEach === 'function') { + return testMethod.afterEach(); } return Promise.resolve(); }); @@ -119,16 +81,18 @@ export default function testStorySnapshots(options = {}) { describe(kind, () => { // eslint-disable-next-line for (const story of group.stories) { - if (options.storyNameRegex && !story.name.match(options.storyNameRegex)) { + if (storyNameRegex && !story.name.match(storyNameRegex)) { // eslint-disable-next-line continue; } it(story.name, () => { - const context = { fileName, kind, story: story.name, isRNStorybook }; - return options.test({ + const context = { fileName, kind, story: story.name, framework }; + return testMethod({ story, context, + renderTree, + renderShallowTree, }); }); } diff --git a/addons/storyshots/src/react/loader.js b/addons/storyshots/src/react/loader.js new file mode 100644 index 000000000000..552ed5b5ea80 --- /dev/null +++ b/addons/storyshots/src/react/loader.js @@ -0,0 +1,28 @@ +import runWithRequireContext from '../require_context'; +import hasDependency from '../hasDependency'; +import loadConfig from '../config-loader'; + +function test(options) { + return options.framework === 'react' || (!options.framework && hasDependency('@storybook/react')); +} + +function load(options) { + const { content, contextOpts } = loadConfig({ + configDirPath: options.configPath, + babelConfigPath: '@storybook/react/dist/server/babel_config', + }); + + runWithRequireContext(content, contextOpts); + + return { + framework: 'react', + renderTree: require.requireActual('./renderTree').default, + renderShallowTree: require.requireActual('./renderShallowTree').default, + storybook: require.requireActual('@storybook/react'), + }; +} + +export default { + load, + test, +}; diff --git a/addons/storyshots/src/react/renderShallowTree.js b/addons/storyshots/src/react/renderShallowTree.js new file mode 100644 index 000000000000..022b7c9ae0a0 --- /dev/null +++ b/addons/storyshots/src/react/renderShallowTree.js @@ -0,0 +1,11 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import shallow from 'react-test-renderer/shallow'; + +function getRenderedTree(story, context, { renderer, serializer }) { + const storyElement = story.render(context); + const shallowRenderer = renderer || shallow.createRenderer(); + const tree = shallowRenderer.render(storyElement); + return serializer ? serializer(tree) : tree; +} + +export default getRenderedTree; diff --git a/addons/storyshots/src/react/renderTree.js b/addons/storyshots/src/react/renderTree.js new file mode 100644 index 000000000000..3ad4b81b9eb3 --- /dev/null +++ b/addons/storyshots/src/react/renderTree.js @@ -0,0 +1,11 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import reactTestRenderer from 'react-test-renderer'; + +function getRenderedTree(story, context, { renderer, serializer, ...rendererOptions }) { + const storyElement = story.render(context); + const currentRenderer = renderer || reactTestRenderer.create; + const tree = currentRenderer(storyElement, rendererOptions); + return serializer ? serializer(tree) : tree; +} + +export default getRenderedTree; diff --git a/addons/storyshots/src/rn/loader.js b/addons/storyshots/src/rn/loader.js new file mode 100644 index 000000000000..5d0d3a92534c --- /dev/null +++ b/addons/storyshots/src/rn/loader.js @@ -0,0 +1,29 @@ +/* eslint-disable global-require */ +import path from 'path'; +import hasDependency from '../hasDependency'; + +function test(options) { + return ( + options.framework === 'react-native' || + (!options.framework && hasDependency('@storybook/react-native')) + ); +} + +function load(options) { + const storybook = require.requireActual('@storybook/react-native'); + + const configPath = path.resolve(options.configPath || 'storybook'); + require.requireActual(configPath); + + return { + renderTree: require('../react/renderTree').default, + renderShallowTree: require('../react/renderShallowTree').default, + framework: 'rn', + storybook, + }; +} + +export default { + load, + test, +}; diff --git a/addons/storyshots/src/test-bodies.js b/addons/storyshots/src/test-bodies.js index 7380e72ac0dc..2cc98c8c72bc 100644 --- a/addons/storyshots/src/test-bodies.js +++ b/addons/storyshots/src/test-bodies.js @@ -1,43 +1,54 @@ -import reactTestRenderer from 'react-test-renderer'; -import shallow from 'react-test-renderer/shallow'; import 'jest-specific-snapshot'; import { getSnapshotFileName } from './utils'; -function getRenderedTree(story, context, { renderer, serializer, ...rendererOptions }) { - const currentRenderer = renderer || reactTestRenderer.create; - const storyElement = story.render(context); - const tree = currentRenderer(storyElement, rendererOptions); - return serializer ? serializer(tree) : tree; -} - -export const snapshotWithOptions = options => ({ story, context, snapshotFileName }) => { - const tree = getRenderedTree(story, context, options); - - if (snapshotFileName) { - expect(tree).toMatchSpecificSnapshot(snapshotFileName); - } else { - expect(tree).toMatchSnapshot(); +export const snapshotWithOptions = options => ({ + story, + context, + renderTree, + snapshotFileName, +}) => { + const result = renderTree(story, context, options); + + function match(tree) { + if (snapshotFileName) { + expect(tree).toMatchSpecificSnapshot(snapshotFileName); + } else { + expect(tree).toMatchSnapshot(); + } + + if (typeof tree.unmount === 'function') { + tree.unmount(); + } } - if (typeof tree.unmount === 'function') { - tree.unmount(); + if (typeof result.then === 'function') { + return result.then(match); } -}; -export const multiSnapshotWithOptions = options => ({ story, context }) => { - snapshotWithOptions(options)({ story, context, snapshotFileName: getSnapshotFileName(context) }); + return match(result); }; -export const snapshot = snapshotWithOptions({}); - -export function shallowSnapshot({ story, context, options: { renderer, serializer } }) { - const shallowRenderer = renderer || shallow.createRenderer(); - const tree = shallowRenderer.render(story.render(context)); - const serializedTree = serializer ? serializer(tree) : tree; - expect(serializedTree).toMatchSnapshot(); +export const multiSnapshotWithOptions = options => ({ story, context, renderTree }) => + snapshotWithOptions(options)({ + story, + context, + renderTree, + snapshotFileName: getSnapshotFileName(context), + }); + +export function shallowSnapshot({ story, context, renderShallowTree, options = {} }) { + const result = renderShallowTree(story, context, options); + expect(result).toMatchSnapshot(); } -export function renderOnly({ story, context }) { - const storyElement = story.render(context); - reactTestRenderer.create(storyElement); +export function renderOnly({ story, context, renderTree }) { + const result = renderTree(story, context, {}); + + if (typeof result.then === 'function') { + return result; + } + + return undefined; } + +export const snapshot = snapshotWithOptions({}); diff --git a/addons/storyshots/src/test-body-image-snapshot.js b/addons/storyshots/src/test-body-image-snapshot.js index 94aadf4de741..f05b83464b0b 100644 --- a/addons/storyshots/src/test-body-image-snapshot.js +++ b/addons/storyshots/src/test-body-image-snapshot.js @@ -11,7 +11,7 @@ export const imageSnapshot = ({ let page; // Hold ref to the page to screenshot. const testFn = ({ context }) => { - if (context.isRNStorybook) { + if (context.framework === 'rn') { // Skip tests since we de not support RN image snapshots. console.error( "It seems you are running imageSnapshot on RN app and it's not supported. Skipping test." diff --git a/addons/storyshots/src/utils.test.js b/addons/storyshots/src/utils.test.js new file mode 100644 index 000000000000..1bd20e07effd --- /dev/null +++ b/addons/storyshots/src/utils.test.js @@ -0,0 +1,46 @@ +import { getPossibleStoriesFiles, getSnapshotFileName } from './utils'; + +describe('getSnapshotFileName', () => { + it('fileName is provided - snapshot is stored in __snapshots__ dir', () => { + const context = { fileName: 'foo.js' }; + + const result = getSnapshotFileName(context); + const platformAgnosticResult = result.replace(/\\|\//g, '/'); + + expect(platformAgnosticResult).toBe('__snapshots__/foo.storyshot'); + }); + + it('fileName with multiple extensions is provided - only the last extension is replaced', () => { + const context = { fileName: 'foo.web.stories.js' }; + + const result = getSnapshotFileName(context); + const platformAgnosticResult = result.replace(/\\|\//g, '/'); + + expect(platformAgnosticResult).toBe('__snapshots__/foo.web.stories.storyshot'); + }); + + it('fileName with dir is provided - __snapshots__ dir is created inside another dir', () => { + const context = { fileName: 'test/foo.js' }; + + const result = getSnapshotFileName(context); + const platformAgnosticResult = result.replace(/\\|\//g, '/'); + + expect(platformAgnosticResult).toBe('test/__snapshots__/foo.storyshot'); + }); +}); + +describe('getPossibleStoriesFiles', () => { + it('storyshots is provided and all the posible stories file names are returned', () => { + const storyshots = 'test/__snapshots__/foo.web.stories.storyshot'; + + const result = getPossibleStoriesFiles(storyshots); + const platformAgnosticResult = result.map(path => path.replace(/\\|\//g, '/')); + + expect(platformAgnosticResult).toEqual([ + 'test/foo.web.stories.js', + 'test/foo.web.stories.jsx', + 'test/foo.web.stories.ts', + 'test/foo.web.stories.tsx', + ]); + }); +}); diff --git a/addons/storyshots/src/vue/loader.js b/addons/storyshots/src/vue/loader.js new file mode 100644 index 000000000000..4b8e33429437 --- /dev/null +++ b/addons/storyshots/src/vue/loader.js @@ -0,0 +1,38 @@ +import global from 'global'; +import runWithRequireContext from '../require_context'; +import hasDependency from '../hasDependency'; +import loadConfig from '../config-loader'; + +function mockVueToIncludeCompiler() { + jest.mock('vue', () => require.requireActual('vue/dist/vue.common.js')); +} + +function test(options) { + return options.framework === 'vue' || (!options.framework && hasDependency('@storybook/vue')); +} + +function load(options) { + global.STORYBOOK_ENV = 'vue'; + mockVueToIncludeCompiler(); + + const { content, contextOpts } = loadConfig({ + configDirPath: options.configPath, + babelConfigPath: '@storybook/vue/dist/server/babel_config', + }); + + runWithRequireContext(content, contextOpts); + + return { + framework: 'vue', + renderTree: require.requireActual('./renderTree').default, + renderShallowTree: () => { + throw new Error('Shallow renderer is not supported for vue'); + }, + storybook: require.requireActual('@storybook/vue'), + }; +} + +export default { + load, + test, +}; diff --git a/addons/storyshots/src/vue/renderTree.js b/addons/storyshots/src/vue/renderTree.js new file mode 100644 index 000000000000..888c9820365f --- /dev/null +++ b/addons/storyshots/src/vue/renderTree.js @@ -0,0 +1,13 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import Vue from 'vue'; + +function getRenderedTree(story, context) { + const storyElement = story.render(context); + + const Constructor = Vue.extend(storyElement); + const vm = new Constructor().$mount(); + + return vm.$el; +} + +export default getRenderedTree; diff --git a/addons/storyshots/stories/__snapshots__/storyshot.shallow.test.js.snap b/addons/storyshots/stories/__snapshots__/storyshot.shallow.test.js.snap new file mode 100644 index 000000000000..15dd9f09577d --- /dev/null +++ b/addons/storyshots/stories/__snapshots__/storyshot.shallow.test.js.snap @@ -0,0 +1,122 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Another Button with some emoji 1`] = ` + + 😀 😎 👍 💯 + +`; + +exports[`Storyshots Another Button with text 1`] = ` + + Hello Button + +`; + +exports[`Storyshots Button with some emoji 1`] = ` + + 😀 😎 👍 💯 + +`; + +exports[`Storyshots Button with text 1`] = ` + + Hello Button + +`; + +exports[`Storyshots Welcome to Storybook 1`] = ` + + + Welcome to storybook + +

+ This is a UI component dev environment for your app. +

+

+ We've added some basic stories inside the + + + src/stories + + + directory. +
+ A story is a single state of one or more UI components. You can have as many stories as you want. +
+ (Basically a story is like a visual test case.) +

+

+ See these sample + + + stories + + + for a component called + + + Button + + . +

+

+ Just like that, you can add your own components as stories. +
+ You can also edit those components and see changes right away. +
+ (Try editing the + + Button + + stories located at + + src/stories/index.js + + .) +

+

+ Usually we create stories with smaller UI components in the app. +
+ Have a look at the + + + Writing Stories + + + section in our documentation. +

+ + + NOTE: + +
+ Have a look at the + + + .storybook/webpack.config.js + + + to add webpack loaders and plugins you are using in this project. +
+
+`; diff --git a/addons/storyshots/stories/__snapshots__/storyshot.shallowWithOptions.test.js.snap b/addons/storyshots/stories/__snapshots__/storyshot.shallowWithOptions.test.js.snap new file mode 100644 index 000000000000..c9abf91b5660 --- /dev/null +++ b/addons/storyshots/stories/__snapshots__/storyshot.shallowWithOptions.test.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Another Button with some emoji 1`] = `"{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"😀 😎 👍 💯\\",\\"className\\":\\"css-1yjiefr\\"},\\"_owner\\":null,\\"_store\\":{}}"`; + +exports[`Storyshots Another Button with text 1`] = `"{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"Hello Button\\",\\"className\\":\\"css-1yjiefr\\"},\\"_owner\\":null,\\"_store\\":{}}"`; + +exports[`Storyshots Button with some emoji 1`] = `"{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"😀 😎 👍 💯\\",\\"className\\":\\"css-1yjiefr\\"},\\"_owner\\":null,\\"_store\\":{}}"`; + +exports[`Storyshots Button with text 1`] = `"{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"Hello Button\\",\\"className\\":\\"css-1yjiefr\\"},\\"_owner\\":null,\\"_store\\":{}}"`; + +exports[`Storyshots Welcome to Storybook 1`] = `"{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":[{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"Welcome to storybook\\"},\\"_owner\\":null,\\"_store\\":{}},{\\"type\\":\\"p\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"This is a UI component dev environment for your app.\\"},\\"_owner\\":null,\\"_store\\":{}},{\\"type\\":\\"p\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":[\\"We've added some basic stories inside the\\",\\" \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"src/stories\\"},\\"_owner\\":null,\\"_store\\":{}},\\" \\",\\"directory.\\",{\\"type\\":\\"br\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{},\\"_owner\\":null,\\"_store\\":{}},\\"A story is a single state of one or more UI components. You can have as many stories as you want.\\",{\\"type\\":\\"br\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{},\\"_owner\\":null,\\"_store\\":{}},\\"(Basically a story is like a visual test case.)\\"]},\\"_owner\\":null,\\"_store\\":{}},{\\"type\\":\\"p\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":[\\"See these sample\\",\\" \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"role\\":\\"button\\",\\"tabIndex\\":\\"0\\",\\"children\\":\\"stories\\"},\\"_owner\\":null,\\"_store\\":{}},\\" \\",\\"for a component called\\",\\" \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"Button\\"},\\"_owner\\":null,\\"_store\\":{}},\\".\\"]},\\"_owner\\":null,\\"_store\\":{}},{\\"type\\":\\"p\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":[\\"Just like that, you can add your own components as stories.\\",{\\"type\\":\\"br\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{},\\"_owner\\":null,\\"_store\\":{}},\\"You can also edit those components and see changes right away.\\",{\\"type\\":\\"br\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{},\\"_owner\\":null,\\"_store\\":{}},\\"(Try editing the \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"Button\\"},\\"_owner\\":null,\\"_store\\":{}},\\" stories located at \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"src/stories/index.js\\"},\\"_owner\\":null,\\"_store\\":{}},\\".)\\"]},\\"_owner\\":null,\\"_store\\":{}},{\\"type\\":\\"p\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":[\\"Usually we create stories with smaller UI components in the app.\\",{\\"type\\":\\"br\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{},\\"_owner\\":null,\\"_store\\":{}},\\"Have a look at the\\",\\" \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"href\\":\\"https://storybook.js.org/basics/writing-stories\\",\\"target\\":\\"_blank\\",\\"rel\\":\\"noopener noreferrer\\",\\"children\\":\\"Writing Stories\\"},\\"_owner\\":null,\\"_store\\":{}},\\" \\",\\"section in our documentation.\\"]},\\"_owner\\":null,\\"_store\\":{}},{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":[{\\"type\\":\\"b\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\"NOTE:\\"},\\"_owner\\":null,\\"_store\\":{}},{\\"type\\":\\"br\\",\\"key\\":null,\\"ref\\":null,\\"props\\":{},\\"_owner\\":null,\\"_store\\":{}},\\"Have a look at the\\",\\" \\",{\\"key\\":null,\\"ref\\":null,\\"props\\":{\\"children\\":\\".storybook/webpack.config.js\\"},\\"_owner\\":null,\\"_store\\":{}},\\" \\",\\"to add webpack loaders and plugins you are using in this project.\\"]},\\"_owner\\":null,\\"_store\\":{}}]},\\"_owner\\":null,\\"_store\\":{}}"`; diff --git a/addons/storyshots/stories/storyshot.renderOnly.test.js b/addons/storyshots/stories/storyshot.renderOnly.test.js new file mode 100644 index 000000000000..e620764ed585 --- /dev/null +++ b/addons/storyshots/stories/storyshot.renderOnly.test.js @@ -0,0 +1,8 @@ +import path from 'path'; +import initStoryshots, { renderOnly } from '../src'; + +initStoryshots({ + framework: 'react', + configPath: path.join(__dirname, '..', '.storybook'), + test: renderOnly, +}); diff --git a/addons/storyshots/stories/storyshot.shallow.test.js b/addons/storyshots/stories/storyshot.shallow.test.js new file mode 100644 index 000000000000..ade88a49a696 --- /dev/null +++ b/addons/storyshots/stories/storyshot.shallow.test.js @@ -0,0 +1,8 @@ +import path from 'path'; +import initStoryshots, { shallowSnapshot } from '../src'; + +initStoryshots({ + framework: 'react', + configPath: path.join(__dirname, '..', '.storybook'), + test: shallowSnapshot, +}); diff --git a/addons/storyshots/stories/storyshot.shallowWithOptions.test.js b/addons/storyshots/stories/storyshot.shallowWithOptions.test.js new file mode 100644 index 000000000000..c5c9a79871c6 --- /dev/null +++ b/addons/storyshots/stories/storyshot.shallowWithOptions.test.js @@ -0,0 +1,14 @@ +import path from 'path'; +import initStoryshots, { shallowSnapshot } from '../src'; + +initStoryshots({ + framework: 'react', + configPath: path.join(__dirname, '..', '.storybook'), + test: data => + shallowSnapshot({ + ...data, + options: { + serializer: JSON.stringify, + }, + }), +}); diff --git a/examples/angular-cli/angularshots.test.js b/examples/angular-cli/angularshots.test.js new file mode 100644 index 000000000000..3b6b1f9ad06a --- /dev/null +++ b/examples/angular-cli/angularshots.test.js @@ -0,0 +1,8 @@ +import path from 'path'; +import initStoryshots, { multiSnapshotWithOptions } from '@storybook/addon-storyshots'; + +initStoryshots({ + framework: 'angular', + configPath: path.join(__dirname, '.storybook'), + test: multiSnapshotWithOptions({}), +}); diff --git a/examples/angular-cli/package.json b/examples/angular-cli/package.json index 2a34ee7fd862..ee9e2604e909 100644 --- a/examples/angular-cli/package.json +++ b/examples/angular-cli/package.json @@ -33,6 +33,7 @@ "@storybook/addon-actions": "^3.4.0-alpha.4", "@storybook/addon-links": "^3.4.0-alpha.4", "@storybook/addon-notes": "^3.4.0-alpha.4", + "@storybook/addon-storyshots": "^3.4.0-alpha.4", "@storybook/addons": "^3.4.0-alpha.4", "@storybook/angular": "^3.4.0-alpha.4", "@types/jasmine": "~2.8.3", diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-actions.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-actions.stories.storyshot new file mode 100644 index 000000000000..744f3a1be919 --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/addon-actions.stories.storyshot @@ -0,0 +1,45 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Addon Actions Action and method 1`] = ` + + + + + + + + + +`; + +exports[`Storyshots Addon Actions Action only 1`] = ` + + + + + + + + + +`; diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot new file mode 100644 index 000000000000..414dcc6097ba --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot @@ -0,0 +1,108 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Addon Knobs All knobs 1`] = ` + + + + +
+ + +

+ My name is Jane, +

+ + +

+ today is Jan 20, 2017 +

+ + + +

+ I have a stock of 20 apple, costing $ 2.25 each. +

+ + + + + +

+ Sorry. +

+ + +

+ Also, I have: +

+ + +
    + + + +
  • + + Laptop + +
  • +
  • + + Book + +
  • +
  • + + Whiskey + +
  • + + +
+ + + +

+ Nice to meet you! +

+ + + + + +
+ + +
+
+`; + +exports[`Storyshots Addon Knobs Simple 1`] = ` + + + + +
+ I am John Doe and I'm 44 years old. +
+ + +
+ Phone Number: 555-55-55 +
+ + +
+
+`; diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-links.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-links.stories.storyshot new file mode 100644 index 000000000000..08d0da6171b5 --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/addon-links.stories.storyshot @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Another Button button with link to another story 1`] = ` + + + + + + + + + +`; diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-notes.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-notes.stories.storyshot new file mode 100644 index 000000000000..94b5ff1c35a0 --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/addon-notes.stories.storyshot @@ -0,0 +1,45 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Addon Notes Note with HTML 1`] = ` + + + + + + + + + +`; + +exports[`Storyshots Addon Notes Simple note 1`] = ` + + + + + + + + + +`; diff --git a/examples/angular-cli/src/stories/__snapshots__/app.component.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/app.component.stories.storyshot new file mode 100644 index 000000000000..675463672c7a --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/app.component.stories.storyshot @@ -0,0 +1,105 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots App Component Component with separate template 1`] = ` + + + + +
+ + This should be hidden, if not - scss is not loaded as needed. + +
+ + +
+ + +

+ + Welcome to app! + +

+ + + + + +
+ + +

+ Here are some links to help you start: +

+ + + + + + +
+
+`; diff --git a/examples/angular-cli/src/stories/__snapshots__/custom-metadata.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/custom-metadata.stories.storyshot new file mode 100644 index 000000000000..d927a530a22b --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/custom-metadata.stories.storyshot @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Custom Pipe Default 1`] = ` + + +

+ CustomPipe: foobar +

+
+
+`; + +exports[`Storyshots Custom Pipe/With Knobs NameComponent 1`] = ` + + +

+ CustomPipe: foobar +

+
+
+`; + +exports[`Storyshots Custom ngModule metadata simple 1`] = ` + + + + +

+ Static name: +

+ + +
    + + + + + +
+ + +
+
+`; + +exports[`Storyshots Custom ngModule metadata with knobs 1`] = ` + + + + +

+ Dynamic knob: +

+ + +
    + + + + + +
+ + +
+
+`; diff --git a/examples/angular-cli/src/stories/__snapshots__/index.storyshot b/examples/angular-cli/src/stories/__snapshots__/index.storyshot new file mode 100644 index 000000000000..4b9cce2ec8be --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/index.storyshot @@ -0,0 +1,414 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Button with some emoji 1`] = ` + + + + + + + + + +`; + +exports[`Storyshots Button with text 1`] = ` + + + + +

+ This is a template +

+ + + + + + + + + + + + + + +
+ + +

+ Welcome to storybook +

+ + +

+ This is a UI component dev environment for your app. +

+ + +

+ + We've added some basic stories inside the + + src/stories + + directory. + +
+ + A story is a single state of one or more UI components. You can have as many stories as + you want. + +
+ + (Basically a story is like a visual test case.) + +

+ + +

+ + See these sample + + stories + + for a component called + + + Button + + . + +

+ + +

+ + Just like that, you can add your own components as stories. + +
+ + You can also edit those components and see changes right away. + +
+ + (Try editing the + + Button + + stories + located at + + src/stories/index.js + + .) + +

+ + +

+ + Usually we create stories with smaller UI components in the app. +
+ + Have a look at the + + + Writing Stories + + + section in our documentation. + +

+ + +

+ + + + NOTE: + + + +
+ + Have a look at the + + .storybook/webpack.config.js + + + to add webpack loaders and plugins you are using in this project. + +

+ + +
+ + +
+ + +
+
+`; + +exports[`Storyshots Welcome to Storybook 1`] = ` + + + + +
+ + +

+ Welcome to storybook +

+ + +

+ This is a UI component dev environment for your app. +

+ + +

+ + We've added some basic stories inside the + + src/stories + + directory. + +
+ + A story is a single state of one or more UI components. You can have as many stories as + you want. + +
+ + (Basically a story is like a visual test case.) + +

+ + +

+ + See these sample + + stories + + for a component called + + + Button + + . + +

+ + +

+ + Just like that, you can add your own components as stories. + +
+ + You can also edit those components and see changes right away. + +
+ + (Try editing the + + Button + + stories + located at + + src/stories/index.js + + .) + +

+ + +

+ + Usually we create stories with smaller UI components in the app. +
+ + Have a look at the + + + Writing Stories + + + section in our documentation. + +

+ + +

+ + + + NOTE: + + + +
+ + Have a look at the + + .storybook/webpack.config.js + + + to add webpack loaders and plugins you are using in this project. + +

+ + +
+ + +
+
+`; diff --git a/examples/angular-cli/src/stories/addon-links.stories.ts b/examples/angular-cli/src/stories/addon-links.stories.ts new file mode 100644 index 000000000000..c33f82b72565 --- /dev/null +++ b/examples/angular-cli/src/stories/addon-links.stories.ts @@ -0,0 +1,11 @@ +import { linkTo } from '@storybook/addon-links'; +import { storiesOf } from '@storybook/angular'; +import { Button } from '@storybook/angular/demo'; + +storiesOf('Another Button', module).add('button with link to another story', () => ({ + component: Button, + props: { + text: 'Go to Welcome Story', + onClick: linkTo('Welcome'), + }, +})); diff --git a/examples/angular-cli/src/stories/app.component.stories.ts b/examples/angular-cli/src/stories/app.component.stories.ts new file mode 100644 index 000000000000..f965a780e13d --- /dev/null +++ b/examples/angular-cli/src/stories/app.component.stories.ts @@ -0,0 +1,7 @@ +import { storiesOf } from '@storybook/angular'; +import { AppComponent } from '../app/app.component'; + +storiesOf('App Component', module).add('Component with separate template', () => ({ + component: AppComponent, + props: {}, +})); diff --git a/examples/angular-cli/src/stories/component-with-di/__snapshots__/di.component.stories.storyshot b/examples/angular-cli/src/stories/component-with-di/__snapshots__/di.component.stories.storyshot new file mode 100644 index 000000000000..84e7b5be377d --- /dev/null +++ b/examples/angular-cli/src/stories/component-with-di/__snapshots__/di.component.stories.storyshot @@ -0,0 +1,91 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Component dependencies inputs and inject dependencies 1`] = ` + + +
+ + +
+ All dependencies are defined: true +
+ + +
+ Title: Component dependencies +
+ + +
+ Injector: function Injector_(view, elDef) { + this.view = view; + this.elDef = elDef; + } +
+ + +
+ ElementRef: {"nativeElement":{}} +
+ + +
+ TestToken: 123 +
+ + +
+ + +
+
+`; + +exports[`Storyshots Component dependencies inputs and inject dependencies with knobs 1`] = ` + + +
+ + +
+ All dependencies are defined: true +
+ + +
+ Title: Component dependencies +
+ + +
+ Injector: function Injector_(view, elDef) { + this.view = view; + this.elDef = elDef; + } +
+ + +
+ ElementRef: {"nativeElement":{}} +
+ + +
+ TestToken: 123 +
+ + +
+ + +
+
+`; diff --git a/examples/angular-cli/src/stories/custom-metadata.stories.ts b/examples/angular-cli/src/stories/custom-metadata.stories.ts index 9b146c1e91c7..48c03a3ed644 100644 --- a/examples/angular-cli/src/stories/custom-metadata.stories.ts +++ b/examples/angular-cli/src/stories/custom-metadata.stories.ts @@ -1,8 +1,8 @@ import { storiesOf } from '@storybook/angular'; import { withKnobs, text } from '@storybook/addon-knobs/angular'; -import { NameComponent } from './name.component'; -import { CustomPipePipe } from './custom.pipe'; +import { NameComponent } from './moduleMetadata/name.component'; +import { CustomPipePipe } from './moduleMetadata/custom.pipe'; import { DummyService } from './moduleMetadata/dummy.service'; import { ServiceComponent } from './moduleMetadata/service.component'; diff --git a/examples/angular-cli/src/stories/customControlValueAccessor/__snapshots__/custom-cva-component.stories.storyshot b/examples/angular-cli/src/stories/customControlValueAccessor/__snapshots__/custom-cva-component.stories.storyshot new file mode 100644 index 000000000000..63c11a12c5e8 --- /dev/null +++ b/examples/angular-cli/src/stories/customControlValueAccessor/__snapshots__/custom-cva-component.stories.storyshot @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots ngModel custom ControlValueAccessor 1`] = ` + + + + +
+ Type anything +
+ + + + + +
+
+`; diff --git a/examples/angular-cli/src/stories/index.ts b/examples/angular-cli/src/stories/index.ts index 0a5a42b402e2..8d466dd40a45 100644 --- a/examples/angular-cli/src/stories/index.ts +++ b/examples/angular-cli/src/stories/index.ts @@ -1,8 +1,5 @@ import { storiesOf } from '@storybook/angular'; -import { linkTo } from '@storybook/addon-links'; - import { Welcome, Button } from '@storybook/angular/demo'; -import { AppComponent } from '../app/app.component'; storiesOf('Welcome', module).add('to Storybook', () => ({ component: Welcome, @@ -35,16 +32,3 @@ storiesOf('Button', module) onClick: () => {}, }, })); - -storiesOf('Another Button', module).add('button with link to another story', () => ({ - component: Button, - props: { - text: 'Go to Welcome Story', - onClick: linkTo('Welcome'), - }, -})); - -storiesOf('App Component', module).add('Component with separate template', () => ({ - component: AppComponent, - props: {}, -})); diff --git a/examples/angular-cli/src/stories/custom.pipe.ts b/examples/angular-cli/src/stories/moduleMetadata/custom.pipe.ts similarity index 100% rename from examples/angular-cli/src/stories/custom.pipe.ts rename to examples/angular-cli/src/stories/moduleMetadata/custom.pipe.ts diff --git a/examples/angular-cli/src/stories/name.component.ts b/examples/angular-cli/src/stories/moduleMetadata/name.component.ts similarity index 100% rename from examples/angular-cli/src/stories/name.component.ts rename to examples/angular-cli/src/stories/moduleMetadata/name.component.ts diff --git a/examples/vue-kitchen-sink/.babelrc b/examples/vue-kitchen-sink/.babelrc index bc4c4c4af5d2..0e1b2524d3d2 100644 --- a/examples/vue-kitchen-sink/.babelrc +++ b/examples/vue-kitchen-sink/.babelrc @@ -2,6 +2,11 @@ "presets": [ ["env", { "modules": false }], "vue" - ] + ], + "env": { + "test": { + "presets": ["env"] + } + } } diff --git a/examples/vue-kitchen-sink/.storybook/config.js b/examples/vue-kitchen-sink/.storybook/config.js index b386ac70f002..385320a351f4 100644 --- a/examples/vue-kitchen-sink/.storybook/config.js +++ b/examples/vue-kitchen-sink/.storybook/config.js @@ -5,8 +5,8 @@ import Vuex from 'vuex' import MyButton from '../src/stories/Button.vue' -Vue.component('my-button', MyButton) -Vue.use(Vuex) +Vue.component('my-button', MyButton); +Vue.use(Vuex); function loadStories() { require('../src/stories'); diff --git a/examples/vue-kitchen-sink/package.json b/examples/vue-kitchen-sink/package.json index 5717558a8d16..c12307b3dcee 100644 --- a/examples/vue-kitchen-sink/package.json +++ b/examples/vue-kitchen-sink/package.json @@ -8,6 +8,7 @@ "@storybook/addon-knobs": "^3.4.0-alpha.4", "@storybook/addon-links": "^3.4.0-alpha.4", "@storybook/addon-notes": "^3.4.0-alpha.4", + "@storybook/addon-storyshots": "^3.4.0-alpha.4", "@storybook/addon-viewport": "^3.4.0-alpha.4", "@storybook/addons": "^3.4.0-alpha.4", "@storybook/vue": "^3.4.0-alpha.4", diff --git a/examples/vue-kitchen-sink/src/stories/Welcome.vue b/examples/vue-kitchen-sink/src/stories/Welcome.vue index c3543330f4d1..42e291c2cc53 100644 --- a/examples/vue-kitchen-sink/src/stories/Welcome.vue +++ b/examples/vue-kitchen-sink/src/stories/Welcome.vue @@ -53,6 +53,9 @@ + +