Skip to content

Commit

Permalink
chore: add vNext storybook builders support and context awareness for…
Browse files Browse the repository at this point in the history
… migration generator (#19484)

* feat(.storybook): update typings with builders support

* feat(tools): enable builders for local storybooks

* feat(tools): setup storybook based on context

* style(tools): fix lint errors

* fixup! feat(tools): setup storybook based on context
  • Loading branch information
Hotell authored Aug 25, 2021
1 parent 2cbc189 commit dd0e01e
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 82 deletions.
18 changes: 15 additions & 3 deletions .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,30 @@ const { TsconfigPathsPlugin } = require('tsconfig-paths-webpack-plugin');
*/

/**
* @typedef {{check:boolean; checkOptions: Record<string,unknown>; reactDocgen: string | boolean; reactDocgenTypescriptOptions: Record<string,unknown>}} StorybookTsConfig
* @typedef {{
* check:boolean;
* checkOptions: Record<string,unknown>;
* reactDocgen: string | boolean;
* reactDocgenTypescriptOptions: Record<string,unknown>
* }} StorybookTsConfig
*/

/**
* @typedef {{stories: string[] ; addons: string[]; typescript: StorybookTsConfig; babel: (options:Record<string,unknown>)=>Promise<Record<string,unknown>>; webpackFinal: StorybookWebpackConfig}} StorybookConfig
* @typedef {{
* stories: string[];
* addons: string[];
* typescript: StorybookTsConfig;
* babel: (options:Record<string,unknown>)=>Promise<Record<string,unknown>>;
* webpackFinal: StorybookWebpackConfig;
* core: {builder:'webpack5'};
* }} StorybookConfig
*/

/**
* @typedef {{loader: string; options: { [index: string]: any }}} LoaderObjectDef
*/

module.exports = /** @type {Pick<StorybookConfig,'addons' |'stories' |'webpackFinal'>} */ ({
module.exports = /** @type {Omit<StorybookConfig,'typescript'|'babel'>} */ ({
stories: [],
addons: [
'@storybook/addon-essentials',
Expand Down
180 changes: 113 additions & 67 deletions tools/generators/migrate-converged-pkg/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,11 @@ describe('migrate-converged-pkg generator', () => {
return json;
});

/* eslint-disable @fluentui/max-len */
await expect(generator(tree, options)).rejects.toMatchInlineSnapshot(
// eslint-disable-next-line @fluentui/max-len
`[Error: @proj/react-dummy is not converged package. Make sure to run the migration on packages with version 9.x.x]`,
);
/* eslint-enable @fluentui/max-len */
});
});

Expand Down Expand Up @@ -188,7 +189,7 @@ describe('migrate-converged-pkg generator', () => {
outDir: 'dist',
preserveConstEnums: true,
target: 'ES2020',
types: ['jest', 'custom-global', 'inline-style-expand-shorthand', 'storybook__addons'],
types: ['jest', 'custom-global', 'inline-style-expand-shorthand'],
},
extends: '../../tsconfig.base.json',
include: ['src'],
Expand All @@ -211,7 +212,6 @@ describe('migrate-converged-pkg generator', () => {
'jest',
'custom-global',
'inline-style-expand-shorthand',
'storybook__addons',
'@testing-library/jest-dom',
'foo-bar',
]);
Expand Down Expand Up @@ -392,68 +392,16 @@ describe('migrate-converged-pkg generator', () => {
});

describe(`storybook updates`, () => {
it(`should setup local storybook`, async () => {
const projectConfig = readProjectConfiguration(tree, options.name);
const projectStorybookConfigPath = `${projectConfig.root}/.storybook`;

expect(tree.exists(projectStorybookConfigPath)).toBeFalsy();

await generator(tree, options);

expect(tree.exists(projectStorybookConfigPath)).toBeTruthy();
expect(readJson(tree, `${projectStorybookConfigPath}/tsconfig.json`)).toMatchInlineSnapshot(`
Object {
"compilerOptions": Object {
"allowJs": true,
"checkJs": true,
},
"exclude": Array [
"../**/*.test.ts",
"../**/*.test.js",
"../**/*.test.tsx",
"../**/*.test.jsx",
],
"extends": "../tsconfig.json",
"include": Array [
"../src/**/*",
"*.js",
],
}
`);

/* eslint-disable @fluentui/max-len */
expect(tree.read(`${projectStorybookConfigPath}/main.js`)?.toString('utf-8')).toMatchInlineSnapshot(`
"const rootMain = require('../../../.storybook/main');
function setup(_config?: Partial<{ createDummyStories: boolean }>) {
const defaults = { createDummyStories: true };
const config = { ...defaults, ..._config };

module.exports = /** @type {Pick<import('../../../.storybook/main').StorybookConfig,'addons'|'stories'|'webpackFinal'>} */ ({
stories: [...rootMain.stories, '../src/**/*.stories.mdx', '../src/**/*.stories.@(ts|tsx)'],
addons: [...rootMain.addons],
webpackFinal: (config, options) => {
const localConfig = { ...rootMain.webpackFinal(config, options) };
return localConfig;
},
});"
`);
/* eslint-enable @fluentui/max-len */

expect(tree.read(`${projectStorybookConfigPath}/preview.js`)?.toString('utf-8')).toMatchInlineSnapshot(`
"import * as rootPreview from '../../../.storybook/preview';
/** @type {typeof rootPreview.decorators} */
export const decorators = [...rootPreview.decorators];
/** @type {typeof rootPreview.parameters} */
export const parameters = { ...rootPreview.parameters };"
`);
});

function setup() {
const workspaceConfig = readWorkspaceConfiguration(tree);
const projectConfig = readProjectConfiguration(tree, options.name);
const normalizedProjectName = options.name.replace(`@${workspaceConfig.npmScope}/`, '');
const reactExamplesConfig = readProjectConfiguration(tree, '@proj/react-examples');
const pathToStoriesWithinReactExamples = `${reactExamplesConfig.root}/src/${normalizedProjectName}`;
const projectStorybookConfigPath = `${projectConfig.root}/.storybook`;

const paths = {
reactExamples: {
Expand All @@ -470,21 +418,23 @@ describe('migrate-converged-pkg generator', () => {
},
};

tree.write(
paths.reactExamples.storyFileOne,
stripIndents`
if (config.createDummyStories) {
tree.write(
paths.reactExamples.storyFileOne,
stripIndents`
import * as Implementation from '${options.name}';
export const Foo = (props: FooProps) => { return <div>Foo</div>; }
`,
);
);

tree.write(
paths.reactExamples.storyFileTwo,
stripIndents`
tree.write(
paths.reactExamples.storyFileTwo,
stripIndents`
import * as Implementation from '${options.name}';
export const FooOther = (props: FooPropsOther) => { return <div>FooOther</div>; }
`,
);
);
}

function getMovedStoriesData() {
const movedStoriesExportNames = {
Expand Down Expand Up @@ -515,8 +465,104 @@ describe('migrate-converged-pkg generator', () => {
normalizedProjectName,
pathToStoriesWithinReactExamples,
getMovedStoriesData,
projectStorybookConfigPath,
};
}
it(`should setup package storybook when needed`, async () => {
const { projectStorybookConfigPath, projectConfig } = setup();

expect(tree.exists(projectStorybookConfigPath)).toBeFalsy();

await generator(tree, options);

expect(tree.exists(projectStorybookConfigPath)).toBeTruthy();
expect(readJson(tree, `${projectStorybookConfigPath}/tsconfig.json`)).toMatchInlineSnapshot(`
Object {
"compilerOptions": Object {
"allowJs": true,
"checkJs": true,
},
"exclude": Array [
"../**/*.test.ts",
"../**/*.test.js",
"../**/*.test.tsx",
"../**/*.test.jsx",
],
"extends": "../tsconfig.json",
"include": Array [
"../src/**/*",
"*.js",
],
}
`);

expect(tree.read(`${projectStorybookConfigPath}/main.js`)?.toString('utf-8')).toMatchInlineSnapshot(`
"const rootMain = require('../../../.storybook/main');
module.exports = /** @type {Omit<import('../../../.storybook/main'), 'typescript'|'babel'>} */ ({
...rootMain,
stories: [...rootMain.stories, '../src/**/*.stories.mdx', '../src/**/*.stories.@(ts|tsx)'],
addons: [...rootMain.addons],
webpackFinal: (config, options) => {
const localConfig = { ...rootMain.webpackFinal(config, options) };
// add your own webpack tweaks if needed
return localConfig;
},
});"
`);

expect(tree.read(`${projectStorybookConfigPath}/preview.js`)?.toString('utf-8')).toMatchInlineSnapshot(`
"import * as rootPreview from '../../../.storybook/preview';
/** @type {typeof rootPreview.decorators} */
export const decorators = [...rootPreview.decorators];
/** @type {typeof rootPreview.parameters} */
export const parameters = { ...rootPreview.parameters };"
`);

expect(readJson<TsConfig>(tree, `${projectConfig.root}/tsconfig.json`).compilerOptions.types).toContain(
'storybook__addons',
);
});

it(`should remove unused existing storybook setup`, async () => {
const { projectStorybookConfigPath, projectConfig } = setup({ createDummyStories: false });

const mainJsFilePath = `${projectStorybookConfigPath}/main.js`;
const packageJsonPath = `${projectConfig.root}/package.json`;

let pkgJson: PackageJson = readJson(tree, packageJsonPath);

tree.write(mainJsFilePath, 'module.exports = {}');

updateJson(tree, packageJsonPath, (json: PackageJson) => {
json.scripts = json.scripts || {};

Object.assign(json.scripts, {
start: 'echo "hello"',
storybook: 'echo "hello"',
'build-storybook': 'echo "hello"',
});
return json;
});

pkgJson = readJson(tree, packageJsonPath);

expect(tree.exists(projectStorybookConfigPath)).toBeTruthy();
expect(tree.exists(mainJsFilePath)).toBeTruthy();

await generator(tree, options);

expect(tree.exists(mainJsFilePath)).toBeFalsy();
expect(Object.keys(pkgJson.scripts || [])).not.toContain(['start', 'storybook', 'build-storybook']);
expect(tree.exists(projectStorybookConfigPath)).toBeFalsy();
expect(readJson<TsConfig>(tree, `${projectConfig.root}/tsconfig.json`).compilerOptions.types).not.toContain(
'storybook__addons',
);
});

it(`should work if there are no package stories in react-examples`, async () => {
const reactExamplesConfig = readProjectConfiguration(tree, '@proj/react-examples');
Expand Down
Loading

0 comments on commit dd0e01e

Please sign in to comment.