Skip to content

Commit 61a5988

Browse files
committed
fix(@schematics/angular): use fixture.whenStable() instead of fixture.detectChanges() for zoneless apps
1 parent 7458ca1 commit 61a5988

File tree

7 files changed

+138
-5
lines changed

7 files changed

+138
-5
lines changed

packages/schematics/angular/application/files/module-files/src/app/app__suffix__.spec.ts.template

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ describe('App', () => {
2020
expect(app).toBeTruthy();
2121
});
2222

23-
it('should render title', () => {
23+
it('should render title', <% if(zoneless) { %> async <% } %>() => {
2424
const fixture = TestBed.createComponent(App);
25-
fixture.detectChanges();
25+
<%= zoneless ? 'await fixture.whenStable();' : 'fixture.detectChanges();' %>
2626
const compiled = fixture.nativeElement as HTMLElement;
2727
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, <%= name %>');
2828
});

packages/schematics/angular/application/files/standalone-files/src/app/app__suffix__.spec.ts.template

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ describe('App', () => {
1414
expect(app).toBeTruthy();
1515
});
1616

17-
it('should render title', () => {
17+
it('should render title', <% if(zoneless) { %> async <% } %>() => {
1818
const fixture = TestBed.createComponent(App);
19-
fixture.detectChanges();
19+
<%= zoneless ? 'await fixture.whenStable();' : 'fixture.detectChanges();' %>
2020
const compiled = fixture.nativeElement as HTMLElement;
2121
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, <%= name %>');
2222
});

packages/schematics/angular/application/index_spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,66 @@ describe('Application Schematic', () => {
829829
const fileContent = tree.readContent(path);
830830
expect(fileContent).not.toMatch(/provideZone(less)?ChangeDetection/gi);
831831
});
832+
833+
it('should add fixture.whenStable() in initial spec files when zoneless application', async () => {
834+
const tree = await schematicRunner.runSchematic(
835+
'application',
836+
{
837+
...defaultOptions,
838+
standalone: false,
839+
},
840+
workspaceTree,
841+
);
842+
843+
const content = tree.readContent('/projects/foo/src/app/app.spec.ts');
844+
expect(content).toContain('fixture.whenStable()');
845+
expect(content).not.toContain('fixture.detectChanges()');
846+
});
847+
848+
it('should not add fixture.whenStable() in initial spec files when its not zoneless application', async () => {
849+
const tree = await schematicRunner.runSchematic(
850+
'application',
851+
{
852+
...defaultOptions,
853+
zoneless: false,
854+
standalone: false,
855+
},
856+
workspaceTree,
857+
);
858+
859+
const content = tree.readContent('/projects/foo/src/app/app.spec.ts');
860+
expect(content).not.toContain('fixture.whenStable()');
861+
expect(content).toContain('fixture.detectChanges()');
862+
});
863+
});
864+
865+
it('should add fixture.whenStable() when zoneless application', async () => {
866+
const tree = await schematicRunner.runSchematic(
867+
'application',
868+
{
869+
...defaultOptions,
870+
},
871+
workspaceTree,
872+
);
873+
874+
const content = tree.readContent('/projects/foo/src/app/app.spec.ts');
875+
expect(content).toContain('fixture.whenStable()');
876+
expect(content).not.toContain('fixture.detectChanges()');
877+
});
878+
879+
it('should not add fixture.whenStable() in initial spec files when its not zoneless application', async () => {
880+
const tree = await schematicRunner.runSchematic(
881+
'application',
882+
{
883+
...defaultOptions,
884+
zoneless: false,
885+
},
886+
workspaceTree,
887+
);
888+
889+
const content = tree.readContent('/projects/foo/src/app/app.spec.ts');
890+
expect(content).not.toContain('fixture.whenStable()');
891+
expect(content).toContain('fixture.detectChanges()');
832892
});
833893

834894
it('should call the tailwind schematic when style is tailwind', async () => {

packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.spec.ts.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ describe('<%= classifiedName %>', () => {
1414

1515
fixture = TestBed.createComponent(<%= classifiedName %>);
1616
component = fixture.componentInstance;
17-
fixture.detectChanges();
17+
<%= zoneless ? 'await fixture.whenStable();' : 'fixture.detectChanges();' %>
1818
});
1919

2020
it('should create', () => {

packages/schematics/angular/component/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { addDeclarationToNgModule } from '../utility/add-declaration-to-ng-modul
2424
import { findModuleFromOptions } from '../utility/find-module';
2525
import { parseName } from '../utility/parse-name';
2626
import { createProjectSchematic } from '../utility/project';
27+
import { isZonelessApp } from '../utility/project-targets';
2728
import { validateClassName, validateHtmlSelector } from '../utility/validation';
2829
import { buildDefaultPath } from '../utility/workspace';
2930
import { Schema as ComponentOptions, Style } from './schema';
@@ -59,6 +60,7 @@ export default createProjectSchematic<ComponentOptions>((options, { project, tre
5960
strings.classify(options.name) +
6061
(options.addTypeToClassName && options.type ? strings.classify(options.type) : '');
6162
validateClassName(classifiedName);
63+
const zoneless = isZonelessApp(project);
6264

6365
const skipStyleFile = options.inlineStyle || options.style === Style.None;
6466
const templateSource = apply(url('./files'), [
@@ -72,6 +74,7 @@ export default createProjectSchematic<ComponentOptions>((options, { project, tre
7274
...options,
7375
// Add a new variable for the classified name, conditionally including the type
7476
classifiedName,
77+
zoneless,
7578
}),
7679
!options.type
7780
? forEach(((file) => {

packages/schematics/angular/component/index_spec.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,15 @@ describe('Component Schematic', () => {
531531

532532
await expectAsync(schematicRunner.runSchematic('component', options, appTree)).toBeRejected();
533533
});
534+
535+
it('should add fixture.whenStable() in spec file for non standalone', async () => {
536+
const options = { ...defaultNonStandaloneOptions };
537+
const tree = await schematicRunner.runSchematic('component', options, appTree);
538+
const tsContent = tree.readContent('/projects/baz/src/app/foo/foo.component.spec.ts');
539+
540+
expect(tsContent).toContain('fixture.whenStable()');
541+
expect(tsContent).not.toContain('fixture.detectChanges()');
542+
});
534543
});
535544

536545
it('should export the component as default when exportDefault is true', async () => {
@@ -554,4 +563,53 @@ describe('Component Schematic', () => {
554563
const specContent = tree.readContent('/projects/bar/src/app/foo/foo.component.spec.ts');
555564
expect(specContent).toContain("import { FooComponent } from './foo.component';");
556565
});
566+
567+
it('should add fixture.whenStable() in spec file when zoneless and standalone apps', async () => {
568+
const tree = await schematicRunner.runSchematic('component', { ...defaultOptions }, appTree);
569+
const tsContent = tree.readContent('/projects/bar/src/app/foo/foo.component.spec.ts');
570+
571+
expect(tsContent).toContain('fixture.whenStable()');
572+
expect(tsContent).not.toContain('fixture.detectChanges()');
573+
});
574+
575+
describe('with zone.js application', () => {
576+
const zoneAppOptions: ApplicationOptions = {
577+
...appOptions,
578+
name: 'baz',
579+
zoneless: false,
580+
};
581+
582+
it('should add not fixture.whenStable() in spec file for standalone', async () => {
583+
appTree = await schematicRunner.runSchematic(
584+
'application',
585+
{ ...zoneAppOptions, standalone: true },
586+
appTree,
587+
);
588+
const tree = await schematicRunner.runSchematic(
589+
'component',
590+
{ ...defaultOptions, standalone: true, project: 'baz' },
591+
appTree,
592+
);
593+
594+
const tsContent = tree.readContent('/projects/baz/src/app/foo/foo.component.spec.ts');
595+
596+
expect(tsContent).not.toContain('fixture.whenStable()');
597+
expect(tsContent).toContain('fixture.detectChanges()');
598+
});
599+
600+
it('should add not fixture.whenStable() in spec file for standalone is false', async () => {
601+
appTree = await schematicRunner.runSchematic('application', { ...zoneAppOptions }, appTree);
602+
603+
const tree = await schematicRunner.runSchematic(
604+
'component',
605+
{ ...defaultOptions, standalone: false, skipImport: true, project: 'baz' },
606+
appTree,
607+
);
608+
609+
const tsContent = tree.readContent('/projects/baz/src/app/foo/foo.component.spec.ts');
610+
611+
expect(tsContent).not.toContain('fixture.whenStable()');
612+
expect(tsContent).toContain('fixture.detectChanges()');
613+
});
614+
});
557615
});

packages/schematics/angular/utility/project-targets.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,15 @@ export function isUsingApplicationBuilder(project: ProjectDefinition): boolean {
2121

2222
return isUsingApplicationBuilder;
2323
}
24+
25+
export function isZonelessApp(project: ProjectDefinition): boolean {
26+
const buildTarget = project.targets.get('build');
27+
if (!buildTarget?.options?.polyfills) {
28+
return true;
29+
}
30+
31+
const polyfills = buildTarget.options.polyfills as string[] | string;
32+
const polyfillsList = Array.isArray(polyfills) ? polyfills : [polyfills];
33+
34+
return !polyfillsList.includes('zone.js');
35+
}

0 commit comments

Comments
 (0)