diff --git a/packages/schematics/angular/collection.json b/packages/schematics/angular/collection.json index 317e0ec6fe60..6a2d5dfe4f6c 100755 --- a/packages/schematics/angular/collection.json +++ b/packages/schematics/angular/collection.json @@ -60,6 +60,12 @@ "description": "Create a guard.", "schema": "./guard/schema.json" }, + "resolver": { + "aliases": [ "r" ], + "factory": "./resolver", + "description": "Create a resolver.", + "schema": "./resolver/schema.json" + }, "interceptor": { "factory": "./interceptor", "description": "Create an interceptor.", diff --git a/packages/schematics/angular/resolver/files/__name@dasherize__.resolver.spec.ts.template b/packages/schematics/angular/resolver/files/__name@dasherize__.resolver.spec.ts.template new file mode 100644 index 000000000000..3cd1e381185e --- /dev/null +++ b/packages/schematics/angular/resolver/files/__name@dasherize__.resolver.spec.ts.template @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { <%= classify(name) %>Resolver } from './<%= dasherize(name) %>.resolver'; + +describe('<%= classify(name) %>Resolver', () => { + let resolver: <%= classify(name) %>Resolver; + + beforeEach(() => { + TestBed.configureTestingModule({}); + resolver = TestBed.inject(<%= classify(name) %>Resolver); + }); + + it('should be created', () => { + expect(resolver).toBeTruthy(); + }); +}); diff --git a/packages/schematics/angular/resolver/files/__name@dasherize__.resolver.ts.template b/packages/schematics/angular/resolver/files/__name@dasherize__.resolver.ts.template new file mode 100644 index 000000000000..ffeaaf4193ed --- /dev/null +++ b/packages/schematics/angular/resolver/files/__name@dasherize__.resolver.ts.template @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { + Router, Resolve, + RouterStateSnapshot, + ActivatedRouteSnapshot +} from '@angular/router'; +import { Observable, of } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class <%= classify(name) %>Resolver implements Resolve { + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return of(true); + } +} diff --git a/packages/schematics/angular/resolver/index.ts b/packages/schematics/angular/resolver/index.ts new file mode 100644 index 000000000000..73f3282982d3 --- /dev/null +++ b/packages/schematics/angular/resolver/index.ts @@ -0,0 +1,49 @@ +/** +* @license +* Copyright Google Inc. All Rights Reserved. +* +* Use of this source code is governed by an MIT-style license that can be +* found in the LICENSE file at https://angular.io/license +*/ +import { strings } from '@angular-devkit/core'; +import { + Rule, + Tree, + apply, + applyTemplates, + chain, + filter, + mergeWith, + move, + noop, + url, +} from '@angular-devkit/schematics'; +import { parseName } from '../utility/parse-name'; +import { createDefaultPath } from '../utility/workspace'; +import { Schema } from './schema'; + + +export default function (options: Schema): Rule { + return async (host: Tree) => { + if (options.path === undefined) { + options.path = await createDefaultPath(host, options.project as string); + } + + const parsedPath = parseName(options.path, options.name); + options.name = parsedPath.name; + options.path = parsedPath.path; + + const templateSource = apply(url('./files'), [ + options.skipTests ? filter(path => !path.endsWith('.spec.ts.template')) : noop(), + applyTemplates({ + ...strings, + ...options, + }), + move(parsedPath.path + (options.flat ? '' : '/' + strings.dasherize(options.name))), + ]); + + return chain([ + mergeWith(templateSource), + ]); + }; +} diff --git a/packages/schematics/angular/resolver/index_spec.ts b/packages/schematics/angular/resolver/index_spec.ts new file mode 100644 index 000000000000..f7b599cb26e9 --- /dev/null +++ b/packages/schematics/angular/resolver/index_spec.ts @@ -0,0 +1,80 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; +import { Schema as ApplicationOptions } from '../application/schema'; +import { Schema as WorkspaceOptions } from '../workspace/schema'; +import { Schema } from './schema'; + +describe('resolver Schematic', () => { + const schematicRunner = new SchematicTestRunner( + '@schematics/angular', + require.resolve('../collection.json'), + ); + const defaultOptions: Schema = { + name: 'foo', + flat: true, + project: 'bar', + }; + const workspaceOptions: WorkspaceOptions = { + name: 'workspace', + newProjectRoot: 'projects', + version: '6.0.0', + }; + + const appOptions: ApplicationOptions = { + name: 'bar', + inlineStyle: false, + inlineTemplate: false, + routing: false, + skipTests: false, + skipPackageJson: false, + }; + let appTree: UnitTestTree; + beforeEach(async () => { + appTree = await schematicRunner.runSchematicAsync('workspace', workspaceOptions).toPromise(); + appTree = await schematicRunner.runSchematicAsync('application', appOptions, appTree) + .toPromise(); + }); + + it('should create a resolver', async () => { + const tree = await schematicRunner.runSchematicAsync('resolver', defaultOptions, appTree) + .toPromise(); + const files = tree.files; + expect(files).toContain('/projects/bar/src/app/foo.resolver.spec.ts'); + expect(files).toContain('/projects/bar/src/app/foo.resolver.ts'); + }); + + it('should respect the skipTests flag', async () => { + const options = { ...defaultOptions, skipTests: true }; + + const tree = await schematicRunner.runSchematicAsync('resolver', options, appTree) + .toPromise(); + const files = tree.files; + expect(files).not.toContain('/projects/bar/src/app/foo.resolver.spec.ts'); + expect(files).toContain('/projects/bar/src/app/foo.resolver.ts'); + }); + + it('should respect the flat flag', async () => { + const options = { ...defaultOptions, flat: false }; + + const tree = await schematicRunner.runSchematicAsync('resolver', options, appTree) + .toPromise(); + const files = tree.files; + expect(files).toContain('/projects/bar/src/app/foo/foo.resolver.spec.ts'); + expect(files).toContain('/projects/bar/src/app/foo/foo.resolver.ts'); + }); + + it('should respect the sourceRoot value', async () => { + const config = JSON.parse(appTree.readContent('/angular.json')); + config.projects.bar.sourceRoot = 'projects/bar/custom'; + appTree.overwrite('/angular.json', JSON.stringify(config, null, 2)); + appTree = await schematicRunner.runSchematicAsync('resolver', defaultOptions, appTree) + .toPromise(); + expect(appTree.files).toContain('/projects/bar/custom/app/foo.resolver.ts'); + }); +}); diff --git a/packages/schematics/angular/resolver/schema.json b/packages/schematics/angular/resolver/schema.json new file mode 100644 index 000000000000..feb7c99c956f --- /dev/null +++ b/packages/schematics/angular/resolver/schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "SchematicsAngularResolver", + "title": "Angular resolver Options Schema", + "type": "object", + "description": "Generates a new, generic resolver definition in the given or default project.", + "properties": { + "name": { + "type": "string", + "description": "The name of the new resolver.", + "$default": { + "$source": "argv", + "index": 0 + }, + "x-prompt": "What name would you like to use for the resolver?" + }, + "skipTests": { + "type": "boolean", + "description": "When true, does not create \"spec.ts\" test files for the new resolver.", + "default": false, + "x-user-analytics": 12 + }, + "flat": { + "type": "boolean", + "description": "When true (the default), creates the new files at the top level of the current project.", + "default": true + }, + "path": { + "type": "string", + "format": "path", + "description": "The path at which to create the interface that defines the resolver, relative to the current workspace.", + "visible": false + }, + "project": { + "type": "string", + "description": "The name of the project.", + "$default": { + "$source": "projectName" + } + } + }, + "required": [ + "name" + ] +}