fix(ivy): ensure errors are thrown during checkNoChanges for style/class bindings (#33103)
Prior to this fix, all style/class bindings (e.g. `[style]` and `[class.foo]`) would quietly update a binding value if and when the current binding value changes during checkNoChanges. With this patch, all styling instructions will properly check to see if the value has changed during the second pass of detectChanges() if checkNoChanges is active. PR Close #33103
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import { NO_ERRORS_SCHEMA, DebugElement } from '@angular/core';
|
||||
import { inject, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { inject, ComponentFixture, TestBed, fakeAsync, flushMicrotasks, tick } from '@angular/core/testing';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
@ -529,7 +529,8 @@ describe('AppComponent', () => {
|
||||
it('should call `scrollAfterRender` (via `onDocInserted`) when navigate to a new Doc', fakeAsync(() => {
|
||||
locationService.go('guide/pipes');
|
||||
tick(1); // triggers the HTTP response for the document
|
||||
fixture.detectChanges(); // triggers the event that calls `onDocInserted`
|
||||
fixture.detectChanges(); // passes the new doc to the `DocViewer`
|
||||
flushMicrotasks(); // triggers the `DocViewer` event that calls `onDocInserted`
|
||||
|
||||
expect(scrollAfterRenderSpy).toHaveBeenCalledWith(scrollDelay);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Meta, Title } from '@angular/platform-browser';
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { Observable, asapScheduler, of } from 'rxjs';
|
||||
|
||||
import { FILE_NOT_FOUND_ID, FETCHING_ERROR_ID } from 'app/documents/document.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
@ -21,6 +21,8 @@ describe('DocViewerComponent', () => {
|
||||
let docViewerEl: HTMLElement;
|
||||
let docViewer: TestDocViewerComponent;
|
||||
|
||||
const safeFlushAsapScheduler = () => asapScheduler.actions.length && asapScheduler.flush();
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CustomElementsModule, TestModule],
|
||||
@ -42,19 +44,20 @@ describe('DocViewerComponent', () => {
|
||||
describe('#doc', () => {
|
||||
let renderSpy: jasmine.Spy;
|
||||
|
||||
const setCurrentDoc = (contents: string|null, id = 'fizz/buzz') => {
|
||||
parentComponent.currentDoc = {contents, id};
|
||||
parentFixture.detectChanges();
|
||||
const setCurrentDoc = (newDoc: TestParentComponent['currentDoc']) => {
|
||||
parentComponent.currentDoc = newDoc && {id: 'fizz/buzz', ...newDoc};
|
||||
parentFixture.detectChanges(); // Run change detection to propagate the new doc to `DocViewer`.
|
||||
safeFlushAsapScheduler(); // Flush `asapScheduler` to trigger `DocViewer#render()`.
|
||||
};
|
||||
|
||||
beforeEach(() => renderSpy = spyOn(docViewer, 'render').and.callFake(() => of(undefined)));
|
||||
|
||||
it('should render the new document', () => {
|
||||
setCurrentDoc('foo', 'bar');
|
||||
setCurrentDoc({contents: 'foo', id: 'bar'});
|
||||
expect(renderSpy).toHaveBeenCalledTimes(1);
|
||||
expect(renderSpy.calls.mostRecent().args).toEqual([{id: 'bar', contents: 'foo'}]);
|
||||
|
||||
setCurrentDoc(null, 'baz');
|
||||
setCurrentDoc({contents: null, id: 'baz'});
|
||||
expect(renderSpy).toHaveBeenCalledTimes(2);
|
||||
expect(renderSpy.calls.mostRecent().args).toEqual([{id: 'baz', contents: null}]);
|
||||
});
|
||||
@ -63,24 +66,20 @@ describe('DocViewerComponent', () => {
|
||||
const obs = new ObservableWithSubscriptionSpies();
|
||||
renderSpy.and.returnValue(obs);
|
||||
|
||||
setCurrentDoc('foo', 'bar');
|
||||
setCurrentDoc({contents: 'foo', id: 'bar'});
|
||||
expect(obs.subscribeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(obs.unsubscribeSpies[0]).not.toHaveBeenCalled();
|
||||
|
||||
setCurrentDoc('baz', 'qux');
|
||||
setCurrentDoc({contents: 'baz', id: 'qux'});
|
||||
expect(obs.subscribeSpy).toHaveBeenCalledTimes(2);
|
||||
expect(obs.unsubscribeSpies[0]).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should ignore falsy document values', () => {
|
||||
parentComponent.currentDoc = null;
|
||||
parentFixture.detectChanges();
|
||||
|
||||
setCurrentDoc(null);
|
||||
expect(renderSpy).not.toHaveBeenCalled();
|
||||
|
||||
parentComponent.currentDoc = undefined;
|
||||
parentFixture.detectChanges();
|
||||
|
||||
setCurrentDoc(undefined);
|
||||
expect(renderSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -92,14 +91,17 @@ describe('DocViewerComponent', () => {
|
||||
expect(renderSpy).not.toHaveBeenCalled();
|
||||
|
||||
docViewer.doc = {contents: 'Some content', id: 'some-id'};
|
||||
safeFlushAsapScheduler();
|
||||
expect(renderSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
docViewer.ngOnDestroy();
|
||||
|
||||
docViewer.doc = {contents: 'Other content', id: 'other-id'};
|
||||
safeFlushAsapScheduler();
|
||||
expect(renderSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
docViewer.doc = {contents: 'More content', id: 'more-id'};
|
||||
safeFlushAsapScheduler();
|
||||
expect(renderSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Component, ElementRef, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
|
||||
import { Title, Meta } from '@angular/platform-browser';
|
||||
|
||||
import { Observable, of, timer } from 'rxjs';
|
||||
import { catchError, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||
import { asapScheduler, Observable, of, timer } from 'rxjs';
|
||||
import { catchError, observeOn, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||
|
||||
import { DocumentContents, FILE_NOT_FOUND_ID, FETCHING_ERROR_ID } from 'app/documents/document.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
@ -78,6 +78,7 @@ export class DocViewerComponent implements OnDestroy {
|
||||
|
||||
this.docContents$
|
||||
.pipe(
|
||||
observeOn(asapScheduler),
|
||||
switchMap(newDoc => this.render(newDoc)),
|
||||
takeUntil(this.onDestroy$),
|
||||
)
|
||||
|
Reference in New Issue
Block a user