From de171d8ddef6b4ecb950dbb3f3bb7be130828127 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Mon, 27 Jun 2022 14:53:11 -0700 Subject: [PATCH] test(core): replace hand-written instructions in change detection tests with TestBed (#46529) This commit updates a set of change detection tests to avoid using hand-written instructions and replace them with TestBed APIs. PR Close #46529 --- .../test/render3/change_detection_spec.ts | 232 +++++++++--------- 1 file changed, 110 insertions(+), 122 deletions(-) diff --git a/packages/core/test/render3/change_detection_spec.ts b/packages/core/test/render3/change_detection_spec.ts index 99457ef95ce..fb31c4ed02d 100644 --- a/packages/core/test/render3/change_detection_spec.ts +++ b/packages/core/test/render3/change_detection_spec.ts @@ -6,54 +6,45 @@ * found in the LICENSE file at https://angular.io/license */ -import {NgIf} from '@angular/common'; +import {CommonModule, DOCUMENT} from '@angular/common'; +import {TestBed} from '@angular/core/testing'; import {withBody} from '@angular/private/testing'; -import {ChangeDetectionStrategy, DoCheck, OnInit} from '../../src/core'; +import {Component, DoCheck, OnInit, Renderer2, RendererFactory2} from '../../src/core'; import {whenRendered} from '../../src/render3/component'; -import {AttributeMarker, getRenderedText, LifecycleHooksFeature, ɵɵadvance, ɵɵdefineComponent, ɵɵgetCurrentView, ɵɵproperty, ɵɵtextInterpolate1, ɵɵtextInterpolate2} from '../../src/render3/index'; -import {detectChanges, markDirty, tick, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵlistener, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all'; -import {RenderFlags} from '../../src/render3/interfaces/definition'; -import {Renderer3, RendererFactory3} from '../../src/render3/interfaces/renderer'; -import {FLAGS, LViewFlags} from '../../src/render3/interfaces/view'; - -import {containerEl, createComponent, renderComponent, requestAnimationFrame} from './render_util'; +import {getRenderedText} from '../../src/render3/index'; +import {detectChanges, markDirty} from '../../src/render3/instructions/all'; describe('change detection', () => { describe('markDirty, detectChanges, whenRendered, getRenderedText', () => { let mycompOninit: MyComponentWithOnInit; + + @Component({ + selector: 'my-comp', + standalone: true, + template: '{{ value }}', + }) class MyComponent implements DoCheck { value: string = 'works'; doCheckCount = 0; ngDoCheck(): void { this.doCheckCount++; } - - static ɵfac = () => new MyComponent(); - static ɵcmp = ɵɵdefineComponent({ - type: MyComponent, - selectors: [['my-comp']], - decls: 2, - vars: 1, - template: - (rf: RenderFlags, ctx: MyComponent) => { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'span'); - ɵɵtext(1); - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵadvance(1); - ɵɵtextInterpolate(ctx.value); - } - } - }); } + @Component({ + selector: 'my-comp-oninit', + standalone: true, + template: '{{ value }}', + }) class MyComponentWithOnInit implements OnInit, DoCheck { value: string = 'works'; doCheckCount = 0; + constructor() { + mycompOninit = this; + } + ngOnInit() { markDirty(this); } @@ -66,28 +57,19 @@ describe('change detection', () => { this.value = 'click works'; markDirty(this); } - - static ɵfac = () => mycompOninit = new MyComponentWithOnInit(); - static ɵcmp = ɵɵdefineComponent({ - type: MyComponentWithOnInit, - selectors: [['my-comp-oninit']], - decls: 2, - vars: 1, - template: - (rf: RenderFlags, ctx: MyComponentWithOnInit) => { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'span'); - ɵɵtext(1); - ɵɵelementEnd(); - } - if (rf & RenderFlags.Update) { - ɵɵadvance(1); - ɵɵtextInterpolate(ctx.value); - } - } - }); } + @Component({ + selector: 'my-parent-comp', + standalone: true, + imports: [CommonModule, MyComponentWithOnInit], + template: ` + --> +
+ +
+ `, + }) class MyParentComponent implements OnInit { show = false; value = 'parent'; @@ -99,47 +81,28 @@ describe('change detection', () => { this.show = true; markDirty(this); } - - static ɵfac = () => new MyParentComponent(); - static ɵcmp = ɵɵdefineComponent({ - type: MyParentComponent, - selectors: [['my-parent-comp']], - decls: 2, - vars: 1, - dependencies: [NgIf, MyComponentWithOnInit], - consts: [[AttributeMarker.Template, 'ngIf']], - template: - (rf: RenderFlags, ctx: MyParentComponent) => { - if (rf & RenderFlags.Create) { - ɵɵtext(0, ' -->\n'); - ɵɵtemplate(1, (rf, ctx) => { - if (rf & RenderFlags.Create) { - ɵɵelementStart(0, 'div'); - ɵɵelement(1, 'my-comp-oninit'); - ɵɵelementEnd(); - } - }, 2, 0, 'div', 0); - } - if (rf & RenderFlags.Update) { - ɵɵadvance(1); - ɵɵproperty('ngIf', ctx.show); - } - } - }); } it('should mark a component dirty and schedule change detection', withBody('my-comp', () => { - const myComp = renderComponent(MyComponent, {hostFeatures: [LifecycleHooksFeature]}); + const fixture = TestBed.createComponent(MyComponent); + fixture.detectChanges(); + + const myComp = fixture.componentInstance; expect(getRenderedText(myComp)).toEqual('works'); myComp.value = 'updated'; markDirty(myComp); expect(getRenderedText(myComp)).toEqual('works'); - requestAnimationFrame.flush(); + + fixture.detectChanges(); + expect(getRenderedText(myComp)).toEqual('updated'); })); it('should detectChanges on a component', withBody('my-comp', () => { - const myComp = renderComponent(MyComponent, {hostFeatures: [LifecycleHooksFeature]}); + const fixture = TestBed.createComponent(MyComponent); + fixture.detectChanges(); + + const myComp = fixture.componentInstance; expect(getRenderedText(myComp)).toEqual('works'); myComp.value = 'updated'; detectChanges(myComp); @@ -148,25 +111,34 @@ describe('change detection', () => { it('should detectChanges after markDirty is called multiple times within ngOnInit', withBody('my-comp-oninit', () => { - const myParentComp = - renderComponent(MyParentComponent, {hostFeatures: [LifecycleHooksFeature]}); + const fixture = TestBed.createComponent(MyParentComponent); + fixture.detectChanges(); + + const myParentComp = fixture.componentInstance; expect(myParentComp.show).toBe(false); myParentComp.click(); - requestAnimationFrame.flush(); + fixture.detectChanges(); + expect(myParentComp.show).toBe(true); const myComp = mycompOninit; expect(getRenderedText(myComp)).toEqual('works'); expect(myComp.doCheckCount).toBe(1); myComp.click(); expect(getRenderedText(myComp)).toEqual('works'); - requestAnimationFrame.flush(); + + fixture.detectChanges(); + expect(getRenderedText(myComp)).toEqual('click works'); expect(myComp.doCheckCount).toBe(2); })); it('should detectChanges only once if markDirty is called multiple times', withBody('my-comp', () => { - const myComp = renderComponent(MyComponent, {hostFeatures: [LifecycleHooksFeature]}); + const fixture = TestBed.createComponent(MyComponent); + fixture.detectChanges(); + + const myComp = fixture.componentInstance; + expect(getRenderedText(myComp)).toEqual('works'); expect(myComp.doCheckCount).toBe(1); myComp.value = 'ignore'; @@ -174,59 +146,75 @@ describe('change detection', () => { myComp.value = 'updated'; markDirty(myComp); expect(getRenderedText(myComp)).toEqual('works'); - requestAnimationFrame.flush(); + + fixture.detectChanges(); + expect(getRenderedText(myComp)).toEqual('updated'); expect(myComp.doCheckCount).toBe(2); })); it('should notify whenRendered', withBody('my-comp', async () => { - const myComp = renderComponent(MyComponent, {hostFeatures: [LifecycleHooksFeature]}); + const fixture = TestBed.createComponent(MyComponent); + fixture.detectChanges(); + + const myComp = fixture.componentInstance; await whenRendered(myComp); myComp.value = 'updated'; markDirty(myComp); - setTimeout(requestAnimationFrame.flush, 0); + setTimeout(() => fixture.detectChanges(), 0); await whenRendered(myComp); expect(getRenderedText(myComp)).toEqual('updated'); })); }); - it('should call begin and end when the renderer factory implements them', () => { - const log: string[] = []; + it('should call begin and end when the renderer factory implements them', + withBody('', () => { + const log: string[] = []; - const testRendererFactory: RendererFactory3 = { - createRenderer: (): Renderer3 => { - return document; - }, - begin: () => log.push('begin'), - end: () => log.push('end'), - }; + const testRendererFactory: RendererFactory2 = { + createRenderer: (): Renderer2 => { + return document as unknown as Renderer2; + }, + begin: () => log.push('begin'), + end: () => log.push('end'), + }; - class MyComponent { - get value(): string { - log.push('detect changes'); - return 'works'; - } + @Component({ + selector: 'my-comp', + standalone: true, + template: '{{ value }}', + }) + class MyComponent { + get value(): string { + log.push('detect changes'); + return 'works'; + } + } - static ɵfac = () => new MyComponent(); - static ɵcmp = ɵɵdefineComponent({ - type: MyComponent, - selectors: [['my-comp']], - decls: 1, - vars: 1, - template: - (rf: RenderFlags, ctx: MyComponent) => { - if (rf & RenderFlags.Create) { - ɵɵtext(0); - } - if (rf & RenderFlags.Update) { - ɵɵtextInterpolate(ctx.value); - } - } - }); - } + TestBed.configureTestingModule({ + providers: [ + { + provide: DOCUMENT, + useFactory: () => document, + }, + { + provide: RendererFactory2, + useValue: testRendererFactory, + } + ] + }); - const myComp = renderComponent(MyComponent, {rendererFactory: testRendererFactory}); - expect(getRenderedText(myComp)).toEqual('works'); - expect(log).toEqual(['begin', 'detect changes', 'end']); - }); + const fixture = TestBed.createComponent(MyComponent); + fixture.detectChanges(); + + const myComp = fixture.componentInstance; + expect(getRenderedText(myComp)).toEqual('works'); + + expect(log).toEqual([ + 'begin', + 'detect changes', // regular change detection cycle + 'end', + 'detect changes' // check no changes cycle + ]); + })); });