fix(angular): properly generate storybook stories across barrel files (#3662)

Previously stories haven't been generated properly when
barrel files had been used to import
components in
Angular modules

ISSUES CLOSED: 2813
This commit is contained in:
Juri Strumpflohner 2020-09-15 13:25:11 +02:00 committed by GitHub
parent 44069bf0c8
commit 929f2f4b9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 35 deletions

View File

@ -22,6 +22,7 @@ import {
getKnobType,
} from '../component-story/component-story';
import { applyWithSkipExisting } from '@nrwl/workspace/src/utils/ast-utils';
import { join, normalize } from '@angular-devkit/core';
export default function (schema: CreateComponentSpecFileSchema): Rule {
return chain([createComponentSpecFile(schema)]);
@ -45,8 +46,11 @@ export function createComponentSpecFile({
return (tree: Tree, context: SchematicContext): Rule => {
const e2eLibIntegrationFolderPath =
getProjectConfig(tree, projectName + '-e2e').sourceRoot + '/integration';
const fullComponentPath =
libPath + '/' + componentPath + '/' + componentFileName + '.ts';
const fullComponentPath = join(
normalize(libPath),
componentPath,
`${componentFileName}.ts`
);
const props = getInputPropertyDeclarations(tree, fullComponentPath).map(
(node) => {
const decoratorContent = findNodes(

View File

@ -14,6 +14,7 @@ import {
getSourceNodes,
applyWithSkipExisting,
} from '@nrwl/workspace/src/utils/ast-utils';
import { join, normalize } from '@angular-devkit/core';
export interface CreateComponentStoriesFileSchema {
libPath: string;
@ -35,7 +36,7 @@ export function createComponentStoriesFile({
return (tree: Tree, context: SchematicContext): Rule => {
const props = getInputDescriptors(
tree,
libPath + '/' + componentPath + '/' + componentFileName + '.ts'
join(normalize(libPath), componentPath, `${componentFileName}.ts`)
);
return applyWithSkipExisting(url('./files'), [
template({

View File

@ -156,29 +156,6 @@ export async function createTestUILib(libName: string): Promise<Tree> {
}),
appTree
);
const modulePath = `libs/${libName}/src/lib/${libName}.module.ts`;
appTree.overwrite(
modulePath,
`import * as ButtonExports from './test-button/test-button.component';
${appTree.read(modulePath)}`
);
appTree = await callRule(
externalSchematic('@schematics/angular', 'module', {
name: 'nested',
project: libName,
path: `libs/${libName}/src/lib`,
}),
appTree
);
appTree = await callRule(
externalSchematic('@schematics/angular', 'component', {
name: 'nested-button',
project: libName,
module: 'nested',
path: `libs/${libName}/src/lib/nested`,
}),
appTree
);
appTree.overwrite(
`libs/${libName}/src/lib/test-button/test-button.component.ts`,
`
@ -209,6 +186,69 @@ export class TestButtonComponent implements OnInit {
`libs/${libName}/src/lib/test-button/test-button.component.html`,
`<button [attr.type]="type" [ngClass]="style"></button>`
);
const modulePath = `libs/${libName}/src/lib/${libName}.module.ts`;
appTree.overwrite(
modulePath,
`import * as ButtonExports from './test-button/test-button.component';
${appTree.read(modulePath)}`
);
// create a module with component that gets exported in a barrel file
appTree = await callRule(
externalSchematic('@schematics/angular', 'module', {
name: 'barrel',
project: libName,
}),
appTree
);
appTree = await callRule(
externalSchematic('@schematics/angular', 'component', {
name: 'barrel-button',
project: libName,
path: `libs/${libName}/src/lib/barrel`,
module: 'barrel',
}),
appTree
);
appTree.create(
`libs/${libName}/src/lib/barrel/barrel-button/index.ts`,
`export * from './barrel-button.component';`
);
appTree.overwrite(
`libs/${libName}/src/lib/barrel/barrel.module.ts`,
`import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BarrelButtonComponent } from './barrel-button';
@NgModule({
imports: [CommonModule],
declarations: [BarrelButtonComponent],
})
export class BarrelModule {}`
);
// create another button in a nested subpath
appTree = await callRule(
externalSchematic('@schematics/angular', 'module', {
name: 'nested',
project: libName,
path: `libs/${libName}/src/lib`,
}),
appTree
);
appTree = await callRule(
externalSchematic('@schematics/angular', 'component', {
name: 'nested-button',
project: libName,
module: 'nested',
path: `libs/${libName}/src/lib/nested`,
}),
appTree
);
appTree = await callRule(
externalSchematic('@schematics/angular', 'component', {
name: 'test-other',
@ -216,6 +256,7 @@ export class TestButtonComponent implements OnInit {
}),
appTree
);
return appTree;
}

View File

@ -13,6 +13,8 @@ import { getTsSourceFile, getDecoratorMetadata } from '../../utils/ast-utils';
import { CreateComponentSpecFileSchema } from '../component-cypress-spec/component-cypress-spec';
import { CreateComponentStoriesFileSchema } from '../component-story/component-story';
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import { directoryExists } from '@nrwl/workspace/src/utils/fileutils';
import { join, normalize } from '@angular-devkit/core';
export interface StorybookStoriesSchema {
name: string;
@ -39,8 +41,8 @@ export function createAllStories(
moduleFilePaths.push(filePath);
});
return chain(
moduleFilePaths.map((filePath) => {
const file = getTsSourceFile(tree, filePath);
moduleFilePaths.map((moduleFilePath) => {
const file = getTsSourceFile(tree, moduleFilePath);
const ngModuleDecorators = getDecoratorMetadata(
file,
@ -49,7 +51,7 @@ export function createAllStories(
);
if (ngModuleDecorators.length === 0) {
throw new SchematicsException(
`No @NgModule decorator in ${filePath}`
`No @NgModule decorator in ${moduleFilePath}`
);
}
const ngModuleDecorator = ngModuleDecorators[0];
@ -66,7 +68,7 @@ export function createAllStories(
});
if (!declarationsPropertyAssignment) {
context.logger.warn(
stripIndents`No stories generated because there were no components declared in ${filePath}. Hint: you can always generate stories later with the 'nx generate @nrwl/angular:stories --name=${projectName}' command`
stripIndents`No stories generated because there were no components declared in ${moduleFilePath}. Hint: you can always generate stories later with the 'nx generate @nrwl/angular:stories --name=${projectName}' command`
);
return noop();
}
@ -83,6 +85,12 @@ export function createAllStories(
const imports = file.statements.filter(
(statement) => statement.kind === SyntaxKind.ImportDeclaration
);
const modulePath = moduleFilePath.substr(
0,
moduleFilePath.lastIndexOf('/')
);
const componentInfo = declaredComponents.map((componentName) => {
try {
const importStatement = imports.find((statement) => {
@ -106,11 +114,61 @@ export function createAllStories(
.find((node) => node.kind === SyntaxKind.StringLiteral)
.getText()
.slice(1, -1);
const path = fullPath.slice(0, fullPath.lastIndexOf('/'));
const componentFileName = fullPath.slice(
fullPath.lastIndexOf('/') + 1
// if it is a directory, search recursively for the component
let fullCmpImportPath = moduleFilePath.slice(
0,
moduleFilePath.lastIndexOf('/')
);
return { name: componentName, path, componentFileName };
if (fullCmpImportPath.startsWith('/')) {
fullCmpImportPath = fullCmpImportPath.slice(
1,
fullCmpImportPath.length
);
}
const componentImportPath = join(
normalize(fullCmpImportPath),
fullPath
);
const dir = tree.getDir(componentImportPath);
if (dir && dir.subfiles.length > 0) {
let path = null;
let componentFileName = null;
// search the fullPath for component declarations
tree.getDir(componentImportPath).visit((componentFilePath) => {
if (componentFilePath.endsWith('.ts')) {
const content = tree
.read(componentFilePath)
.toString('utf-8');
if (content.indexOf(`class ${componentName}`) > -1) {
path = componentFilePath
.slice(0, componentFilePath.lastIndexOf('/'))
.replace(modulePath, '.');
componentFileName = componentFilePath.slice(
componentFilePath.lastIndexOf('/') + 1,
componentFilePath.lastIndexOf('.')
);
return;
}
}
});
if (path === null) {
throw new SchematicsException(
`Path to component ${componentName} couldn't be found. Please open an issue on https://github.com/nrwl/nx/issues.`
);
}
return { name: componentName, path, componentFileName };
} else {
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}`
@ -119,7 +177,6 @@ export function createAllStories(
}
});
const modulePath = filePath.substr(0, filePath.lastIndexOf('/'));
return chain(
componentInfo
.filter((info) => info !== undefined)

View File

@ -22,6 +22,13 @@ Array [
"/libs/test-ui-lib/src/lib/test-button/test-button.component.spec.ts",
"/libs/test-ui-lib/src/lib/test-button/test-button.component.ts",
"/libs/test-ui-lib/src/lib/test-button/test-button.component.stories.ts",
"/libs/test-ui-lib/src/lib/barrel/barrel.module.ts",
"/libs/test-ui-lib/src/lib/barrel/barrel-button/barrel-button.component.css",
"/libs/test-ui-lib/src/lib/barrel/barrel-button/barrel-button.component.html",
"/libs/test-ui-lib/src/lib/barrel/barrel-button/barrel-button.component.spec.ts",
"/libs/test-ui-lib/src/lib/barrel/barrel-button/barrel-button.component.ts",
"/libs/test-ui-lib/src/lib/barrel/barrel-button/index.ts",
"/libs/test-ui-lib/src/lib/barrel/barrel-button/barrel-button.component.stories.ts",
"/libs/test-ui-lib/src/lib/nested/nested.module.ts",
"/libs/test-ui-lib/src/lib/nested/nested-button/nested-button.component.css",
"/libs/test-ui-lib/src/lib/nested/nested-button/nested-button.component.html",
@ -50,6 +57,7 @@ Array [
"/apps/test-ui-lib-e2e/src/support/index.ts",
"/apps/test-ui-lib-e2e/src/integration/test-button/test-button.component.spec.ts",
"/apps/test-ui-lib-e2e/src/integration/test-other/test-other.component.spec.ts",
"/apps/test-ui-lib-e2e/src/integration/barrel-button/barrel-button.component.spec.ts",
"/apps/test-ui-lib-e2e/src/integration/nested-button/nested-button.component.spec.ts",
]
`;