diff --git a/packages/schematics/angular/app-shell/index_spec.ts b/packages/schematics/angular/app-shell/index_spec.ts index 46c6bd1caad6..86c033fc8e96 100644 --- a/packages/schematics/angular/app-shell/index_spec.ts +++ b/packages/schematics/angular/app-shell/index_spec.ts @@ -40,63 +40,71 @@ describe('App Shell Schematic', () => { beforeEach(async () => { appTree = await schematicRunner.runSchematic('workspace', workspaceOptions); - appTree = await schematicRunner.runSchematic('application', appOptions, appTree); }); - it('should add app shell configuration', async () => { - const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); - const filePath = '/angular.json'; - const content = tree.readContent(filePath); - const workspace = JSON.parse(content); - const target = workspace.projects.bar.architect['build']; - expect(target.configurations.production.appShell).toBeTrue(); - }); + describe('non standalone application', () => { + beforeEach(async () => { + appTree = await schematicRunner.runSchematic( + 'application', + { ...appOptions, standalone: false }, + appTree, + ); + }); - it('should ensure the client app has a router-outlet', async () => { - appTree = await schematicRunner.runSchematic('workspace', workspaceOptions); - appTree = await schematicRunner.runSchematic( - 'application', - { ...appOptions, routing: false }, - appTree, - ); - await expectAsync( - schematicRunner.runSchematic('app-shell', defaultOptions, appTree), - ).toBeRejected(); - }); + it('should add app shell configuration', async () => { + const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); + const filePath = '/angular.json'; + const content = tree.readContent(filePath); + const workspace = JSON.parse(content); + const target = workspace.projects.bar.architect['build']; + expect(target.configurations.production.appShell).toBeTrue(); + }); - it('should add a server app', async () => { - const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); - const filePath = '/projects/bar/src/app/app.module.server.ts'; - expect(tree.exists(filePath)).toEqual(true); - }); + it('should ensure the client app has a router-outlet', async () => { + appTree = await schematicRunner.runSchematic('workspace', workspaceOptions); + appTree = await schematicRunner.runSchematic( + 'application', + { ...appOptions, routing: false }, + appTree, + ); + await expectAsync( + schematicRunner.runSchematic('app-shell', defaultOptions, appTree), + ).toBeRejected(); + }); - it('should add router module to client app module', async () => { - const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); - const filePath = '/projects/bar/src/app/app.module.ts'; - const content = tree.readContent(filePath); - expect(content).toMatch(/import { RouterModule } from '@angular\/router';/); - }); + it('should add a server app', async () => { + const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); + const filePath = '/projects/bar/src/app/app.module.server.ts'; + expect(tree.exists(filePath)).toEqual(true); + }); - it('should not fail when AppModule have imported RouterModule already', async () => { - const updateRecorder = appTree.beginUpdate('/projects/bar/src/app/app.module.ts'); - updateRecorder.insertLeft(0, "import { RouterModule } from '@angular/router';"); - appTree.commitUpdate(updateRecorder); + it('should add router module to client app module', async () => { + const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); + const filePath = '/projects/bar/src/app/app.module.ts'; + const content = tree.readContent(filePath); + expect(content).toMatch(/import { RouterModule } from '@angular\/router';/); + }); - const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); - const filePath = '/projects/bar/src/app/app.module.ts'; - const content = tree.readContent(filePath); - expect(content).toMatch(/import { RouterModule } from '@angular\/router';/); - }); + it('should not fail when AppModule have imported RouterModule already', async () => { + const updateRecorder = appTree.beginUpdate('/projects/bar/src/app/app.module.ts'); + updateRecorder.insertLeft(0, "import { RouterModule } from '@angular/router';"); + appTree.commitUpdate(updateRecorder); - describe('Add router-outlet', () => { - function makeInlineTemplate(tree: UnitTestTree, template?: string): void { - template = - template || - ` + const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); + const filePath = '/projects/bar/src/app/app.module.ts'; + const content = tree.readContent(filePath); + expect(content).toMatch(/import { RouterModule } from '@angular\/router';/); + }); + + describe('Add router-outlet', () => { + function makeInlineTemplate(tree: UnitTestTree, template?: string): void { + template = + template || + `

App works!

`; - const newText = ` + const newText = ` import { Component } from '@angular/core'; @Component({ @@ -109,112 +117,100 @@ describe('App Shell Schematic', () => { export class AppComponent { } `; - tree.overwrite('/projects/bar/src/app/app.component.ts', newText); - tree.delete('/projects/bar/src/app/app.component.html'); - } - - it('should not re-add the router outlet (external template)', async () => { - const htmlPath = '/projects/bar/src/app/app.component.html'; - appTree.overwrite(htmlPath, ''); - const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); - const content = tree.readContent(htmlPath); - const matches = content.match(/<\/router-outlet>/g); - const numMatches = matches ? matches.length : 0; - expect(numMatches).toEqual(1); + tree.overwrite('/projects/bar/src/app/app.component.ts', newText); + tree.delete('/projects/bar/src/app/app.component.html'); + } + + it('should not re-add the router outlet (external template)', async () => { + const htmlPath = '/projects/bar/src/app/app.component.html'; + appTree.overwrite(htmlPath, ''); + const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); + const content = tree.readContent(htmlPath); + const matches = content.match(/<\/router-outlet>/g); + const numMatches = matches ? matches.length : 0; + expect(numMatches).toEqual(1); + }); + + it('should not re-add the router outlet (inline template)', async () => { + makeInlineTemplate(appTree, ''); + const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); + const content = tree.readContent('/projects/bar/src/app/app.component.ts'); + const matches = content.match(/<\/router-outlet>/g); + const numMatches = matches ? matches.length : 0; + expect(numMatches).toEqual(1); + }); }); - it('should not re-add the router outlet (inline template)', async () => { - makeInlineTemplate(appTree, ''); + it('should add router imports to server module', async () => { const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); - const content = tree.readContent('/projects/bar/src/app/app.component.ts'); - const matches = content.match(/<\/router-outlet>/g); - const numMatches = matches ? matches.length : 0; - expect(numMatches).toEqual(1); + const filePath = '/projects/bar/src/app/app.module.server.ts'; + const content = tree.readContent(filePath); + expect(content).toMatch(/import { Routes, RouterModule } from '@angular\/router';/); }); - }); - - it('should add router imports to server module', async () => { - const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); - const filePath = '/projects/bar/src/app/app.module.server.ts'; - const content = tree.readContent(filePath); - expect(content).toMatch(/import { Routes, RouterModule } from '@angular\/router';/); - }); - it('should work if server config was added prior to running the app-shell schematic', async () => { - let tree = await schematicRunner.runSchematic('server', defaultOptions, appTree); - tree = await schematicRunner.runSchematic('app-shell', defaultOptions, tree); - expect(tree.exists('/projects/bar/src/app/app-shell/app-shell.component.ts')).toBe(true); - }); + it('should work if server config was added prior to running the app-shell schematic', async () => { + let tree = await schematicRunner.runSchematic('server', defaultOptions, appTree); + tree = await schematicRunner.runSchematic('app-shell', defaultOptions, tree); + expect(tree.exists('/projects/bar/src/app/app-shell/app-shell.component.ts')).toBe(true); + }); - it('should define a server route', async () => { - const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); - const filePath = '/projects/bar/src/app/app.module.server.ts'; - const content = tree.readContent(filePath); - expect(content).toMatch(/const routes: Routes = \[/); - }); + it('should define a server route', async () => { + const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); + const filePath = '/projects/bar/src/app/app.module.server.ts'; + const content = tree.readContent(filePath); + expect(content).toMatch(/const routes: Routes = \[/); + }); - it('should import RouterModule with forRoot', async () => { - const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); - const filePath = '/projects/bar/src/app/app.module.server.ts'; - const content = tree.readContent(filePath); - expect(content).toMatch( - /const routes: Routes = \[ { path: 'shell', component: AppShellComponent }\];/, - ); - expect(content).toMatch(/ServerModule,\r?\n\s*RouterModule\.forRoot\(routes\),/); - }); + it('should import RouterModule with forRoot', async () => { + const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); + const filePath = '/projects/bar/src/app/app.module.server.ts'; + const content = tree.readContent(filePath); + expect(content).toMatch( + /const routes: Routes = \[ { path: 'shell', component: AppShellComponent }\];/, + ); + expect(content).toMatch(/ServerModule,\r?\n\s*RouterModule\.forRoot\(routes\),/); + }); - it('should create the shell component', async () => { - const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); - expect(tree.exists('/projects/bar/src/app/app-shell/app-shell.component.ts')).toBe(true); - const content = tree.readContent('/projects/bar/src/app/app.module.server.ts'); - expect(content).toMatch(/app-shell\.component/); + it('should create the shell component', async () => { + const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); + expect(tree.exists('/projects/bar/src/app/app-shell/app-shell.component.ts')).toBe(true); + const content = tree.readContent('/projects/bar/src/app/app.module.server.ts'); + expect(content).toMatch(/app-shell\.component/); + }); }); describe('standalone application', () => { - const standaloneAppName = 'baz'; - const standaloneAppOptions: ApplicationOptions = { - ...appOptions, - name: standaloneAppName, - standalone: true, - }; - const defaultStandaloneOptions: AppShellOptions = { - project: standaloneAppName, - }; - beforeEach(async () => { - appTree = await schematicRunner.runSchematic('application', standaloneAppOptions, appTree); + appTree = await schematicRunner.runSchematic('application', appOptions, appTree); }); it('should ensure the client app has a router-outlet', async () => { - appTree = await schematicRunner.runSchematic('workspace', workspaceOptions); + const appName = 'baz'; appTree = await schematicRunner.runSchematic( 'application', - { ...standaloneAppOptions, routing: false }, + { + ...appOptions, + name: appName, + routing: false, + }, appTree, ); + await expectAsync( - schematicRunner.runSchematic('app-shell', defaultStandaloneOptions, appTree), + schematicRunner.runSchematic('app-shell', { ...defaultOptions, project: appName }, appTree), ).toBeRejected(); }); it('should create the shell component', async () => { - const tree = await schematicRunner.runSchematic( - 'app-shell', - defaultStandaloneOptions, - appTree, - ); - expect(tree.exists('/projects/baz/src/app/app-shell/app-shell.component.ts')).toBe(true); - const content = tree.readContent('/projects/baz/src/app/app.config.server.ts'); + const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); + expect(tree.exists('/projects/bar/src/app/app-shell/app-shell.component.ts')).toBe(true); + const content = tree.readContent('/projects/bar/src/app/app.config.server.ts'); expect(content).toMatch(/app-shell\.component/); }); it('should define a server route', async () => { - const tree = await schematicRunner.runSchematic( - 'app-shell', - defaultStandaloneOptions, - appTree, - ); - const filePath = '/projects/baz/src/app/app.config.server.ts'; + const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); + const filePath = '/projects/bar/src/app/app.config.server.ts'; const content = tree.readContent(filePath); expect(tags.oneLine`${content}`).toContain(tags.oneLine`{ provide: ROUTES, @@ -229,23 +225,15 @@ describe('App Shell Schematic', () => { }); it(`should add import to 'ROUTES' token from '@angular/router'`, async () => { - const tree = await schematicRunner.runSchematic( - 'app-shell', - defaultStandaloneOptions, - appTree, - ); - const filePath = '/projects/baz/src/app/app.config.server.ts'; + const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); + const filePath = '/projects/bar/src/app/app.config.server.ts'; const content = tree.readContent(filePath); expect(content).toContain(`import { ROUTES } from '@angular/router';`); }); it(`should add import to 'AppShellComponent'`, async () => { - const tree = await schematicRunner.runSchematic( - 'app-shell', - defaultStandaloneOptions, - appTree, - ); - const filePath = '/projects/baz/src/app/app.config.server.ts'; + const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); + const filePath = '/projects/bar/src/app/app.config.server.ts'; const content = tree.readContent(filePath); expect(content).toContain( `import { AppShellComponent } from './app-shell/app-shell.component';`, @@ -275,7 +263,8 @@ describe('App Shell Schematic', () => { appTree.overwrite('/angular.json', JSON.stringify(config, undefined, 2)); } - beforeEach(() => { + beforeEach(async () => { + appTree = await schematicRunner.runSchematic('application', appOptions, appTree); convertBuilderToLegacyBrowser(); }); diff --git a/packages/schematics/angular/application/index.ts b/packages/schematics/angular/application/index.ts index 83e906143a19..5967c138fdd0 100644 --- a/packages/schematics/angular/application/index.ts +++ b/packages/schematics/angular/application/index.ts @@ -37,13 +37,6 @@ export default function (options: ApplicationOptions): Rule { const { appDir, appRootSelector, componentOptions, folderName, sourceDir } = await getAppOptions(host, options); - if (options.standalone) { - context.logger.warn( - 'Standalone application structure is new and not yet supported by many existing' + - ` 'ng add' and 'ng update' integrations with community libraries.`, - ); - } - return chain([ addAppToWorkspaceFile(options, appDir, folderName), options.standalone @@ -183,20 +176,14 @@ function addAppToWorkspaceFile( ]; schematicsWithTests.forEach((type) => { - if (!(`@schematics/angular:${type}` in schematics)) { - schematics[`@schematics/angular:${type}`] = {}; - } - (schematics[`@schematics/angular:${type}`] as JsonObject).skipTests = true; + ((schematics[`@schematics/angular:${type}`] ??= {}) as JsonObject).skipTests = true; }); } - if (options.standalone) { + if (!options.standalone) { const schematicsWithStandalone = ['component', 'directive', 'pipe']; schematicsWithStandalone.forEach((type) => { - if (!(`@schematics/angular:${type}` in schematics)) { - schematics[`@schematics/angular:${type}`] = {}; - } - (schematics[`@schematics/angular:${type}`] as JsonObject).standalone = true; + ((schematics[`@schematics/angular:${type}`] ??= {}) as JsonObject).standalone = false; }); } diff --git a/packages/schematics/angular/application/index_spec.ts b/packages/schematics/angular/application/index_spec.ts index d6d97b964089..7ab935992295 100644 --- a/packages/schematics/angular/application/index_spec.ts +++ b/packages/schematics/angular/application/index_spec.ts @@ -40,12 +40,13 @@ describe('Application Schematic', () => { }); it('should create all files of an application', async () => { - const options = { ...defaultOptions }; - - const tree = await schematicRunner.runSchematic('application', options, workspaceTree); + const tree = await schematicRunner.runSchematic( + 'application', + { ...defaultOptions, standalone: false }, + workspaceTree, + ); - const files = tree.files; - expect(files).toEqual( + expect(tree.files).toEqual( jasmine.arrayContaining([ '/projects/foo/tsconfig.app.json', '/projects/foo/tsconfig.spec.json', @@ -89,52 +90,6 @@ describe('Application Schematic', () => { expect(workspace.projects.foo.prefix).toEqual('pre'); }); - it('should handle the routing flag', async () => { - const options = { ...defaultOptions, routing: true }; - - const tree = await schematicRunner.runSchematic('application', options, workspaceTree); - - const files = tree.files; - expect(files).toContain('/projects/foo/src/app/app.module.ts'); - expect(files).toContain('/projects/foo/src/app/app-routing.module.ts'); - const moduleContent = tree.readContent('/projects/foo/src/app/app.module.ts'); - expect(moduleContent).toMatch(/import { AppRoutingModule } from '.\/app-routing.module'/); - const routingModuleContent = tree.readContent('/projects/foo/src/app/app-routing.module.ts'); - expect(routingModuleContent).toMatch(/RouterModule.forRoot\(routes\)/); - }); - - it('should import BrowserModule in the app module', async () => { - const tree = await schematicRunner.runSchematic('application', defaultOptions, workspaceTree); - - const path = '/projects/foo/src/app/app.module.ts'; - const content = tree.readContent(path); - expect(content).toMatch(/import { BrowserModule } from '@angular\/platform-browser';/); - }); - - it('should declare app component in the app module', async () => { - const tree = await schematicRunner.runSchematic('application', defaultOptions, workspaceTree); - - const path = '/projects/foo/src/app/app.module.ts'; - const content = tree.readContent(path); - expect(content).toMatch(/import { AppComponent } from '\.\/app\.component';/); - }); - - it(`should set 'defaultEncapsulation' in main.ts when 'ViewEncapsulation' is provided`, async () => { - const tree = await schematicRunner.runSchematic( - 'application', - { - ...defaultOptions, - viewEncapsulation: ViewEncapsulation.ShadowDom, - }, - workspaceTree, - ); - - const path = '/projects/foo/src/main.ts'; - const content = tree.readContent(path); - expect(content).toContain('defaultEncapsulation: ViewEncapsulation.ShadowDom'); - expect(content).toContain(`import { ViewEncapsulation } from '@angular/core'`); - }); - it('should set the right paths in the tsconfig.app.json', async () => { const tree = await schematicRunner.runSchematic('application', defaultOptions, workspaceTree); @@ -214,85 +169,6 @@ describe('Application Schematic', () => { }); }); - it('should create correct files when using minimal', async () => { - const options = { ...defaultOptions, minimal: true }; - const tree = await schematicRunner.runSchematic('application', options, workspaceTree); - - const files = tree.files; - [ - '/projects/foo/tsconfig.spec.json', - '/projects/foo/src/app/app.component.css', - '/projects/foo/src/app/app.component.html', - '/projects/foo/src/app/app.component.spec.ts', - ].forEach((x) => expect(files).not.toContain(x)); - - expect(files).toEqual( - jasmine.arrayContaining([ - '/projects/foo/tsconfig.app.json', - '/projects/foo/src/favicon.ico', - '/projects/foo/src/index.html', - '/projects/foo/src/main.ts', - '/projects/foo/src/styles.css', - '/projects/foo/src/app/app.module.ts', - '/projects/foo/src/app/app.component.ts', - ]), - ); - }); - - it('should create correct files when using minimal and inlineStyle=false', async () => { - const options = { ...defaultOptions, minimal: true, inlineStyle: false }; - const tree = await schematicRunner.runSchematic('application', options, workspaceTree); - - const files = tree.files; - [ - '/projects/foo/tsconfig.spec.json', - '/projects/foo/karma.conf.js', - '/projects/foo/src/test.ts', - '/projects/foo/src/app/app.component.html', - '/projects/foo/src/app/app.component.spec.ts', - ].forEach((x) => expect(files).not.toContain(x)); - - expect(files).toEqual( - jasmine.arrayContaining([ - '/projects/foo/tsconfig.app.json', - '/projects/foo/src/favicon.ico', - '/projects/foo/src/index.html', - '/projects/foo/src/main.ts', - '/projects/foo/src/styles.css', - '/projects/foo/src/app/app.module.ts', - '/projects/foo/src/app/app.component.css', - '/projects/foo/src/app/app.component.ts', - ]), - ); - }); - - it('should create correct files when using minimal and inlineTemplate=false', async () => { - const options = { ...defaultOptions, minimal: true, inlineTemplate: false }; - const tree = await schematicRunner.runSchematic('application', options, workspaceTree); - - const files = tree.files; - [ - '/projects/foo/tsconfig.spec.json', - '/projects/foo/karma.conf.js', - '/projects/foo/src/test.ts', - '/projects/foo/src/app/app.component.css', - '/projects/foo/src/app/app.component.spec.ts', - ].forEach((x) => expect(files).not.toContain(x)); - - expect(files).toEqual( - jasmine.arrayContaining([ - '/projects/foo/tsconfig.app.json', - '/projects/foo/src/favicon.ico', - '/projects/foo/src/index.html', - '/projects/foo/src/main.ts', - '/projects/foo/src/styles.css', - '/projects/foo/src/app/app.module.ts', - '/projects/foo/src/app/app.component.html', - '/projects/foo/src/app/app.component.ts', - ]), - ); - }); - describe(`update package.json`, () => { it(`should add build-angular to devDependencies`, async () => { const tree = await schematicRunner.runSchematic('application', defaultOptions, workspaceTree); @@ -357,7 +233,6 @@ describe('Application Schematic', () => { '/src/index.html', '/src/main.ts', '/src/styles.css', - '/src/app/app.module.ts', '/src/app/app.component.css', '/src/app/app.component.html', '/src/app/app.component.spec.ts', @@ -524,82 +399,238 @@ describe('Application Schematic', () => { expect(cfg.projects['@myscope/myapp']).toBeDefined(); }); - describe('standalone', () => { - it('should create all files of a standalone application', async () => { - const options = { ...defaultOptions, standalone: true }; + it('should create correct files when using minimal', async () => { + const options = { ...defaultOptions, minimal: true }; + const tree = await schematicRunner.runSchematic('application', options, workspaceTree); - const tree = await schematicRunner.runSchematic('application', options, workspaceTree); + const files = tree.files; + [ + '/projects/foo/tsconfig.spec.json', + '/projects/foo/src/app/app.component.css', + '/projects/foo/src/app/app.component.html', + '/projects/foo/src/app/app.component.spec.ts', + ].forEach((x) => expect(files).not.toContain(x)); - const files = tree.files; - expect(files).toEqual( - jasmine.arrayContaining([ - '/projects/foo/tsconfig.app.json', - '/projects/foo/tsconfig.spec.json', - '/projects/foo/src/favicon.ico', - '/projects/foo/src/index.html', - '/projects/foo/src/main.ts', - '/projects/foo/src/styles.css', - '/projects/foo/src/app/app.config.ts', - '/projects/foo/src/app/app.component.css', - '/projects/foo/src/app/app.component.html', - '/projects/foo/src/app/app.component.spec.ts', - '/projects/foo/src/app/app.component.ts', - ]), - ); - }); + expect(files).toEqual( + jasmine.arrayContaining([ + '/projects/foo/tsconfig.app.json', + '/projects/foo/src/favicon.ico', + '/projects/foo/src/index.html', + '/projects/foo/src/main.ts', + '/projects/foo/src/styles.css', + '/projects/foo/src/app/app.component.ts', + ]), + ); + }); - it('should not create any module files', async () => { - const options = { ...defaultOptions, standalone: true }; + it('should create correct files when using minimal and inlineStyle=false', async () => { + const options = { ...defaultOptions, minimal: true, inlineStyle: false }; + const tree = await schematicRunner.runSchematic('application', options, workspaceTree); - const tree = await schematicRunner.runSchematic('application', options, workspaceTree); - const moduleFiles = tree.files.filter((file) => file.endsWith('.module.ts')); - expect(moduleFiles.length).toEqual(0); - }); + const files = tree.files; + [ + '/projects/foo/tsconfig.spec.json', + '/projects/foo/karma.conf.js', + '/projects/foo/src/test.ts', + '/projects/foo/src/app/app.component.html', + '/projects/foo/src/app/app.component.spec.ts', + ].forEach((x) => expect(files).not.toContain(x)); - it('should create a standalone component', async () => { - const options = { ...defaultOptions, standalone: true }; + expect(files).toEqual( + jasmine.arrayContaining([ + '/projects/foo/tsconfig.app.json', + '/projects/foo/src/favicon.ico', + '/projects/foo/src/index.html', + '/projects/foo/src/main.ts', + '/projects/foo/src/styles.css', + '/projects/foo/src/app/app.component.css', + '/projects/foo/src/app/app.component.ts', + ]), + ); + }); - const tree = await schematicRunner.runSchematic('application', options, workspaceTree); + it('should create correct files when using minimal and inlineTemplate=false', async () => { + const options = { ...defaultOptions, minimal: true, inlineTemplate: false }; + const tree = await schematicRunner.runSchematic('application', options, workspaceTree); + + const files = tree.files; + [ + '/projects/foo/tsconfig.spec.json', + '/projects/foo/karma.conf.js', + '/projects/foo/src/test.ts', + '/projects/foo/src/app/app.component.css', + '/projects/foo/src/app/app.component.spec.ts', + ].forEach((x) => expect(files).not.toContain(x)); + + expect(files).toEqual( + jasmine.arrayContaining([ + '/projects/foo/tsconfig.app.json', + '/projects/foo/src/favicon.ico', + '/projects/foo/src/index.html', + '/projects/foo/src/main.ts', + '/projects/foo/src/styles.css', + '/projects/foo/src/app/app.component.html', + '/projects/foo/src/app/app.component.ts', + ]), + ); + }); + + it('should create all files of a standalone application', async () => { + const options = { ...defaultOptions, standalone: true }; + + const tree = await schematicRunner.runSchematic('application', options, workspaceTree); - const component = tree.readContent('/projects/foo/src/app/app.component.ts'); - expect(component).toMatch(/standalone: true/); + const files = tree.files; + expect(files).toEqual( + jasmine.arrayContaining([ + '/projects/foo/tsconfig.app.json', + '/projects/foo/tsconfig.spec.json', + '/projects/foo/src/favicon.ico', + '/projects/foo/src/index.html', + '/projects/foo/src/main.ts', + '/projects/foo/src/styles.css', + '/projects/foo/src/app/app.config.ts', + '/projects/foo/src/app/app.component.css', + '/projects/foo/src/app/app.component.html', + '/projects/foo/src/app/app.component.spec.ts', + '/projects/foo/src/app/app.component.ts', + ]), + ); + }); + + it('should not create any module files', async () => { + const options = { ...defaultOptions, standalone: true }; + + const tree = await schematicRunner.runSchematic('application', options, workspaceTree); + const moduleFiles = tree.files.filter((file) => file.endsWith('.module.ts')); + expect(moduleFiles.length).toEqual(0); + }); + + it('should create a standalone component', async () => { + const options = { ...defaultOptions, standalone: true }; + + const tree = await schematicRunner.runSchematic('application', options, workspaceTree); + + const component = tree.readContent('/projects/foo/src/app/app.component.ts'); + expect(component).toMatch(/standalone: true/); + }); + + it('should create routing information by default', async () => { + const options = { ...defaultOptions, standalone: true }; + + const tree = await schematicRunner.runSchematic('application', options, workspaceTree); + + expect(tree.files).toContain('/projects/foo/src/app/app.routes.ts'); + + const component = tree.readContent('/projects/foo/src/app/app.component.ts'); + expect(component).toContain(`import { RouterOutlet } from '@angular/router';`); + expect(component).toContain(`imports: [CommonModule, RouterOutlet]`); + + const config = tree.readContent('/projects/foo/src/app/app.config.ts'); + expect(config).toContain(`import { provideRouter } from '@angular/router';`); + expect(config).toContain(`import { routes } from './app.routes';`); + expect(config).toContain('provideRouter(routes)'); + }); + + it('should create a main.ts', async () => { + const options = { ...defaultOptions, standalone: true }; + const tree = await schematicRunner.runSchematic('application', options, workspaceTree); + + const main = tree.readContent('/projects/foo/src/main.ts'); + expect(main).toContain('bootstrapApplication'); + }); + + describe('standalone=false', () => { + it(`should set 'defaultEncapsulation' in main.ts when 'ViewEncapsulation' is provided`, async () => { + const tree = await schematicRunner.runSchematic( + 'application', + { + ...defaultOptions, + standalone: false, + viewEncapsulation: ViewEncapsulation.ShadowDom, + }, + workspaceTree, + ); + + const path = '/projects/foo/src/main.ts'; + const content = tree.readContent(path); + expect(content).toContain('defaultEncapsulation: ViewEncapsulation.ShadowDom'); + expect(content).toContain(`import { ViewEncapsulation } from '@angular/core'`); }); - it('should create routing information by default', async () => { - const options = { ...defaultOptions, standalone: true }; + it('should handle the routing flag', async () => { + const options = { ...defaultOptions, routing: true, standalone: false }; const tree = await schematicRunner.runSchematic('application', options, workspaceTree); - expect(tree.files).toContain('/projects/foo/src/app/app.routes.ts'); + const files = tree.files; + expect(files).toContain('/projects/foo/src/app/app.module.ts'); + expect(files).toContain('/projects/foo/src/app/app-routing.module.ts'); + const moduleContent = tree.readContent('/projects/foo/src/app/app.module.ts'); + expect(moduleContent).toMatch(/import { AppRoutingModule } from '.\/app-routing.module'/); + const routingModuleContent = tree.readContent('/projects/foo/src/app/app-routing.module.ts'); + expect(routingModuleContent).toMatch(/RouterModule.forRoot\(routes\)/); + }); - const component = tree.readContent('/projects/foo/src/app/app.component.ts'); - expect(component).toContain(`import { RouterOutlet } from '@angular/router';`); - expect(component).toContain(`imports: [CommonModule, RouterOutlet]`); + it('should import BrowserModule in the app module', async () => { + const tree = await schematicRunner.runSchematic( + 'application', + { ...defaultOptions, standalone: false }, + workspaceTree, + ); - const config = tree.readContent('/projects/foo/src/app/app.config.ts'); - expect(config).toContain(`import { provideRouter } from '@angular/router';`); - expect(config).toContain(`import { routes } from './app.routes';`); - expect(config).toContain('provideRouter(routes)'); + const path = '/projects/foo/src/app/app.module.ts'; + const content = tree.readContent(path); + expect(content).toMatch(/import { BrowserModule } from '@angular\/platform-browser';/); }); - it('should create a main.ts', async () => { - const options = { ...defaultOptions, standalone: true }; - const tree = await schematicRunner.runSchematic('application', options, workspaceTree); + it('should declare app component in the app module', async () => { + const tree = await schematicRunner.runSchematic( + 'application', + { ...defaultOptions, standalone: false }, + workspaceTree, + ); - const main = tree.readContent('/projects/foo/src/main.ts'); - expect(main).toContain('bootstrapApplication'); + const path = '/projects/foo/src/app/app.module.ts'; + const content = tree.readContent(path); + expect(content).toMatch(/import { AppComponent } from '\.\/app\.component';/); }); - it('should set the default schematic options to be standalone', async () => { - const options = { ...defaultOptions, standalone: true }; + it('should create all files of an application', async () => { + const options = { ...defaultOptions, standalone: false }; + const tree = await schematicRunner.runSchematic('application', options, workspaceTree); + const files = tree.files; + expect(files).toEqual( + jasmine.arrayContaining([ + '/projects/foo/tsconfig.app.json', + '/projects/foo/tsconfig.spec.json', + '/projects/foo/src/main.ts', + '/projects/foo/src/styles.css', + '/projects/foo/src/app/app-routing.module.ts', + '/projects/foo/src/app/app.module.ts', + '/projects/foo/src/app/app.component.css', + '/projects/foo/src/app/app.component.html', + '/projects/foo/src/app/app.component.spec.ts', + '/projects/foo/src/app/app.component.ts', + ]), + ); + }); + + it('should set the default schematic options to be standalone=false', async () => { + const tree = await schematicRunner.runSchematic( + 'application', + { ...defaultOptions, standalone: false }, + workspaceTree, + ); + const workspace = JSON.parse(tree.readContent('/angular.json')); expect(workspace.projects.foo.schematics).toEqual( jasmine.objectContaining({ - '@schematics/angular:component': { standalone: true }, - '@schematics/angular:directive': { standalone: true }, - '@schematics/angular:pipe': { standalone: true }, + '@schematics/angular:component': { standalone: false }, + '@schematics/angular:directive': { standalone: false }, + '@schematics/angular:pipe': { standalone: false }, }), ); }); diff --git a/packages/schematics/angular/application/schema.json b/packages/schematics/angular/application/schema.json index 95936b544b45..a71c7b358ddd 100644 --- a/packages/schematics/angular/application/schema.json +++ b/packages/schematics/angular/application/schema.json @@ -105,7 +105,7 @@ "standalone": { "description": "Creates an application based upon the standalone API, without NgModules.", "type": "boolean", - "default": false, + "default": true, "x-user-analytics": "ep.ng_standalone" } }, diff --git a/packages/schematics/angular/component/index_spec.ts b/packages/schematics/angular/component/index_spec.ts index 627f503dea3c..c45c8fde106a 100644 --- a/packages/schematics/angular/component/index_spec.ts +++ b/packages/schematics/angular/component/index_spec.ts @@ -47,29 +47,14 @@ describe('Component Schematic', () => { skipTests: false, skipPackageJson: false, }; + let appTree: UnitTestTree; + beforeEach(async () => { appTree = await schematicRunner.runSchematic('workspace', workspaceOptions); appTree = await schematicRunner.runSchematic('application', appOptions, appTree); }); - it('should create a component', async () => { - const options = { ...defaultOptions }; - const tree = await schematicRunner.runSchematic('component', options, appTree); - const files = tree.files; - expect(files).toEqual( - jasmine.arrayContaining([ - '/projects/bar/src/app/foo/foo.component.css', - '/projects/bar/src/app/foo/foo.component.html', - '/projects/bar/src/app/foo/foo.component.spec.ts', - '/projects/bar/src/app/foo/foo.component.ts', - ]), - ); - const moduleContent = tree.readContent('/projects/bar/src/app/app.module.ts'); - expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo\/foo.component'/); - expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooComponent\r?\n/m); - }); - it('should set change detection to OnPush', async () => { const options = { ...defaultOptions, changeDetection: 'OnPush' }; @@ -117,50 +102,6 @@ describe('Component Schematic', () => { ); }); - it('should find the closest module', async () => { - const options = { ...defaultOptions }; - const fooModule = '/projects/bar/src/app/foo/foo.module.ts'; - appTree.create( - fooModule, - ` - import { NgModule } from '@angular/core'; - - @NgModule({ - imports: [], - declarations: [] - }) - export class FooModule { } - `, - ); - - const tree = await schematicRunner.runSchematic('component', options, appTree); - const fooModuleContent = tree.readContent(fooModule); - expect(fooModuleContent).toMatch(/import { FooComponent } from '.\/foo.component'/); - }); - - it('should export the component', async () => { - const options = { ...defaultOptions, export: true }; - - const tree = await schematicRunner.runSchematic('component', options, appTree); - const appModuleContent = tree.readContent('/projects/bar/src/app/app.module.ts'); - expect(appModuleContent).toMatch(/exports: \[\n(\s*) {2}FooComponent\n\1\]/); - }); - - it('should import into a specified module', async () => { - const options = { ...defaultOptions, module: 'app.module.ts' }; - - const tree = await schematicRunner.runSchematic('component', options, appTree); - const appModule = tree.readContent('/projects/bar/src/app/app.module.ts'); - - expect(appModule).toMatch(/import { FooComponent } from '.\/foo\/foo.component'/); - }); - - it('should fail if specified module does not exist', async () => { - const options = { ...defaultOptions, module: '/projects/bar/src/app.moduleXXX.ts' }; - - await expectAsync(schematicRunner.runSchematic('component', options, appTree)).toBeRejected(); - }); - it('should handle upper case paths', async () => { const pathOption = 'projects/bar/src/app/SOME/UPPER/DIR'; const options = { ...defaultOptions, path: pathOption }; @@ -310,42 +251,6 @@ describe('Component Schematic', () => { expect(tree.files).toContain('/projects/bar/src/app/foo/foo.html'); }); - it('should use the module flag even if the module is a routing module', async () => { - const routingFileName = 'app-routing.module.ts'; - const routingModulePath = `/projects/bar/src/app/${routingFileName}`; - const newTree = createAppModule(appTree, routingModulePath); - const options = { ...defaultOptions, module: routingFileName }; - const tree = await schematicRunner.runSchematic('component', options, newTree); - const content = tree.readContent(routingModulePath); - expect(content).toMatch(/import { FooComponent } from '.\/foo\/foo.component/); - }); - - it('should handle a path in the name option', async () => { - const options = { ...defaultOptions, name: 'dir/test-component' }; - - const tree = await schematicRunner.runSchematic('component', options, appTree); - const content = tree.readContent('/projects/bar/src/app/app.module.ts'); - expect(content).toMatch( - /import { TestComponentComponent } from '\.\/dir\/test-component\/test-component.component'/, - ); - }); - - it('should handle a path in the name and module options', async () => { - appTree = await schematicRunner.runSchematic( - 'module', - { name: 'admin/module', project: 'bar' }, - appTree, - ); - - const options = { ...defaultOptions, name: 'other/test-component', module: 'admin/module' }; - appTree = await schematicRunner.runSchematic('component', options, appTree); - - const content = appTree.readContent('/projects/bar/src/app/admin/module/module.module.ts'); - expect(content).toMatch( - /import { TestComponentComponent } from '..\/..\/other\/test-component\/test-component.component'/, - ); - }); - it('should create the right selector with a path in the name', async () => { const options = { ...defaultOptions, name: 'sub/test' }; appTree = await schematicRunner.runSchematic('component', options, appTree); @@ -360,23 +265,6 @@ describe('Component Schematic', () => { expect(content).not.toMatch(/selector: 'app-test'/); }); - 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)); - - // should fail without a module in that dir - await expectAsync( - schematicRunner.runSchematic('component', defaultOptions, appTree), - ).toBeRejected(); - - // move the module - appTree.rename('/projects/bar/src/app/app.module.ts', '/projects/bar/custom/app/app.module.ts'); - appTree = await schematicRunner.runSchematic('component', defaultOptions, appTree); - - expect(appTree.files).toContain('/projects/bar/custom/app/foo/foo.component.ts'); - }); - it('should respect the skipTests option', async () => { const options = { ...defaultOptions, skipTests: true }; const tree = await schematicRunner.runSchematic('component', options, appTree); @@ -434,4 +322,148 @@ describe('Component Schematic', () => { expect(testContent).toContain('imports: [FooComponent]'); expect(testContent).not.toContain('declarations'); }); + + describe('standalone=false', () => { + const defaultNonStandaloneOptions: ComponentOptions = { + ...defaultOptions, + standalone: false, + project: 'baz', + }; + + beforeEach(async () => { + appTree = await schematicRunner.runSchematic( + 'application', + { ...appOptions, standalone: false, name: 'baz' }, + appTree, + ); + }); + + it('should create a component', async () => { + const options = { ...defaultNonStandaloneOptions }; + const tree = await schematicRunner.runSchematic('component', options, appTree); + const files = tree.files; + expect(files).toEqual( + jasmine.arrayContaining([ + '/projects/baz/src/app/foo/foo.component.css', + '/projects/baz/src/app/foo/foo.component.html', + '/projects/baz/src/app/foo/foo.component.spec.ts', + '/projects/baz/src/app/foo/foo.component.ts', + ]), + ); + const moduleContent = tree.readContent('/projects/baz/src/app/app.module.ts'); + expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo\/foo.component'/); + expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooComponent\r?\n/m); + }); + + it('should use the module flag even if the module is a routing module', async () => { + const routingFileName = 'app-routing.module.ts'; + const routingModulePath = `/projects/baz/src/app/${routingFileName}`; + const newTree = createAppModule(appTree, routingModulePath); + const options = { ...defaultNonStandaloneOptions, module: routingFileName }; + const tree = await schematicRunner.runSchematic('component', options, newTree); + const content = tree.readContent(routingModulePath); + expect(content).toMatch(/import { FooComponent } from '.\/foo\/foo.component/); + }); + + it('should handle a path in the name option', async () => { + const options = { ...defaultNonStandaloneOptions, name: 'dir/test-component' }; + + const tree = await schematicRunner.runSchematic('component', options, appTree); + const content = tree.readContent('/projects/baz/src/app/app.module.ts'); + expect(content).toMatch( + /import { TestComponentComponent } from '\.\/dir\/test-component\/test-component.component'/, + ); + }); + + it('should handle a path in the name and module options', async () => { + appTree = await schematicRunner.runSchematic( + 'module', + { name: 'admin/module', project: 'baz' }, + appTree, + ); + + const options = { + ...defaultNonStandaloneOptions, + name: 'other/test-component', + module: 'admin/module', + }; + appTree = await schematicRunner.runSchematic('component', options, appTree); + + const content = appTree.readContent('/projects/baz/src/app/admin/module/module.module.ts'); + expect(content).toMatch( + /import { TestComponentComponent } from '..\/..\/other\/test-component\/test-component.component'/, + ); + }); + + it('should find the closest module', async () => { + const options = { ...defaultNonStandaloneOptions }; + const fooModule = '/projects/baz/src/app/foo/foo.module.ts'; + appTree.create( + fooModule, + ` + import { NgModule } from '@angular/core'; + + @NgModule({ + imports: [], + declarations: [] + }) + export class FooModule { } + `, + ); + + const tree = await schematicRunner.runSchematic('component', options, appTree); + const fooModuleContent = tree.readContent(fooModule); + expect(fooModuleContent).toMatch(/import { FooComponent } from '.\/foo.component'/); + }); + + it('should export the component', async () => { + const options = { ...defaultNonStandaloneOptions, export: true }; + + const tree = await schematicRunner.runSchematic('component', options, appTree); + const appModuleContent = tree.readContent('/projects/baz/src/app/app.module.ts'); + expect(appModuleContent).toMatch(/exports: \[\n(\s*) {2}FooComponent\n\1\]/); + }); + + it('should import into a specified module', async () => { + const options = { ...defaultNonStandaloneOptions, module: 'app.module.ts' }; + + const tree = await schematicRunner.runSchematic('component', options, appTree); + const appModule = tree.readContent('/projects/baz/src/app/app.module.ts'); + + expect(appModule).toMatch(/import { FooComponent } from '.\/foo\/foo.component'/); + }); + + it('should respect the sourceRoot value', async () => { + const config = JSON.parse(appTree.readContent('/angular.json')); + config.projects.baz.sourceRoot = 'projects/baz/custom'; + appTree.overwrite('/angular.json', JSON.stringify(config, null, 2)); + + // should fail without a module in that dir + await expectAsync( + schematicRunner.runSchematic('component', defaultNonStandaloneOptions, appTree), + ).toBeRejected(); + + // move the module + appTree.rename( + '/projects/baz/src/app/app.module.ts', + '/projects/baz/custom/app/app.module.ts', + ); + appTree = await schematicRunner.runSchematic( + 'component', + defaultNonStandaloneOptions, + appTree, + ); + + expect(appTree.files).toContain('/projects/baz/custom/app/foo/foo.component.ts'); + }); + + it('should fail if specified module does not exist', async () => { + const options = { + ...defaultNonStandaloneOptions, + module: '/projects/baz/src/app.moduleXXX.ts', + }; + + await expectAsync(schematicRunner.runSchematic('component', options, appTree)).toBeRejected(); + }); + }); }); diff --git a/packages/schematics/angular/component/schema.json b/packages/schematics/angular/component/schema.json index b7c3f952791e..e2e3914b41b9 100644 --- a/packages/schematics/angular/component/schema.json +++ b/packages/schematics/angular/component/schema.json @@ -54,7 +54,7 @@ "standalone": { "description": "Whether the generated component is standalone.", "type": "boolean", - "default": false, + "default": true, "x-user-analytics": "ep.ng_standalone" }, "viewEncapsulation": { diff --git a/packages/schematics/angular/config/index_spec.ts b/packages/schematics/angular/config/index_spec.ts index 9b549271f09a..a180b8f2c147 100644 --- a/packages/schematics/angular/config/index_spec.ts +++ b/packages/schematics/angular/config/index_spec.ts @@ -11,7 +11,7 @@ import { Schema as ApplicationOptions } from '../application/schema'; import { Schema as WorkspaceOptions } from '../workspace/schema'; import { Schema as ConfigOptions, Type as ConfigType } from './schema'; -describe('Application Schematic', () => { +describe('Config Schematic', () => { const schematicRunner = new SchematicTestRunner( '@schematics/angular', require.resolve('../collection.json'), diff --git a/packages/schematics/angular/directive/index_spec.ts b/packages/schematics/angular/directive/index_spec.ts index 6cdf847ee005..8ca7b42d8eb6 100644 --- a/packages/schematics/angular/directive/index_spec.ts +++ b/packages/schematics/angular/directive/index_spec.ts @@ -45,18 +45,6 @@ describe('Directive Schematic', () => { appTree = await schematicRunner.runSchematic('application', appOptions, appTree); }); - it('should create a directive', async () => { - const options = { ...defaultOptions }; - - const tree = await schematicRunner.runSchematic('directive', options, appTree); - const files = tree.files; - expect(files).toContain('/projects/bar/src/app/foo.directive.spec.ts'); - expect(files).toContain('/projects/bar/src/app/foo.directive.ts'); - const moduleContent = tree.readContent('/projects/bar/src/app/app.module.ts'); - expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo.directive'/); - expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooDirective\r?\n/m); - }); - it('should create respect the flat flag', async () => { const options = { ...defaultOptions, flat: false }; @@ -66,50 +54,6 @@ describe('Directive Schematic', () => { expect(files).toContain('/projects/bar/src/app/foo/foo.directive.ts'); }); - it('should find the closest module', async () => { - const options = { ...defaultOptions, flat: false }; - const fooModule = '/projects/bar/src/app/foo/foo.module.ts'; - appTree.create( - fooModule, - ` - import { NgModule } from '@angular/core'; - - @NgModule({ - imports: [], - declarations: [] - }) - export class FooModule { } - `, - ); - - const tree = await schematicRunner.runSchematic('directive', options, appTree); - const fooModuleContent = tree.readContent(fooModule); - expect(fooModuleContent).toMatch(/import { FooDirective } from '.\/foo.directive'/); - }); - - it('should export the directive', async () => { - const options = { ...defaultOptions, export: true }; - - const tree = await schematicRunner.runSchematic('directive', options, appTree); - const appModuleContent = tree.readContent('/projects/bar/src/app/app.module.ts'); - expect(appModuleContent).toMatch(/exports: \[\n(\s*) {2}FooDirective\n\1\]/); - }); - - it('should import into a specified module', async () => { - const options = { ...defaultOptions, module: 'app.module.ts' }; - - const tree = await schematicRunner.runSchematic('directive', options, appTree); - const appModule = tree.readContent('/projects/bar/src/app/app.module.ts'); - - expect(appModule).toMatch(/import { FooDirective } from '.\/foo.directive'/); - }); - - it('should fail if specified module does not exist', async () => { - const options = { ...defaultOptions, module: '/projects/bar/src/app/app.moduleXXX.ts' }; - - await expectAsync(schematicRunner.runSchematic('directive', options, appTree)).toBeRejected(); - }); - it('should converts dash-cased-name to a camelCasedSelector', async () => { const options = { ...defaultOptions, name: 'my-dir' }; @@ -150,23 +94,6 @@ describe('Directive Schematic', () => { expect(content).toMatch(/selector: '\[foo\]'/); }); - 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)); - - // should fail without a module in that dir - await expectAsync( - schematicRunner.runSchematic('directive', defaultOptions, appTree), - ).toBeRejected(); - - // move the module - appTree.rename('/projects/bar/src/app/app.module.ts', '/projects/bar/custom/app/app.module.ts'); - appTree = await schematicRunner.runSchematic('directive', defaultOptions, appTree); - - expect(appTree.files).toContain('/projects/bar/custom/app/foo.directive.ts'); - }); - it('should respect skipTests flag', async () => { const options = { ...defaultOptions, skipTests: true }; @@ -179,10 +106,107 @@ describe('Directive Schematic', () => { it('should create a standalone directive', async () => { const options = { ...defaultOptions, standalone: true }; const tree = await schematicRunner.runSchematic('directive', options, appTree); - const moduleContent = tree.readContent('/projects/bar/src/app/app.module.ts'); const directiveContent = tree.readContent('/projects/bar/src/app/foo.directive.ts'); expect(directiveContent).toContain('standalone: true'); expect(directiveContent).toContain('class FooDirective'); - expect(moduleContent).not.toContain('FooDirective'); + }); + + describe('standalone=false', () => { + const defaultNonStandaloneOptions: DirectiveOptions = { + ...defaultOptions, + standalone: false, + project: 'baz', + }; + + beforeEach(async () => { + appTree = await schematicRunner.runSchematic( + 'application', + { ...appOptions, standalone: false, name: 'baz' }, + appTree, + ); + }); + + it('should create a directive', async () => { + const options = { ...defaultNonStandaloneOptions }; + + const tree = await schematicRunner.runSchematic('directive', options, appTree); + const files = tree.files; + expect(files).toContain('/projects/baz/src/app/foo.directive.spec.ts'); + expect(files).toContain('/projects/baz/src/app/foo.directive.ts'); + const moduleContent = tree.readContent('/projects/baz/src/app/app.module.ts'); + expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo.directive'/); + expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooDirective\r?\n/m); + }); + + it('should respect the sourceRoot value', async () => { + const config = JSON.parse(appTree.readContent('/angular.json')); + config.projects.baz.sourceRoot = 'projects/baz/custom'; + appTree.overwrite('/angular.json', JSON.stringify(config, null, 2)); + + // should fail without a module in that dir + await expectAsync( + schematicRunner.runSchematic('directive', defaultNonStandaloneOptions, appTree), + ).toBeRejected(); + + // move the module + appTree.rename( + '/projects/baz/src/app/app.module.ts', + '/projects/baz/custom/app/app.module.ts', + ); + appTree = await schematicRunner.runSchematic( + 'directive', + defaultNonStandaloneOptions, + appTree, + ); + + expect(appTree.files).toContain('/projects/baz/custom/app/foo.directive.ts'); + }); + + it('should find the closest module', async () => { + const options = { ...defaultNonStandaloneOptions, flat: false }; + const fooModule = '/projects/baz/src/app/foo/foo.module.ts'; + appTree.create( + fooModule, + ` + import { NgModule } from '@angular/core'; + + @NgModule({ + imports: [], + declarations: [] + }) + export class FooModule { } + `, + ); + + const tree = await schematicRunner.runSchematic('directive', options, appTree); + const fooModuleContent = tree.readContent(fooModule); + expect(fooModuleContent).toMatch(/import { FooDirective } from '.\/foo.directive'/); + }); + + it('should export the directive', async () => { + const options = { ...defaultNonStandaloneOptions, export: true }; + + const tree = await schematicRunner.runSchematic('directive', options, appTree); + const appModuleContent = tree.readContent('/projects/baz/src/app/app.module.ts'); + expect(appModuleContent).toMatch(/exports: \[\n(\s*) {2}FooDirective\n\1\]/); + }); + + it('should import into a specified module', async () => { + const options = { ...defaultNonStandaloneOptions, module: 'app.module.ts' }; + + const tree = await schematicRunner.runSchematic('directive', options, appTree); + const appModule = tree.readContent('/projects/baz/src/app/app.module.ts'); + + expect(appModule).toMatch(/import { FooDirective } from '.\/foo.directive'/); + }); + + it('should fail if specified module does not exist', async () => { + const options = { + ...defaultNonStandaloneOptions, + module: '/projects/baz/src/app/app.moduleXXX.ts', + }; + + await expectAsync(schematicRunner.runSchematic('directive', options, appTree)).toBeRejected(); + }); }); }); diff --git a/packages/schematics/angular/directive/schema.json b/packages/schematics/angular/directive/schema.json index 71a5656f3605..a5664d79c1ca 100644 --- a/packages/schematics/angular/directive/schema.json +++ b/packages/schematics/angular/directive/schema.json @@ -63,7 +63,7 @@ "standalone": { "description": "Whether the generated directive is standalone.", "type": "boolean", - "default": false, + "default": true, "x-user-analytics": "ep.ng_standalone" }, "flat": { diff --git a/packages/schematics/angular/e2e/index.ts b/packages/schematics/angular/e2e/index.ts index 36d383710e12..83731a8f02f0 100644 --- a/packages/schematics/angular/e2e/index.ts +++ b/packages/schematics/angular/e2e/index.ts @@ -22,6 +22,7 @@ import { DependencyType, ExistingBehavior, addDependency, + addRootProvider, updateWorkspace, } from '@schematics/angular/utility'; import { posix as path } from 'path'; @@ -92,6 +93,11 @@ export default function (options: E2eOptions): Rule { move(e2eRootPath), ]), ), + addRootProvider( + relatedAppName, + ({ code, external }) => + code`${external('provideProtractorTestingSupport', '@angular/platform-browser')}()`, + ), ...E2E_DEV_DEPENDENCIES.map((name) => addDependency(name, latestVersions[name], { type: DependencyType.Dev, diff --git a/packages/schematics/angular/library/index_spec.ts b/packages/schematics/angular/library/index_spec.ts index dcfa251de67b..a7e44a803683 100644 --- a/packages/schematics/angular/library/index_spec.ts +++ b/packages/schematics/angular/library/index_spec.ts @@ -43,7 +43,7 @@ describe('Library Schematic', () => { workspaceTree = await schematicRunner.runSchematic('workspace', workspaceOptions); }); - it('should create files', async () => { + it('should create correct files', async () => { const tree = await schematicRunner.runSchematic('library', defaultOptions, workspaceTree); const files = tree.files; @@ -55,7 +55,6 @@ describe('Library Schematic', () => { '/projects/foo/tsconfig.lib.json', '/projects/foo/tsconfig.lib.prod.json', '/projects/foo/src/my-index.ts', - '/projects/foo/src/lib/foo.module.ts', '/projects/foo/src/lib/foo.component.spec.ts', '/projects/foo/src/lib/foo.component.ts', '/projects/foo/src/lib/foo.service.spec.ts', @@ -64,6 +63,17 @@ describe('Library Schematic', () => { ); }); + it('should not add reference to module file in entry-file', async () => { + const tree = await schematicRunner.runSchematic('library', defaultOptions, workspaceTree); + expect(tree.readContent('/projects/foo/src/my-index.ts')).not.toContain('foo.module'); + }); + + it('should create a standalone component', async () => { + const tree = await schematicRunner.runSchematic('library', defaultOptions, workspaceTree); + const componentContent = tree.readContent('/projects/foo/src/lib/foo.component.ts'); + expect(componentContent).toContain('standalone: true'); + }); + describe('custom projectRoot', () => { const customProjectRootOptions: GenerateLibrarySchema = { name: 'foo', @@ -90,7 +100,6 @@ describe('Library Schematic', () => { '/some/other/directory/bar/tsconfig.lib.json', '/some/other/directory/bar/tsconfig.lib.prod.json', '/some/other/directory/bar/src/my-index.ts', - '/some/other/directory/bar/src/lib/foo.module.ts', '/some/other/directory/bar/src/lib/foo.component.spec.ts', '/some/other/directory/bar/src/lib/foo.component.ts', '/some/other/directory/bar/src/lib/foo.service.spec.ts', @@ -204,13 +213,6 @@ describe('Library Schematic', () => { expect(svcContent).toMatch(/providedIn: 'root'/); }); - it('should export the component in the NgModule', async () => { - const tree = await schematicRunner.runSchematic('library', defaultOptions, workspaceTree); - - const fileContent = getFileContent(tree, '/projects/foo/src/lib/foo.module.ts'); - expect(fileContent).toMatch(/exports: \[\n(\s*) {2}FooComponent\n\1\]/); - }); - describe(`update package.json`, () => { it(`should add ng-packagr to devDependencies`, async () => { const tree = await schematicRunner.runSchematic('library', defaultOptions, workspaceTree); @@ -318,7 +320,7 @@ describe('Library Schematic', () => { const pkgJsonPath = '/projects/myscope/mylib/package.json'; expect(tree.files).toContain(pkgJsonPath); - expect(tree.files).toContain('/projects/myscope/mylib/src/lib/mylib.module.ts'); + expect(tree.files).toContain('/projects/myscope/mylib/src/lib/mylib.service.ts'); expect(tree.files).toContain('/projects/myscope/mylib/src/lib/mylib.component.ts'); const pkgJson = JSON.parse(tree.readContent(pkgJsonPath)); @@ -391,13 +393,24 @@ describe('Library Schematic', () => { ); }); - describe('standalone', () => { - const defaultStandaloneOptions = { ...defaultOptions, standalone: true }; + describe('standalone=false', () => { + const defaultNonStandaloneOptions = { ...defaultOptions, standalone: false }; - it('should create correct files', async () => { + it('should export the component in the NgModule', async () => { const tree = await schematicRunner.runSchematic( 'library', - defaultStandaloneOptions, + defaultNonStandaloneOptions, + workspaceTree, + ); + + const fileContent = getFileContent(tree, '/projects/foo/src/lib/foo.module.ts'); + expect(fileContent).toMatch(/exports: \[\n(\s*) {2}FooComponent\n\1\]/); + }); + + it('should create files', async () => { + const tree = await schematicRunner.runSchematic( + 'library', + defaultNonStandaloneOptions, workspaceTree, ); @@ -410,6 +423,7 @@ describe('Library Schematic', () => { '/projects/foo/tsconfig.lib.json', '/projects/foo/tsconfig.lib.prod.json', '/projects/foo/src/my-index.ts', + '/projects/foo/src/lib/foo.module.ts', '/projects/foo/src/lib/foo.component.spec.ts', '/projects/foo/src/lib/foo.component.ts', '/projects/foo/src/lib/foo.service.spec.ts', @@ -417,24 +431,5 @@ describe('Library Schematic', () => { ]), ); }); - - it('should not add reference to module file in entry-file', async () => { - const tree = await schematicRunner.runSchematic( - 'library', - defaultStandaloneOptions, - workspaceTree, - ); - expect(tree.readContent('/projects/foo/src/my-index.ts')).not.toContain('foo.module'); - }); - - it('should create a standalone component', async () => { - const tree = await schematicRunner.runSchematic( - 'library', - defaultStandaloneOptions, - workspaceTree, - ); - const componentContent = tree.readContent('/projects/foo/src/lib/foo.component.ts'); - expect(componentContent).toContain('standalone: true'); - }); }); }); diff --git a/packages/schematics/angular/library/schema.json b/packages/schematics/angular/library/schema.json index 4d6638f04937..d2b1ff037279 100644 --- a/packages/schematics/angular/library/schema.json +++ b/packages/schematics/angular/library/schema.json @@ -51,7 +51,7 @@ "standalone": { "description": "Creates a library based upon the standalone API, without NgModules.", "type": "boolean", - "default": false, + "default": true, "x-user-analytics": "ep.ng_standalone" } }, diff --git a/packages/schematics/angular/module/index_spec.ts b/packages/schematics/angular/module/index_spec.ts index 2bd31e16afca..21aa8f9ddcc6 100644 --- a/packages/schematics/angular/module/index_spec.ts +++ b/packages/schematics/angular/module/index_spec.ts @@ -33,6 +33,7 @@ describe('Module Schematic', () => { name: 'bar', inlineStyle: false, inlineTemplate: false, + standalone: false, routing: true, skipTests: false, skipPackageJson: false, diff --git a/packages/schematics/angular/ng-new/index_spec.ts b/packages/schematics/angular/ng-new/index_spec.ts index a53b8de6f5c6..95c4cc77e4be 100644 --- a/packages/schematics/angular/ng-new/index_spec.ts +++ b/packages/schematics/angular/ng-new/index_spec.ts @@ -37,13 +37,15 @@ describe('Ng New Schematic', () => { jasmine.arrayContaining([ '/bar/tsconfig.app.json', '/bar/src/main.ts', - '/bar/src/app/app.module.ts', + '/bar/src/app/app.config.ts', ]), ); + + expect(files).not.toEqual(jasmine.arrayContaining(['/bar/src/app/app.module.ts'])); }); - it('should create files of a standalone application', async () => { - const options = { ...defaultOptions, standalone: true }; + it('should create module files of a standalone=false application', async () => { + const options = { ...defaultOptions, standalone: false }; const tree = await schematicRunner.runSchematic('ng-new', options); const files = tree.files; @@ -51,11 +53,9 @@ describe('Ng New Schematic', () => { jasmine.arrayContaining([ '/bar/tsconfig.app.json', '/bar/src/main.ts', - '/bar/src/app/app.routes.ts', - '/bar/src/app/app.config.ts', + '/bar/src/app/app.module.ts', ]), ); - expect(files).not.toEqual(jasmine.arrayContaining(['/bar/src/app/app.module.ts'])); }); it('should should set the prefix in angular.json and in app.component.ts', async () => { @@ -66,10 +66,11 @@ describe('Ng New Schematic', () => { expect(content).toMatch(/"prefix": "pre"/); }); - it('should set up the app module', async () => { + it('should set up the app module when standalone=false', async () => { const options: NgNewOptions = { name: 'foo', version: '6.0.0', + standalone: false, }; const tree = await schematicRunner.runSchematic('ng-new', options); diff --git a/packages/schematics/angular/ng-new/schema.json b/packages/schematics/angular/ng-new/schema.json index e827f696c0b7..321c4ef90dda 100644 --- a/packages/schematics/angular/ng-new/schema.json +++ b/packages/schematics/angular/ng-new/schema.json @@ -137,7 +137,7 @@ "standalone": { "description": "Creates an application based upon the standalone API, without NgModules.", "type": "boolean", - "default": false, + "default": true, "x-user-analytics": "ep.ng_standalone" } }, diff --git a/packages/schematics/angular/pipe/index_spec.ts b/packages/schematics/angular/pipe/index_spec.ts index 6c4822f06a3e..9116e1f2544d 100644 --- a/packages/schematics/angular/pipe/index_spec.ts +++ b/packages/schematics/angular/pipe/index_spec.ts @@ -40,122 +40,142 @@ describe('Pipe Schematic', () => { skipPackageJson: false, }; let appTree: UnitTestTree; - beforeEach(async () => { - appTree = await schematicRunner.runSchematic('workspace', workspaceOptions); - appTree = await schematicRunner.runSchematic('application', appOptions, appTree); - }); - - it('should create a pipe', async () => { - const options = { ...defaultOptions }; - - const tree = await schematicRunner.runSchematic('pipe', options, appTree); - const files = tree.files; - expect(files).toContain('/projects/bar/src/app/foo.pipe.spec.ts'); - expect(files).toContain('/projects/bar/src/app/foo.pipe.ts'); - const moduleContent = getFileContent(tree, '/projects/bar/src/app/app.module.ts'); - expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo.pipe'/); - expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooPipe\r?\n/m); - const fileContent = tree.readContent('/projects/bar/src/app/foo.pipe.ts'); - expect(fileContent).toContain('transform(value: unknown, ...args: unknown[])'); - }); - - it('should import into a specified module', async () => { - const options = { ...defaultOptions, module: 'app.module.ts' }; - - const tree = await schematicRunner.runSchematic('pipe', options, appTree); - const appModule = getFileContent(tree, '/projects/bar/src/app/app.module.ts'); - - expect(appModule).toMatch(/import { FooPipe } from '.\/foo.pipe'/); - }); - - it('should fail if specified module does not exist', async () => { - const options = { ...defaultOptions, module: '/projects/bar/src/app/app.moduleXXX.ts' }; - - await expectAsync(schematicRunner.runSchematic('pipe', options, appTree)).toBeRejected(); - }); - - it('should handle a path in the name and module options', async () => { - appTree = await schematicRunner.runSchematic( - 'module', - { name: 'admin/module', project: 'bar' }, - appTree, - ); - - const options = { ...defaultOptions, module: 'admin/module' }; - appTree = await schematicRunner.runSchematic('pipe', options, appTree); - - const content = appTree.readContent('/projects/bar/src/app/admin/module/module.module.ts'); - expect(content).toMatch(/import { FooPipe } from '\.\.\/\.\.\/foo.pipe'/); - }); - - it('should export the pipe', async () => { - const options = { ...defaultOptions, export: true }; - const tree = await schematicRunner.runSchematic('pipe', options, appTree); - const appModuleContent = getFileContent(tree, '/projects/bar/src/app/app.module.ts'); - expect(appModuleContent).toMatch(/exports: \[\n(\s*) {2}FooPipe\n\1\]/); + const defaultNonStandaloneOptions: PipeOptions = { ...defaultOptions, standalone: false }; + + describe('standalone=false', () => { + beforeEach(async () => { + appTree = await schematicRunner.runSchematic('workspace', workspaceOptions); + appTree = await schematicRunner.runSchematic( + 'application', + { ...appOptions, standalone: false }, + appTree, + ); + }); + + it('should create a pipe', async () => { + const tree = await schematicRunner.runSchematic('pipe', defaultNonStandaloneOptions, appTree); + const files = tree.files; + expect(files).toContain('/projects/bar/src/app/foo.pipe.spec.ts'); + expect(files).toContain('/projects/bar/src/app/foo.pipe.ts'); + const moduleContent = getFileContent(tree, '/projects/bar/src/app/app.module.ts'); + expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo.pipe'/); + expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooPipe\r?\n/m); + const fileContent = tree.readContent('/projects/bar/src/app/foo.pipe.ts'); + expect(fileContent).toContain('transform(value: unknown, ...args: unknown[])'); + }); + + it('should import into a specified module', async () => { + const options = { ...defaultNonStandaloneOptions, module: 'app.module.ts' }; + + const tree = await schematicRunner.runSchematic('pipe', options, appTree); + const appModule = getFileContent(tree, '/projects/bar/src/app/app.module.ts'); + + expect(appModule).toMatch(/import { FooPipe } from '.\/foo.pipe'/); + }); + + it('should fail if specified module does not exist', async () => { + const options = { + ...defaultNonStandaloneOptions, + module: '/projects/bar/src/app/app.moduleXXX.ts', + }; + + await expectAsync(schematicRunner.runSchematic('pipe', options, appTree)).toBeRejected(); + }); + + it('should handle a path in the name and module options', async () => { + appTree = await schematicRunner.runSchematic( + 'module', + { name: 'admin/module', project: 'bar' }, + appTree, + ); + + const options = { ...defaultNonStandaloneOptions, module: 'admin/module' }; + appTree = await schematicRunner.runSchematic('pipe', options, appTree); + + const content = appTree.readContent('/projects/bar/src/app/admin/module/module.module.ts'); + expect(content).toMatch(/import { FooPipe } from '\.\.\/\.\.\/foo.pipe'/); + }); + + it('should export the pipe', async () => { + const options = { ...defaultNonStandaloneOptions, export: true }; + + const tree = await schematicRunner.runSchematic('pipe', options, appTree); + const appModuleContent = getFileContent(tree, '/projects/bar/src/app/app.module.ts'); + expect(appModuleContent).toMatch(/exports: \[\n(\s*) {2}FooPipe\n\1\]/); + }); + + it('should respect the flat flag', async () => { + const options = { ...defaultNonStandaloneOptions, flat: false }; + + const tree = await schematicRunner.runSchematic('pipe', options, appTree); + const files = tree.files; + expect(files).toContain('/projects/bar/src/app/foo/foo.pipe.spec.ts'); + expect(files).toContain('/projects/bar/src/app/foo/foo.pipe.ts'); + const moduleContent = getFileContent(tree, '/projects/bar/src/app/app.module.ts'); + expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo\/foo.pipe'/); + expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooPipe\r?\n/m); + }); + + it('should use the module flag even if the module is a routing module', async () => { + const routingFileName = 'app-routing.module.ts'; + const routingModulePath = `/projects/bar/src/app/${routingFileName}`; + const newTree = createAppModule(appTree, routingModulePath); + const options = { ...defaultNonStandaloneOptions, module: routingFileName }; + const tree = await schematicRunner.runSchematic('pipe', options, newTree); + const content = getFileContent(tree, routingModulePath); + expect(content).toMatch(/import { FooPipe } from '.\/foo.pipe/); + }); + + 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)); + + // should fail without a module in that dir + await expectAsync( + schematicRunner.runSchematic('pipe', defaultNonStandaloneOptions, appTree), + ).toBeRejected(); + + // move the module + appTree.rename( + '/projects/bar/src/app/app.module.ts', + '/projects/bar/custom/app/app.module.ts', + ); + appTree = await schematicRunner.runSchematic('pipe', defaultNonStandaloneOptions, appTree); + expect(appTree.files).toContain('/projects/bar/custom/app/foo.pipe.ts'); + }); }); - it('should respect the flat flag', async () => { - const options = { ...defaultOptions, flat: false }; - - const tree = await schematicRunner.runSchematic('pipe', options, appTree); - const files = tree.files; - expect(files).toContain('/projects/bar/src/app/foo/foo.pipe.spec.ts'); - expect(files).toContain('/projects/bar/src/app/foo/foo.pipe.ts'); - const moduleContent = getFileContent(tree, '/projects/bar/src/app/app.module.ts'); - expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo\/foo.pipe'/); - expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooPipe\r?\n/m); - }); - - it('should use the module flag even if the module is a routing module', async () => { - const routingFileName = 'app-routing.module.ts'; - const routingModulePath = `/projects/bar/src/app/${routingFileName}`; - const newTree = createAppModule(appTree, routingModulePath); - const options = { ...defaultOptions, module: routingFileName }; - const tree = await schematicRunner.runSchematic('pipe', options, newTree); - const content = getFileContent(tree, routingModulePath); - expect(content).toMatch(/import { FooPipe } from '.\/foo.pipe/); - }); - - 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)); - - // should fail without a module in that dir - await expectAsync(schematicRunner.runSchematic('pipe', defaultOptions, appTree)).toBeRejected(); - - // move the module - appTree.rename('/projects/bar/src/app/app.module.ts', '/projects/bar/custom/app/app.module.ts'); - appTree = await schematicRunner.runSchematic('pipe', defaultOptions, appTree); - expect(appTree.files).toContain('/projects/bar/custom/app/foo.pipe.ts'); - }); - - it('should respect the skipTests flag', async () => { - const options = { ...defaultOptions, skipTests: true }; - - const tree = await schematicRunner.runSchematic('pipe', options, appTree); - const files = tree.files; - expect(files).not.toContain('/projects/bar/src/app/foo.pipe.spec.ts'); - expect(files).toContain('/projects/bar/src/app/foo.pipe.ts'); - }); - - it('should create a standalone pipe', async () => { - const options = { ...defaultOptions, standalone: true }; - const tree = await schematicRunner.runSchematic('pipe', options, appTree); - const moduleContent = tree.readContent('/projects/bar/src/app/app.module.ts'); - const pipeContent = tree.readContent('/projects/bar/src/app/foo.pipe.ts'); - expect(pipeContent).toContain('standalone: true'); - expect(pipeContent).toContain('class FooPipe'); - expect(moduleContent).not.toContain('FooPipe'); - }); - - it('should error when class name contains invalid characters', async () => { - const options = { ...defaultOptions, name: '1Clazz' }; - - await expectAsync(schematicRunner.runSchematic('pipe', options, appTree)).toBeRejectedWithError( - 'Class name "1Clazz" is invalid.', - ); + describe('standalone=true', () => { + beforeEach(async () => { + appTree = await schematicRunner.runSchematic('workspace', workspaceOptions); + appTree = await schematicRunner.runSchematic('application', { ...appOptions }, appTree); + }); + it('should create a standalone pipe', async () => { + const tree = await schematicRunner.runSchematic('pipe', defaultOptions, appTree); + const moduleContent = tree.readContent('/projects/bar/src/app/app.module.ts'); + const pipeContent = tree.readContent('/projects/bar/src/app/foo.pipe.ts'); + expect(pipeContent).toContain('standalone: true'); + expect(pipeContent).toContain('class FooPipe'); + expect(moduleContent).not.toContain('FooPipe'); + }); + + it('should respect the skipTests flag', async () => { + const options = { ...defaultOptions, skipTests: true }; + + const tree = await schematicRunner.runSchematic('pipe', options, appTree); + const files = tree.files; + expect(files).not.toContain('/projects/bar/src/app/foo.pipe.spec.ts'); + expect(files).toContain('/projects/bar/src/app/foo.pipe.ts'); + }); + + it('should error when class name contains invalid characters', async () => { + const options = { ...defaultOptions, name: '1Clazz' }; + + await expectAsync( + schematicRunner.runSchematic('pipe', options, appTree), + ).toBeRejectedWithError('Class name "1Clazz" is invalid.'); + }); }); }); diff --git a/packages/schematics/angular/pipe/schema.json b/packages/schematics/angular/pipe/schema.json index 0275266eac1a..ce9ba2699b7f 100644 --- a/packages/schematics/angular/pipe/schema.json +++ b/packages/schematics/angular/pipe/schema.json @@ -49,7 +49,7 @@ "standalone": { "description": "Whether the generated pipe is standalone.", "type": "boolean", - "default": false, + "default": true, "x-user-analytics": "ep.ng_standalone" }, "module": { diff --git a/packages/schematics/angular/server/index_spec.ts b/packages/schematics/angular/server/index_spec.ts index a296e1562427..615b11995889 100644 --- a/packages/schematics/angular/server/index_spec.ts +++ b/packages/schematics/angular/server/index_spec.ts @@ -23,10 +23,6 @@ describe('Server Schematic', () => { const defaultOptions: ServerOptions = { project: 'bar', }; - const workspaceUniversalOptions: ServerOptions = { - project: 'workspace', - }; - const workspaceOptions: WorkspaceOptions = { name: 'workspace', newProjectRoot: 'projects', @@ -43,86 +39,68 @@ describe('Server Schematic', () => { skipPackageJson: false, }; - const initialWorkspaceAppOptions: ApplicationOptions = { - name: 'workspace', - projectRoot: '', - inlineStyle: false, - inlineTemplate: false, - routing: false, - style: Style.Css, - skipTests: false, - skipPackageJson: false, - }; - let appTree: UnitTestTree; beforeEach(async () => { appTree = await schematicRunner.runSchematic('workspace', workspaceOptions); - appTree = await schematicRunner.runSchematic( - 'application', - initialWorkspaceAppOptions, - appTree, - ); - appTree = await schematicRunner.runSchematic('application', appOptions, appTree); }); - it('should create a root module file', async () => { - const tree = await schematicRunner.runSchematic('server', defaultOptions, appTree); - const filePath = '/projects/bar/src/app/app.module.server.ts'; - expect(tree.exists(filePath)).toBeTrue(); - }); + describe('non standalone application', () => { + beforeEach(async () => { + appTree = await schematicRunner.runSchematic( + 'application', + { ...appOptions, standalone: false }, + appTree, + ); + }); - it('should create a main file', async () => { - const tree = await schematicRunner.runSchematic('server', defaultOptions, appTree); - const filePath = '/projects/bar/src/main.server.ts'; - expect(tree.exists(filePath)).toBeTrue(); - const contents = tree.readContent(filePath); - expect(contents).toContain( - `export { AppServerModule as default } from './app/app.module.server';`, - ); - }); + it('should create a root module file', async () => { + const tree = await schematicRunner.runSchematic('server', defaultOptions, appTree); + const filePath = '/projects/bar/src/app/app.module.server.ts'; + expect(tree.exists(filePath)).toBeTrue(); + }); - it('should add dependency: @angular/platform-server', async () => { - const tree = await schematicRunner.runSchematic('server', defaultOptions, appTree); - const filePath = '/package.json'; - const contents = tree.readContent(filePath); - expect(contents).toMatch(/"@angular\/platform-server": "/); - }); + it('should create a main file', async () => { + const tree = await schematicRunner.runSchematic('server', defaultOptions, appTree); + const filePath = '/projects/bar/src/main.server.ts'; + expect(tree.exists(filePath)).toBeTrue(); + const contents = tree.readContent(filePath); + expect(contents).toContain( + `export { AppServerModule as default } from './app/app.module.server';`, + ); + }); - it('should install npm dependencies', async () => { - await schematicRunner.runSchematic('server', defaultOptions, appTree); - expect(schematicRunner.tasks.length).toBe(1); - expect(schematicRunner.tasks[0].name).toBe('node-package'); - expect((schematicRunner.tasks[0].options as { command: string }).command).toBe('install'); - }); + it('should add dependency: @angular/platform-server', async () => { + const tree = await schematicRunner.runSchematic('server', defaultOptions, appTree); + const filePath = '/package.json'; + const contents = tree.readContent(filePath); + expect(contents).toMatch(/"@angular\/platform-server": "/); + }); + + it('should install npm dependencies', async () => { + await schematicRunner.runSchematic('server', defaultOptions, appTree); + expect(schematicRunner.tasks.length).toBe(1); + expect(schematicRunner.tasks[0].name).toBe('node-package'); + expect((schematicRunner.tasks[0].options as { command: string }).command).toBe('install'); + }); - it('should update tsconfig.app.json', async () => { - const tree = await schematicRunner.runSchematic('server', defaultOptions, appTree); - const filePath = '/projects/bar/tsconfig.app.json'; - const contents = parseJson(tree.readContent(filePath).toString()); - expect(contents.compilerOptions.types).toEqual(['node']); - expect(contents.files).toEqual(['src/main.ts', 'src/main.server.ts']); + it('should update tsconfig.app.json', async () => { + const tree = await schematicRunner.runSchematic('server', defaultOptions, appTree); + const filePath = '/projects/bar/tsconfig.app.json'; + const contents = parseJson(tree.readContent(filePath).toString()); + expect(contents.compilerOptions.types).toEqual(['node']); + expect(contents.files).toEqual(['src/main.ts', 'src/main.server.ts']); + }); }); describe('standalone application', () => { - let standaloneAppOptions; - let defaultStandaloneOptions: ServerOptions; beforeEach(async () => { - const standaloneAppName = 'baz'; - standaloneAppOptions = { - ...appOptions, - name: standaloneAppName, - standalone: true, - }; - defaultStandaloneOptions = { - project: standaloneAppName, - }; - appTree = await schematicRunner.runSchematic('application', standaloneAppOptions, appTree); + appTree = await schematicRunner.runSchematic('application', appOptions, appTree); }); it('should create not root module file', async () => { - const tree = await schematicRunner.runSchematic('server', defaultStandaloneOptions, appTree); - const filePath = '/projects/baz/src/app/app.module.server.ts'; + const tree = await schematicRunner.runSchematic('server', defaultOptions, appTree); + const filePath = '/projects/bar/src/app/app.module.server.ts'; expect(tree.exists(filePath)).toEqual(false); }); @@ -136,16 +114,16 @@ describe('Server Schematic', () => { }); it('should create a main file', async () => { - const tree = await schematicRunner.runSchematic('server', defaultStandaloneOptions, appTree); - const filePath = '/projects/baz/src/main.server.ts'; + const tree = await schematicRunner.runSchematic('server', defaultOptions, appTree); + const filePath = '/projects/bar/src/main.server.ts'; expect(tree.exists(filePath)).toBeTrue(); const contents = tree.readContent(filePath); expect(contents).toContain(`bootstrapApplication(AppComponent, config)`); }); it('should create server app config file', async () => { - const tree = await schematicRunner.runSchematic('server', defaultStandaloneOptions, appTree); - const filePath = '/projects/baz/src/app/app.config.server.ts'; + const tree = await schematicRunner.runSchematic('server', defaultOptions, appTree); + const filePath = '/projects/bar/src/app/app.config.server.ts'; expect(tree.exists(filePath)).toBeTrue(); const contents = tree.readContent(filePath); expect(contents).toContain(`const serverConfig: ApplicationConfig = {`); @@ -174,7 +152,8 @@ describe('Server Schematic', () => { appTree.overwrite('/angular.json', JSON.stringify(config, undefined, 2)); } - beforeEach(() => { + beforeEach(async () => { + appTree = await schematicRunner.runSchematic('application', appOptions, appTree); convertBuilderToLegacyBrowser(); }); diff --git a/packages/schematics/angular/service-worker/index_spec.ts b/packages/schematics/angular/service-worker/index_spec.ts index 644e98ac00bb..8dc0dcf11fd5 100644 --- a/packages/schematics/angular/service-worker/index_spec.ts +++ b/packages/schematics/angular/service-worker/index_spec.ts @@ -63,27 +63,6 @@ describe('Service Worker Schematic', () => { expect(pkg.dependencies['@angular/service-worker']).toEqual(version); }); - it('should import ServiceWorkerModule', async () => { - const tree = await schematicRunner.runSchematic('service-worker', defaultOptions, appTree); - const pkgText = tree.readContent('/projects/bar/src/app/app.module.ts'); - expect(pkgText).toMatch(/import \{ ServiceWorkerModule \} from '@angular\/service-worker'/); - }); - - it('should add the SW import to the NgModule imports', async () => { - const tree = await schematicRunner.runSchematic('service-worker', defaultOptions, appTree); - const pkgText = tree.readContent('/projects/bar/src/app/app.module.ts'); - expect(pkgText).toMatch( - new RegExp( - "(\\s+)ServiceWorkerModule\\.register\\('ngsw-worker\\.js', \\{\\n" + - '\\1 enabled: !isDevMode\\(\\),\\n' + - '\\1 // Register the ServiceWorker as soon as the application is stable\\n' + - '\\1 // or after 30 seconds \\(whichever comes first\\)\\.\\n' + - "\\1 registrationStrategy: 'registerWhenStable:30000'\\n" + - '\\1}\\)', - ), - ); - }); - it('should put the ngsw-config.json file in the project root', async () => { const tree = await schematicRunner.runSchematic('service-worker', defaultOptions, appTree); const path = '/projects/bar/ngsw-config.json'; @@ -154,55 +133,72 @@ describe('Service Worker Schematic', () => { expect(tree.exists('/ngsw-config.json')).toBe(true); }); - describe('standalone', () => { + it(`should add the 'provideServiceWorker' to providers`, async () => { + const tree = await schematicRunner.runSchematic('service-worker', defaultOptions, appTree); + const content = tree.readContent('/projects/bar/src/app/app.config.ts'); + expect(tags.oneLine`${content}`).toContain(tags.oneLine` + providers: [provideServiceWorker('ngsw-worker.js', { + enabled: !isDevMode(), + registrationStrategy: 'registerWhenStable:30000' + })] + `); + }); + + it(`should import 'isDevMode' from '@angular/core'`, async () => { + const tree = await schematicRunner.runSchematic('service-worker', defaultOptions, appTree); + const content = tree.readContent('/projects/bar/src/app/app.config.ts'); + expect(content).toContain(`import { ApplicationConfig, isDevMode } from '@angular/core';`); + }); + + it(`should import 'provideServiceWorker' from '@angular/service-worker'`, async () => { + const tree = await schematicRunner.runSchematic('service-worker', defaultOptions, appTree); + const content = tree.readContent('/projects/bar/src/app/app.config.ts'); + expect(content).toContain(`import { provideServiceWorker } from '@angular/service-worker';`); + }); + + describe('standalone=false', () => { const name = 'buz'; - const standaloneAppOptions: ApplicationOptions = { + const nonStandaloneAppOptions: ApplicationOptions = { ...appOptions, name, - standalone: true, + standalone: false, }; - const standaloneSWOptions: ServiceWorkerOptions = { + const nonStandaloneSWOptions: ServiceWorkerOptions = { ...defaultOptions, project: name, }; beforeEach(async () => { - appTree = await schematicRunner.runSchematic('application', standaloneAppOptions, appTree); + appTree = await schematicRunner.runSchematic('application', nonStandaloneAppOptions, appTree); }); - it(`should add the 'provideServiceWorker' to providers`, async () => { + it('should import ServiceWorkerModule', async () => { const tree = await schematicRunner.runSchematic( 'service-worker', - standaloneSWOptions, + nonStandaloneSWOptions, appTree, ); - const content = tree.readContent('/projects/buz/src/app/app.config.ts'); - expect(tags.oneLine`${content}`).toContain(tags.oneLine` - providers: [provideServiceWorker('ngsw-worker.js', { - enabled: !isDevMode(), - registrationStrategy: 'registerWhenStable:30000' - })] - `); + const pkgText = tree.readContent('/projects/buz/src/app/app.module.ts'); + expect(pkgText).toMatch(/import \{ ServiceWorkerModule \} from '@angular\/service-worker'/); }); - it(`should import 'isDevMode' from '@angular/core'`, async () => { + it('should add the SW import to the NgModule imports', async () => { const tree = await schematicRunner.runSchematic( 'service-worker', - standaloneSWOptions, + nonStandaloneSWOptions, appTree, ); - const content = tree.readContent('/projects/buz/src/app/app.config.ts'); - expect(content).toContain(`import { ApplicationConfig, isDevMode } from '@angular/core';`); - }); - - it(`should import 'provideServiceWorker' from '@angular/service-worker'`, async () => { - const tree = await schematicRunner.runSchematic( - 'service-worker', - standaloneSWOptions, - appTree, + const pkgText = tree.readContent('/projects/buz/src/app/app.module.ts'); + expect(pkgText).toMatch( + new RegExp( + "(\\s+)ServiceWorkerModule\\.register\\('ngsw-worker\\.js', \\{\\n" + + '\\1 enabled: !isDevMode\\(\\),\\n' + + '\\1 // Register the ServiceWorker as soon as the application is stable\\n' + + '\\1 // or after 30 seconds \\(whichever comes first\\)\\.\\n' + + "\\1 registrationStrategy: 'registerWhenStable:30000'\\n" + + '\\1}\\)', + ), ); - const content = tree.readContent('/projects/buz/src/app/app.config.ts'); - expect(content).toContain(`import { provideServiceWorker } from '@angular/service-worker';`); }); });