From 97c2b4242f3f5a2056a392c40689e10551f0432b Mon Sep 17 00:00:00 2001 From: igor Date: Tue, 26 Dec 2017 16:32:18 +0200 Subject: [PATCH 01/41] Initial dirty-half-working-POC of the storyshots for angular --- addons/storyshots/package.json | 2 + addons/storyshots/src/angular/helpers.js | 62 +++++ addons/storyshots/src/angular/utils.js | 31 +++ addons/storyshots/src/index.js | 25 +- addons/storyshots/src/test-bodies.js | 62 ++++- addons/storyshots/src/utils.js | 2 + .../src/client/preview/angular/helpers.ts | 4 +- app/angular/src/client/preview/client_api.js | 8 +- .../src/client/preview/client_api.test.js | 71 ++++-- app/angular/src/client/preview/story_store.js | 12 +- examples/angular-cli/angularshots.test.js | 8 + examples/angular-cli/package.json | 1 + .../addon-actions.stories.storyshot | 31 +++ .../addon-knobs.stories.storyshot | 77 +++++++ .../addon-notes.stories.storyshot | 31 +++ .../custom-metadata.stories.storyshot | 62 +++++ .../src/stories/__snapshots__/index.storyshot | 218 ++++++++++++++++++ .../src/stories/addon-actions.stories.ts | 23 ++ .../src/stories/addon-knobs.stories.ts | 69 ++++++ .../src/stories/addon-notes.stories.ts | 33 +++ .../src/stories/custom-metadata.stories.ts | 67 ++++++ .../custom-cva-component.stories.storyshot | 17 ++ examples/angular-cli/src/stories/index.ts | 189 +-------------- jest.config.js | 6 + package.json | 3 +- tsconfig.json | 13 ++ yarn.lock | 80 ++++++- 27 files changed, 987 insertions(+), 220 deletions(-) create mode 100644 addons/storyshots/src/angular/helpers.js create mode 100644 addons/storyshots/src/angular/utils.js create mode 100644 examples/angular-cli/angularshots.test.js create mode 100644 examples/angular-cli/src/stories/__snapshots__/addon-actions.stories.storyshot create mode 100644 examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot create mode 100644 examples/angular-cli/src/stories/__snapshots__/addon-notes.stories.storyshot create mode 100644 examples/angular-cli/src/stories/__snapshots__/custom-metadata.stories.storyshot create mode 100644 examples/angular-cli/src/stories/__snapshots__/index.storyshot create mode 100644 examples/angular-cli/src/stories/addon-actions.stories.ts create mode 100644 examples/angular-cli/src/stories/addon-knobs.stories.ts create mode 100644 examples/angular-cli/src/stories/addon-notes.stories.ts create mode 100644 examples/angular-cli/src/stories/custom-metadata.stories.ts create mode 100644 examples/angular-cli/src/stories/customControlValueAccessor/__snapshots__/custom-cva-component.stories.storyshot create mode 100644 tsconfig.json diff --git a/addons/storyshots/package.json b/addons/storyshots/package.json index a882c91872a0..a83d544425e7 100644 --- a/addons/storyshots/package.json +++ b/addons/storyshots/package.json @@ -19,6 +19,7 @@ "babel-runtime": "^6.26.0", "glob": "^7.1.2", "global": "^4.3.2", + "jest-preset-angular": "^5.0.0", "jest-specific-snapshot": "^0.3.0", "prop-types": "^15.6.0", "read-pkg-up": "^3.0.0" @@ -40,6 +41,7 @@ }, "peerDependencies": { "@storybook/addons": "^3.3.0-rc.0", + "@angular/core": ">=4.0.0", "babel-core": "^6.26.0 | ^7.0.0-0", "react": "*", "react-test-renderer": "*" diff --git a/addons/storyshots/src/angular/helpers.js b/addons/storyshots/src/angular/helpers.js new file mode 100644 index 000000000000..6de591d4431d --- /dev/null +++ b/addons/storyshots/src/angular/helpers.js @@ -0,0 +1,62 @@ +import { Component } from '@angular/core'; +import { getPropMetadata, getAnnotations, getParameters } from './utils'; + +function getComponentMetadata({ component, props = {}, propsMeta = {}, moduleMetadata = {} }) { + const componentMetadata = getAnnotations(component)[0] || {}; + const propsMetadata = getPropMetadata(component); + const paramsMetadata = getParameters(component); + + // Object.keys(propsMeta).forEach(key => { + // propsMetadata[key] = props[key]; + // }); + + Object.keys(propsMeta).forEach(key => { + propsMetadata[key] = propsMeta[key]; + }); + + const { imports = [], schemas = [], declarations = [], providers = [] } = moduleMetadata; + + return { + component, + props, + componentMeta: componentMetadata, + propsMeta: propsMetadata, + params: paramsMetadata, + moduleMeta: { + imports, + schemas, + declarations, + providers, + }, + }; +} + +function getAnnotatedComponent(meta, component, propsMeta, params) { + const StoryshotComponent = function StoryshotComponent(...args) { + component.call(this, ...args); + }; + + StoryshotComponent.prototype = Object.create(component.prototype); + StoryshotComponent.annotations = [new Component(meta)]; + StoryshotComponent.parameters = params; + StoryshotComponent.propsMetadata = propsMeta; + + return StoryshotComponent; +} + +export function createAnnotatedComponent(story) { + const { component, componentMeta, props, propsMeta, params, moduleMeta } = getComponentMetadata( + story + ); + + const AnnotatedComponent = getAnnotatedComponent(componentMeta, component, propsMeta, [ + ...params, + ...moduleMeta.providers.map(provider => [provider]), + ]); + + return { + component: AnnotatedComponent, + props, + propsMeta, + }; +} diff --git a/addons/storyshots/src/angular/utils.js b/addons/storyshots/src/angular/utils.js new file mode 100644 index 000000000000..03a810a8d7e8 --- /dev/null +++ b/addons/storyshots/src/angular/utils.js @@ -0,0 +1,31 @@ +/* eslint-disable no-param-reassign */ +/* globals window */ + +function getMeta(component, [name1, name2], defaultValue) { + if (!name2) { + name2 = name1; + name1 = `__${name1}__`; + } + + if (component[name1]) { + return component[name1]; + } + + if (component[name2]) { + return component[name2]; + } + + return window.Reflect.getMetadata(name2, component) || defaultValue; +} + +export function getAnnotations(component) { + return getMeta(component, ['annotations'], []); +} + +export function getPropMetadata(component) { + return getMeta(component, ['__prop__metadata__', 'propMetadata'], {}); +} + +export function getParameters(component) { + return getMeta(component, ['parameters'], []); +} diff --git a/addons/storyshots/src/index.js b/addons/storyshots/src/index.js index c43acb301824..cb274fea5b79 100644 --- a/addons/storyshots/src/index.js +++ b/addons/storyshots/src/index.js @@ -1,3 +1,4 @@ +/* eslint-disable no-loop-func */ import path from 'path'; import fs from 'fs'; import glob from 'glob'; @@ -22,6 +23,7 @@ export { getSnapshotFileName }; let storybook; let configPath; +let framework; global.STORYBOOK_REACT_CLASSES = global.STORYBOOK_REACT_CLASSES || {}; const babel = require('babel-core'); @@ -38,11 +40,14 @@ export default function testStorySnapshots(options = {}) { const isStorybook = options.framework === 'react' || (!options.framework && hasDependency('@storybook/react')); + const isAngularStorybook = + options.framework === 'angular' || (!options.framework && hasDependency('@storybook/angular')); const isRNStorybook = options.framework === 'react-native' || (!options.framework && hasDependency('@storybook/react-native')); if (isStorybook) { + framework = 'react'; storybook = require.requireActual('@storybook/react'); // eslint-disable-next-line const loadBabelConfig = require('@storybook/react/dist/server/babel_config') @@ -57,8 +62,26 @@ export default function testStorySnapshots(options = {}) { dirname: configDirPath, }; + runWithRequireContext(content, contextOpts); + } else if (isAngularStorybook) { + framework = 'angular'; + storybook = require.requireActual('@storybook/angular'); + // eslint-disable-next-line + const loadBabelConfig = require('@storybook/angular/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) { + framework = 'rn'; storybook = require.requireActual('@storybook/react-native'); configPath = path.resolve(options.configPath || 'storybook'); @@ -109,7 +132,7 @@ export default function testStorySnapshots(options = {}) { } it(story.name, () => { - const context = { fileName, kind, story: story.name }; + const context = { fileName, kind, story: story.name, framework }; return options.test({ story, context, diff --git a/addons/storyshots/src/test-bodies.js b/addons/storyshots/src/test-bodies.js index 7380e72ac0dc..ba2bc96c0d36 100644 --- a/addons/storyshots/src/test-bodies.js +++ b/addons/storyshots/src/test-bodies.js @@ -1,11 +1,69 @@ +import 'jest-preset-angular/setupJest'; +import AngularSnapshotSerializer from 'jest-preset-angular/AngularSnapshotSerializer'; +import HTMLCommentSerializer from 'jest-preset-angular/HTMLCommentSerializer'; +import { TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; + import reactTestRenderer from 'react-test-renderer'; import shallow from 'react-test-renderer/shallow'; -import 'jest-specific-snapshot'; +import { addSerializer } from 'jest-specific-snapshot'; import { getSnapshotFileName } from './utils'; +import { createAnnotatedComponent } from './angular/helpers'; + +addSerializer(HTMLCommentSerializer); +addSerializer(AngularSnapshotSerializer); function getRenderedTree(story, context, { renderer, serializer, ...rendererOptions }) { - const currentRenderer = renderer || reactTestRenderer.create; const storyElement = story.render(context); + + if (context.framework === 'angular') { + const { + // props, + // component, + moduleMetadata = { imports: [], schemas: [], declarations: [], providers: [] }, + } = storyElement; + + const moduleMeta = { + imports: [], + schemas: [], + declarations: [], + providers: [], + ...moduleMetadata, + }; + + const { component: annotatedComponent } = createAnnotatedComponent(storyElement); + + TestBed.configureTestingModule({ + imports: [...moduleMeta.imports], + declarations: [annotatedComponent, ...moduleMeta.declarations], + providers: [...moduleMeta.providers], + schemas: [NO_ERRORS_SCHEMA, ...moduleMeta.schemas], + }); + + // this is async. Should be somehow called in beforeEach + TestBed.compileComponents(); + + const tree = TestBed.createComponent(annotatedComponent); + tree.detectChanges(); + + return tree; + + // TestBed.configureTestingModule({ + // imports: [...moduleMeta.imports], + // declarations: [component, ...moduleMeta.declarations], + // providers: [...moduleMeta.providers], + // schemas: [NO_ERRORS_SCHEMA, ...moduleMeta.schemas], + // }); + // + // TestBed.compileComponents(); + // + // const tree = TestBed.createComponent(component); + // tree.detectChanges(); + // + // return tree; + } + + const currentRenderer = renderer || reactTestRenderer.create; const tree = currentRenderer(storyElement, rendererOptions); return serializer ? serializer(tree) : tree; } diff --git a/addons/storyshots/src/utils.js b/addons/storyshots/src/utils.js index 36d8ec5c6c6e..fa1281d63c02 100644 --- a/addons/storyshots/src/utils.js +++ b/addons/storyshots/src/utils.js @@ -11,6 +11,8 @@ export function getPossibleStoriesFiles(storyshotFile) { return [ path.format({ dir: path.dirname(dir), name, ext: '.js' }), path.format({ dir: path.dirname(dir), name, ext: '.jsx' }), + path.format({ dir: path.dirname(dir), name, ext: '.ts' }), + path.format({ dir: path.dirname(dir), name, ext: '.tsx' }), ]; } diff --git a/app/angular/src/client/preview/angular/helpers.ts b/app/angular/src/client/preview/angular/helpers.ts index 1c10dbbc98b5..0e7a88df0e97 100644 --- a/app/angular/src/client/preview/angular/helpers.ts +++ b/app/angular/src/client/preview/angular/helpers.ts @@ -6,11 +6,11 @@ import { NgModuleRef } from '@angular/core'; import {FormsModule} from '@angular/forms' +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { BrowserModule } from '@angular/platform-browser'; import _debounce from 'lodash-es/debounce'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './components/app.component'; import { ErrorComponent } from './components/error.component'; import { NoPreviewComponent } from './components/no-preview.component'; diff --git a/app/angular/src/client/preview/client_api.js b/app/angular/src/client/preview/client_api.js index 0a17a4d48c5d..821f930bc6d1 100644 --- a/app/angular/src/client/preview/client_api.js +++ b/app/angular/src/client/preview/client_api.js @@ -69,8 +69,10 @@ export default class ClientApi { getStory ); + const fileName = m ? m.filename : null; + // Add the fully decorated getStory function. - this._storyStore.addStory(kind, storyName, getDecoratedStory); + this._storyStore.addStory(kind, storyName, getDecoratedStory, fileName); return api; }; @@ -84,11 +86,13 @@ export default class ClientApi { getStorybook() { return this._storyStore.getStoryKinds().map(kind => { + const fileName = this._storyStore.getStoryFileName(kind); + const stories = this._storyStore.getStories(kind).map(name => { const render = this._storyStore.getStory(kind, name); return { name, render }; }); - return { kind, stories }; + return { kind, fileName, stories }; }); } } diff --git a/app/angular/src/client/preview/client_api.test.js b/app/angular/src/client/preview/client_api.test.js index cb67ce2e59ab..2d8569d82c44 100644 --- a/app/angular/src/client/preview/client_api.test.js +++ b/app/angular/src/client/preview/client_api.test.js @@ -7,8 +7,8 @@ class StoryStore { this.stories = []; } - addStory(kind, story, fn) { - this.stories.push({ kind, story, fn }); + addStory(kind, story, fn, fileName) { + this.stories.push({ kind, story, fn, fileName }); } getStoryKinds() { @@ -29,6 +29,11 @@ class StoryStore { }, []); } + getStoryFileName(kind) { + const story = this.stories.find(info => info.kind === kind); + return story ? story.fileName : null; + } + getStory(kind, name) { return this.stories.reduce((fn, info) => { if (!fn && info.kind === kind && info.story === name) { @@ -55,7 +60,7 @@ describe('preview.client_api', () => { }, }); - api.storiesOf('none').aa(); + api.storiesOf('none', module).aa(); expect(data).toBe('foo'); }); @@ -76,7 +81,7 @@ describe('preview.client_api', () => { }); api - .storiesOf('none') + .storiesOf('none', module) .aa() .bb(); expect(data).toEqual(['foo', 'bar']); @@ -92,7 +97,7 @@ describe('preview.client_api', () => { }, }); - api.storiesOf('none').aa(); + api.storiesOf('none', module).aa(); expect(data).toBe('function'); }); @@ -112,7 +117,7 @@ describe('preview.client_api', () => { }, }); - api.storiesOf('none').bb(); + api.storiesOf('none', module).bb(); expect(data).toBe('foo'); }); @@ -127,7 +132,7 @@ describe('preview.client_api', () => { }, }); - api.storiesOf(kind).aa(); + api.storiesOf(kind, module).aa(); expect(data).toBe(kind); }); }); @@ -136,7 +141,7 @@ describe('preview.client_api', () => { it('should add local decorators', () => { const storyStore = new StoryStore(); const api = new ClientAPI({ storyStore }); - const localApi = api.storiesOf('none'); + const localApi = api.storiesOf('none', module); localApi.addDecorator(fn => `aa-${fn()}`); localApi.add('storyName', () => 'Hello'); @@ -147,7 +152,7 @@ describe('preview.client_api', () => { const storyStore = new StoryStore(); const api = new ClientAPI({ storyStore }); api.addDecorator(fn => `bb-${fn()}`); - const localApi = api.storiesOf('none'); + const localApi = api.storiesOf('none', module); localApi.add('storyName', () => 'Hello'); expect(storyStore.stories[0].fn()).toBe('bb-Hello'); @@ -156,7 +161,7 @@ describe('preview.client_api', () => { it('should utilize both decorators at once', () => { const storyStore = new StoryStore(); const api = new ClientAPI({ storyStore }); - const localApi = api.storiesOf('none'); + const localApi = api.storiesOf('none', module); api.addDecorator(fn => `aa-${fn()}`); localApi.addDecorator(fn => `bb-${fn()}`); @@ -168,7 +173,7 @@ describe('preview.client_api', () => { it('should pass the context', () => { const storyStore = new StoryStore(); const api = new ClientAPI({ storyStore }); - const localApi = api.storiesOf('none'); + const localApi = api.storiesOf('none', module); localApi.addDecorator(fn => `aa-${fn()}`); localApi.add('storyName', ({ kind, story }) => `${kind}-${story}`); @@ -183,7 +188,7 @@ describe('preview.client_api', () => { it('should have access to the context', () => { const storyStore = new StoryStore(); const api = new ClientAPI({ storyStore }); - const localApi = api.storiesOf('none'); + const localApi = api.storiesOf('none', module); localApi.addDecorator((fn, { kind, story }) => `${kind}-${story}-${fn()}`); localApi.add('storyName', () => 'Hello'); @@ -222,16 +227,53 @@ describe('preview.client_api', () => { 'story-2.1': () => 'story-2.1', 'story-2.2': () => 'story-2.2', }; - const kind1 = api.storiesOf('kind-1'); + const kind1 = api.storiesOf('kind-1', { filename: 'kind1.js' }); + kind1.add('story-1.1', functions['story-1.1']); + kind1.add('story-1.2', functions['story-1.2']); + const kind2 = api.storiesOf('kind-2', { filename: 'kind2.js' }); + kind2.add('story-2.1', functions['story-2.1']); + kind2.add('story-2.2', functions['story-2.2']); + const book = api.getStorybook(); + expect(book).toEqual([ + { + kind: 'kind-1', + fileName: 'kind1.js', + stories: [ + { name: 'story-1.1', render: functions['story-1.1'] }, + { name: 'story-1.2', render: functions['story-1.2'] }, + ], + }, + { + kind: 'kind-2', + fileName: 'kind2.js', + stories: [ + { name: 'story-2.1', render: functions['story-2.1'] }, + { name: 'story-2.2', render: functions['story-2.2'] }, + ], + }, + ]); + }); + + it('should return storybook with file names when module with file name provided', () => { + const storyStore = new StoryStore(); + const api = new ClientAPI({ storyStore }); + const functions = { + 'story-1.1': () => 'story-1.1', + 'story-1.2': () => 'story-1.2', + 'story-2.1': () => 'story-2.1', + 'story-2.2': () => 'story-2.2', + }; + const kind1 = api.storiesOf('kind-1', { filename: 'foo' }); kind1.add('story-1.1', functions['story-1.1']); kind1.add('story-1.2', functions['story-1.2']); - const kind2 = api.storiesOf('kind-2'); + const kind2 = api.storiesOf('kind-2', { filename: 'bar' }); kind2.add('story-2.1', functions['story-2.1']); kind2.add('story-2.2', functions['story-2.2']); const book = api.getStorybook(); expect(book).toEqual([ { kind: 'kind-1', + fileName: 'foo', stories: [ { name: 'story-1.1', render: functions['story-1.1'] }, { name: 'story-1.2', render: functions['story-1.2'] }, @@ -239,6 +281,7 @@ describe('preview.client_api', () => { }, { kind: 'kind-2', + fileName: 'bar', stories: [ { name: 'story-2.1', render: functions['story-2.1'] }, { name: 'story-2.2', render: functions['story-2.2'] }, diff --git a/app/angular/src/client/preview/story_store.js b/app/angular/src/client/preview/story_store.js index 99ebbc02f740..000deeb2aaa9 100644 --- a/app/angular/src/client/preview/story_store.js +++ b/app/angular/src/client/preview/story_store.js @@ -12,10 +12,11 @@ export default class StoryStore { this._data = {}; } - addStory(kind, name, fn) { + addStory(kind, name, fn, fileName) { if (!this._data[kind]) { this._data[kind] = { kind, + fileName, index: getId(), stories: {}, }; @@ -47,6 +48,15 @@ export default class StoryStore { .map(info => info.name); } + getStoryFileName(kind) { + const storiesKind = this._data[kind]; + if (!storiesKind) { + return null; + } + + return storiesKind.fileName; + } + getStory(kind, name) { const storiesKind = this._data[kind]; if (!storiesKind) { 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 3194f55468d0..cce505531608 100644 --- a/examples/angular-cli/package.json +++ b/examples/angular-cli/package.json @@ -34,6 +34,7 @@ "@storybook/addon-actions": "^3.3.0-rc.0", "@storybook/addon-links": "^3.3.0-rc.0", "@storybook/addon-notes": "^3.3.0-rc.0", + "@storybook/addon-storyshots": "^3.3.0-rc.0", "@storybook/addons": "^3.3.0-rc.0", "@storybook/angular": "^3.3.0-rc.0", "@types/jasmine": "~2.8.2", 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..f544931485dc --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/addon-actions.stories.storyshot @@ -0,0 +1,31 @@ +// 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..a6d2a30d2997 --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot @@ -0,0 +1,77 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Addon Knobs All knobs 1`] = ` + + +
+ + +

+ My name is , +

+ + +

+ today is +

+ + + + + + +

+ I'm out of . +

+ + + + +

+ Also, I have: +

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

+ Leave me alone! +

+ + +
+ +
+`; + +exports[`Storyshots Addon Knobs Simple 1`] = ` + + +
+ I am and I'm years old. +
+ +
+`; 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..182ee5c71d16 --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/addon-notes.stories.storyshot @@ -0,0 +1,31 @@ +// 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__/custom-metadata.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/custom-metadata.stories.storyshot new file mode 100644 index 000000000000..d3a4b5f7c282 --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/custom-metadata.stories.storyshot @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Custom Pipe Default 1`] = ` + +

+ CustomPipe: undefined +

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

+ CustomPipe: undefined +

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

+ : +

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

+ : +

+ + +
+`; 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..0eecf1b5062f --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/index.storyshot @@ -0,0 +1,218 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Another Button button with link to another story 1`] = ` + + + + + +`; + +exports[`Storyshots Button with some emoji 1`] = ` + + + + + +`; + +exports[`Storyshots Button with text 1`] = ` + + + + + +`; + +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-actions.stories.ts b/examples/angular-cli/src/stories/addon-actions.stories.ts new file mode 100644 index 000000000000..8154940d7232 --- /dev/null +++ b/examples/angular-cli/src/stories/addon-actions.stories.ts @@ -0,0 +1,23 @@ +import { storiesOf } from '@storybook/angular'; +import { action } from '@storybook/addon-actions'; +import { Button } from '@storybook/angular/demo'; + +storiesOf('Addon Actions', module) + .add('Action only', () => ({ + component: Button, + props: { + text: 'Action only', + onClick: action('log 1') + } + })) + .add('Action and method', () => ({ + component: Button, + props: { + text: 'Action and Method', + onClick: e => { + console.log(e); + e.preventDefault(); + action('log2')(e.target); + } + } + })); diff --git a/examples/angular-cli/src/stories/addon-knobs.stories.ts b/examples/angular-cli/src/stories/addon-knobs.stories.ts new file mode 100644 index 000000000000..f3528056afd8 --- /dev/null +++ b/examples/angular-cli/src/stories/addon-knobs.stories.ts @@ -0,0 +1,69 @@ +import { storiesOf } from '@storybook/angular'; +import { action } from '@storybook/addon-actions'; + +import { + withKnobs, + text, + number, + boolean, + array, + select, + color, + date, + button, +} from '@storybook/addon-knobs/angular'; + +import { SimpleKnobsComponent } from './knobs.component'; +import { AllKnobsComponent } from './all-knobs.component'; + +storiesOf('Addon Knobs', module) + .addDecorator(withKnobs) + .add('Simple', () => { + const name = text('Name', 'John Doe'); + const age = number('Age', 44); + + return { + component: SimpleKnobsComponent, + props: { + name, + age + } + }; + }) + .add('All knobs', () => { + const name = text('Name', 'Jane'); + const stock = number('Stock', 20, { + range: true, + min: 0, + max: 30, + step: 5, + }); + const fruits = { + apples: 'Apple', + bananas: 'Banana', + cherries: 'Cherry', + }; + const fruit = select('Fruit', fruits, 'apple'); + const price = number('Price', 2.25); + + const border = color('Border', 'deeppink'); + const today = date('Today', new Date('Jan 20 2017')); + const items = array('Items', ['Laptop', 'Book', 'Whiskey']); + const nice = boolean('Nice', true); + button('Arbitrary action', action('You clicked it!')); + + return { + component: AllKnobsComponent, + props: { + name, + stock, + fruits, + fruit, + price, + border, + today, + items, + nice + } + }; + }); diff --git a/examples/angular-cli/src/stories/addon-notes.stories.ts b/examples/angular-cli/src/stories/addon-notes.stories.ts new file mode 100644 index 000000000000..bee7284d04ee --- /dev/null +++ b/examples/angular-cli/src/stories/addon-notes.stories.ts @@ -0,0 +1,33 @@ +import { storiesOf } from '@storybook/angular'; +import { withNotes } from '@storybook/addon-notes'; +import { Button } from '@storybook/angular/demo'; + +storiesOf('Addon Notes', module) + .add( + 'Simple note', + withNotes({ text: 'My notes on some button' })(() => ({ + component: Button, + props: { + text: 'Notes on some Button', + onClick: () => {}, + } + })) + ) + .add( + 'Note with HTML', + withNotes({ + text: ` +

My notes on emojis

+ + It's not all that important to be honest, but.. + + Emojis are great, I love emojis, in fact I like using them in my Component notes too! ๐Ÿ˜‡ + `, + })(() => ({ + component: Button, + props: { + text: 'Notes with HTML', + onClick: () => {}, + } + })) + ); diff --git a/examples/angular-cli/src/stories/custom-metadata.stories.ts b/examples/angular-cli/src/stories/custom-metadata.stories.ts new file mode 100644 index 000000000000..80e014205ae0 --- /dev/null +++ b/examples/angular-cli/src/stories/custom-metadata.stories.ts @@ -0,0 +1,67 @@ +import { storiesOf } from '@storybook/angular'; +import { withKnobs, text } from '@storybook/addon-knobs/angular'; + +import { NameComponent } from './name.component'; +import { CustomPipePipe } from './custom.pipe'; +import { DummyService } from './moduleMetadata/dummy.service'; +import { ServiceComponent } from './moduleMetadata/service.component' + +storiesOf('Custom Pipe', module) + .add('Default', () => ({ + component: NameComponent, + props: { + field: 'foobar', + }, + moduleMetadata: { + imports: [], + schemas: [], + declarations: [CustomPipePipe], + providers: [] + } + })); + +storiesOf('Custom Pipe/With Knobs', module) + .addDecorator(withKnobs) + .add('NameComponent', () => ({ + component: NameComponent, + props: { + field: text('field', 'foobar'), + }, + moduleMetadata: { + imports: [], + schemas: [], + declarations: [CustomPipePipe], + providers: [] + } + })); + +storiesOf('Custom ngModule metadata', module) + .add('simple', () => ({ + component: ServiceComponent, + props: { + name: 'Static name' + }, + moduleMetadata: { + imports: [], + schemas: [], + declarations: [], + providers: [DummyService] + } + })) + .addDecorator(withKnobs) + .add('with knobs', () => { + const name = text('Name', 'Dynamic knob'); + + return { + component: ServiceComponent, + props: { + name + }, + moduleMetadata: { + imports: [], + schemas: [], + declarations: [], + providers: [DummyService] + } + }; + }); 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..1ae6eea489e9 --- /dev/null +++ b/examples/angular-cli/src/stories/customControlValueAccessor/__snapshots__/custom-cva-component.stories.storyshot @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots ngModel custom ControlValueAccessor 1`] = ` + + +
+ +
+ + +
+`; diff --git a/examples/angular-cli/src/stories/index.ts b/examples/angular-cli/src/stories/index.ts index d755b4898847..08847b245744 100644 --- a/examples/angular-cli/src/stories/index.ts +++ b/examples/angular-cli/src/stories/index.ts @@ -1,35 +1,14 @@ import { storiesOf } from '@storybook/angular'; - -import { withNotes } from '@storybook/addon-notes'; -import { action } from '@storybook/addon-actions'; import { linkTo } from '@storybook/addon-links'; -import { - withKnobs, - text, - number, - boolean, - array, - select, - color, - date, - button, -} from '@storybook/addon-knobs/angular'; - import { Welcome, Button } from '@storybook/angular/demo'; -import { SimpleKnobsComponent } from './knobs.component'; -import { AllKnobsComponent } from './all-knobs.component'; import { AppComponent } from '../app/app.component'; -import { DummyService } from './moduleMetadata/dummy.service'; -import { ServiceComponent } from './moduleMetadata/service.component' -import { NameComponent } from './name.component'; -import { CustomPipePipe } from './custom.pipe'; storiesOf('Welcome', module) .add('to Storybook', () => ({ component: Welcome, props: {} - })) + })); storiesOf('Button', module) .add('with text', () => ({ @@ -45,7 +24,7 @@ storiesOf('Button', module) text: '๐Ÿ˜€ ๐Ÿ˜Ž ๐Ÿ‘ ๐Ÿ’ฏ', onClick: () => {} } - })) + })); storiesOf('Another Button', module) .add('button with link to another story', () => ({ @@ -54,173 +33,11 @@ storiesOf('Another Button', module) text: 'Go to Welcome Story', onClick: linkTo('Welcome') } - })) + })); storiesOf('App Component', module) .add('Component with separate template', () => ({ component: AppComponent, props: {} - })) - -storiesOf('Addon Actions', module) - .add('Action only', () => ({ - component: Button, - props: { - text: 'Action only', - onClick: action('log 1') - } - })) - .add('Action and method', () => ({ - component: Button, - props: { - text: 'Action and Method', - onClick: e => { - console.log(e); - e.preventDefault(); - action('log2')(e.target); - } - } })); -storiesOf('Addon Notes', module) - .add( - 'Simple note', - withNotes({ text: 'My notes on some button' })(() => ({ - component: Button, - props: { - text: 'Notes on some Button', - onClick: () => {}, - } - })) - ) - .add( - 'Note with HTML', - withNotes({ - text: ` -

My notes on emojis

- - It's not all that important to be honest, but.. - - Emojis are great, I love emojis, in fact I like using them in my Component notes too! ๐Ÿ˜‡ - `, - })(() => ({ - component: Button, - props: { - text: 'Notes with HTML', - onClick: () => {}, - } - })) - ); - - -storiesOf('Addon Knobs', module) - .addDecorator(withKnobs) - .add('Simple', () => { - const name = text('Name', 'John Doe'); - const age = number('Age', 44); - - return { - component: SimpleKnobsComponent, - props: { - name, - age - } - }; - }) - .add('All knobs', () => { - const name = text('Name', 'Jane'); - const stock = number('Stock', 20, { - range: true, - min: 0, - max: 30, - step: 5, - }); - const fruits = { - apples: 'Apple', - bananas: 'Banana', - cherries: 'Cherry', - }; - const fruit = select('Fruit', fruits, 'apple'); - const price = number('Price', 2.25); - - const border = color('Border', 'deeppink'); - const today = date('Today', new Date('Jan 20 2017')); - const items = array('Items', ['Laptop', 'Book', 'Whiskey']); - const nice = boolean('Nice', true); - button('Arbitrary action', action('You clicked it!')); - - return { - component: AllKnobsComponent, - props: { - name, - stock, - fruits, - fruit, - price, - border, - today, - items, - nice - } - }; - }); - -storiesOf('Custom ngModule metadata', module) - .add('simple', () => ({ - component: ServiceComponent, - props: { - name: 'Static name' - }, - moduleMetadata: { - imports: [], - schemas: [], - declarations: [], - providers: [DummyService] - } - })) - .addDecorator(withKnobs) - .add('with knobs', () => { - const name = text('Name', 'Dynamic knob'); - - return { - component: ServiceComponent, - props: { - name - }, - moduleMetadata: { - imports: [], - schemas: [], - declarations: [], - providers: [DummyService] - } - }; - }); - -storiesOf('Custom Pipe', module) - .add('Default', () => ({ - component: NameComponent, - props: { - field: 'foobar', - }, - moduleMetadata: { - imports: [], - schemas: [], - declarations: [CustomPipePipe], - providers: [] - } - })); - -storiesOf('Custom Pipe/With Knobs', module) - .addDecorator(withKnobs) - .add('NameComponent', () => ({ - component: NameComponent, - props: { - field: text('field', 'foobar'), - }, - moduleMetadata: { - imports: [], - schemas: [], - declarations: [CustomPipePipe], - providers: [] - } - })); diff --git a/jest.config.js b/jest.config.js index 62bfa9a4effe..7b89e00ef731 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,7 +13,12 @@ module.exports = { '/lib', '/examples/cra-kitchen-sink', '/examples/official-storybook', + '/examples/angular-cli', ], + transform: { + '^.+\\.tsx?$': 'ts-jest', + '^.+\\.jsx?$': 'babel-jest', + }, transformIgnorePatterns: ['/node_modules/(?!lodash-es/.*)'], testPathIgnorePatterns: ['/node_modules/', 'addon-jest.test.js', '/cli/test/'], collectCoverage: false, @@ -29,4 +34,5 @@ module.exports = { setupTestFrameworkScriptFile: './scripts/jest.init.js', setupFiles: ['raf/polyfill'], testURL: 'http://localhost', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], }; diff --git a/package.json b/package.json index 816fec70e2e6..338794c6b297 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,8 @@ "remark-lint-code-eslint": "^2.0.0", "remark-preset-lint-recommended": "^3.0.1", "shelljs": "^0.7.8", - "symlink-dir": "^1.1.1" + "symlink-dir": "^1.1.1", + "ts-jest": "^22.0.0" }, "engines": { "node": ">=8.0.0", diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000000..1b9dd767251f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "skipLibCheck": true, + "noImplicitAny": true, + "lib": [ + "es2016", + "dom" + ] + } +} diff --git a/yarn.lock b/yarn.lock index 812d2fd41350..1226a465397b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1166,7 +1166,7 @@ babel-plugin-external-helpers@^6.18.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-istanbul@^4.0.0, babel-plugin-istanbul@^4.1.5: +babel-plugin-istanbul@^4.0.0, babel-plugin-istanbul@^4.1.4, babel-plugin-istanbul@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz#6760cdd977f411d3e175bb064f2bc327d99b2b6e" dependencies: @@ -1881,7 +1881,7 @@ babel-preset-jest@^21.2.0: babel-plugin-jest-hoist "^21.2.0" babel-plugin-syntax-object-rest-spread "^6.13.0" -babel-preset-jest@^22.0.3: +babel-preset-jest@^22.0.1, babel-preset-jest@^22.0.3: version "22.0.3" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-22.0.3.tgz#e2bb6f6b4a509d3ea0931f013db78c5a84856693" dependencies: @@ -3442,6 +3442,22 @@ cosmiconfig@^3.1.0: parse-json "^3.0.0" require-from-string "^2.0.1" +cpx@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/cpx/-/cpx-1.5.0.tgz#185be018511d87270dedccc293171e37655ab88f" + dependencies: + babel-runtime "^6.9.2" + chokidar "^1.6.0" + duplexer "^0.1.1" + glob "^7.0.5" + glob2base "^0.0.12" + minimatch "^3.0.2" + mkdirp "^0.5.1" + resolve "^1.1.7" + safe-buffer "^5.0.1" + shell-quote "^1.6.1" + subarg "^1.0.0" + crc@3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/crc/-/crc-3.3.0.tgz#fa622e1bc388bf257309082d6b65200ce67090ba" @@ -5272,6 +5288,10 @@ find-cache-dir@^1.0.0: make-dir "^1.0.0" pkg-dir "^2.0.0" +find-index@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" + find-parent-dir@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54" @@ -5424,6 +5444,14 @@ fs-extra@3.0.1: jsonfile "^3.0.0" universalify "^0.1.0" +fs-extra@4.0.3, fs-extra@^4.0.0, fs-extra@^4.0.1, fs-extra@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^0.30.0: version "0.30.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" @@ -5442,14 +5470,6 @@ fs-extra@^1.0.0: jsonfile "^2.1.0" klaw "^1.0.0" -fs-extra@^4.0.0, fs-extra@^4.0.1, fs-extra@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-readdir-recursive@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -5735,6 +5755,12 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" +glob2base@^0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" + dependencies: + find-index "^0.1.1" + glob@7.0.x: version "7.0.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" @@ -7240,7 +7266,7 @@ jest-config@^21.2.1: jest-validate "^21.2.1" pretty-format "^21.2.1" -jest-config@^22.0.4: +jest-config@^22.0.1, jest-config@^22.0.4: version "22.0.4" resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-22.0.4.tgz#9c2a46c0907b1a1af54d9cdbf18e99b447034e11" dependencies: @@ -7519,6 +7545,13 @@ jest-mock@^22.0.3: version "22.0.3" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-22.0.3.tgz#c875e47b5b729c6c020a2fab317b275c0cf88961" +jest-preset-angular@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/jest-preset-angular/-/jest-preset-angular-5.0.0.tgz#e0b0b67f94a5992f8c59e9a82c3790d5d60bb55d" + dependencies: + jest-zone-patch "^0.0.8" + ts-jest "^22.0.0" + jest-regex-util@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-20.0.3.tgz#85bbab5d133e44625b19faf8c6aa5122d085d762" @@ -7775,6 +7808,10 @@ jest-worker@^22.0.3: dependencies: merge-stream "^1.0.1" +jest-zone-patch@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/jest-zone-patch/-/jest-zone-patch-0.0.8.tgz#90fa3b5b60e95ad3e624dd2c3eb59bb1dcabd371" + jest@20.0.4, jest@^20.0.4: version "20.0.4" resolved "https://registry.yarnpkg.com/jest/-/jest-20.0.4.tgz#3dd260c2989d6dad678b1e9cc4d91944f6d602ac" @@ -13111,6 +13148,12 @@ stylus@^0.54.5: sax "0.5.x" source-map "0.1.x" +subarg@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" + dependencies: + minimist "^1.1.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -13513,6 +13556,21 @@ try-catch@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/try-catch/-/try-catch-1.0.0.tgz#3797dab39a266775f4d0da5cbf42aca3f03608e6" +ts-jest@^22.0.0: + version "22.0.0" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-22.0.0.tgz#cce1a5f1106150ca002d09f7e85913355ceb5e8d" + dependencies: + babel-core "^6.24.1" + babel-plugin-istanbul "^4.1.4" + babel-plugin-transform-es2015-modules-commonjs "^6.24.1" + babel-preset-jest "^22.0.1" + cpx "^1.5.0" + fs-extra "4.0.3" + jest-config "^22.0.1" + pkg-dir "^2.0.0" + source-map-support "^0.5.0" + yargs "^10.0.3" + ts-loader@^2.2.2: version "2.3.7" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-2.3.7.tgz#a9028ced473bee12f28a75f9c5b139979d33f2fc" From edb1cf60000a33f60568802a514cbd1b1bd0c727 Mon Sep 17 00:00:00 2001 From: igor Date: Tue, 26 Dec 2017 17:41:05 +0200 Subject: [PATCH 02/41] Refactor storyshots index.js --- addons/storyshots/src/angular/loader.js | 39 +++++++++++ addons/storyshots/src/frameworkLoader.js | 17 +++++ addons/storyshots/src/hasDependency.js | 13 ++++ addons/storyshots/src/index.js | 89 ++++-------------------- addons/storyshots/src/react/loader.js | 37 ++++++++++ addons/storyshots/src/rn/loader.js | 26 +++++++ 6 files changed, 145 insertions(+), 76 deletions(-) create mode 100644 addons/storyshots/src/angular/loader.js create mode 100644 addons/storyshots/src/frameworkLoader.js create mode 100644 addons/storyshots/src/hasDependency.js create mode 100644 addons/storyshots/src/react/loader.js create mode 100644 addons/storyshots/src/rn/loader.js diff --git a/addons/storyshots/src/angular/loader.js b/addons/storyshots/src/angular/loader.js new file mode 100644 index 000000000000..14de8b4d4cb5 --- /dev/null +++ b/addons/storyshots/src/angular/loader.js @@ -0,0 +1,39 @@ +/* eslint-disable global-require,import/no-extraneous-dependencies */ +import path from 'path'; +import runWithRequireContext from '../require_context'; +import hasDependency from '../hasDependency'; + +const babel = require('babel-core'); + +function test(options) { + return ( + options.framework === 'angular' || (!options.framework && hasDependency('@storybook/angular')) + ); +} + +function load(options) { + const storybook = require.requireActual('@storybook/angular'); + const loadBabelConfig = require('@storybook/angular/dist/server/babel_config').default; + + const configDirPath = path.resolve(options.configPath || '.storybook'); + const 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); + + return { + framework: 'angular', + storybook, + }; +} + +export default { + load, + test, +}; diff --git a/addons/storyshots/src/frameworkLoader.js b/addons/storyshots/src/frameworkLoader.js new file mode 100644 index 000000000000..f23156db1274 --- /dev/null +++ b/addons/storyshots/src/frameworkLoader.js @@ -0,0 +1,17 @@ +import loaderReact from './react/loader'; +import loaderRn from './rn/loader'; +import loaderAngular from './angular/loader'; + +const loaders = [loaderReact, loaderAngular, loaderRn]; + +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 cb274fea5b79..1683d78d2f00 100644 --- a/addons/storyshots/src/index.js +++ b/addons/storyshots/src/index.js @@ -1,12 +1,12 @@ /* eslint-disable no-loop-func */ -import path from 'path'; + import fs from 'fs'; import glob from 'glob'; import global, { describe, it } 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'; @@ -21,79 +21,17 @@ export { export { getSnapshotFileName }; -let storybook; -let configPath; -let framework; 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 default function testStorySnapshots(options = {}) { - addons.setChannel(createChannel()); - - const isStorybook = - options.framework === 'react' || (!options.framework && hasDependency('@storybook/react')); - const isAngularStorybook = - options.framework === 'angular' || (!options.framework && hasDependency('@storybook/angular')); - const isRNStorybook = - options.framework === 'react-native' || - (!options.framework && hasDependency('@storybook/react-native')); - - if (isStorybook) { - framework = 'react'; - 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 (isAngularStorybook) { - framework = 'angular'; - storybook = require.requireActual('@storybook/angular'); - // eslint-disable-next-line - const loadBabelConfig = require('@storybook/angular/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) { - framework = 'rn'; - 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'); } + addons.setChannel(createChannel()); + + const { framework, storybook } = loadFramework(options); + // NOTE: keep `suit` typo for backwards compatibility const suite = options.suite || options.suit || 'Storyshots'; const stories = storybook.getStorybook(); @@ -103,15 +41,14 @@ export default function testStorySnapshots(options = {}) { } // 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; + 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) { @@ -126,14 +63,14 @@ 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, framework }; - return options.test({ + return testMethod({ story, context, }); diff --git a/addons/storyshots/src/react/loader.js b/addons/storyshots/src/react/loader.js new file mode 100644 index 000000000000..53c128a66cd9 --- /dev/null +++ b/addons/storyshots/src/react/loader.js @@ -0,0 +1,37 @@ +/* eslint-disable global-require,import/no-extraneous-dependencies */ +import path from 'path'; +import runWithRequireContext from '../require_context'; +import hasDependency from '../hasDependency'; + +const babel = require('babel-core'); + +function test(options) { + return options.framework === 'react' || (!options.framework && hasDependency('@storybook/react')); +} + +function load(options) { + const storybook = require.requireActual('@storybook/react'); + const loadBabelConfig = require('@storybook/react/dist/server/babel_config').default; + + const configDirPath = path.resolve(options.configPath || '.storybook'); + const 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); + + return { + framework: 'react', + storybook, + }; +} + +export default { + load, + test, +}; diff --git a/addons/storyshots/src/rn/loader.js b/addons/storyshots/src/rn/loader.js new file mode 100644 index 000000000000..f5f28b430c27 --- /dev/null +++ b/addons/storyshots/src/rn/loader.js @@ -0,0 +1,26 @@ +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 { + framework: 'rn', + storybook, + }; +} + +export default { + load, + test, +}; From c208016a43aa42224c21eb4ae29b4897189f759b Mon Sep 17 00:00:00 2001 From: igor Date: Tue, 26 Dec 2017 18:44:36 +0200 Subject: [PATCH 03/41] Refactor test-bodies --- addons/storyshots/src/angular/loader.js | 1 + addons/storyshots/src/angular/renderTree.js | 61 +++++++++++++ addons/storyshots/src/index.js | 18 ++-- addons/storyshots/src/react/loader.js | 1 + addons/storyshots/src/react/renderTree.js | 10 +++ addons/storyshots/src/react/test-bodies.js | 14 +++ addons/storyshots/src/rn/loader.js | 2 + addons/storyshots/src/test-bodies.js | 99 ++++----------------- 8 files changed, 112 insertions(+), 94 deletions(-) create mode 100644 addons/storyshots/src/angular/renderTree.js create mode 100644 addons/storyshots/src/react/renderTree.js create mode 100644 addons/storyshots/src/react/test-bodies.js diff --git a/addons/storyshots/src/angular/loader.js b/addons/storyshots/src/angular/loader.js index 14de8b4d4cb5..6c84e35b5b83 100644 --- a/addons/storyshots/src/angular/loader.js +++ b/addons/storyshots/src/angular/loader.js @@ -28,6 +28,7 @@ function load(options) { runWithRequireContext(content, contextOpts); return { + renderTree: require('./renderTree').default, framework: 'angular', storybook, }; diff --git a/addons/storyshots/src/angular/renderTree.js b/addons/storyshots/src/angular/renderTree.js new file mode 100644 index 000000000000..f871d495faa0 --- /dev/null +++ b/addons/storyshots/src/angular/renderTree.js @@ -0,0 +1,61 @@ +import 'jest-preset-angular/setupJest'; +import AngularSnapshotSerializer from 'jest-preset-angular/AngularSnapshotSerializer'; +import HTMLCommentSerializer from 'jest-preset-angular/HTMLCommentSerializer'; +import { TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { addSerializer } from 'jest-specific-snapshot'; +import { createAnnotatedComponent } from './helpers'; + +addSerializer(HTMLCommentSerializer); +addSerializer(AngularSnapshotSerializer); + +function getRenderedTree(story, context) { + const storyElement = story.render(context); + + const { + // props, + // component, + moduleMetadata = { imports: [], schemas: [], declarations: [], providers: [] }, + } = storyElement; + + const moduleMeta = { + imports: [], + schemas: [], + declarations: [], + providers: [], + ...moduleMetadata, + }; + + const { component: annotatedComponent } = createAnnotatedComponent(storyElement); + + TestBed.configureTestingModule({ + imports: [...moduleMeta.imports], + declarations: [annotatedComponent, ...moduleMeta.declarations], + providers: [...moduleMeta.providers], + schemas: [NO_ERRORS_SCHEMA, ...moduleMeta.schemas], + }); + + // this is async. Should be somehow called in beforeEach + TestBed.compileComponents(); + + const tree = TestBed.createComponent(annotatedComponent); + tree.detectChanges(); + + return tree; + + // TestBed.configureTestingModule({ + // imports: [...moduleMeta.imports], + // declarations: [component, ...moduleMeta.declarations], + // providers: [...moduleMeta.providers], + // schemas: [NO_ERRORS_SCHEMA, ...moduleMeta.schemas], + // }); + // + // TestBed.compileComponents(); + // + // const tree = TestBed.createComponent(component); + // tree.detectChanges(); + // + // return tree; +} + +export default getRenderedTree; diff --git a/addons/storyshots/src/index.js b/addons/storyshots/src/index.js index 1683d78d2f00..fe169f0106a3 100644 --- a/addons/storyshots/src/index.js +++ b/addons/storyshots/src/index.js @@ -1,27 +1,24 @@ /* eslint-disable no-loop-func */ - import fs from 'fs'; import glob from 'glob'; import global, { describe, it } from 'global'; - import addons from '@storybook/addons'; - import loadFramework from './frameworkLoader'; import createChannel from './storybook-channel-mock'; -import { snapshotWithOptions } from './test-bodies'; import { getPossibleStoriesFiles, getSnapshotFileName } from './utils'; +import { multiSnapshotWithOptions, snapshotWithOptions, snapshot } from './test-bodies'; +import { shallowSnapshot, renderOnly } from './react/test-bodies'; + +global.STORYBOOK_REACT_CLASSES = global.STORYBOOK_REACT_CLASSES || {}; export { + getSnapshotFileName, snapshot, multiSnapshotWithOptions, snapshotWithOptions, shallowSnapshot, renderOnly, -} from './test-bodies'; - -export { getSnapshotFileName }; - -global.STORYBOOK_REACT_CLASSES = global.STORYBOOK_REACT_CLASSES || {}; +}; export default function testStorySnapshots(options = {}) { if (typeof describe !== 'function') { @@ -30,7 +27,7 @@ export default function testStorySnapshots(options = {}) { addons.setChannel(createChannel()); - const { framework, storybook } = loadFramework(options); + const { framework, storybook, renderTree } = loadFramework(options); // NOTE: keep `suit` typo for backwards compatibility const suite = options.suite || options.suit || 'Storyshots'; @@ -73,6 +70,7 @@ export default function testStorySnapshots(options = {}) { return testMethod({ story, context, + renderTree, }); }); } diff --git a/addons/storyshots/src/react/loader.js b/addons/storyshots/src/react/loader.js index 53c128a66cd9..dc1ac6d065bd 100644 --- a/addons/storyshots/src/react/loader.js +++ b/addons/storyshots/src/react/loader.js @@ -26,6 +26,7 @@ function load(options) { runWithRequireContext(content, contextOpts); return { + renderTree: require('./renderTree').default, framework: 'react', storybook, }; diff --git a/addons/storyshots/src/react/renderTree.js b/addons/storyshots/src/react/renderTree.js new file mode 100644 index 000000000000..4d35e7dff84f --- /dev/null +++ b/addons/storyshots/src/react/renderTree.js @@ -0,0 +1,10 @@ +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/react/test-bodies.js b/addons/storyshots/src/react/test-bodies.js new file mode 100644 index 000000000000..e100771499d1 --- /dev/null +++ b/addons/storyshots/src/react/test-bodies.js @@ -0,0 +1,14 @@ +import reactTestRenderer from 'react-test-renderer'; +import shallow from 'react-test-renderer/shallow'; + +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 function renderOnly({ story, context }) { + const storyElement = story.render(context); + reactTestRenderer.create(storyElement); +} diff --git a/addons/storyshots/src/rn/loader.js b/addons/storyshots/src/rn/loader.js index f5f28b430c27..eaddd550bb4f 100644 --- a/addons/storyshots/src/rn/loader.js +++ b/addons/storyshots/src/rn/loader.js @@ -1,3 +1,4 @@ +/* eslint-disable global-require */ import path from 'path'; import hasDependency from '../hasDependency'; @@ -15,6 +16,7 @@ function load(options) { require.requireActual(configPath); return { + renderTree: require('../react/renderTree').default, framework: 'rn', storybook, }; diff --git a/addons/storyshots/src/test-bodies.js b/addons/storyshots/src/test-bodies.js index ba2bc96c0d36..951f24e7edcd 100644 --- a/addons/storyshots/src/test-bodies.js +++ b/addons/storyshots/src/test-bodies.js @@ -1,75 +1,13 @@ -import 'jest-preset-angular/setupJest'; -import AngularSnapshotSerializer from 'jest-preset-angular/AngularSnapshotSerializer'; -import HTMLCommentSerializer from 'jest-preset-angular/HTMLCommentSerializer'; -import { TestBed } from '@angular/core/testing'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; - -import reactTestRenderer from 'react-test-renderer'; -import shallow from 'react-test-renderer/shallow'; -import { addSerializer } from 'jest-specific-snapshot'; +import 'jest-specific-snapshot'; import { getSnapshotFileName } from './utils'; -import { createAnnotatedComponent } from './angular/helpers'; - -addSerializer(HTMLCommentSerializer); -addSerializer(AngularSnapshotSerializer); - -function getRenderedTree(story, context, { renderer, serializer, ...rendererOptions }) { - const storyElement = story.render(context); - - if (context.framework === 'angular') { - const { - // props, - // component, - moduleMetadata = { imports: [], schemas: [], declarations: [], providers: [] }, - } = storyElement; - - const moduleMeta = { - imports: [], - schemas: [], - declarations: [], - providers: [], - ...moduleMetadata, - }; - - const { component: annotatedComponent } = createAnnotatedComponent(storyElement); - - TestBed.configureTestingModule({ - imports: [...moduleMeta.imports], - declarations: [annotatedComponent, ...moduleMeta.declarations], - providers: [...moduleMeta.providers], - schemas: [NO_ERRORS_SCHEMA, ...moduleMeta.schemas], - }); - - // this is async. Should be somehow called in beforeEach - TestBed.compileComponents(); - const tree = TestBed.createComponent(annotatedComponent); - tree.detectChanges(); - - return tree; - - // TestBed.configureTestingModule({ - // imports: [...moduleMeta.imports], - // declarations: [component, ...moduleMeta.declarations], - // providers: [...moduleMeta.providers], - // schemas: [NO_ERRORS_SCHEMA, ...moduleMeta.schemas], - // }); - // - // TestBed.compileComponents(); - // - // const tree = TestBed.createComponent(component); - // tree.detectChanges(); - // - // return tree; - } - - const currentRenderer = renderer || reactTestRenderer.create; - const tree = currentRenderer(storyElement, rendererOptions); - return serializer ? serializer(tree) : tree; -} - -export const snapshotWithOptions = options => ({ story, context, snapshotFileName }) => { - const tree = getRenderedTree(story, context, options); +export const snapshotWithOptions = options => ({ + story, + context, + renderTree, + snapshotFileName, +}) => { + const tree = renderTree(story, context, options); if (snapshotFileName) { expect(tree).toMatchSpecificSnapshot(snapshotFileName); @@ -82,20 +20,13 @@ export const snapshotWithOptions = options => ({ story, context, snapshotFileNam } }; -export const multiSnapshotWithOptions = options => ({ story, context }) => { - snapshotWithOptions(options)({ story, context, snapshotFileName: getSnapshotFileName(context) }); +export const multiSnapshotWithOptions = options => ({ story, context, renderTree }) => { + snapshotWithOptions(options)({ + story, + context, + renderTree, + snapshotFileName: getSnapshotFileName(context), + }); }; 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 function renderOnly({ story, context }) { - const storyElement = story.render(context); - reactTestRenderer.create(storyElement); -} From e0bae92d04183969ac269035d5c3829efd74f90e Mon Sep 17 00:00:00 2001 From: igor Date: Tue, 26 Dec 2017 19:14:28 +0200 Subject: [PATCH 04/41] Better copy-past from app/angular --- addons/storyshots/src/angular/app.token.ts | 4 + addons/storyshots/src/angular/helpers.js | 62 -------- addons/storyshots/src/angular/helpers.ts | 136 ++++++++++++++++++ addons/storyshots/src/angular/renderTree.js | 40 +----- addons/storyshots/src/angular/types.ts | 28 ++++ .../src/angular/{utils.js => utils.ts} | 15 +- .../custom-cva-component.stories.storyshot | 1 + 7 files changed, 182 insertions(+), 104 deletions(-) create mode 100644 addons/storyshots/src/angular/app.token.ts delete mode 100644 addons/storyshots/src/angular/helpers.js create mode 100644 addons/storyshots/src/angular/helpers.ts create mode 100644 addons/storyshots/src/angular/types.ts rename addons/storyshots/src/angular/{utils.js => utils.ts} (53%) diff --git a/addons/storyshots/src/angular/app.token.ts b/addons/storyshots/src/angular/app.token.ts new file mode 100644 index 000000000000..bf45fcd7720f --- /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.js b/addons/storyshots/src/angular/helpers.js deleted file mode 100644 index 6de591d4431d..000000000000 --- a/addons/storyshots/src/angular/helpers.js +++ /dev/null @@ -1,62 +0,0 @@ -import { Component } from '@angular/core'; -import { getPropMetadata, getAnnotations, getParameters } from './utils'; - -function getComponentMetadata({ component, props = {}, propsMeta = {}, moduleMetadata = {} }) { - const componentMetadata = getAnnotations(component)[0] || {}; - const propsMetadata = getPropMetadata(component); - const paramsMetadata = getParameters(component); - - // Object.keys(propsMeta).forEach(key => { - // propsMetadata[key] = props[key]; - // }); - - Object.keys(propsMeta).forEach(key => { - propsMetadata[key] = propsMeta[key]; - }); - - const { imports = [], schemas = [], declarations = [], providers = [] } = moduleMetadata; - - return { - component, - props, - componentMeta: componentMetadata, - propsMeta: propsMetadata, - params: paramsMetadata, - moduleMeta: { - imports, - schemas, - declarations, - providers, - }, - }; -} - -function getAnnotatedComponent(meta, component, propsMeta, params) { - const StoryshotComponent = function StoryshotComponent(...args) { - component.call(this, ...args); - }; - - StoryshotComponent.prototype = Object.create(component.prototype); - StoryshotComponent.annotations = [new Component(meta)]; - StoryshotComponent.parameters = params; - StoryshotComponent.propsMetadata = propsMeta; - - return StoryshotComponent; -} - -export function createAnnotatedComponent(story) { - const { component, componentMeta, props, propsMeta, params, moduleMeta } = getComponentMetadata( - story - ); - - const AnnotatedComponent = getAnnotatedComponent(componentMeta, component, propsMeta, [ - ...params, - ...moduleMeta.providers.map(provider => [provider]), - ]); - - return { - component: AnnotatedComponent, - props, - propsMeta, - }; -} diff --git a/addons/storyshots/src/angular/helpers.ts b/addons/storyshots/src/angular/helpers.ts new file mode 100644 index 000000000000..7f664fbd3439 --- /dev/null +++ b/addons/storyshots/src/angular/helpers.ts @@ -0,0 +1,136 @@ +import { + Type, + NgModule, + Component, +} from '@angular/core'; +import {FormsModule} from '@angular/forms' +import { BrowserModule } from '@angular/platform-browser'; + +import { STORY } from './app.token'; +import { getAnnotations, getParameters, getPropMetadata } from './utils'; +import { NgModuleMetadata, NgStory, NgProvidedData } from './types'; + +interface IComponent extends Type { + annotations: any[]; + parameters: any[]; + propsMetadata: any[] +} + +const getComponentMetadata = ( + { component, props = {}, propsMeta = {}, moduleMetadata = { + imports: [], + schemas: [], + declarations: [], + providers: [] + } }: NgStory +) => { + if (!component || typeof component !== 'function') { + throw new Error('No valid component provided'); + } + + const componentMetadata = getAnnotations(component)[0] || {}; + const propsMetadata = getPropMetadata(component); + const paramsMetadata = getParameters(component); + + Object.keys(propsMeta).map(key => { + (propsMetadata)[key] = (propsMeta)[key]; + }); + + const { + imports = [], + schemas = [], + declarations = [], + providers = [] + } = moduleMetadata; + + return { + component, + props, + componentMeta: componentMetadata, + propsMeta: propsMetadata, + params: paramsMetadata, + moduleMeta: { + imports, + schemas, + declarations, + providers + } + }; +}; + +const getAnnotatedComponent = (meta: NgModule, + component: any, + propsMeta: { [p: string]: any }, + params: any[]): IComponent => { + const NewComponent: any = function NewComponent(...args: any[]) { + component.call(this, ...args); + }; + + NewComponent.prototype = Object.create(component.prototype); + NewComponent.annotations = [new Component(meta)]; + NewComponent.parameters = params; + NewComponent.propsMetadata = propsMeta; + + return NewComponent; +}; + +const getModule = ( + declarations: Array | any[]>, + entryComponents: Array | any[]>, + bootstrap: Array | any[]>, + data: NgProvidedData, + moduleMetadata: NgModuleMetadata = { + imports: [], + schemas: [], + declarations: [], + providers: [] + } +): any => { + return { + declarations: [...declarations, ...moduleMetadata.declarations], + imports: [BrowserModule, FormsModule, ...moduleMetadata.imports], + providers: [{ provide: STORY, useValue: Object.assign({}, data) }, ...moduleMetadata.providers], + entryComponents: [...entryComponents], + schemas: [...moduleMetadata.schemas], + bootstrap: [...bootstrap] + }; +}; + +export const initModuleData = (currentStory: NgStory): any => { + const { + component, + componentMeta, + props, + propsMeta, + params, + moduleMeta + } = getComponentMetadata(currentStory); + + if (!componentMeta) { + throw new Error('No component metadata available'); + } + + const AnnotatedComponent = getAnnotatedComponent( + componentMeta, + component, + propsMeta, + [...params, ...moduleMeta.providers.map(provider => [provider])] + ); + + const story = { + component: AnnotatedComponent, + props, + propsMeta + }; + + return { + AnnotatedComponent, + moduleMeta: getModule( + [AnnotatedComponent], + [AnnotatedComponent], + [], + story, + moduleMeta + ), + }; +}; diff --git a/addons/storyshots/src/angular/renderTree.js b/addons/storyshots/src/angular/renderTree.js index f871d495faa0..83b36968c556 100644 --- a/addons/storyshots/src/angular/renderTree.js +++ b/addons/storyshots/src/angular/renderTree.js @@ -4,58 +4,32 @@ import HTMLCommentSerializer from 'jest-preset-angular/HTMLCommentSerializer'; import { TestBed } from '@angular/core/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { addSerializer } from 'jest-specific-snapshot'; -import { createAnnotatedComponent } from './helpers'; +import { initModuleData } from './helpers.ts'; addSerializer(HTMLCommentSerializer); addSerializer(AngularSnapshotSerializer); function getRenderedTree(story, context) { - const storyElement = story.render(context); + const curentStory = story.render(context); - const { - // props, - // component, - moduleMetadata = { imports: [], schemas: [], declarations: [], providers: [] }, - } = storyElement; - - const moduleMeta = { - imports: [], - schemas: [], - declarations: [], - providers: [], - ...moduleMetadata, - }; - - const { component: annotatedComponent } = createAnnotatedComponent(storyElement); + const { moduleMeta, AnnotatedComponent } = initModuleData(curentStory); TestBed.configureTestingModule({ imports: [...moduleMeta.imports], - declarations: [annotatedComponent, ...moduleMeta.declarations], + declarations: [...moduleMeta.declarations], providers: [...moduleMeta.providers], schemas: [NO_ERRORS_SCHEMA, ...moduleMeta.schemas], + entryComponents: [...moduleMeta.entryComponents], + bootstrap: [...moduleMeta.bootstrap], }); // this is async. Should be somehow called in beforeEach TestBed.compileComponents(); - const tree = TestBed.createComponent(annotatedComponent); + const tree = TestBed.createComponent(AnnotatedComponent); tree.detectChanges(); return tree; - - // TestBed.configureTestingModule({ - // imports: [...moduleMeta.imports], - // declarations: [component, ...moduleMeta.declarations], - // providers: [...moduleMeta.providers], - // schemas: [NO_ERRORS_SCHEMA, ...moduleMeta.schemas], - // }); - // - // TestBed.compileComponents(); - // - // const tree = TestBed.createComponent(component); - // 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..7109bcf1226d --- /dev/null +++ b/addons/storyshots/src/angular/types.ts @@ -0,0 +1,28 @@ +export interface NgModuleMetadata { + declarations: Array, + imports: Array, + schemas: Array, + providers: Array, +} + +export interface ICollection {[p: string]: any} + +export interface NgStory { + component: any, + props: ICollection, + propsMeta: ICollection, + moduleMetadata?: NgModuleMetadata +} + +export interface NgError { + message: string, + stack: string +} + +export type NgProvidedData = NgStory | NgError; + +export interface IContext { + [p: string]: any +} + +export type IGetStoryWithContext = (context: IContext) => NgStory diff --git a/addons/storyshots/src/angular/utils.js b/addons/storyshots/src/angular/utils.ts similarity index 53% rename from addons/storyshots/src/angular/utils.js rename to addons/storyshots/src/angular/utils.ts index 03a810a8d7e8..1ec935cd6e9a 100644 --- a/addons/storyshots/src/angular/utils.js +++ b/addons/storyshots/src/angular/utils.ts @@ -1,7 +1,4 @@ -/* eslint-disable no-param-reassign */ -/* globals window */ - -function getMeta(component, [name1, name2], defaultValue) { +function getMeta(component: any, [name1, name2]: any, defaultValue: any) { if (!name2) { name2 = name1; name1 = `__${name1}__`; @@ -15,17 +12,17 @@ function getMeta(component, [name1, name2], defaultValue) { return component[name2]; } - return window.Reflect.getMetadata(name2, component) || defaultValue; + return (window)['Reflect'].getMetadata(name2, component) || defaultValue; } -export function getAnnotations(component) { +export function getAnnotations(component: any) { return getMeta(component, ['annotations'], []); } -export function getPropMetadata(component) { +export function getPropMetadata(component: any) { return getMeta(component, ['__prop__metadata__', 'propMetadata'], {}); } -export function getParameters(component) { +export function getParameters(component: any) { return getMeta(component, ['parameters'], []); -} +} \ No newline at end of file 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 index 1ae6eea489e9..13372af892f3 100644 --- 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 @@ -10,6 +10,7 @@ exports[`Storyshots ngModel custom ControlValueAccessor 1`] = ` From 91f17b6cbdfd0bc7e916e84b3130d0ccac55cd34 Mon Sep 17 00:00:00 2001 From: igor Date: Tue, 26 Dec 2017 19:20:31 +0200 Subject: [PATCH 05/41] Remove things --- addons/storyshots/src/angular/helpers.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/addons/storyshots/src/angular/helpers.ts b/addons/storyshots/src/angular/helpers.ts index 7f664fbd3439..6ab46d84cca9 100644 --- a/addons/storyshots/src/angular/helpers.ts +++ b/addons/storyshots/src/angular/helpers.ts @@ -3,8 +3,6 @@ import { NgModule, Component, } from '@angular/core'; -import {FormsModule} from '@angular/forms' -import { BrowserModule } from '@angular/platform-browser'; import { STORY } from './app.token'; import { getAnnotations, getParameters, getPropMetadata } from './utils'; @@ -88,7 +86,7 @@ const getModule = ( ): any => { return { declarations: [...declarations, ...moduleMetadata.declarations], - imports: [BrowserModule, FormsModule, ...moduleMetadata.imports], + imports: [...moduleMetadata.imports], providers: [{ provide: STORY, useValue: Object.assign({}, data) }, ...moduleMetadata.providers], entryComponents: [...entryComponents], schemas: [...moduleMetadata.schemas], From d9942cf96e25f9f10c1cf45590a38b5b7a160d64 Mon Sep 17 00:00:00 2001 From: igor Date: Fri, 29 Dec 2017 14:06:34 +0200 Subject: [PATCH 06/41] Fix indentation in package.json --- addons/storyshots/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/storyshots/package.json b/addons/storyshots/package.json index 6a54283e6315..b7ac0014dc9e 100644 --- a/addons/storyshots/package.json +++ b/addons/storyshots/package.json @@ -41,7 +41,7 @@ "react-dom": "^16.1.0" }, "peerDependencies": { - "@angular/core": ">=4.0.0", + "@angular/core": ">=4.0.0", "@storybook/addons": "^3.3.3", "babel-core": "^6.26.0 || ^7.0.0-0", "react": "*", From 2c460e4c36cdd09724671a23740cd75538a7e21b Mon Sep 17 00:00:00 2001 From: igor Date: Fri, 29 Dec 2017 16:22:47 +0200 Subject: [PATCH 07/41] Extract more stories to separate files --- .../src/stories/addon-links.stories.ts | 13 +++++++++++++ .../src/stories/app.component.stories.ts | 9 +++++++++ .../src/stories/custom-metadata.stories.ts | 4 ++-- examples/angular-cli/src/stories/index.ts | 19 ------------------- .../{ => moduleMetadata}/custom.pipe.ts | 0 .../{ => moduleMetadata}/name.component.ts | 0 6 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 examples/angular-cli/src/stories/addon-links.stories.ts create mode 100644 examples/angular-cli/src/stories/app.component.stories.ts rename examples/angular-cli/src/stories/{ => moduleMetadata}/custom.pipe.ts (100%) rename examples/angular-cli/src/stories/{ => moduleMetadata}/name.component.ts (100%) 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..f446c7347883 --- /dev/null +++ b/examples/angular-cli/src/stories/addon-links.stories.ts @@ -0,0 +1,13 @@ +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..99f5dbca2867 --- /dev/null +++ b/examples/angular-cli/src/stories/app.component.stories.ts @@ -0,0 +1,9 @@ +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/custom-metadata.stories.ts b/examples/angular-cli/src/stories/custom-metadata.stories.ts index 80e014205ae0..1dd685721957 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/index.ts b/examples/angular-cli/src/stories/index.ts index 08847b245744..d7c8b7abe09c 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', () => ({ @@ -25,19 +22,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 From 887182f44b8f499f2410e4d897354359e61b0441 Mon Sep 17 00:00:00 2001 From: igor Date: Fri, 29 Dec 2017 16:25:36 +0200 Subject: [PATCH 08/41] Support promises in storyshots --- addons/storyshots/src/angular/renderTree.js | 15 ++++++------- addons/storyshots/src/angular/utils.ts | 12 +++++++++- addons/storyshots/src/test-bodies.js | 25 +++++++++++++-------- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/addons/storyshots/src/angular/renderTree.js b/addons/storyshots/src/angular/renderTree.js index 83b36968c556..34a868be01da 100644 --- a/addons/storyshots/src/angular/renderTree.js +++ b/addons/storyshots/src/angular/renderTree.js @@ -10,9 +10,9 @@ addSerializer(HTMLCommentSerializer); addSerializer(AngularSnapshotSerializer); function getRenderedTree(story, context) { - const curentStory = story.render(context); + const currentStory = story.render(context); - const { moduleMeta, AnnotatedComponent } = initModuleData(curentStory); + const { moduleMeta, AnnotatedComponent } = initModuleData(currentStory); TestBed.configureTestingModule({ imports: [...moduleMeta.imports], @@ -23,13 +23,12 @@ function getRenderedTree(story, context) { bootstrap: [...moduleMeta.bootstrap], }); - // this is async. Should be somehow called in beforeEach - TestBed.compileComponents(); + return TestBed.compileComponents().then(() => { + const tree = TestBed.createComponent(AnnotatedComponent); + tree.detectChanges(); - const tree = TestBed.createComponent(AnnotatedComponent); - tree.detectChanges(); - - return tree; + return tree; + }); } export default getRenderedTree; diff --git a/addons/storyshots/src/angular/utils.ts b/addons/storyshots/src/angular/utils.ts index 1ec935cd6e9a..fe83324fa0de 100644 --- a/addons/storyshots/src/angular/utils.ts +++ b/addons/storyshots/src/angular/utils.ts @@ -1,3 +1,7 @@ +import { ษตReflectionCapabilities } from '@angular/core'; + +const reflectionCapabilities = new ษตReflectionCapabilities(); + function getMeta(component: any, [name1, name2]: any, defaultValue: any) { if (!name2) { name2 = name1; @@ -24,5 +28,11 @@ export function getPropMetadata(component: any) { } export function getParameters(component: any) { - return getMeta(component, ['parameters'], []); + const params = reflectionCapabilities.parameters(component); + + if (!params || !params[0]) { + return getMeta(component, ['parameters'], []); + } + + return params; } \ No newline at end of file diff --git a/addons/storyshots/src/test-bodies.js b/addons/storyshots/src/test-bodies.js index 951f24e7edcd..9c0c036a060c 100644 --- a/addons/storyshots/src/test-bodies.js +++ b/addons/storyshots/src/test-bodies.js @@ -7,26 +7,33 @@ export const snapshotWithOptions = options => ({ renderTree, snapshotFileName, }) => { - const tree = renderTree(story, context, options); + const result = renderTree(story, context, options); - if (snapshotFileName) { - expect(tree).toMatchSpecificSnapshot(snapshotFileName); - } else { - expect(tree).toMatchSnapshot(); + 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); } + + return match(result); }; -export const multiSnapshotWithOptions = options => ({ story, context, renderTree }) => { +export const multiSnapshotWithOptions = options => ({ story, context, renderTree }) => snapshotWithOptions(options)({ story, context, renderTree, snapshotFileName: getSnapshotFileName(context), }); -}; export const snapshot = snapshotWithOptions({}); From 9cffe0a2b2246011807cda3685897a4954e8c841 Mon Sep 17 00:00:00 2001 From: igor Date: Fri, 29 Dec 2017 16:26:51 +0200 Subject: [PATCH 09/41] Allow loading html templates --- jest.config.js | 7 +++++-- package.json | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/jest.config.js b/jest.config.js index dd9980ee8312..218590867dc5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,7 @@ module.exports = { + globals: { + __TRANSFORM_HTML__: true, + }, cacheDirectory: '.cache/jest', clearMocks: true, moduleNameMapper: { @@ -16,8 +19,8 @@ module.exports = { '/examples/angular-cli', ], transform: { - '^.+\\.tsx?$': 'ts-jest', '^.+\\.jsx?$': 'babel-jest', + '^.+\\.(ts|html)$': '/node_modules/jest-preset-angular/preprocessor.js', }, testPathIgnorePatterns: ['/node_modules/', 'addon-jest.test.js', '/cli/test/'], collectCoverage: false, @@ -33,5 +36,5 @@ module.exports = { setupTestFrameworkScriptFile: './scripts/jest.init.js', setupFiles: ['raf/polyfill'], testURL: 'http://localhost', - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node', '.html'], }; diff --git a/package.json b/package.json index 4b37445af605..a6e0dea492da 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "jest-enzyme": "^4.0.2", "jest-image-snapshot": "^2.2.1", "jest-jasmine2": "^22.0.4", + "jest-preset-angular": "^5.0.0", "lerna": "^2.5.1", "lint-staged": "^6.0.0", "lodash": "^4.17.4", From 29e84bd2437be64c7b6b4ab7f0ed6905ac05381a Mon Sep 17 00:00:00 2001 From: igor Date: Fri, 29 Dec 2017 16:27:17 +0200 Subject: [PATCH 10/41] Update snapshots --- .../addon-actions.stories.storyshot | 4 +- .../addon-links.stories.storyshot | 16 ++++ .../app.component.stories.storyshot | 85 +++++++++++++++++++ .../src/stories/__snapshots__/index.storyshot | 15 ---- .../custom-cva-component.stories.storyshot | 1 - 5 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 examples/angular-cli/src/stories/__snapshots__/addon-links.stories.storyshot create mode 100644 examples/angular-cli/src/stories/__snapshots__/app.component.stories.storyshot diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-actions.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-actions.stories.storyshot index f544931485dc..f86a428fa3f4 100644 --- a/examples/angular-cli/src/stories/__snapshots__/addon-actions.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/addon-actions.stories.storyshot @@ -7,7 +7,7 @@ exports[`Storyshots Addon Actions Action and method 1`] = ` > @@ -22,7 +22,7 @@ exports[`Storyshots Addon Actions Action only 1`] = ` > 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..07405a58e2db --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/addon-links.stories.storyshot @@ -0,0 +1,16 @@ +// 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__/app.component.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/app.component.stories.storyshot new file mode 100644 index 000000000000..e94e0896ce43 --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/app.component.stories.storyshot @@ -0,0 +1,85 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots App Component Component with separate template 1`] = ` + + +
+ + +

+ + Welcome to app! + +

+ + + + + +
+

+ Here are some links to help you start: +

+ + + +
+`; diff --git a/examples/angular-cli/src/stories/__snapshots__/index.storyshot b/examples/angular-cli/src/stories/__snapshots__/index.storyshot index 0eecf1b5062f..00b63d099123 100644 --- a/examples/angular-cli/src/stories/__snapshots__/index.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/index.storyshot @@ -1,20 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Another Button button with link to another story 1`] = ` - - - - - -`; - exports[`Storyshots Button with some emoji 1`] = ` From b09b631c2496019614fa5bb79007ff733ab9a040 Mon Sep 17 00:00:00 2001 From: igor Date: Fri, 29 Dec 2017 17:24:02 +0200 Subject: [PATCH 11/41] Few cleanups --- addons/storyshots/src/index.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/addons/storyshots/src/index.js b/addons/storyshots/src/index.js index fe169f0106a3..f5c2339fbc65 100644 --- a/addons/storyshots/src/index.js +++ b/addons/storyshots/src/index.js @@ -27,17 +27,16 @@ export default function testStorySnapshots(options = {}) { addons.setChannel(createChannel()); - const { framework, storybook, renderTree } = loadFramework(options); - - // NOTE: keep `suit` typo for backwards compatibility - const suite = options.suite || options.suit || 'Storyshots'; + const { storybook, framework, renderTree } = 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) + // 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 = { From 3cbd772a347b9d025d93ffcb5ff0caaaa30843e8 Mon Sep 17 00:00:00 2001 From: igor Date: Tue, 2 Jan 2018 00:13:34 +0200 Subject: [PATCH 12/41] Add vue storyshots fork --- .editorconfig | 2 +- addons/storyshots/package.json | 3 +- addons/storyshots/src/frameworkLoader.js | 3 +- addons/storyshots/src/vue/loader.js | 44 ++ addons/storyshots/src/vue/renderTree.js | 12 + examples/vue-kitchen-sink/.babelrc | 7 +- examples/vue-kitchen-sink/package.json | 1 + .../vue-kitchen-sink/src/stories/Welcome.vue | 3 + .../src/stories/__snapshots__/index.storyshot | 444 ++++++++++++++++++ examples/vue-kitchen-sink/vueshots.test.js | 8 + jest.config.js | 4 +- package.json | 1 + yarn.lock | 33 +- 13 files changed, 559 insertions(+), 6 deletions(-) create mode 100644 addons/storyshots/src/vue/loader.js create mode 100644 addons/storyshots/src/vue/renderTree.js create mode 100644 examples/vue-kitchen-sink/src/stories/__snapshots__/index.storyshot create mode 100644 examples/vue-kitchen-sink/vueshots.test.js diff --git a/.editorconfig b/.editorconfig index 641825cc12a6..8928b4779fd3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,6 +3,6 @@ root = true [*] end_of_line = lf -[*.{js,json,ts}] +[*.{js,json,ts,vue}] indent_style = space indent_size = 2 diff --git a/addons/storyshots/package.json b/addons/storyshots/package.json index b7ac0014dc9e..eaf298af7153 100644 --- a/addons/storyshots/package.json +++ b/addons/storyshots/package.json @@ -45,6 +45,7 @@ "@storybook/addons": "^3.3.3", "babel-core": "^6.26.0 || ^7.0.0-0", "react": "*", - "react-test-renderer": "*" + "react-test-renderer": "*", + "vue": "^2.5.13" } } diff --git a/addons/storyshots/src/frameworkLoader.js b/addons/storyshots/src/frameworkLoader.js index f23156db1274..047759ec3a98 100644 --- a/addons/storyshots/src/frameworkLoader.js +++ b/addons/storyshots/src/frameworkLoader.js @@ -1,8 +1,9 @@ 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]; +const loaders = [loaderReact, loaderAngular, loaderRn, loaderVue]; function loadFramework(options) { const loader = loaders.find(frameworkLoader => frameworkLoader.test(options)); diff --git a/addons/storyshots/src/vue/loader.js b/addons/storyshots/src/vue/loader.js new file mode 100644 index 000000000000..f3e858853ab7 --- /dev/null +++ b/addons/storyshots/src/vue/loader.js @@ -0,0 +1,44 @@ +/* eslint-disable global-require,import/no-extraneous-dependencies */ +import path from 'path'; +import runWithRequireContext from '../require_context'; +import hasDependency from '../hasDependency'; + +const babel = require('babel-core'); + +function test(options) { + return options.framework === 'vue' || (!options.framework && hasDependency('@storybook/vue')); +} + +function load(options) { + // Mock vue to have compiler-included build + jest.mock('vue', () => { + const vueCommonJs = require('vue/dist/vue.common.js'); + return vueCommonJs; + }); + + const storybook = require.requireActual('@storybook/vue'); + const loadBabelConfig = require('@storybook/vue/dist/server/babel_config').default; + + const configDirPath = path.resolve(options.configPath || '.storybook'); + const 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); + + return { + renderTree: require('./renderTree').default, + framework: 'vue', + storybook, + }; +} + +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..75d8c3e3e82b --- /dev/null +++ b/addons/storyshots/src/vue/renderTree.js @@ -0,0 +1,12 @@ +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/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/package.json b/examples/vue-kitchen-sink/package.json index 9e913c5b6a91..ee101633f03a 100644 --- a/examples/vue-kitchen-sink/package.json +++ b/examples/vue-kitchen-sink/package.json @@ -8,6 +8,7 @@ "@storybook/addon-knobs": "^3.3.3", "@storybook/addon-links": "^3.3.3", "@storybook/addon-notes": "^3.3.3", + "@storybook/addon-storyshots": "^3.3.3", "@storybook/addon-viewport": "^3.3.3", "@storybook/addons": "^3.3.3", "@storybook/vue": "^3.3.3", 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 @@ + +