feat(change_detection): allow all legal programs in the dev mode

BEFORE:

The following would throw in the dev mode because `f` would return a new array when called by checkNoChanges.

@Component({
  template: `
    {{f()}}
  `
})
class A {
  f() { return [1]; }
}

AFTER:

The checkNoChanges function compares only primitives types for equality, and deeply compares iterables. Other objects cannot cause checkNoChanges to throw. This means that the dev mode would never fail given a legal program, but may allow some illegal programs.
This commit is contained in:
vsavkin
2016-01-05 13:42:21 -08:00
committed by Rado Kirov
parent db87baeb98
commit 42231f5719
10 changed files with 141 additions and 12 deletions
@@ -887,6 +887,13 @@ export function main() {
'Expression [\'"]a in location[\'"] has changed after it was checked'));
});
it('should not throw when two arrays are structurally the same', () => {
var val = _createChangeDetector('a', new TestDataWithGetter(() => ['value']));
val.changeDetector.detectChanges();
expect(() => { val.changeDetector.checkNoChanges(); }).not.toThrow();
});
it('should not break the next run', () => {
var val = _createChangeDetector('a', new TestData('value'));
expect(() => val.changeDetector.checkNoChanges())
@@ -1597,6 +1604,12 @@ class TestData {
constructor(public a: any) {}
}
class TestDataWithGetter {
constructor(private fn: Function) {}
get a() { return this.fn(); }
}
class TestDispatcher implements ChangeDispatcher {
log: string[];
debugLog: string[];
@@ -0,0 +1,60 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach
} from 'angular2/testing_internal';
import {ChangeDetectionUtil} from 'angular2/src/core/change_detection/change_detection_util';
export function main() {
describe("ChangeDetectionUtil", () => {
describe("devModeEqual", () => {
it("should do the deep comparison of iterables", () => {
expect(ChangeDetectionUtil.devModeEqual([['one']], [['one']])).toBe(true);
expect(ChangeDetectionUtil.devModeEqual(['one'], ['one', 'two'])).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(['one', 'two'], ['one'])).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(['one'], 'one')).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(['one'], new Object())).toBe(false);
expect(ChangeDetectionUtil.devModeEqual('one', ['one'])).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(new Object(), ['one'])).toBe(false);
});
it("should compare primitive numbers", () => {
expect(ChangeDetectionUtil.devModeEqual(1, 1)).toBe(true);
expect(ChangeDetectionUtil.devModeEqual(1, 2)).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(new Object(), 2)).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(1, new Object())).toBe(false);
});
it("should compare primitive strings", () => {
expect(ChangeDetectionUtil.devModeEqual('one', 'one')).toBe(true);
expect(ChangeDetectionUtil.devModeEqual('one', 'two')).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(new Object(), 'one')).toBe(false);
expect(ChangeDetectionUtil.devModeEqual('one', new Object())).toBe(false);
});
it("should compare primitive booleans", () => {
expect(ChangeDetectionUtil.devModeEqual(true, true)).toBe(true);
expect(ChangeDetectionUtil.devModeEqual(true, false)).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(new Object(), true)).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(true, new Object())).toBe(false);
});
it("should compare null", () => {
expect(ChangeDetectionUtil.devModeEqual(null, null)).toBe(true);
expect(ChangeDetectionUtil.devModeEqual(null, 1)).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(new Object(), null)).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(null, new Object())).toBe(false);
});
it("should return true for other objects", () => {
expect(ChangeDetectionUtil.devModeEqual(new Object(), new Object())).toBe(true);
});
});
});
}