mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
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
This commit is contained in:
parent
2e59f9f612
commit
de171d8dde
1 changed files with 110 additions and 122 deletions
|
|
@ -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: '<span>{{ value }}</span>',
|
||||
})
|
||||
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: '<span>{{ value }}</span>',
|
||||
})
|
||||
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: `
|
||||
-->
|
||||
<div *ngIf="show">
|
||||
<my-comp-oninit></my-comp-oninit>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
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('<my-comp></my-comp>', () => {
|
||||
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
|
||||
]);
|
||||
}));
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue