Skip to content

Commit

Permalink
fix(nx): clean up some storybook bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
Isaac Mann authored and vsavkin committed Nov 15, 2019
1 parent 44d45d3 commit 2425436
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 136 deletions.
75 changes: 71 additions & 4 deletions e2e/storybook.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
ensureProject,
tmpProjPath
} from './utils';
import { writeFileSync } from 'fs';
import { writeFileSync, mkdirSync } from 'fs';

forEachCli(() => {
describe('Storybook schematics', () => {
Expand All @@ -24,7 +24,52 @@ forEachCli(() => {
const mylib2 = uniq('test-ui-lib-react');
runCLI(`generate @nrwl/react:lib ${mylib2} --no-interactive`);
runCLI(
`generate @nrwl/react:component TestComponent --project=${mylib2} --no-interactive`
`generate @nrwl/react:component Button --project=${mylib2} --no-interactive`
);
writeFileSync(
tmpProjPath(`libs/${mylib2}/src/lib/button.tsx`),
`
import React from 'react';
import './button.css';
export type ButtonStyle = 'default' | 'primary' | 'warning';
/* eslint-disable-next-line */
export interface ButtonProps {
text?: string;
style?: ButtonStyle;
padding?: number;
}
export const Button = (props: ButtonProps) => {
return (
<button className={props.style} style={{ padding: \`\${props.padding}px\` }}>
{props.text}
</button>
);
};
export default Button;
`
);
writeFileSync(
tmpProjPath(`libs/${mylib2}/src/lib/button.stories.tsx`),
`
import React from 'react';
import { text, number } from '@storybook/addon-knobs';
import { Button, ButtonStyle } from './button';
export default { title: 'Button' };
export const primary = () => (
<Button
padding={number('Padding', 0)}
style={text('Style', 'default') as ButtonStyle}
text={text('Text', 'Click me')}
/>
);
`
);

runCLI(
Expand Down Expand Up @@ -62,6 +107,28 @@ forEachCli(() => {
`generate @nrwl/react:storybook-configuration ${mylib2} --configureCypress --no-interactive`
);

mkdirSync(tmpProjPath(`apps/${mylib2}-e2e/src/integration`));
writeFileSync(
tmpProjPath(`apps/${mylib2}-e2e/src/integration/button.spec.ts`),
`
describe('react-ui', () => {
it('should render the component', () => {
cy.visit(
'/iframe.html?id=button--primary&knob-Style=default&knob-Padding&knob-Text=Click%20me'
);
cy.get('button').should('exist');
cy.get('button').should('have.class', 'default');
});
it('should adjust the knobs', () => {
cy.visit(
'/iframe.html?id=button--primary&knob-Style=primary&knob-Padding=10&knob-Text=Other'
);
cy.get('button').should('have.class', 'primary');
});
});
`
);

expect(
runCLI(`run ${mylib}-e2e:e2e --configuration=headless --no-watch`)
).toContain('All specs passed!');
Expand All @@ -74,7 +141,7 @@ forEachCli(() => {
export function createTestUILib(libName: string): void {
runCLI(`g @nrwl/angular:library ${libName} --no-interactive`);
runCLI(
`g @schematics/angular:component test-button --project=${libName} --no-interactive`
`g @nrwl/angular:component test-button --project=${libName} --no-interactive`
);

writeFileSync(
Expand Down Expand Up @@ -114,6 +181,6 @@ export class TestButtonComponent implements OnInit {
`
);
runCLI(
`g @schematics/angular:component test-other --project=${libName} --no-interactive`
`g @nrwl/angular:component test-other --project=${libName} --no-interactive`
);
}
104 changes: 58 additions & 46 deletions packages/angular/src/schematics/stories/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,58 +82,70 @@ export function createAllStories(
statement => statement.kind === SyntaxKind.ImportDeclaration
);
const componentInfo = declaredComponents.map(componentName => {
const importStatement = imports.find(statement => {
const importedIdentifiers = statement
try {
const importStatement = imports.find(statement => {
const importedIdentifiers = statement
.getChildren()
.find(node => node.kind === SyntaxKind.ImportClause)
.getChildren()
.find(node => node.kind === SyntaxKind.NamedImports)
.getChildren()
.find(node => node.kind === SyntaxKind.SyntaxList)
.getChildren()
.filter(node => node.kind === SyntaxKind.ImportSpecifier)
.map(node => node.getText());
return importedIdentifiers.includes(componentName);
});
const fullPath = importStatement
.getChildren()
.find(node => node.kind === SyntaxKind.ImportClause)
.getChildren()
.find(node => node.kind === SyntaxKind.NamedImports)
.getChildren()
.find(node => node.kind === SyntaxKind.SyntaxList)
.getChildren()
.filter(node => node.kind === SyntaxKind.ImportSpecifier)
.map(node => node.getText());
return importedIdentifiers.includes(componentName);
});
const fullPath = importStatement
.getChildren()
.find(node => node.kind === SyntaxKind.StringLiteral)
.getText()
.slice(1, -1);
const path = fullPath.slice(0, fullPath.lastIndexOf('/'));
const componentFileName = fullPath.slice(
fullPath.lastIndexOf('/') + 1
);
return { name: componentName, path, componentFileName };
.find(node => node.kind === SyntaxKind.StringLiteral)
.getText()
.slice(1, -1);
const path = fullPath.slice(0, fullPath.lastIndexOf('/'));
const componentFileName = fullPath.slice(
fullPath.lastIndexOf('/') + 1
);
return { name: componentName, path, componentFileName };
} catch (ex) {
context.logger.warn(
`Could not generate a story for ${componentName}. Error: ${ex}`
);
return undefined;
}
});

const moduleName = getFirstNgModuleName(file);

return chain(
componentInfo.map(info =>
chain([
schematic<CreateComponentStoriesFileSchema>('component-story', {
libPath,
moduleFileName: fileName,
ngModuleClassName: moduleName,
componentName: info.name,
componentPath: info.path,
componentFileName: info.componentFileName
}),
generateCypressSpecs
? schematic<CreateComponentSpecFileSchema>(
'component-cypress-spec',
{
projectName,
libPath,
componentName: info.name,
componentPath: info.path,
componentFileName: info.componentFileName
}
)
: () => {}
])
)
componentInfo
.filter(info => info !== undefined)
.map(info =>
chain([
schematic<CreateComponentStoriesFileSchema>(
'component-story',
{
libPath,
moduleFileName: fileName,
ngModuleClassName: moduleName,
componentName: info.name,
componentPath: info.path,
componentFileName: info.componentFileName
}
),
generateCypressSpecs
? schematic<CreateComponentSpecFileSchema>(
'component-cypress-spec',
{
projectName,
libPath,
componentName: info.name,
componentPath: info.path,
componentFileName: info.componentFileName
}
)
: () => {}
])
)
);
})
);
Expand Down
7 changes: 4 additions & 3 deletions packages/storybook/collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
"name": "Nx Cypress",
"version": "0.1",
"schematics": {
"ng-add": {
"factory": "./src/schematics/ng-add/ng-add",
"schema": "./src/schematics/ng-add/schema.json",
"init": {
"factory": "./src/schematics/init/init",
"schema": "./src/schematics/init/schema.json",
"description": "Add storybook configuration to the workspace",
"aliases": ["ng-add"],
"hidden": true
},
"configuration": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import { configure, addDecorator } from '<%= uiFramework %>';
import { withKnobs } from '@storybook/addon-knobs';

addDecorator(withKnobs);
configure(require.context('../src/lib', true, /\.stories\.ts$/), module);
configure(require.context('../src/lib', true, /\.stories\.tsx?$/), module);
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,17 @@ const rootWebpackConfig = require('<%= offsetFromRoot %>../.storybook/webpack.co
// Export a function. Accept the base config as the only param.
module.exports = async ({ config, mode }) => {
config = await rootWebpackConfig({ config, mode });
<% if(uiFramework === '@storybook/react') { %>
config.module.rules.push({
test: /\.(ts|tsx)$/,
loader: require.resolve('babel-loader'),
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript'
]
}
});<% } %>
return config;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createEmptyWorkspace } from '@nrwl/workspace/testing';

import { runSchematic } from '../../utils/testing';

describe('ng-add', () => {
describe('init', () => {
let appTree: Tree;

beforeEach(() => {
Expand All @@ -14,7 +14,7 @@ describe('ng-add', () => {
});

it('should add dependencies into `package.json` file', async () => {
const tree = await runSchematic('ng-add', {}, appTree);
const tree = await runSchematic('init', {}, appTree);
const packageJson = readJsonInTree(tree, 'package.json');
expect(packageJson.devDependencies['@storybook/angular']).toBeDefined();
expect(packageJson.devDependencies['@storybook/addon-knobs']).toBeDefined();
Expand Down
4 changes: 2 additions & 2 deletions packages/storybook/src/utils/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export async function createTestUILib(libName: string): Promise<Tree> {
appTree
);
appTree = await callRule(
externalSchematic('@schematics/angular', 'component', {
externalSchematic('@nrwl/angular', 'component', {
name: 'test-button',
project: libName
}),
Expand Down Expand Up @@ -84,7 +84,7 @@ export class TestButtonComponent implements OnInit {
`<button [attr.type]="type" [ngClass]="style"></button>`
);
appTree = await callRule(
externalSchematic('@schematics/angular', 'component', {
externalSchematic('@nrwl/angular', 'component', {
name: 'test-other',
project: libName
}),
Expand Down
78 changes: 0 additions & 78 deletions packages/storybook/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ import {
forEach
} from '@angular-devkit/schematics';

import {
findPropertyInAstObject,
appendPropertyInAstObject,
insertPropertyInAstObjectInOrder,
appendValueInAstArray
} from '@schematics/angular/utility/json-utils';

import { get } from 'http';
import { SourceFile, createSourceFile, ScriptTarget } from 'typescript';

Expand Down Expand Up @@ -52,77 +45,6 @@ export function safeFileDelete(tree: Tree, path: string): boolean {
}
}

export function addPropertyToPackageJson(
tree: Tree,
context: SchematicContext,
propertyName: string,
propertyValue: { [key: string]: string }
) {
addPropertyToJsonAst(
tree,
context,
'/package.json',
propertyName,
propertyValue
);
}

export function addPropertyToJsonAst(
tree: Tree,
context: SchematicContext,
jsonPath: string,
propertyName: string,
propertyValue: { [key: string]: string } | JsonValue,
appendInArray = false
) {
const jsonAst = getJsonFile(tree, jsonPath);
const jsonNode = findPropertyInAstObject(jsonAst, propertyName);
const recorder = tree.beginUpdate(jsonPath);

if (!jsonNode) {
// outer node missing, add key/value
appendPropertyInAstObject(
recorder,
jsonAst,
propertyName,
propertyValue,
Constants.jsonIndentLevel
);
} else if (jsonNode.kind === 'object') {
// property exists, update values
for (const [key, value] of Object.entries(propertyValue as {
[key: string]: string;
})) {
const innerNode = findPropertyInAstObject(jsonNode, key);

if (!innerNode) {
// 'propertyName' not found, add it
context.logger.debug(`creating ${key} with ${value}`);

insertPropertyInAstObjectInOrder(
recorder,
jsonNode,
key,
value,
Constants.jsonIndentLevel
);
} else {
// 'propertyName' found, overwrite value
context.logger.debug(`overwriting ${key} with ${value}`);

const { end, start } = innerNode;

recorder.remove(start.offset, end.offset - start.offset);
recorder.insertRight(start.offset, JSON.stringify(value));
}
}
} else if (jsonNode.kind === 'array' && appendInArray) {
appendValueInAstArray(recorder, jsonNode, propertyValue);
}

tree.commitUpdate(recorder);
}

/**
* Attempt to retrieve the latest package version from NPM
* Return an optional "latest" version in case of error
Expand Down

0 comments on commit 2425436

Please sign in to comment.