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:
parent
44069bf0c8
commit
929f2f4b9d
@ -22,6 +22,7 @@ import {
|
|||||||
getKnobType,
|
getKnobType,
|
||||||
} from '../component-story/component-story';
|
} from '../component-story/component-story';
|
||||||
import { applyWithSkipExisting } from '@nrwl/workspace/src/utils/ast-utils';
|
import { applyWithSkipExisting } from '@nrwl/workspace/src/utils/ast-utils';
|
||||||
|
import { join, normalize } from '@angular-devkit/core';
|
||||||
|
|
||||||
export default function (schema: CreateComponentSpecFileSchema): Rule {
|
export default function (schema: CreateComponentSpecFileSchema): Rule {
|
||||||
return chain([createComponentSpecFile(schema)]);
|
return chain([createComponentSpecFile(schema)]);
|
||||||
@ -45,8 +46,11 @@ export function createComponentSpecFile({
|
|||||||
return (tree: Tree, context: SchematicContext): Rule => {
|
return (tree: Tree, context: SchematicContext): Rule => {
|
||||||
const e2eLibIntegrationFolderPath =
|
const e2eLibIntegrationFolderPath =
|
||||||
getProjectConfig(tree, projectName + '-e2e').sourceRoot + '/integration';
|
getProjectConfig(tree, projectName + '-e2e').sourceRoot + '/integration';
|
||||||
const fullComponentPath =
|
const fullComponentPath = join(
|
||||||
libPath + '/' + componentPath + '/' + componentFileName + '.ts';
|
normalize(libPath),
|
||||||
|
componentPath,
|
||||||
|
`${componentFileName}.ts`
|
||||||
|
);
|
||||||
const props = getInputPropertyDeclarations(tree, fullComponentPath).map(
|
const props = getInputPropertyDeclarations(tree, fullComponentPath).map(
|
||||||
(node) => {
|
(node) => {
|
||||||
const decoratorContent = findNodes(
|
const decoratorContent = findNodes(
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
getSourceNodes,
|
getSourceNodes,
|
||||||
applyWithSkipExisting,
|
applyWithSkipExisting,
|
||||||
} from '@nrwl/workspace/src/utils/ast-utils';
|
} from '@nrwl/workspace/src/utils/ast-utils';
|
||||||
|
import { join, normalize } from '@angular-devkit/core';
|
||||||
|
|
||||||
export interface CreateComponentStoriesFileSchema {
|
export interface CreateComponentStoriesFileSchema {
|
||||||
libPath: string;
|
libPath: string;
|
||||||
@ -35,7 +36,7 @@ export function createComponentStoriesFile({
|
|||||||
return (tree: Tree, context: SchematicContext): Rule => {
|
return (tree: Tree, context: SchematicContext): Rule => {
|
||||||
const props = getInputDescriptors(
|
const props = getInputDescriptors(
|
||||||
tree,
|
tree,
|
||||||
libPath + '/' + componentPath + '/' + componentFileName + '.ts'
|
join(normalize(libPath), componentPath, `${componentFileName}.ts`)
|
||||||
);
|
);
|
||||||
return applyWithSkipExisting(url('./files'), [
|
return applyWithSkipExisting(url('./files'), [
|
||||||
template({
|
template({
|
||||||
|
|||||||
@ -156,29 +156,6 @@ export async function createTestUILib(libName: string): Promise<Tree> {
|
|||||||
}),
|
}),
|
||||||
appTree
|
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(
|
appTree.overwrite(
|
||||||
`libs/${libName}/src/lib/test-button/test-button.component.ts`,
|
`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`,
|
`libs/${libName}/src/lib/test-button/test-button.component.html`,
|
||||||
`<button [attr.type]="type" [ngClass]="style"></button>`
|
`<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(
|
appTree = await callRule(
|
||||||
externalSchematic('@schematics/angular', 'component', {
|
externalSchematic('@schematics/angular', 'component', {
|
||||||
name: 'test-other',
|
name: 'test-other',
|
||||||
@ -216,6 +256,7 @@ export class TestButtonComponent implements OnInit {
|
|||||||
}),
|
}),
|
||||||
appTree
|
appTree
|
||||||
);
|
);
|
||||||
|
|
||||||
return appTree;
|
return appTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import { getTsSourceFile, getDecoratorMetadata } from '../../utils/ast-utils';
|
|||||||
import { CreateComponentSpecFileSchema } from '../component-cypress-spec/component-cypress-spec';
|
import { CreateComponentSpecFileSchema } from '../component-cypress-spec/component-cypress-spec';
|
||||||
import { CreateComponentStoriesFileSchema } from '../component-story/component-story';
|
import { CreateComponentStoriesFileSchema } from '../component-story/component-story';
|
||||||
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
|
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 {
|
export interface StorybookStoriesSchema {
|
||||||
name: string;
|
name: string;
|
||||||
@ -39,8 +41,8 @@ export function createAllStories(
|
|||||||
moduleFilePaths.push(filePath);
|
moduleFilePaths.push(filePath);
|
||||||
});
|
});
|
||||||
return chain(
|
return chain(
|
||||||
moduleFilePaths.map((filePath) => {
|
moduleFilePaths.map((moduleFilePath) => {
|
||||||
const file = getTsSourceFile(tree, filePath);
|
const file = getTsSourceFile(tree, moduleFilePath);
|
||||||
|
|
||||||
const ngModuleDecorators = getDecoratorMetadata(
|
const ngModuleDecorators = getDecoratorMetadata(
|
||||||
file,
|
file,
|
||||||
@ -49,7 +51,7 @@ export function createAllStories(
|
|||||||
);
|
);
|
||||||
if (ngModuleDecorators.length === 0) {
|
if (ngModuleDecorators.length === 0) {
|
||||||
throw new SchematicsException(
|
throw new SchematicsException(
|
||||||
`No @NgModule decorator in ${filePath}`
|
`No @NgModule decorator in ${moduleFilePath}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const ngModuleDecorator = ngModuleDecorators[0];
|
const ngModuleDecorator = ngModuleDecorators[0];
|
||||||
@ -66,7 +68,7 @@ export function createAllStories(
|
|||||||
});
|
});
|
||||||
if (!declarationsPropertyAssignment) {
|
if (!declarationsPropertyAssignment) {
|
||||||
context.logger.warn(
|
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();
|
return noop();
|
||||||
}
|
}
|
||||||
@ -83,6 +85,12 @@ export function createAllStories(
|
|||||||
const imports = file.statements.filter(
|
const imports = file.statements.filter(
|
||||||
(statement) => statement.kind === SyntaxKind.ImportDeclaration
|
(statement) => statement.kind === SyntaxKind.ImportDeclaration
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const modulePath = moduleFilePath.substr(
|
||||||
|
0,
|
||||||
|
moduleFilePath.lastIndexOf('/')
|
||||||
|
);
|
||||||
|
|
||||||
const componentInfo = declaredComponents.map((componentName) => {
|
const componentInfo = declaredComponents.map((componentName) => {
|
||||||
try {
|
try {
|
||||||
const importStatement = imports.find((statement) => {
|
const importStatement = imports.find((statement) => {
|
||||||
@ -106,11 +114,61 @@ export function createAllStories(
|
|||||||
.find((node) => node.kind === SyntaxKind.StringLiteral)
|
.find((node) => node.kind === SyntaxKind.StringLiteral)
|
||||||
.getText()
|
.getText()
|
||||||
.slice(1, -1);
|
.slice(1, -1);
|
||||||
|
|
||||||
|
// if it is a directory, search recursively for the component
|
||||||
|
let fullCmpImportPath = moduleFilePath.slice(
|
||||||
|
0,
|
||||||
|
moduleFilePath.lastIndexOf('/')
|
||||||
|
);
|
||||||
|
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 path = fullPath.slice(0, fullPath.lastIndexOf('/'));
|
||||||
const componentFileName = fullPath.slice(
|
const componentFileName = fullPath.slice(
|
||||||
fullPath.lastIndexOf('/') + 1
|
fullPath.lastIndexOf('/') + 1
|
||||||
);
|
);
|
||||||
return { name: componentName, path, componentFileName };
|
return { name: componentName, path, componentFileName };
|
||||||
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
context.logger.warn(
|
context.logger.warn(
|
||||||
`Could not generate a story for ${componentName}. Error: ${ex}`
|
`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(
|
return chain(
|
||||||
componentInfo
|
componentInfo
|
||||||
.filter((info) => info !== undefined)
|
.filter((info) => info !== undefined)
|
||||||
|
|||||||
@ -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.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.ts",
|
||||||
"/libs/test-ui-lib/src/lib/test-button/test-button.component.stories.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.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.css",
|
||||||
"/libs/test-ui-lib/src/lib/nested/nested-button/nested-button.component.html",
|
"/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/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-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/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",
|
"/apps/test-ui-lib-e2e/src/integration/nested-button/nested-button.component.spec.ts",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user