From 4c0231af7e76ba03532be93f29deb3d960655653 Mon Sep 17 00:00:00 2001 From: Mihaly Bardos Date: Fri, 8 May 2020 13:21:00 +0200 Subject: [PATCH] feat(schematic): Schematics for dt-e2e component. Co-authored-by: Fabian Friedl --- .prettierignore | 1 + angular.json | 5 + .../schematics/src/testing/workspace.ts | 2 +- libs/workspace/collection.json | 9 +- .../src/schematics/dt-component-e2e/README.md | 13 ++ .../__snapshots__/index.spec.ts.snap | 176 ++++++++++++++++++ .../__name@dasherize__.e2e.ts | 23 +++ .../__name@dasherize__.html | 1 + .../__name@dasherize__.module.ts | 29 +++ .../__name@dasherize__.po.ts | 19 ++ .../__name@dasherize__/__name@dasherize__.ts | 25 +++ .../fixtures/alert.fixture.ts | 18 ++ .../fixtures/app.routing.module.ts.fixture.ts | 49 +++++ .../schematics/dt-component-e2e/index.spec.ts | 155 +++++++++++++++ .../src/schematics/dt-component-e2e/index.ts | 130 +++++++++++++ .../schematics/dt-component-e2e/schema.json | 17 ++ .../src/schematics/dt-component-e2e/schema.ts | 19 ++ .../src/schematics/utils/ast-utils.ts | 80 ++++++++ .../workspace/src}/schematics/utils/change.ts | 16 ++ libs/workspace/tsconfig.json | 3 +- libs/workspace/tsconfig.lib.json | 2 +- libs/workspace/webpack.config.js | 1 + tools/schematics/utils/ast-utils.ts | 62 ------ 23 files changed, 789 insertions(+), 66 deletions(-) create mode 100644 libs/workspace/src/schematics/dt-component-e2e/README.md create mode 100644 libs/workspace/src/schematics/dt-component-e2e/__snapshots__/index.spec.ts.snap create mode 100644 libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.e2e.ts create mode 100644 libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.html create mode 100644 libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.module.ts create mode 100644 libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.po.ts create mode 100644 libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.ts create mode 100644 libs/workspace/src/schematics/dt-component-e2e/fixtures/alert.fixture.ts create mode 100644 libs/workspace/src/schematics/dt-component-e2e/fixtures/app.routing.module.ts.fixture.ts create mode 100644 libs/workspace/src/schematics/dt-component-e2e/index.spec.ts create mode 100644 libs/workspace/src/schematics/dt-component-e2e/index.ts create mode 100644 libs/workspace/src/schematics/dt-component-e2e/schema.json create mode 100644 libs/workspace/src/schematics/dt-component-e2e/schema.ts create mode 100644 libs/workspace/src/schematics/utils/ast-utils.ts rename {tools => libs/workspace/src}/schematics/utils/change.ts (75%) diff --git a/.prettierignore b/.prettierignore index e6b0a11948..f8537105d3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,6 +5,7 @@ /apps/demos/src/nav-items.template /tools/schematics/*/files/**/* +/libs/workspace/src/schematics/*/files/**/* /test/**/* /tools/stylelint/**/* diff --git a/angular.json b/angular.json index 22902898fe..483896491d 100644 --- a/angular.json +++ b/angular.json @@ -908,6 +908,11 @@ "input": "./libs/workspace", "glob": "{package,builders,collection}.json", "output": "." + }, + { + "input": "./libs/workspace/src", + "glob": "schematics/*/files/**/*.{ts,html,json,md,scss}", + "output": "./src" } ] }, diff --git a/libs/barista-components/schematics/src/testing/workspace.ts b/libs/barista-components/schematics/src/testing/workspace.ts index ce0d1f0f1f..06c6501385 100644 --- a/libs/barista-components/schematics/src/testing/workspace.ts +++ b/libs/barista-components/schematics/src/testing/workspace.ts @@ -21,7 +21,7 @@ import { Style, } from '@schematics/angular/application/schema'; import { Schema as WorkspaceOptions } from '@schematics/angular/workspace/schema'; -import { addFixtureToTree } from '.'; +import { addFixtureToTree } from './fixture'; import { runExternalSchematic } from './run-schematic'; export async function createWorkspace(tree?: Tree): Promise { diff --git a/libs/workspace/collection.json b/libs/workspace/collection.json index 8d2fa968c6..9313e4c583 100644 --- a/libs/workspace/collection.json +++ b/libs/workspace/collection.json @@ -2,5 +2,12 @@ "$schema": "../../node_modules/@angular-devkit/schematics/collection-schema.json", "name": "workspace", "version": "0.0.1", - "schematics": {} + "schematics": { + "dt-components-e2e": { + "description": "Create a new e2e setup in the components-e2e app for a given component name", + "factory": "./src/schematics/dt-component-e2e/index", + "schema": "./src/schematics/dt-component-e2e/schema.json", + "aliases": ["dt-e2e"] + } + } } diff --git a/libs/workspace/src/schematics/dt-component-e2e/README.md b/libs/workspace/src/schematics/dt-component-e2e/README.md new file mode 100644 index 0000000000..3ed94cc34c --- /dev/null +++ b/libs/workspace/src/schematics/dt-component-e2e/README.md @@ -0,0 +1,13 @@ +# Shipped schematics + +This schematics generates the component for barista e2e tests. The only needed +parameter is the name of the component. + +## Usage + +- Please run: `ng build workspace` and after that, + `ng g ./dist/libs/workspace:dt-e2e --name={name of your component}` + +### Testing + +The schematics can be tested with the `ng test workspace` command. diff --git a/libs/workspace/src/schematics/dt-component-e2e/__snapshots__/index.spec.ts.snap b/libs/workspace/src/schematics/dt-component-e2e/__snapshots__/index.spec.ts.snap new file mode 100644 index 0000000000..0e318e8dab --- /dev/null +++ b/libs/workspace/src/schematics/dt-component-e2e/__snapshots__/index.spec.ts.snap @@ -0,0 +1,176 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Add E2E Component should change files appropriately 1`] = ` +"/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the \\"License\\"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an \\"AS IS\\" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-nocheck +// Code copy pasted from original app.routing.module +// Used only for testing purposes +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +export const routes: Routes = [ + { + path: 'autocomplete', + loadChildren: () => + import('').then((module) => module.DtE2EAutocompleteModule), + }, + { + path: 'button', + loadChildren: () => import('').then((module) => module.DtE2EButtonModule), + }, + { + path: 'alert', + loadChildren: () => + import('../components/alert/alert.module').then( + (module) => module.DtE2EAlertModule, + ), + }, +]; + +@NgModule({ + imports: [ + CommonModule, + RouterModule.forRoot(routes, { + scrollPositionRestoration: 'enabled', + anchorScrolling: 'enabled', + paramsInheritanceStrategy: 'always', + enableTracing: false, // Can be set for debugging the router + initialNavigation: 'enabled', + }), + ], + exports: [RouterModule], +}) +export class AppRoutingModule {} +" +`; + +exports[`Add E2E Component should generate files with the appropriate content 1`] = ` +"/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the \\"License\\"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an \\"AS IS\\" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { alert } from './alert.po'; + +fixture('Alert').page('http://localhost:4200/alert'); + +test('should exists', async (testController: TestController) => { + await testController.expect(alert.exists).ok(); +}); +" +`; + +exports[`Add E2E Component should generate files with the appropriate content 2`] = ` +" +" +`; + +exports[`Add E2E Component should generate files with the appropriate content 3`] = ` +"/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the \\"License\\"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an \\"AS IS\\" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { Route, RouterModule } from '@angular/router'; +import { DtAlertModule } from '@dynatrace/barista-components/alert'; +import { DtE2EAlert } from './alert'; + +const routes: Route[] = [{ path: '', component: DtE2EAlert }]; + +@NgModule({ + declarations: [DtE2EAlert], + imports: [CommonModule, RouterModule.forChild(routes), DtAlertModule], +}) +export class DtE2EAlertModule {} +" +`; + +exports[`Add E2E Component should generate files with the appropriate content 4`] = ` +"/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the \\"License\\"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an \\"AS IS\\" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Selector } from 'testcafe'; + +export const alert = Selector('#test-alert'); +" +`; + +exports[`Add E2E Component should generate files with the appropriate content 5`] = ` +"/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the \\"License\\"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an \\"AS IS\\" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'dt-e2e-alert', + templateUrl: 'alert.html', +}) +export class DtE2EAlert {} +" +`; diff --git a/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.e2e.ts b/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.e2e.ts new file mode 100644 index 0000000000..293510c963 --- /dev/null +++ b/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.e2e.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { <%= camelize(name) %> } from './<%= dasherize(name) %>.po'; + +fixture('<%= capitalize(name) %>').page('http://localhost:4200/<%= dasherize(name) %>'); + +test('should exists', async (testController: TestController) => { + await testController.expect(<%= camelize(name) %>.exists).ok(); +}); diff --git a/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.html b/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.html new file mode 100644 index 0000000000..08b95adab4 --- /dev/null +++ b/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.html @@ -0,0 +1 @@ +<<%= selector %> id="test-<%= dasherize(name) %>">> diff --git a/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.module.ts b/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.module.ts new file mode 100644 index 0000000000..20d42eec78 --- /dev/null +++ b/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.module.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { Route, RouterModule } from '@angular/router'; +import { <%= componentModule.name %> } from '<%= componentModule.package %>'; +import { <%= e2eComponent.component %> } from './<%= dasherize(name) %>'; + +const routes: Route[] = [{ path: '', component: <%= e2eComponent.component %> }]; + +@NgModule({ + declarations: [<%= e2eComponent.component %>], + imports: [CommonModule, RouterModule.forChild(routes), <%= componentModule.name %>], +}) +export class <%= e2eComponent.module %> {} diff --git a/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.po.ts b/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.po.ts new file mode 100644 index 0000000000..585122eb2b --- /dev/null +++ b/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.po.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Selector } from 'testcafe'; + +export const <%= camelize(name) %> = Selector('#test-<%= dasherize(name) %>'); diff --git a/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.ts b/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.ts new file mode 100644 index 0000000000..678b9c4c0f --- /dev/null +++ b/libs/workspace/src/schematics/dt-component-e2e/files/apps/components-e2e/src/components/__name@dasherize__/__name@dasherize__.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'dt-e2e-<%= dasherize(name) %>', + templateUrl: '<%= dasherize(name) %>.html', +}) +export class <%= e2eComponent.component %> { + +} diff --git a/libs/workspace/src/schematics/dt-component-e2e/fixtures/alert.fixture.ts b/libs/workspace/src/schematics/dt-component-e2e/fixtures/alert.fixture.ts new file mode 100644 index 0000000000..8feb9851fc --- /dev/null +++ b/libs/workspace/src/schematics/dt-component-e2e/fixtures/alert.fixture.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-nocheck +// this is an empty file for testing purposes diff --git a/libs/workspace/src/schematics/dt-component-e2e/fixtures/app.routing.module.ts.fixture.ts b/libs/workspace/src/schematics/dt-component-e2e/fixtures/app.routing.module.ts.fixture.ts new file mode 100644 index 0000000000..9bbb8c226d --- /dev/null +++ b/libs/workspace/src/schematics/dt-component-e2e/fixtures/app.routing.module.ts.fixture.ts @@ -0,0 +1,49 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-nocheck +// Code copy pasted from original app.routing.module +// Used only for testing purposes +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +export const routes: Routes = [ + { + path: 'autocomplete', + loadChildren: () => + import('').then((module) => module.DtE2EAutocompleteModule), + }, + { + path: 'button', + loadChildren: () => import('').then((module) => module.DtE2EButtonModule), + }, +]; + +@NgModule({ + imports: [ + CommonModule, + RouterModule.forRoot(routes, { + scrollPositionRestoration: 'enabled', + anchorScrolling: 'enabled', + paramsInheritanceStrategy: 'always', + enableTracing: false, // Can be set for debugging the router + initialNavigation: 'enabled', + }), + ], + exports: [RouterModule], +}) +export class AppRoutingModule {} diff --git a/libs/workspace/src/schematics/dt-component-e2e/index.spec.ts b/libs/workspace/src/schematics/dt-component-e2e/index.spec.ts new file mode 100644 index 0000000000..fc4bb188f0 --- /dev/null +++ b/libs/workspace/src/schematics/dt-component-e2e/index.spec.ts @@ -0,0 +1,155 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Tree } from '@angular-devkit/schematics'; +import { + SchematicTestRunner, + UnitTestTree, +} from '@angular-devkit/schematics/testing'; +import { + Schema as ApplicationOptions, + Style, +} from '@schematics/angular/application/schema'; +import { Schema as WorkspaceOptions } from '@schematics/angular/workspace/schema'; +import { promises as fs } from 'fs'; +import { join } from 'path'; + +export async function getFixture(filePath: string): Promise { + const fixturesFolder = join(__dirname, './fixtures'); + return fs.readFile(join(fixturesFolder, filePath), { + encoding: 'utf-8', + }); +} + +export async function addFixtureToTree( + tree: Tree, + source: string, + destination: string, +): Promise { + const content = await getFixture(source); + if (tree.exists(destination)) { + tree.overwrite(destination, content); + return; + } + tree.create(destination, content); +} + +const getNewFiles = (newFiles: string[], oldFiles: string[]): string[] => + newFiles.filter((file) => !oldFiles.includes(file)); + +describe('Add E2E Component', () => { + const workspaceOptions: WorkspaceOptions = { + name: 'workspace', + newProjectRoot: 'apps', + version: '8.0.0', + }; + + const appOptions: ApplicationOptions = { + name: 'myapp', + inlineStyle: false, + inlineTemplate: false, + routing: false, + style: Style.Scss, + skipTests: true, + skipPackageJson: true, + }; + + let initialTree: UnitTestTree; + let schematicRunner: SchematicTestRunner; + let initialFiles: string[]; + + beforeEach(async () => { + schematicRunner = new SchematicTestRunner( + '@dynatrace/barista-components/schematics', + join(__dirname, '../../../collection.json'), + ); + + initialTree = await schematicRunner + .runExternalSchematicAsync( + '@schematics/angular', + 'workspace', + workspaceOptions, + Tree.empty(), + ) + .toPromise(); + + initialTree = await schematicRunner + .runExternalSchematicAsync( + '@schematics/angular', + 'application', + appOptions, + initialTree, + ) + .toPromise(); + + await addFixtureToTree( + initialTree, + 'app.routing.module.ts.fixture.ts', + 'apps/components-e2e/src/app/app.routing.module.ts', + ); + + initialFiles = initialTree.files; + }); + + it('should add the necessary files', async () => { + const result = await schematicRunner + .runSchematicAsync('dt-e2e', { name: 'alert' }, initialTree) + .toPromise(); + + const newFiles = getNewFiles(result.files, initialFiles); + + expect(newFiles).toEqual([ + '/apps/components-e2e/src/components/alert/alert.e2e.ts', + '/apps/components-e2e/src/components/alert/alert.html', + '/apps/components-e2e/src/components/alert/alert.module.ts', + '/apps/components-e2e/src/components/alert/alert.po.ts', + '/apps/components-e2e/src/components/alert/alert.ts', + ]); + }); + + it('should generate files with the appropriate content', async () => { + const result = await schematicRunner + .runSchematicAsync('dt-e2e', { name: 'alert' }, initialTree) + .toPromise(); + + const newFiles = getNewFiles(result.files, initialFiles); + newFiles.map((file) => expect(result.readContent(file)).toMatchSnapshot()); + }); + + it('should change files appropriately', async () => { + const result = await schematicRunner + .runSchematicAsync('dt-e2e', { name: 'alert' }, initialTree) + .toPromise(); + + expect( + result.readContent('apps/components-e2e/src/app/app.routing.module.ts'), + ).toMatchSnapshot(); + }); + + it('should throw error if we already have the files in there', async () => { + await addFixtureToTree( + initialTree, + 'alert.fixture.ts', + '/apps/components-e2e/src/components/alert/alert.ts', + ); + + await expect( + schematicRunner + .runSchematicAsync('dt-e2e', { name: 'alert' }, initialTree) + .toPromise(), + ).rejects.toThrow(); + }); +}); diff --git a/libs/workspace/src/schematics/dt-component-e2e/index.ts b/libs/workspace/src/schematics/dt-component-e2e/index.ts new file mode 100644 index 0000000000..6bb4625737 --- /dev/null +++ b/libs/workspace/src/schematics/dt-component-e2e/index.ts @@ -0,0 +1,130 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { strings } from '@angular-devkit/core'; +import { + apply, + chain, + mergeWith, + Rule, + template, + Tree, + url, +} from '@angular-devkit/schematics'; +import { formatFiles } from '@nrwl/workspace'; +import { join } from 'path'; +import * as ts from 'typescript'; +import { findNodes, getIndentation, getSourceFile } from '../utils/ast-utils'; +import { commitChanges, InsertChange } from '../utils/change'; +import { DtComponentE2EOptions } from './schema'; +interface DtE2EExtendedOptions { + selector: string; + componentModule: { + name: string; + package: string; + }; + e2eComponent: { + component: string; + module: string; + }; + name: string; +} + +function generateComponentOptions( + name: string, +): { name: string; package: string } { + return { + name: `Dt${strings.classify(name)}Module`, + package: `@dynatrace/barista-components/${strings.dasherize(name)}`, + }; +} + +function generateE2EComponentOptions( + name: string, +): { component: string; module: string } { + return { + component: `DtE2E${strings.classify(name)}`, + module: `DtE2E${strings.classify(name)}Module`, + }; +} + +/** + * Adds a new route inside the ui-test routes + */ +export function addRoute(options: DtE2EExtendedOptions): Rule { + return (host: Tree) => { + const modulePath = join( + 'apps', + 'components-e2e', + 'src', + 'app', + 'app.routing.module.ts', + ); + const sourceFile = getSourceFile(host, modulePath); + + /** + * find last route and add new route + */ + const routesDeclaration = findNodes( + sourceFile, + ts.SyntaxKind.VariableDeclaration, + ).find( + (node: ts.VariableDeclaration) => node.name.getText() === 'routes', + ) as ts.VariableDeclaration; + const routesElements = (routesDeclaration.initializer as ts.ArrayLiteralExpression) + .elements; + const lastElement = routesElements[routesElements.length - 1]; + const end = routesElements.hasTrailingComma + ? lastElement.getEnd() + 1 + : lastElement.getEnd(); + const toInsert = `{ + path: '${strings.dasherize(options.name)}', + loadChildren: () => + import('../components/${strings.dasherize( + options.name, + )}/${strings.dasherize(options.name)}.module').then( + (module) => module.${options.e2eComponent.module}, + ), + },`; + const routesChange = new InsertChange(modulePath, end, toInsert); + + return commitChanges(host, [routesChange], modulePath); + }; +} + +export default function (options: DtComponentE2EOptions): Rule { + return async () => { + const extendedOptions: DtE2EExtendedOptions = { + ...options, + selector: `dt-${strings.dasherize(options.name)}`, + componentModule: generateComponentOptions(options.name), + e2eComponent: generateE2EComponentOptions(options.name), + }; + + const templateSource = apply(url('./files'), [ + template({ + ...strings, + ...extendedOptions, + }), + ]); + + return chain([ + mergeWith(templateSource), + addRoute(extendedOptions), + formatFiles(), + ]); + }; +} diff --git a/libs/workspace/src/schematics/dt-component-e2e/schema.json b/libs/workspace/src/schematics/dt-component-e2e/schema.json new file mode 100644 index 0000000000..a8ca5ba14b --- /dev/null +++ b/libs/workspace/src/schematics/dt-component-e2e/schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "DtComponentE2E", + "title": "DtComponent E2E Schema", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the component", + "$default": { + "$source": "argv", + "index": 0 + }, + "x-prompt": "What name would you like to use for your e2e component?" + } + } +} diff --git a/libs/workspace/src/schematics/dt-component-e2e/schema.ts b/libs/workspace/src/schematics/dt-component-e2e/schema.ts new file mode 100644 index 0000000000..f81ad047cf --- /dev/null +++ b/libs/workspace/src/schematics/dt-component-e2e/schema.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface DtComponentE2EOptions { + name: string; +} diff --git a/libs/workspace/src/schematics/utils/ast-utils.ts b/libs/workspace/src/schematics/utils/ast-utils.ts new file mode 100644 index 0000000000..8f31ea5fa1 --- /dev/null +++ b/libs/workspace/src/schematics/utils/ast-utils.ts @@ -0,0 +1,80 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SchematicsException, Tree } from '@angular-devkit/schematics'; +import * as ts from 'typescript'; + +export function getSourceFile(host: Tree, path: string): ts.SourceFile { + const buffer = host.read(path); + if (!buffer) { + throw new SchematicsException(`File ${path} does not exist.`); + } + const content = buffer.toString(); + return ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true); +} + +/** + * Find all nodes from the AST in the subtree of node of SyntaxKind kind. + */ +export function findNodes( + node: ts.Node, + kind: ts.SyntaxKind, + max: number = Infinity, +): ts.Node[] { + if (!node || max === 0) { + return []; + } + + const arr: ts.Node[] = []; + if (node.kind === kind) { + arr.push(node); + max--; + } + if (max > 0) { + for (const child of node.getChildren()) { + // tslint:disable-next-line: no-shadowed-variable + findNodes(child, kind, max).forEach((node: ts.Node) => { + if (max > 0) { + arr.push(node); + } + max--; + }); + + if (max <= 0) { + break; + } + } + } + + return arr; +} + +/** + * Gets the indentation string for the last entry in NodeArray + */ +export function getIndentation( + elements: ts.NodeArray | ts.Node[], +): string { + let indentation = '\n'; + if (elements.length > 0) { + const text = elements[elements.length - 1].getFullText(); + const matches = text.match(/^\r?\n\s*/); + if (matches && matches.length > 0) { + indentation = matches[0]; + } + } + return indentation; +} diff --git a/tools/schematics/utils/change.ts b/libs/workspace/src/schematics/utils/change.ts similarity index 75% rename from tools/schematics/utils/change.ts rename to libs/workspace/src/schematics/utils/change.ts index 5ecd77dd6e..654b65523d 100644 --- a/tools/schematics/utils/change.ts +++ b/libs/workspace/src/schematics/utils/change.ts @@ -1,3 +1,19 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { Tree } from '@angular-devkit/schematics'; import { isArray } from 'rxjs/internal-compatibility'; diff --git a/libs/workspace/tsconfig.json b/libs/workspace/tsconfig.json index d4110679cb..3a3e70a13d 100644 --- a/libs/workspace/tsconfig.json +++ b/libs/workspace/tsconfig.json @@ -4,5 +4,6 @@ "types": ["node", "jest"], "downlevelIteration": true }, - "include": ["**/*.d.ts"] + "include": ["**/*.d.ts"], + "exclude": ["src/schematics/*/files"] } diff --git a/libs/workspace/tsconfig.lib.json b/libs/workspace/tsconfig.lib.json index e423deb192..7aa6658c93 100644 --- a/libs/workspace/tsconfig.lib.json +++ b/libs/workspace/tsconfig.lib.json @@ -6,6 +6,6 @@ "declaration": true, "types": ["node"] }, - "exclude": ["**/*.spec.ts", "src/test-setup.ts"], + "exclude": ["**/*.spec.ts", "src/test-setup.ts", "src/schematics/*/files"], "include": ["**/*.d.ts"] } diff --git a/libs/workspace/webpack.config.js b/libs/workspace/webpack.config.js index 89c43effdc..e15e6b841c 100644 --- a/libs/workspace/webpack.config.js +++ b/libs/workspace/webpack.config.js @@ -15,6 +15,7 @@ const entries = [ 'src/builders/design-tokens/package/index.ts', 'src/builders/design-tokens/dependency-tree/index.ts', 'src/builders/elements/index.ts', + 'src/schematics/dt-component-e2e/index.ts', 'src/index.ts', ]; diff --git a/tools/schematics/utils/ast-utils.ts b/tools/schematics/utils/ast-utils.ts index b71279276a..469c6b4d5c 100644 --- a/tools/schematics/utils/ast-utils.ts +++ b/tools/schematics/utils/ast-utils.ts @@ -23,51 +23,6 @@ import { DtComponentOptions } from '../dt-component/schema'; import { commitChanges, InsertChange } from './change'; import { addNavItem } from './nav-items'; -export function getSourceFile(host: Tree, path: string): ts.SourceFile { - const buffer = host.read(path); - if (!buffer) { - throw new SchematicsException(`File ${path} does not exist.`); - } - const content = buffer.toString(); - return ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true); -} - -/** - * Find all nodes from the AST in the subtree of node of SyntaxKind kind. - */ -export function findNodes( - node: ts.Node, - kind: ts.SyntaxKind, - max: number = Infinity, -): ts.Node[] { - if (!node || max === 0) { - return []; - } - - const arr: ts.Node[] = []; - if (node.kind === kind) { - arr.push(node); - max--; - } - if (max > 0) { - for (const child of node.getChildren()) { - // tslint:disable-next-line: no-shadowed-variable - findNodes(child, kind, max).forEach((node: ts.Node) => { - if (max > 0) { - arr.push(node); - } - max--; - }); - - if (max <= 0) { - break; - } - } - } - - return arr; -} - /** * Get all the nodes from a source. */ @@ -130,23 +85,6 @@ export function addDynatraceAngularComponentsImport( return new InsertChange(path, end, toInsert); } -/** - * Gets the indentation string for the last entry in NodeArray - */ -export function getIndentation( - elements: ts.NodeArray | ts.Node[], -): string { - let indentation = '\n'; - if (elements.length > 0) { - const text = elements[elements.length - 1].getFullText(); - const matches = text.match(/^\r?\n\s*/); - if (matches && matches.length > 0) { - indentation = matches[0]; - } - } - return indentation; -} - export function addImport( sourcePath: string, sourceFile: ts.SourceFile,