From 68fc5da74b0f96949013c1aef60eb07db39fb2ee Mon Sep 17 00:00:00 2001 From: ersimont <8042088+ersimont@users.noreply.github.com> Date: Fri, 13 Nov 2020 16:47:27 -0500 Subject: [PATCH] feat(ng-dev): `AngularContext.tick()` better simulates real change detection --- .../lib/test-context/angular-context.spec.ts | 46 +++++++++++++++---- .../src/lib/test-context/angular-context.ts | 1 + .../src/lib/test-context/component-context.ts | 2 +- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/projects/ng-dev/src/lib/test-context/angular-context.spec.ts b/projects/ng-dev/src/lib/test-context/angular-context.spec.ts index 855f5a71..0857aeda 100644 --- a/projects/ng-dev/src/lib/test-context/angular-context.spec.ts +++ b/projects/ng-dev/src/lib/test-context/angular-context.spec.ts @@ -154,12 +154,12 @@ describe('AngularContext', () => { }); it('runs change detection even if no tasks are queued', () => { - let changeDetectionCount = 0; + let ranChangeDetection = false; @Component({ template: '' }) class LocalComponent implements DoCheck { ngDoCheck(): void { - ++changeDetectionCount; + ranChangeDetection = true; } } TestBed.overrideComponent(LocalComponent, {}); @@ -170,20 +170,20 @@ describe('AngularContext', () => { const componentRef = factory.create(ctx.inject(Injector)); ctx.inject(ApplicationRef).attachView(componentRef.hostView); - expect(changeDetectionCount).toBe(0); + expect(ranChangeDetection).toBe(false); ctx.tick(); - expect(changeDetectionCount).toBe(1); + expect(ranChangeDetection).toBe(true); }); }); it('flushes micro tasks before running change detection', () => { - let promiseResolved = false; - let promiseResolvedBeforeChangeDetection = false; + let ranChangeDetection = false; + let flushedMicroTasksBeforeChangeDetection = false; @Component({ template: '' }) class LocalComponent implements DoCheck { ngDoCheck(): void { - promiseResolvedBeforeChangeDetection = promiseResolved; + ranChangeDetection = true; } } TestBed.overrideComponent(LocalComponent, {}); @@ -195,14 +195,40 @@ describe('AngularContext', () => { ctx.inject(ApplicationRef).attachView(componentRef.hostView); Promise.resolve().then(() => { - promiseResolved = true; + flushedMicroTasksBeforeChangeDetection = !ranChangeDetection; }); ctx.tick(); - expect(promiseResolvedBeforeChangeDetection).toBe(true); + expect(flushedMicroTasksBeforeChangeDetection).toBe(true); }); }); - it('advances `performance.now()` as well', () => { + it('runs change detection after timeouts', () => { + let ranTimeout = false; + let ranChangeDetectionAfterTimeout = false; + + @Component({ template: '' }) + class LocalComponent implements DoCheck { + ngDoCheck(): void { + ranChangeDetectionAfterTimeout = ranTimeout; + } + } + TestBed.overrideComponent(LocalComponent, {}); + + ctx.run(() => { + const resolver = ctx.inject(ComponentFactoryResolver); + const factory = resolver.resolveComponentFactory(LocalComponent); + const componentRef = factory.create(ctx.inject(Injector)); + ctx.inject(ApplicationRef).attachView(componentRef.hostView); + + setTimeout(() => { + ranTimeout = true; + }); + ctx.tick(); + expect(ranChangeDetectionAfterTimeout).toBe(true); + }); + }); + + it('advances `performance.now()`', () => { ctx.run(() => { const start = performance.now(); ctx.tick(10); diff --git a/projects/ng-dev/src/lib/test-context/angular-context.ts b/projects/ng-dev/src/lib/test-context/angular-context.ts index ef63fe6e..dcdd0c10 100644 --- a/projects/ng-dev/src/lib/test-context/angular-context.ts +++ b/projects/ng-dev/src/lib/test-context/angular-context.ts @@ -188,6 +188,7 @@ export class AngularContext { this.runChangeDetection(); tick(convertTime(amount, unit, 'ms')); + this.runChangeDetection(); } /** diff --git a/projects/ng-dev/src/lib/test-context/component-context.ts b/projects/ng-dev/src/lib/test-context/component-context.ts index ba215f08..0e49175d 100644 --- a/projects/ng-dev/src/lib/test-context/component-context.ts +++ b/projects/ng-dev/src/lib/test-context/component-context.ts @@ -59,7 +59,7 @@ export abstract class ComponentContext< protected abstract componentType: Type; /** - * @param moduleMetadata passed along to [TestBed.configureTestingModule()]{@linkcode https://angular.io/api/core/testing/TestBed#configureTestingModule}. Automatically includes {@link NoopAnimationsModule} and {@link HttpClientTestingModule} for you. + * @param moduleMetadata passed along to [TestBed.configureTestingModule()]{@linkcode https://angular.io/api/core/testing/TestBed#configureTestingModule}. Automatically includes {@link NoopAnimationsModule}, in addition to those provided by {@link AngularContext}. */ constructor(moduleMetadata: TestModuleMetadata = {}) { super(extendMetadata(moduleMetadata, { imports: [NoopAnimationsModule] }));