feat(ExpressionChangedAfterItHasBeenCheckedException): more meaningful error message
fixes #9882
This commit is contained in:
parent
eacc9e6541
commit
2de8364de2
|
@ -11,7 +11,9 @@ import {isPrimitive, looseIdentical} from '../facade/lang';
|
|||
|
||||
export {looseIdentical} from '../facade/lang';
|
||||
|
||||
export const UNINITIALIZED = new Object();
|
||||
export const UNINITIALIZED = {
|
||||
toString: () => 'CD_INIT_VALUE'
|
||||
};
|
||||
|
||||
export function devModeEqual(a: any, b: any): boolean {
|
||||
if (isListLikeIterable(a) && isListLikeIterable(b)) {
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {UNINITIALIZED} from '../change_detection/change_detection_util';
|
||||
import {BaseException, WrappedException} from '../facade/exceptions';
|
||||
|
||||
|
||||
/**
|
||||
* An error thrown if application changes model breaking the top-down data flow.
|
||||
*
|
||||
|
@ -44,9 +46,14 @@ import {BaseException, WrappedException} from '../facade/exceptions';
|
|||
*/
|
||||
export class ExpressionChangedAfterItHasBeenCheckedException extends BaseException {
|
||||
constructor(oldValue: any, currValue: any, context: any) {
|
||||
super(
|
||||
`Expression has changed after it was checked. ` +
|
||||
`Previous value: '${oldValue}'. Current value: '${currValue}'`);
|
||||
let msg =
|
||||
`Expression has changed after it was checked. Previous value: '${oldValue}'. Current value: '${currValue}'.`;
|
||||
if (oldValue === UNINITIALIZED) {
|
||||
msg +=
|
||||
` It seems like the view has been created after its parent and its children have been dirty checked.` +
|
||||
` Has it been created in a change detection hook ?`;
|
||||
}
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1002,15 +1002,22 @@ export function main() {
|
|||
|
||||
describe('enforce no new changes', () => {
|
||||
it('should throw when a record gets changed after it has been checked', fakeAsync(() => {
|
||||
var ctx = createCompFixture('<div [someProp]="a"></div>', TestData);
|
||||
|
||||
const ctx = createCompFixture('<div [someProp]="a"></div>', TestData);
|
||||
ctx.componentInstance.a = 1;
|
||||
expect(() => ctx.checkNoChanges())
|
||||
.toThrowError(/:0:5[\s\S]*Expression has changed after it was checked./g);
|
||||
}));
|
||||
|
||||
it('should warn when the view has been created in a cd hook', fakeAsync(() => {
|
||||
const ctx = createCompFixture('<div *gh9882>{{ a }}</div>', TestData);
|
||||
ctx.componentInstance.a = 1;
|
||||
expect(() => ctx.detectChanges())
|
||||
.toThrowError(
|
||||
/It seems like the view has been created after its parent and its children have been dirty checked/);
|
||||
}));
|
||||
|
||||
it('should not throw when two arrays are structurally the same', fakeAsync(() => {
|
||||
var ctx = _bindSimpleValue('a', TestData);
|
||||
const ctx = _bindSimpleValue('a', TestData);
|
||||
ctx.componentInstance.a = ['value'];
|
||||
ctx.detectChanges(false);
|
||||
ctx.componentInstance.a = ['value'];
|
||||
|
@ -1018,9 +1025,8 @@ export function main() {
|
|||
}));
|
||||
|
||||
it('should not break the next run', fakeAsync(() => {
|
||||
var ctx = _bindSimpleValue('a', TestData);
|
||||
const ctx = _bindSimpleValue('a', TestData);
|
||||
ctx.componentInstance.a = 'value';
|
||||
|
||||
expect(() => ctx.checkNoChanges()).toThrow();
|
||||
|
||||
ctx.detectChanges();
|
||||
|
@ -1093,17 +1099,28 @@ export function main() {
|
|||
}
|
||||
|
||||
const ALL_DIRECTIVES = /*@ts2dart_const*/[
|
||||
forwardRef(() => TestDirective), forwardRef(() => TestComponent),
|
||||
forwardRef(() => AnotherComponent), forwardRef(() => TestLocals), forwardRef(() => CompWithRef),
|
||||
forwardRef(() => EmitterDirective), forwardRef(() => PushComp),
|
||||
forwardRef(() => OrderCheckDirective2), forwardRef(() => OrderCheckDirective0),
|
||||
forwardRef(() => OrderCheckDirective1), NgFor
|
||||
forwardRef(() => TestDirective),
|
||||
forwardRef(() => TestComponent),
|
||||
forwardRef(() => AnotherComponent),
|
||||
forwardRef(() => TestLocals),
|
||||
forwardRef(() => CompWithRef),
|
||||
forwardRef(() => EmitterDirective),
|
||||
forwardRef(() => PushComp),
|
||||
forwardRef(() => OrderCheckDirective2),
|
||||
forwardRef(() => OrderCheckDirective0),
|
||||
forwardRef(() => OrderCheckDirective1),
|
||||
forwardRef(() => Gh9882),
|
||||
NgFor,
|
||||
];
|
||||
|
||||
const ALL_PIPES = /*@ts2dart_const*/[
|
||||
forwardRef(() => CountingPipe), forwardRef(() => CountingImpurePipe),
|
||||
forwardRef(() => MultiArgPipe), forwardRef(() => PipeWithOnDestroy),
|
||||
forwardRef(() => IdentityPipe), forwardRef(() => WrappedPipe), AsyncPipe
|
||||
forwardRef(() => CountingPipe),
|
||||
forwardRef(() => CountingImpurePipe),
|
||||
forwardRef(() => MultiArgPipe),
|
||||
forwardRef(() => PipeWithOnDestroy),
|
||||
forwardRef(() => IdentityPipe),
|
||||
forwardRef(() => WrappedPipe),
|
||||
AsyncPipe,
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
|
@ -1259,7 +1276,7 @@ class EmitterDirective {
|
|||
@Output('event') emitter = new EventEmitter<string>();
|
||||
}
|
||||
|
||||
@Directive({selector: '[gh-9882]'})
|
||||
@Directive({selector: '[gh9882]'})
|
||||
class Gh9882 implements AfterContentInit {
|
||||
constructor(private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef<Object>) {
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue