Skip to content

Commit 6830620

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

File tree

7 files changed

+156
-7
lines changed

7 files changed

+156
-7
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
@@ -22,9 +22,9 @@ describe('App', () => {
2222
expect(app).toBeTruthy();
2323
});
2424

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

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
@@ -16,9 +16,9 @@ describe('App', () => {
1616
expect(app).toBeTruthy();
1717
});
1818

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

packages/schematics/angular/application/index_spec.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,72 @@ describe('Application Schematic', () => {
871871
const fileContent = tree.readContent(path);
872872
expect(fileContent).not.toContain('provideZoneChangeDetection');
873873
});
874+
875+
it('should add provideZonelessChangeDetection() in initial spec files when zoneless application', async () => {
876+
const tree = await schematicRunner.runSchematic(
877+
'application',
878+
{
879+
...defaultOptions,
880+
zoneless: true,
881+
standalone: false,
882+
},
883+
workspaceTree,
884+
);
885+
886+
const content = tree.readContent('/projects/foo/src/app/app.spec.ts');
887+
expect(content).toContain('provideZonelessChangeDetection()');
888+
expect(content).toContain('fixture.whenStable()');
889+
expect(content).not.toContain('fixture.detectChanges()');
890+
});
891+
892+
it('should not add provideZonelessChangeDetection() in initial spec files when its not zoneless application', async () => {
893+
const tree = await schematicRunner.runSchematic(
894+
'application',
895+
{
896+
...defaultOptions,
897+
zoneless: false,
898+
standalone: false,
899+
},
900+
workspaceTree,
901+
);
902+
903+
const content = tree.readContent('/projects/foo/src/app/app.spec.ts');
904+
expect(content).not.toContain('provideZonelessChangeDetection()');
905+
expect(content).not.toContain('fixture.whenStable()');
906+
expect(content).toContain('fixture.detectChanges()');
907+
});
908+
});
909+
910+
it('should add provideZonelessChangeDetection() when zoneless application', async () => {
911+
const tree = await schematicRunner.runSchematic(
912+
'application',
913+
{
914+
...defaultOptions,
915+
zoneless: true,
916+
},
917+
workspaceTree,
918+
);
919+
920+
const content = tree.readContent('/projects/foo/src/app/app.spec.ts');
921+
expect(content).toContain('provideZonelessChangeDetection()');
922+
expect(content).toContain('fixture.whenStable()');
923+
expect(content).not.toContain('fixture.detectChanges()');
924+
});
925+
926+
it('should not add provideZonelessChangeDetection() in initial spec files when its not zoneless application', async () => {
927+
const tree = await schematicRunner.runSchematic(
928+
'application',
929+
{
930+
...defaultOptions,
931+
zoneless: false,
932+
},
933+
workspaceTree,
934+
);
935+
936+
const content = tree.readContent('/projects/foo/src/app/app.spec.ts');
937+
expect(content).not.toContain('provideZonelessChangeDetection()');
938+
expect(content).not.toContain('fixture.whenStable()');
939+
expect(content).toContain('fixture.detectChanges()');
874940
});
875941

876942
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: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { ComponentFixture, TestBed } from '@angular/core/testing';
1+
<% if(zoneless) { %>import { provideZonelessChangeDetection } from '@angular/core';
2+
<% } %>import { ComponentFixture, TestBed } from '@angular/core/testing';
23

34
import <% if(!exportDefault) { %>{ <% }%><%= classifiedName %> <% if(!exportDefault) {%>} <% }%>from './<%= dasherize(name) %><%= type ? '.' + dasherize(type): '' %>';
45

@@ -8,13 +9,14 @@ describe('<%= classifiedName %>', () => {
89

910
beforeEach(async () => {
1011
await TestBed.configureTestingModule({
11-
<%= standalone ? 'imports' : 'declarations' %>: [<%= classifiedName %>]
12+
<%= standalone ? 'imports' : 'declarations' %>: [<%= classifiedName %>],<% if(zoneless) { %>
13+
providers: [provideZonelessChangeDetection()]<% } %>
1214
})
1315
.compileComponents();
1416

1517
fixture = TestBed.createComponent(<%= classifiedName %>);
1618
component = fixture.componentInstance;
17-
fixture.detectChanges();
19+
<%= zoneless ? 'await fixture.whenStable();' : 'fixture.detectChanges();' %>
1820
});
1921

2022
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: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,17 @@ describe('Component Schematic', () => {
531531

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

536547
it('should export the component as default when exportDefault is true', async () => {
@@ -554,4 +565,59 @@ describe('Component Schematic', () => {
554565
const specContent = tree.readContent('/projects/bar/src/app/foo/foo.component.spec.ts');
555566
expect(specContent).toContain("import { FooComponent } from './foo.component';");
556567
});
568+
569+
it('should not add provideZonelessChangeDetection() in spec file when not zoneless application', async () => {
570+
const tree = await schematicRunner.runSchematic('component', { ...defaultOptions }, appTree);
571+
const tsContent = tree.readContent('/projects/bar/src/app/foo/foo.component.spec.ts');
572+
573+
expect(tsContent).not.toContain('provideZonelessChangeDetection()');
574+
expect(tsContent).not.toContain('fixture.whenStable()');
575+
expect(tsContent).toContain('fixture.detectChanges()');
576+
});
577+
578+
describe('with zoneless application', () => {
579+
const zonelessAppOptions: ApplicationOptions = {
580+
...appOptions,
581+
name: 'baz',
582+
zoneless: true,
583+
};
584+
585+
it('should add provideZonelessChangeDetection() in spec file for standalone', async () => {
586+
appTree = await schematicRunner.runSchematic(
587+
'application',
588+
{ ...zonelessAppOptions, standalone: true },
589+
appTree,
590+
);
591+
const tree = await schematicRunner.runSchematic(
592+
'component',
593+
{ ...defaultOptions, standalone: true, project: 'baz' },
594+
appTree,
595+
);
596+
597+
const tsContent = tree.readContent('/projects/baz/src/app/foo/foo.component.spec.ts');
598+
599+
expect(tsContent).toContain('provideZonelessChangeDetection()');
600+
expect(tsContent).toContain('fixture.whenStable()');
601+
expect(tsContent).not.toContain('fixture.detectChanges()');
602+
});
603+
604+
it('should add provideZonelessChangeDetection() in spec file for standalone is false', async () => {
605+
appTree = await schematicRunner.runSchematic(
606+
'application',
607+
{ ...zonelessAppOptions },
608+
appTree,
609+
);
610+
611+
const tree = await schematicRunner.runSchematic(
612+
'component',
613+
{ ...defaultOptions, standalone: false, skipImport: true, project: 'baz' },
614+
appTree,
615+
);
616+
617+
const tsContent = tree.readContent('/projects/baz/src/app/foo/foo.component.spec.ts');
618+
expect(tsContent).toContain('provideZonelessChangeDetection()');
619+
expect(tsContent).toContain('fixture.whenStable()');
620+
expect(tsContent).not.toContain('fixture.detectChanges()');
621+
});
622+
});
557623
});

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)