Skip to content

Commit

Permalink
Ts migration/vue (#7578)
Browse files Browse the repository at this point in the history
Ts migration/vue
  • Loading branch information
ndelangen committed Jul 29, 2019
2 parents ec0fcd4 + 02a4e21 commit d1d22d7
Show file tree
Hide file tree
Showing 19 changed files with 251 additions and 147 deletions.
13 changes: 8 additions & 5 deletions addons/jest/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import addons, { Parameters } from '@storybook/addons';
import addons, { Parameters, DecoratorFunction, StoryFn } from '@storybook/addons';
import deprecate from 'util-deprecate';
import { normalize, sep } from 'upath';
import { ADD_TESTS } from './shared';
Expand Down Expand Up @@ -48,16 +48,19 @@ const emitAddTests = ({ kind, story, testFiles, options }: EmitAddTestsArg) => {
});
};

export const withTests = (userOptions: { results: any; filesExt?: string }) => {
export const withTests = (userOptions: {
results: any;
filesExt?: string;
}): DecoratorFunction<unknown> => {
const defaultOptions = {
filesExt: '((\\.specs?)|(\\.tests?))?(\\.[jt]sx?)?$',
};
const options = { ...defaultOptions, ...userOptions };

return (...args: [(string | (() => void)), { kind: string; parameters: AddonParameters }]) => {
return (...args) => {
if (typeof args[0] === 'string') {
return deprecate((storyFn: () => void, { kind }: { kind: string }) => {
emitAddTests({ kind, story: storyFn, testFiles: args as string[], options });
return deprecate((storyFn: StoryFn<unknown>, { kind }: Parameters) => {
emitAddTests({ kind, story: storyFn, testFiles: (args as any) as string[], options });

return storyFn();
}, 'Passing component filenames to the `@storybook/addon-jest` via `withTests` is deprecated. Instead, use the `jest` story parameter');
Expand Down
4 changes: 0 additions & 4 deletions app/react/src/client/preview/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ export interface RenderMainArgs {

export type StoryFnReactReturnType = React.ReactElement<unknown>;

export interface ICollection {
[p: string]: any;
}

export interface IStorybookStory {
name: string;
render: () => any;
Expand Down
9 changes: 7 additions & 2 deletions app/vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"license": "MIT",
"main": "dist/client/index.js",
"jsnext:main": "src/client/index.js",
"types": "dist/client/index.d.ts",
"bin": {
"build-storybook": "./bin/build.js",
"start-storybook": "./bin/index.js",
Expand All @@ -26,13 +26,18 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.2.0-beta.17",
"@storybook/core": "5.2.0-beta.17",
"common-tags": "^1.8.0",
"core-js": "^3.0.1",
"global": "^4.3.2",
"regenerator-runtime": "^0.12.1"
"regenerator-runtime": "^0.12.1",
"webpack": "^4.33.0"
},
"devDependencies": {
"@types/mini-css-extract-plugin": "^0.2.1",
"@types/node": "^12.0.8",
"@types/webpack": "^4.4.32",
"babel-preset-vue": "^2.0.2",
"vue": "^2.6.8",
"vue-loader": "^15.7.0",
Expand Down
File renamed without changes.
File renamed without changes.
95 changes: 0 additions & 95 deletions app/vue/src/client/preview/index.js

This file was deleted.

133 changes: 133 additions & 0 deletions app/vue/src/client/preview/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* eslint-disable prefer-destructuring */
import Vue, { VueConstructor, ComponentOptions } from 'vue';
import { start } from '@storybook/core/client';
import {
ClientStoryApi,
StoryFn,
DecoratorFunction,
StoryContext,
Loadable,
} from '@storybook/addons';

import './globals';
import { IStorybookSection, StoryFnVueReturnType } from './types';

import render, { VALUES } from './render';
import { extractProps } from './util';

export const WRAPS = 'STORYBOOK_WRAPS';

function prepare(rawStory: StoryFnVueReturnType, innerStory?: VueConstructor): VueConstructor {
let story: ComponentOptions<Vue> | VueConstructor;

if (typeof rawStory === 'string') {
story = { template: rawStory };
} else {
story = rawStory as ComponentOptions<Vue>;
}

// @ts-ignore
// eslint-disable-next-line no-underscore-dangle
if (!story._isVue) {
if (innerStory) {
story.components = { ...(story.components || {}), story: innerStory };
}
story = Vue.extend(story);
// @ts-ignore // https://github.com/storybookjs/storybook/pull/7578#discussion_r307984824
} else if (story.options[WRAPS]) {
return story as VueConstructor;
}

return Vue.extend({
// @ts-ignore // https://github.com/storybookjs/storybook/pull/7578#discussion_r307985279
[WRAPS]: story,
// @ts-ignore // https://github.com/storybookjs/storybook/pull/7578#discussion_r307984824
[VALUES]: { ...(innerStory ? innerStory.options[VALUES] : {}), ...extractProps(story) },
functional: true,
render(h, { data, parent, children }) {
return h(
story,
{
...data,
// @ts-ignore // https://github.com/storybookjs/storybook/pull/7578#discussion_r307986196
props: { ...(data.props || {}), ...parent.$root[VALUES] },
},
children
);
},
});
}

const defaultContext: StoryContext = {
id: 'unspecified',
name: 'unspecified',
kind: 'unspecified',
parameters: {},
};

function decorateStory(
storyFn: StoryFn<StoryFnVueReturnType>,
decorators: DecoratorFunction<VueConstructor>[]
): StoryFn<VueConstructor> {
return decorators.reduce(
(decorated: StoryFn<VueConstructor>, decorator) => (context: StoryContext = defaultContext) => {
let story;

const decoratedStory = decorator(p => {
story = decorated(
p
? {
...context,
...p,
parameters: {
...context.parameters,
...p.parameters,
},
}
: context
);

return story;
}, context);

if (!story) {
story = decorated(context);
}

if (decoratedStory === story) {
return story;
}

return prepare(decoratedStory, story);
},
context => prepare(storyFn(context))
);
}
const framework = 'vue';

interface ClientApi extends ClientStoryApi<StoryFnVueReturnType> {
setAddon(addon: any): void;
configure(loader: Loadable, module: NodeModule, framework: string): void;
getStorybook(): IStorybookSection[];
clearDecorators(): void;
forceReRender(): void;
raw: () => any; // todo add type
load: (...args: any[]) => void;
}

const api = start(render, { decorateStory });

export const storiesOf: ClientApi['storiesOf'] = (kind, m) => {
return (api.clientApi.storiesOf(kind, m) as ReturnType<ClientApi['storiesOf']>).addParameters({
framework,
});
};

export const configure: ClientApi['configure'] = (...args) => api.configure(...args, framework);
export const addDecorator: ClientApi['addDecorator'] = api.clientApi.addDecorator;
export const addParameters: ClientApi['addParameters'] = api.clientApi.addParameters;
export const clearDecorators: ClientApi['clearDecorators'] = api.clientApi.clearDecorators;
export const setAddon: ClientApi['setAddon'] = api.clientApi.setAddon;
export const forceReRender: ClientApi['forceReRender'] = api.forceReRender;
export const getStorybook: ClientApi['getStorybook'] = api.clientApi.getStorybook;
export const raw: ClientApi['raw'] = api.clientApi.raw;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { stripIndents } from 'common-tags';
import Vue from 'vue';
import { RenderMainArgs } from './types';

export const COMPONENT = 'STORYBOOK_COMPONENT';
export const VALUES = 'STORYBOOK_VALUES';
Expand All @@ -25,7 +26,7 @@ export default function render({
showError,
showException,
forceRender,
}) {
}: RenderMainArgs) {
Vue.config.errorHandler = showException;

const element = storyFn();
Expand All @@ -48,6 +49,7 @@ export default function render({
root[COMPONENT] = element;
}

// @ts-ignore https://github.com/storybookjs/storybook/pull/7578#discussion_r307986139
root[VALUES] = element.options[VALUES];

if (!root.$el) {
Expand Down
31 changes: 31 additions & 0 deletions app/vue/src/client/preview/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Component, VueConstructor } from 'vue';
import { StoryFn } from '@storybook/addons';
// TODO, 'any' should be what is actually expected from a storyFn

export interface ShowErrorArgs {
title: string;
description: string;
}

export interface RenderMainArgs {
storyFn: StoryFn<VueConstructor>;
selectedKind: string;
selectedStory: string;
showMain: () => void;
showError: (args: ShowErrorArgs) => void;
showException: (...args: any[]) => void;
forceRender: boolean;
}

// TODO: some vue expert needs to look at this
export type StoryFnVueReturnType = string | Component;

export interface IStorybookStory {
name: string;
render: () => any;
}

export interface IStorybookSection {
kind: string;
stories: IStorybookStory[];
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
function getType(fn) {
import { VueConstructor } from 'vue';

function getType(fn: Function) {
const match = fn && fn.toString().match(/^\s*function (\w+)/);
return match ? match[1] : '';
}

// https://github.com/vuejs/vue/blob/dev/src/core/util/props.js#L92
function resolveDefault({ type, default: def }) {
function resolveDefault({ type, default: def }: any) {
if (typeof def === 'function' && getType(type) !== 'Function') {
// known limitation: we dont have the component instance to pass
// known limitation: we don't have the component instance to pass
return def.call();
}

return def;
}

export function extractProps(component) {
export function extractProps(component: VueConstructor) {
// @ts-ignore this options business seems not good according to the types
return Object.entries(component.options.props || {})
.map(([name, prop]) => ({ [name]: resolveDefault(prop) }))
.reduce((wrap, prop) => ({ ...wrap, ...prop }), {});
Expand Down
File renamed without changes.

1 comment on commit d1d22d7

@vercel
Copy link

@vercel vercel bot commented on d1d22d7 Jul 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.