Remove the deprecated functionalities scheduled to be removed in Nx v21. BREAKING CHANGE: Remove the deprecated data persistence operators previously exported in `@nx/angular` and the deprecated testing utils previously exported in `@nx/angular/testing`.
552 lines
15 KiB
TypeScript
552 lines
15 KiB
TypeScript
import { updateJson } from '@nx/devkit';
|
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
import { createSourceFile, ScriptTarget } from 'typescript';
|
|
import {
|
|
addImportToComponent,
|
|
addImportToDirective,
|
|
addImportToModule,
|
|
addImportToPipe,
|
|
addProviderToAppConfig,
|
|
addProviderToBootstrapApplication,
|
|
addViewProviderToComponent,
|
|
isStandalone,
|
|
} from './ast-utils';
|
|
|
|
describe('Angular AST Utils', () => {
|
|
it('should correctly add the imported symbol to the NgModule', () => {
|
|
// ARRANGE
|
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
const pathToModule = `my.module.ts`;
|
|
const originalContents = `import { NgModule } from '@angular/core';
|
|
|
|
@NgModule({})
|
|
export class MyModule {}
|
|
`;
|
|
|
|
tree.write(pathToModule, originalContents);
|
|
|
|
const symbolToAdd = `CommonModule`;
|
|
|
|
const sourceText = tree.read(pathToModule, 'utf-8');
|
|
const tsSourceFile = createSourceFile(
|
|
pathToModule,
|
|
sourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
// ACT
|
|
addImportToModule(tree, tsSourceFile, pathToModule, symbolToAdd);
|
|
|
|
// ASSERT
|
|
expect(tree.read(pathToModule, 'utf-8')).toMatchInlineSnapshot(`
|
|
"import { NgModule } from '@angular/core';
|
|
|
|
@NgModule({ imports: [CommonModule]
|
|
})
|
|
export class MyModule {}
|
|
"
|
|
`);
|
|
});
|
|
|
|
it('should correctly add the imported symbol to the Component', () => {
|
|
// ARRANGE
|
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
const pathToFile = `my.component.ts`;
|
|
const originalContents = `import { Component } from '@angular/core';
|
|
|
|
@Component({})
|
|
export class MyComponent {}
|
|
`;
|
|
|
|
tree.write(pathToFile, originalContents);
|
|
|
|
const symbolToAdd = `CommonModule`;
|
|
|
|
const sourceText = tree.read(pathToFile, 'utf-8');
|
|
const tsSourceFile = createSourceFile(
|
|
pathToFile,
|
|
sourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
// ACT
|
|
addImportToComponent(tree, tsSourceFile, pathToFile, symbolToAdd);
|
|
|
|
// ASSERT
|
|
expect(tree.read(pathToFile, 'utf-8')).toMatchInlineSnapshot(`
|
|
"import { Component } from '@angular/core';
|
|
|
|
@Component({ imports: [CommonModule]
|
|
})
|
|
export class MyComponent {}
|
|
"
|
|
`);
|
|
});
|
|
|
|
it('should correctly add the imported symbol to the Directive', () => {
|
|
// ARRANGE
|
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
const pathToFile = `my.directive.ts`;
|
|
const originalContents = `import { Directive } from '@angular/core';
|
|
|
|
@Directive({})
|
|
export class MyDirective {}
|
|
`;
|
|
|
|
tree.write(pathToFile, originalContents);
|
|
|
|
const symbolToAdd = `CommonModule`;
|
|
|
|
const sourceText = tree.read(pathToFile, 'utf-8');
|
|
const tsSourceFile = createSourceFile(
|
|
pathToFile,
|
|
sourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
// ACT
|
|
addImportToDirective(tree, tsSourceFile, pathToFile, symbolToAdd);
|
|
|
|
// ASSERT
|
|
expect(tree.read(pathToFile, 'utf-8')).toMatchInlineSnapshot(`
|
|
"import { Directive } from '@angular/core';
|
|
|
|
@Directive({ imports: [CommonModule]
|
|
})
|
|
export class MyDirective {}
|
|
"
|
|
`);
|
|
});
|
|
|
|
it('should correctly add the imported symbol to the Pipe', () => {
|
|
// ARRANGE
|
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
const pathToFile = `my.pipe.ts`;
|
|
const originalContents = `import { Pipe } from '@angular/core';
|
|
|
|
@Pipe({})
|
|
export class MyPipe {}
|
|
`;
|
|
|
|
tree.write(pathToFile, originalContents);
|
|
|
|
const symbolToAdd = `CommonModule`;
|
|
|
|
const sourceText = tree.read(pathToFile, 'utf-8');
|
|
const tsSourceFile = createSourceFile(
|
|
pathToFile,
|
|
sourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
// ACT
|
|
addImportToPipe(tree, tsSourceFile, pathToFile, symbolToAdd);
|
|
|
|
// ASSERT
|
|
expect(tree.read(pathToFile, 'utf-8')).toMatchInlineSnapshot(`
|
|
"import { Pipe } from '@angular/core';
|
|
|
|
@Pipe({ imports: [CommonModule]
|
|
})
|
|
export class MyPipe {}
|
|
"
|
|
`);
|
|
});
|
|
|
|
describe('isStandalone', () => {
|
|
it('should return true for a component when "standalone: true" is set', () => {
|
|
// ARRANGE
|
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
const pathToFile = `my.component.ts`;
|
|
const originalContents = `import { Component } from '@angular/core';
|
|
|
|
@Component({
|
|
standalone: true
|
|
})
|
|
export class MyComponent {}
|
|
`;
|
|
|
|
tree.write(pathToFile, originalContents);
|
|
|
|
const sourceText = tree.read(pathToFile, 'utf-8');
|
|
const tsSourceFile = createSourceFile(
|
|
pathToFile,
|
|
sourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
// ACT
|
|
// ASSERT
|
|
expect(isStandalone(tree, tsSourceFile, 'Component')).toBeTruthy();
|
|
});
|
|
|
|
it('should return true for a component when the "standalone" prop is not set and the angular version is 19 or above', () => {
|
|
const tree = createTreeWithEmptyWorkspace({});
|
|
updateJson(tree, 'package.json', (json) => {
|
|
json.dependencies['@angular/core'] = '^19.0.0';
|
|
return json;
|
|
});
|
|
const componentSourceText = `import { Component } from '@angular/core';
|
|
|
|
@Component({})
|
|
export class MyComponent {}
|
|
`;
|
|
const tsSourceFile = createSourceFile(
|
|
'my.component.ts',
|
|
componentSourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
expect(isStandalone(tree, tsSourceFile, 'Component')).toBe(true);
|
|
});
|
|
|
|
it('should return false for a component when "standalone: false" is set', () => {
|
|
// ARRANGE
|
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
const pathToFile = `my.component.ts`;
|
|
const originalContents = `import { Component } from '@angular/core';
|
|
|
|
@Component({
|
|
standalone: false
|
|
})
|
|
export class MyComponent {}
|
|
`;
|
|
|
|
tree.write(pathToFile, originalContents);
|
|
|
|
const sourceText = tree.read(pathToFile, 'utf-8');
|
|
const tsSourceFile = createSourceFile(
|
|
pathToFile,
|
|
sourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
// ACT
|
|
// ASSERT
|
|
expect(isStandalone(tree, tsSourceFile, 'Component')).not.toBeTruthy();
|
|
});
|
|
|
|
it('should return false for a component when the "standalone" prop is not set and the angular version is 18 or below', () => {
|
|
const tree = createTreeWithEmptyWorkspace({});
|
|
updateJson(tree, 'package.json', (json) => {
|
|
json.dependencies['@angular/core'] = '^18.0.0';
|
|
return json;
|
|
});
|
|
const componentSourceText = `import { Component } from '@angular/core';
|
|
|
|
@Component({})
|
|
export class MyComponent {}
|
|
`;
|
|
const tsSourceFile = createSourceFile(
|
|
'my.component.ts',
|
|
componentSourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
expect(isStandalone(tree, tsSourceFile, 'Component')).toBe(false);
|
|
});
|
|
|
|
it('should return true for a directive when "standalone: true" is set', () => {
|
|
// ARRANGE
|
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
const pathToFile = `my.directive.ts`;
|
|
const originalContents = `import { Directive } from '@angular/core';
|
|
|
|
@Directive({
|
|
standalone: true
|
|
})
|
|
export class MyDirective {}
|
|
`;
|
|
|
|
tree.write(pathToFile, originalContents);
|
|
|
|
const sourceText = tree.read(pathToFile, 'utf-8');
|
|
const tsSourceFile = createSourceFile(
|
|
pathToFile,
|
|
sourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
// ACT
|
|
// ASSERT
|
|
expect(isStandalone(tree, tsSourceFile, 'Directive')).toBeTruthy();
|
|
});
|
|
|
|
it('should return true for a directive when the "standalone" prop is not set and the angular version is 19 or above', () => {
|
|
const tree = createTreeWithEmptyWorkspace({});
|
|
updateJson(tree, 'package.json', (json) => {
|
|
json.dependencies['@angular/core'] = '^19.0.0';
|
|
return json;
|
|
});
|
|
const directiveSourceText = `import { Directive } from '@angular/core';
|
|
|
|
@Directive({})
|
|
export class MyDirective {}
|
|
`;
|
|
const tsSourceFile = createSourceFile(
|
|
'my.directive.ts',
|
|
directiveSourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
expect(isStandalone(tree, tsSourceFile, 'Directive')).toBe(true);
|
|
});
|
|
|
|
it('should return false for a directive when "standalone: false" is set', () => {
|
|
const tree = createTreeWithEmptyWorkspace({});
|
|
const directiveSourceText = `import { Directive } from '@angular/core';
|
|
|
|
@Directive({
|
|
standalone: false
|
|
})
|
|
export class MyDirective {}
|
|
`;
|
|
const tsSourceFile = createSourceFile(
|
|
'my.directive.ts',
|
|
directiveSourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
expect(isStandalone(tree, tsSourceFile, 'Directive')).toBe(false);
|
|
});
|
|
|
|
it('should return false for a directive when the "standalone" prop is not set and the angular version is 18 or below', () => {
|
|
const tree = createTreeWithEmptyWorkspace({});
|
|
updateJson(tree, 'package.json', (json) => {
|
|
json.dependencies['@angular/core'] = '^18.0.0';
|
|
return json;
|
|
});
|
|
const directiveSourceText = `import { Directive } from '@angular/core';
|
|
|
|
@Directive({})
|
|
export class MyDirective {}
|
|
`;
|
|
const tsSourceFile = createSourceFile(
|
|
'my.component.ts',
|
|
directiveSourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
expect(isStandalone(tree, tsSourceFile, 'Directive')).toBe(false);
|
|
});
|
|
|
|
it('should return true for a pipe when "standalone: true" is set', () => {
|
|
// ARRANGE
|
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
const pathToFile = `my.pipe.ts`;
|
|
const originalContents = `import { Pipe } from '@angular/core';
|
|
|
|
@Pipe({
|
|
standalone: true
|
|
})
|
|
export class MyPipe {}
|
|
`;
|
|
|
|
tree.write(pathToFile, originalContents);
|
|
|
|
const sourceText = tree.read(pathToFile, 'utf-8');
|
|
const tsSourceFile = createSourceFile(
|
|
pathToFile,
|
|
sourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
// ACT
|
|
// ASSERT
|
|
expect(isStandalone(tree, tsSourceFile, 'Pipe')).toBeTruthy();
|
|
});
|
|
|
|
it('should return true for a pipe when the "standalone" prop is not set and the angular version is 19 or above', () => {
|
|
const tree = createTreeWithEmptyWorkspace({});
|
|
updateJson(tree, 'package.json', (json) => {
|
|
json.dependencies['@angular/core'] = '^19.0.0';
|
|
return json;
|
|
});
|
|
const pipeSourceText = `import { Pipe } from '@angular/core';
|
|
|
|
@Pipe({})
|
|
export class MyPipe {}
|
|
`;
|
|
const tsSourceFile = createSourceFile(
|
|
'my.pipe.ts',
|
|
pipeSourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
expect(isStandalone(tree, tsSourceFile, 'Pipe')).toBe(true);
|
|
});
|
|
|
|
it('should return false for a pipe when "standalone: false" is set', () => {
|
|
const tree = createTreeWithEmptyWorkspace({});
|
|
const pipeSourceText = `import { Pipe } from '@angular/core';
|
|
|
|
@Pipe({
|
|
standalone: false
|
|
})
|
|
export class MyPipe {}
|
|
`;
|
|
const tsSourceFile = createSourceFile(
|
|
'my.pipe.ts',
|
|
pipeSourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
expect(isStandalone(tree, tsSourceFile, 'Pipe')).toBe(false);
|
|
});
|
|
|
|
it('should return false for a pipe when the "standalone" prop is not set and the angular version is 18 or below', () => {
|
|
const tree = createTreeWithEmptyWorkspace({});
|
|
updateJson(tree, 'package.json', (json) => {
|
|
json.dependencies['@angular/core'] = '^18.0.0';
|
|
return json;
|
|
});
|
|
const pipeSourceText = `import { Pipe } from '@angular/core';
|
|
|
|
@Pipe({})
|
|
export class MyPipe {}
|
|
`;
|
|
const tsSourceFile = createSourceFile(
|
|
'my.pipe.ts',
|
|
pipeSourceText,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
expect(isStandalone(tree, tsSourceFile, 'Pipe')).toBe(false);
|
|
});
|
|
});
|
|
|
|
it('should add a provider to the bootstrapApplication call', () => {
|
|
// ARRANGE
|
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
tree.write(
|
|
'main.ts',
|
|
`import { bootstrapApplication } from '@angular/platform-browser';
|
|
import {
|
|
provideRouter,
|
|
withEnabledBlockingInitialNavigation,
|
|
} from '@angular/router';
|
|
import { AppComponent } from './app/app.component';
|
|
import { appRoutes } from './app/app.routes';
|
|
|
|
bootstrapApplication(AppComponent, {
|
|
providers: [
|
|
provideRouter(appRoutes, withEnabledBlockingInitialNavigation()),
|
|
],
|
|
}).catch((err) => console.error(err));`
|
|
);
|
|
|
|
// ACT
|
|
addProviderToBootstrapApplication(tree, 'main.ts', 'provideStore()');
|
|
|
|
// ASSERT
|
|
expect(tree.read('main.ts', 'utf-8')).toMatchInlineSnapshot(`
|
|
"import { bootstrapApplication } from '@angular/platform-browser';
|
|
import {
|
|
provideRouter,
|
|
withEnabledBlockingInitialNavigation,
|
|
} from '@angular/router';
|
|
import { AppComponent } from './app/app.component';
|
|
import { appRoutes } from './app/app.routes';
|
|
|
|
bootstrapApplication(AppComponent, {
|
|
providers: [provideStore(),
|
|
provideRouter(appRoutes, withEnabledBlockingInitialNavigation()),
|
|
],
|
|
}).catch((err) => console.error(err));"
|
|
`);
|
|
});
|
|
|
|
it('should add a provider to the appConfig', () => {
|
|
// ARRANGE
|
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
tree.write(
|
|
'app.config.ts',
|
|
`import { ApplicationConfig } from '@angular/core';
|
|
import { provideRouter } from '@angular/router';
|
|
|
|
import { routes } from './app.routes';
|
|
|
|
export const appConfig: ApplicationConfig = {
|
|
providers: [provideRouter(routes) ]
|
|
};`
|
|
);
|
|
|
|
// ACT
|
|
addProviderToAppConfig(tree, 'app.config.ts', 'provideStore()');
|
|
|
|
// ASSERT
|
|
expect(tree.read('app.config.ts', 'utf-8')).toMatchInlineSnapshot(`
|
|
"import { ApplicationConfig } from '@angular/core';
|
|
import { provideRouter } from '@angular/router';
|
|
|
|
import { routes } from './app.routes';
|
|
|
|
export const appConfig: ApplicationConfig = {
|
|
providers: [provideStore(),provideRouter(routes) ]
|
|
};"
|
|
`);
|
|
});
|
|
|
|
it('should add view provider to a component', () => {
|
|
// ARRANGE
|
|
const pathToComponent = 'app.component.ts';
|
|
const componentOriginal = `import { Component } from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'app-app',
|
|
template: ''
|
|
})
|
|
export class AppComponent {}
|
|
`;
|
|
const providerName = 'MyViewProvider';
|
|
|
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
|
|
tree.write(pathToComponent, componentOriginal);
|
|
|
|
const tsSourceFile = createSourceFile(
|
|
pathToComponent,
|
|
componentOriginal,
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
|
|
// ACT
|
|
addViewProviderToComponent(
|
|
tree,
|
|
tsSourceFile,
|
|
pathToComponent,
|
|
providerName
|
|
);
|
|
|
|
// ASSERT
|
|
expect(tree.read(pathToComponent, 'utf-8')).toMatchInlineSnapshot(`
|
|
"import { Component } from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'app-app',
|
|
template: '',
|
|
viewProviders: [MyViewProvider]
|
|
})
|
|
export class AppComponent {}
|
|
"
|
|
`);
|
|
});
|
|
});
|