From d8c7c274e43c0a20da7a43aa53abaa300ac97cb1 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Wed, 13 May 2015 13:01:31 -0700 Subject: [PATCH] refactor(change_detector): extracted ChangeDetectorDefinition --- modules/angular2/change_detection.ts | 3 +- .../src/change_detection/change_detection.ts | 16 +- .../src/change_detection/interfaces.ts | 12 +- .../change_detection/proto_change_detector.ts | 76 +- .../src/core/compiler/proto_view_factory.js | 11 +- .../change_detection/change_detection_spec.js | 1001 +---------------- .../change_detection/change_detector_spec.js | 987 ++++++++++++++++ .../change_detection_benchmark.js | 5 +- 8 files changed, 1060 insertions(+), 1051 deletions(-) create mode 100644 modules/angular2/test/change_detection/change_detector_spec.js diff --git a/modules/angular2/change_detection.ts b/modules/angular2/change_detection.ts index 2ae81ed104..82a8a3ecac 100644 --- a/modules/angular2/change_detection.ts +++ b/modules/angular2/change_detection.ts @@ -26,7 +26,8 @@ export { ProtoChangeDetector, ChangeDispatcher, ChangeDetector, - ChangeDetection + ChangeDetection, + ChangeDetectorDefinition } from './src/change_detection/interfaces'; export { CHECK_ONCE, diff --git a/modules/angular2/src/change_detection/change_detection.ts b/modules/angular2/src/change_detection/change_detection.ts index 8a792c982c..e3b032ba45 100644 --- a/modules/angular2/src/change_detection/change_detection.ts +++ b/modules/angular2/src/change_detection/change_detection.ts @@ -9,7 +9,7 @@ import {NullPipeFactory} from './pipes/null_pipe'; import {BindingRecord} from './binding_record'; import {DirectiveRecord} from './directive_record'; import {DEFAULT} from './constants'; -import {ChangeDetection, ProtoChangeDetector} from './interfaces'; +import {ChangeDetection, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces'; import {Injectable} from 'angular2/src/di/decorators'; import {List} from 'angular2/src/facade/collection'; @@ -59,11 +59,8 @@ export var defaultPipes = { export class DynamicChangeDetection extends ChangeDetection { constructor(public registry: PipeRegistry) { super(); } - createProtoChangeDetector(name: string, bindingRecords: List, - variableBindings: List, directiveRecords: List, - changeControlStrategy: string = DEFAULT): ProtoChangeDetector { - return new DynamicProtoChangeDetector(this.registry, bindingRecords, variableBindings, - directiveRecords, changeControlStrategy); + createProtoChangeDetector(definition:ChangeDetectorDefinition): ProtoChangeDetector { + return new DynamicProtoChangeDetector(this.registry, definition); } } @@ -79,11 +76,8 @@ export class DynamicChangeDetection extends ChangeDetection { export class JitChangeDetection extends ChangeDetection { constructor(public registry: PipeRegistry) { super(); } - createProtoChangeDetector(name: string, bindingRecords: List, - variableBindings: List, directiveRecords: List, - changeControlStrategy: string = DEFAULT): ProtoChangeDetector { - return new JitProtoChangeDetector(this.registry, bindingRecords, variableBindings, - directiveRecords, changeControlStrategy); + createProtoChangeDetector(definition:ChangeDetectorDefinition): ProtoChangeDetector { + return new JitProtoChangeDetector(this.registry, definition); } } diff --git a/modules/angular2/src/change_detection/interfaces.ts b/modules/angular2/src/change_detection/interfaces.ts index 5eed193aa7..dd2ebf07ef 100644 --- a/modules/angular2/src/change_detection/interfaces.ts +++ b/modules/angular2/src/change_detection/interfaces.ts @@ -1,7 +1,7 @@ import {List} from 'angular2/src/facade/collection'; import {Locals} from './parser/locals'; -import {DEFAULT} from './constants'; import {BindingRecord} from './binding_record'; +import {DirectiveRecord} from './directive_record'; // HACK: workaround for Traceur behavior. // It expects all transpiled modules to contain this marker. @@ -38,9 +38,7 @@ export class ProtoChangeDetector { * @exportedAs angular2/change_detection */ export class ChangeDetection { - createProtoChangeDetector(name: string, bindingRecords: List, variableBindings: List, - directiveRecords: List, - changeControlStrategy: string = DEFAULT): ProtoChangeDetector { + createProtoChangeDetector(definition: ChangeDetectorDefinition): ProtoChangeDetector { return null; } } @@ -65,3 +63,9 @@ export class ChangeDetector { detectChanges() {} checkNoChanges() {} } + +export class ChangeDetectorDefinition { + constructor(public id: string, public strategy: string, public variableNames: List, + public bindingRecords: List, + public directiveRecords: List) {} +} \ No newline at end of file diff --git a/modules/angular2/src/change_detection/proto_change_detector.ts b/modules/angular2/src/change_detection/proto_change_detector.ts index 6b93fc2c29..5cc602ed22 100644 --- a/modules/angular2/src/change_detection/proto_change_detector.ts +++ b/modules/angular2/src/change_detection/proto_change_detector.ts @@ -22,7 +22,12 @@ import { PrefixNot } from './parser/ast'; -import {ChangeDispatcher, ChangeDetector, ProtoChangeDetector} from './interfaces'; +import { + ChangeDispatcher, + ChangeDetector, + ProtoChangeDetector, + ChangeDetectorDefinition +} from './interfaces'; import {ChangeDetectionUtil} from './change_detection_util'; import {DynamicChangeDetector} from './dynamic_change_detector'; import {ChangeDetectorJITGenerator} from './change_detection_jit_generator'; @@ -56,25 +61,21 @@ export var __esModule = true; export class DynamicProtoChangeDetector extends ProtoChangeDetector { _records: List; - constructor(private _pipeRegistry: PipeRegistry, private _bindingRecords: List, - private _variableBindings: List, private _directiveRecords: List, - private _changeControlStrategy: string) { + constructor(private _pipeRegistry: PipeRegistry, private definition: ChangeDetectorDefinition) { super(); + this._records = this._createRecords(definition); } instantiate(dispatcher: any) { - this._createRecordsIfNecessary(); - return new DynamicChangeDetector(this._changeControlStrategy, dispatcher, this._pipeRegistry, - this._records, this._directiveRecords); + return new DynamicChangeDetector(this.definition.strategy, dispatcher, this._pipeRegistry, + this._records, this.definition.directiveRecords); } - _createRecordsIfNecessary() { - if (isBlank(this._records)) { - var recordBuilder = new ProtoRecordBuilder(); - ListWrapper.forEach(this._bindingRecords, - (b) => { recordBuilder.addAst(b, this._variableBindings); }); - this._records = coalesce(recordBuilder.records); - } + _createRecords(definition: ChangeDetectorDefinition) { + var recordBuilder = new ProtoRecordBuilder(); + ListWrapper.forEach(definition.bindingRecords, + (b) => { recordBuilder.addAst(b, definition.variableNames); }); + return coalesce(recordBuilder.records); } } @@ -82,30 +83,23 @@ var _jitProtoChangeDetectorClassCounter: number = 0; export class JitProtoChangeDetector extends ProtoChangeDetector { _factory: Function; - constructor(private _pipeRegistry, private _bindingRecords: List, - private _variableBindings: List, private _directiveRecords: List, - private _changeControlStrategy: string) { + constructor(private _pipeRegistry, private definition: ChangeDetectorDefinition) { super(); - this._factory = null; + this._factory = this._createFactory(definition); } - instantiate(dispatcher: any) { - this._createFactoryIfNecessary(); - return this._factory(dispatcher, this._pipeRegistry); - } + instantiate(dispatcher: any) { return this._factory(dispatcher, this._pipeRegistry); } - _createFactoryIfNecessary() { - if (isBlank(this._factory)) { - var recordBuilder = new ProtoRecordBuilder(); - ListWrapper.forEach(this._bindingRecords, - (b) => { recordBuilder.addAst(b, this._variableBindings); }); - var c = _jitProtoChangeDetectorClassCounter++; - var records = coalesce(recordBuilder.records); - var typeName = `ChangeDetector${c}`; - this._factory = new ChangeDetectorJITGenerator(typeName, this._changeControlStrategy, records, - this._directiveRecords) - .generate(); - } + _createFactory(definition: ChangeDetectorDefinition) { + var recordBuilder = new ProtoRecordBuilder(); + ListWrapper.forEach(definition.bindingRecords, + (b) => { recordBuilder.addAst(b, definition.variableNames); }); + var c = _jitProtoChangeDetectorClassCounter++; + var records = coalesce(recordBuilder.records); + var typeName = `ChangeDetector${c}`; + return new ChangeDetectorJITGenerator(typeName, definition.strategy, records, + this.definition.directiveRecords) + .generate(); } } @@ -114,13 +108,13 @@ class ProtoRecordBuilder { constructor() { this.records = []; } - addAst(b: BindingRecord, variableBindings: List < any >= null) { + addAst(b: BindingRecord, variableNames: List < string >= null) { var last = ListWrapper.last(this.records); if (isPresent(last) && last.bindingRecord.directiveRecord == b.directiveRecord) { last.lastInDirective = false; } - var pr = _ConvertAstIntoProtoRecords.convert(b, this.records.length, variableBindings); + var pr = _ConvertAstIntoProtoRecords.convert(b, this.records.length, variableNames); if (!ListWrapper.isEmpty(pr)) { var last = ListWrapper.last(pr); last.lastInBinding = true; @@ -135,12 +129,12 @@ class _ConvertAstIntoProtoRecords { protoRecords: List; constructor(private bindingRecord: BindingRecord, private contextIndex: number, - private expressionAsString: string, private variableBindings: List) { + private expressionAsString: string, private variableNames: List) { this.protoRecords = []; } - static convert(b: BindingRecord, contextIndex: number, variableBindings: List) { - var c = new _ConvertAstIntoProtoRecords(b, contextIndex, b.ast.toString(), variableBindings); + static convert(b: BindingRecord, contextIndex: number, variableNames: List) { + var c = new _ConvertAstIntoProtoRecords(b, contextIndex, b.ast.toString(), variableNames); b.ast.visit(c); return c.protoRecords; } @@ -159,7 +153,7 @@ class _ConvertAstIntoProtoRecords { visitAccessMember(ast: AccessMember) { var receiver = ast.receiver.visit(this); - if (isPresent(this.variableBindings) && ListWrapper.contains(this.variableBindings, ast.name) && + if (isPresent(this.variableNames) && ListWrapper.contains(this.variableNames, ast.name) && ast.receiver instanceof ImplicitReceiver) { return this._addRecord(RECORD_TYPE_LOCAL, ast.name, ast.name, [], null, receiver); @@ -172,7 +166,7 @@ class _ConvertAstIntoProtoRecords { ; var receiver = ast.receiver.visit(this); var args = this._visitAll(ast.args); - if (isPresent(this.variableBindings) && ListWrapper.contains(this.variableBindings, ast.name)) { + if (isPresent(this.variableNames) && ListWrapper.contains(this.variableNames, ast.name)) { var target = this._addRecord(RECORD_TYPE_LOCAL, ast.name, ast.name, [], null, receiver); return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, "closure", null, args, null, target); } else { diff --git a/modules/angular2/src/core/compiler/proto_view_factory.js b/modules/angular2/src/core/compiler/proto_view_factory.js index 765f7d9495..cdce000b29 100644 --- a/modules/angular2/src/core/compiler/proto_view_factory.js +++ b/modules/angular2/src/core/compiler/proto_view_factory.js @@ -4,7 +4,7 @@ import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {reflector} from 'angular2/src/reflection/reflection'; -import {ChangeDetection, DirectiveIndex, BindingRecord, DirectiveRecord, ProtoChangeDetector} from 'angular2/change_detection'; +import {ChangeDetection, DirectiveIndex, BindingRecord, DirectiveRecord, ProtoChangeDetector, ChangeDetectorDefinition} from 'angular2/change_detection'; import {Component} from '../annotations_impl/annotations'; import * as renderApi from 'angular2/src/render/api'; @@ -171,13 +171,8 @@ export class ProtoViewFactory { name = 'dummy'; } - return this._changeDetection.createProtoChangeDetector( - name, - bindingRecords, - variableNames, - directiveRecords, - changeDetection - ); + var definition = new ChangeDetectorDefinition(name, changeDetection, variableNames, bindingRecords, directiveRecords); + return this._changeDetection.createProtoChangeDetector(definition); } _createElementBinders(protoView, elementBinders, sortedDirectives) { diff --git a/modules/angular2/test/change_detection/change_detection_spec.js b/modules/angular2/test/change_detection/change_detection_spec.js index 5a65d2f522..a52e59f359 100644 --- a/modules/angular2/test/change_detection/change_detection_spec.js +++ b/modules/angular2/test/change_detection/change_detection_spec.js @@ -1,989 +1,22 @@ -import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, IS_DARTIUM} from 'angular2/test_lib'; - -import {isPresent, isBlank, isJsObject, BaseException, FunctionWrapper} from 'angular2/src/facade/lang'; -import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; - -import {Parser} from 'angular2/src/change_detection/parser/parser'; -import {Lexer} from 'angular2/src/change_detection/parser/lexer'; -import {Locals} from 'angular2/src/change_detection/parser/locals'; - -import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, BindingRecord, DirectiveRecord, DirectiveIndex, - PipeRegistry, Pipe, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH, DEFAULT, WrappedValue} from 'angular2/change_detection'; - -import {JitProtoChangeDetector, DynamicProtoChangeDetector} from 'angular2/src/change_detection/proto_change_detector'; +import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; +import {PreGeneratedChangeDetection} from 'angular2/change_detection'; export function main() { - describe("change detection", () => { - StringMapWrapper.forEach( - { "dynamic": (bindingRecords, variableBindings = null, directiveRecords = null, registry = null, strategy = null) => - new DynamicProtoChangeDetector( - registry, - isBlank(bindingRecords) ? [] : bindingRecords, - isBlank(variableBindings) ? [] : variableBindings, - isBlank(directiveRecords) ? [] : directiveRecords, - strategy), - - "JIT": (bindingRecords, variableBindings = null, directiveRecords = null, registry = null, strategy = null) => - new JitProtoChangeDetector( - registry, - isBlank(bindingRecords) ? [] : bindingRecords, - isBlank(variableBindings) ? [] : variableBindings, - isBlank(directiveRecords) ? [] : directiveRecords, - strategy) - - }, (createProtoChangeDetector, name) => { - - if (name == "JIT" && IS_DARTIUM) return; - - var parser = new Parser(new Lexer()); - - function ast(exp:string, location:string = 'location') { - return parser.parseBinding(exp, location); - } - - function dirs(directives:List) { - return new FakeDirectives(directives, []); - } - - function convertLocalsToVariableBindings(locals) { - var variableBindings = []; - var loc = locals; - while(isPresent(loc)) { - MapWrapper.forEach(loc.current, (v, k) => ListWrapper.push(variableBindings, k)); - loc = loc.parent; - } - return variableBindings; - } - - function createChangeDetector(propName:string, exp:string, context = null, locals = null, registry = null) { - var dispatcher = new TestDispatcher(); - - var variableBindings = convertLocalsToVariableBindings(locals); - - var records = [BindingRecord.createForElement(ast(exp), 0, propName)]; - var pcd = createProtoChangeDetector(records, variableBindings, [], registry); - var cd = pcd.instantiate(dispatcher); - cd.hydrate(context, locals, null); - - return {"changeDetector" : cd, "dispatcher" : dispatcher}; - } - - function executeWatch(memo:string, exp:string, context = null, locals = null) { - var res = createChangeDetector(memo, exp, context, locals); - res["changeDetector"].detectChanges(); - return res["dispatcher"].log; - } - - describe(`${name} change detection`, () => { - var dispatcher; - - beforeEach(() => { - dispatcher = new TestDispatcher(); - }); - - it('should do simple watching', () => { - var person = new Person("misko"); - - var c = createChangeDetector('name', 'name', person); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.detectChanges(); - expect(dispatcher.log).toEqual(['name=misko']); - dispatcher.clear(); - - cd.detectChanges(); - expect(dispatcher.log).toEqual([]); - dispatcher.clear(); - - person.name = "Misko"; - cd.detectChanges(); - expect(dispatcher.log).toEqual(['name=Misko']); - }); - - it('should report all changes on the first run including uninitialized values', () => { - expect(executeWatch('value', 'value', new Uninitialized())).toEqual(['value=null']); - }); - - it('should report all changes on the first run including null values', () => { - var td = new TestData(null); - expect(executeWatch('a', 'a', td)).toEqual(['a=null']); - }); - - it("should support literals", () => { - expect(executeWatch('const', '10')).toEqual(['const=10']); - expect(executeWatch('const', '"str"')).toEqual(['const=str']); - expect(executeWatch('const', '"a\n\nb"')).toEqual(['const=a\n\nb']); - }); - - it('simple chained property access', () => { - var address = new Address('Grenoble'); - var person = new Person('Victor', address); - - expect(executeWatch('address.city', 'address.city', person)) - .toEqual(['address.city=Grenoble']); - }); - - it("should support method calls", () => { - var person = new Person('Victor'); - expect(executeWatch('m', 'sayHi("Jim")', person)).toEqual(['m=Hi, Jim']); - }); - - it("should support function calls", () => { - var td = new TestData(() => (a) => a); - expect(executeWatch('value', 'a()(99)', td)).toEqual(['value=99']); - }); - - it("should support chained method calls", () => { - var person = new Person('Victor'); - var td = new TestData(person); - expect(executeWatch('m', 'a.sayHi("Jim")', td)).toEqual(['m=Hi, Jim']); - }); - - it("should support literal array", () => { - var c = createChangeDetector('array', '[1,2]'); - c["changeDetector"].detectChanges(); - expect(c["dispatcher"].loggedValues).toEqual([[1, 2]]); - - c = createChangeDetector('array', '[1,a]', new TestData(2)); - c["changeDetector"].detectChanges(); - expect(c["dispatcher"].loggedValues).toEqual([[1, 2]]); - }); - - it("should support literal maps", () => { - var c = createChangeDetector('map', '{z:1}'); - c["changeDetector"].detectChanges(); - expect(c["dispatcher"].loggedValues[0]['z']).toEqual(1); - - c = createChangeDetector('map', '{z:a}', new TestData(1)); - c["changeDetector"].detectChanges(); - expect(c["dispatcher"].loggedValues[0]['z']).toEqual(1); - }); - - it("should support binary operations", () => { - expect(executeWatch('exp', '10 + 2')).toEqual(['exp=12']); - expect(executeWatch('exp', '10 - 2')).toEqual(['exp=8']); - - expect(executeWatch('exp', '10 * 2')).toEqual(['exp=20']); - expect(executeWatch('exp', '10 / 2')).toEqual([`exp=${5.0}`]); //dart exp=5.0, js exp=5 - expect(executeWatch('exp', '11 % 2')).toEqual(['exp=1']); - - expect(executeWatch('exp', '1 == 1')).toEqual(['exp=true']); - expect(executeWatch('exp', '1 != 1')).toEqual(['exp=false']); - - expect(executeWatch('exp', '1 < 2')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']); - - expect(executeWatch('exp', '2 > 1')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']); - - expect(executeWatch('exp', '1 <= 2')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 <= 2')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 <= 1')).toEqual(['exp=false']); - - expect(executeWatch('exp', '2 >= 1')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 >= 2')).toEqual(['exp=true']); - expect(executeWatch('exp', '1 >= 2')).toEqual(['exp=false']); - - expect(executeWatch('exp', 'true && true')).toEqual(['exp=true']); - expect(executeWatch('exp', 'true && false')).toEqual(['exp=false']); - - expect(executeWatch('exp', 'true || false')).toEqual(['exp=true']); - expect(executeWatch('exp', 'false || false')).toEqual(['exp=false']); - }); - - it("should support negate", () => { - expect(executeWatch('exp', '!true')).toEqual(['exp=false']); - expect(executeWatch('exp', '!!true')).toEqual(['exp=true']); - }); - - it("should support conditionals", () => { - expect(executeWatch('m', '1 < 2 ? 1 : 2')).toEqual(['m=1']); - expect(executeWatch('m', '1 > 2 ? 1 : 2')).toEqual(['m=2']); - }); - - describe("keyed access", () => { - it("should support accessing a list item", () => { - expect(executeWatch('array[0]', '["foo", "bar"][0]')).toEqual(['array[0]=foo']); - }); - - it("should support accessing a map item", () => { - expect(executeWatch('map[foo]', '{"foo": "bar"}["foo"]')).toEqual(['map[foo]=bar']); - }); - }); - - it("should support interpolation", () => { - var ast = parser.parseInterpolation("B{{a}}A", "location"); - var pcd = createProtoChangeDetector([BindingRecord.createForElement(ast, 0, "memo")]); - - var cd = pcd.instantiate(dispatcher); - cd.hydrate(new TestData("value"), null, null); - - cd.detectChanges(); - - expect(dispatcher.log).toEqual(["memo=BvalueA"]); - }); - - describe("change notification", () => { - describe("simple checks", () => { - it("should pass a change record to the dispatcher", () => { - var person = new Person('bob'); - var c = createChangeDetector('name', 'name', person); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.detectChanges(); - - expect(dispatcher.loggedValues).toEqual(['bob']); - }); - }); - - describe("pipes", () => { - it("should pass a change record to the dispatcher", () => { - var registry = new FakePipeRegistry('pipe', () => new CountingPipe()); - - var person = new Person('bob'); - var c = createChangeDetector('name', 'name | pipe', person, null, registry); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.detectChanges(); - - expect(dispatcher.loggedValues).toEqual(['bob state:0']); - }); - }); - - describe("updating directives", () => { - var dirRecord1 = new DirectiveRecord(new DirectiveIndex(0, 0), true, true, DEFAULT); - var dirRecord2 = new DirectiveRecord(new DirectiveIndex(0, 1), true, true, DEFAULT); - var dirRecordNoCallbacks = new DirectiveRecord(new DirectiveIndex(0, 0), false, false, DEFAULT); - - function updateA(exp:string, dirRecord) { - return BindingRecord.createForDirective(ast(exp), "a", (o,v) => o.a = v, dirRecord); - } - - function updateB(exp:string, dirRecord) { - return BindingRecord.createForDirective(ast(exp), "b", (o,v) => o.b = v, dirRecord); - } - - var directive1; - var directive2; - - beforeEach(() => { - directive1 = new TestDirective(); - directive2 = new TestDirective(); - }); - - it("should happen directly, without invoking the dispatcher", () => { - var pcd = createProtoChangeDetector([updateA("42", dirRecord1)], [], [dirRecord1]); - - var cd = pcd.instantiate(dispatcher); - - cd.hydrate(null, null, dirs([directive1])); - - cd.detectChanges(); - - expect(dispatcher.loggedValues).toEqual([]); - expect(directive1.a).toEqual(42); - }); - - describe("onChange", () => { - it("should notify the directive when a group of records changes", () => { - var pcd = createProtoChangeDetector([ - updateA("1", dirRecord1), - updateB("2", dirRecord1), - updateA("3", dirRecord2) - ], [], [dirRecord1, dirRecord2]); - - var cd = pcd.instantiate(dispatcher); - - cd.hydrate(null, null, dirs([directive1, directive2])); - - cd.detectChanges(); - - expect(directive1.changes).toEqual({'a': 1, 'b': 2}); - expect(directive2.changes).toEqual({'a': 3}); - }); - - it("should not call onChange when callOnChange is false", () => { - var pcd = createProtoChangeDetector([ - updateA("1", dirRecordNoCallbacks) - ], [], [dirRecordNoCallbacks]); - - var cd = pcd.instantiate(dispatcher); - - cd.hydrate(null, null, dirs([directive1])); - - cd.detectChanges(); - - expect(directive1.changes).toEqual(null); - }); - }); - - describe("onAllChangesDone", () => { - it("should be called after processing all the children", () => { - var pcd = createProtoChangeDetector([], [], [dirRecord1, dirRecord2]); - - var cd = pcd.instantiate(dispatcher); - cd.hydrate(null, null, dirs([directive1, directive2])); - - cd.detectChanges(); - - expect(directive1.onChangesDoneCalled).toBe(true); - expect(directive2.onChangesDoneCalled).toBe(true); - }); - - - it("should not be called when onAllChangesDone is false", () => { - var pcd = createProtoChangeDetector([ - updateA("1", dirRecordNoCallbacks) - ], [], [dirRecordNoCallbacks]); - - var cd = pcd.instantiate(dispatcher); - - cd.hydrate(null, null, dirs([directive1])); - - cd.detectChanges(); - - expect(directive1.onChangesDoneCalled).toEqual(false); - }); - - it("should be called in reverse order so the child is always notified before the parent", () => { - var pcd = createProtoChangeDetector([], [], [dirRecord1, dirRecord2]); - var cd = pcd.instantiate(dispatcher); - - var onChangesDoneCalls = []; - var td1; - td1 = new TestDirective(() => ListWrapper.push(onChangesDoneCalls, td1)); - var td2; - td2 = new TestDirective(() => ListWrapper.push(onChangesDoneCalls, td2)); - cd.hydrate(null, null, dirs([td1, td2])); - - cd.detectChanges(); - - expect(onChangesDoneCalls).toEqual([td2, td1]); - }); - - it("should be called before processing shadow dom children", () => { - var pcd = createProtoChangeDetector([], null, [dirRecord1]); - var shadowDomChildPCD = createProtoChangeDetector([updateA("1", dirRecord1)], null, [dirRecord1]); - - var parent = pcd.instantiate(dispatcher); - - var child = shadowDomChildPCD.instantiate(dispatcher); - parent.addShadowDomChild(child); - - var directiveInShadowDom = new TestDirective(); - var parentDirective = new TestDirective(() => { - expect(directiveInShadowDom.a).toBe(null); - }); - - parent.hydrate(null, null, dirs([parentDirective])); - child.hydrate(null, null, dirs([directiveInShadowDom])); - - parent.detectChanges(); - }); - }); - }); - }); - - describe("reading directives", () => { - var index = new DirectiveIndex(0, 0); - var dirRecord = new DirectiveRecord(index, false, false, DEFAULT); - - it("should read directive properties", () => { - var directive = new TestDirective(); - directive.a = "aaa"; - - var pcd = createProtoChangeDetector([BindingRecord.createForHostProperty(index, ast("a"), "prop")], null, [dirRecord]); - var cd = pcd.instantiate(dispatcher); - cd.hydrate(null, null, dirs([directive])); - - cd.detectChanges(); - - expect(dispatcher.loggedValues).toEqual(['aaa']); - }); - }); - - describe("enforce no new changes", () => { - it("should throw when a record gets changed after it has been checked", () => { - var pcd = createProtoChangeDetector([ - BindingRecord.createForElement(ast("a"), 0, "a") - ]); - - var dispatcher = new TestDispatcher(); - var cd = pcd.instantiate(dispatcher); - cd.hydrate(new TestData('value'), null, null); - - expect(() => { - cd.checkNoChanges(); - }).toThrowError(new RegExp("Expression 'a in location' has changed after it was checked")); - }); - - it("should not break the next run", () => { - var pcd = createProtoChangeDetector([ - BindingRecord.createForElement(ast("a"), 0, "a") - ]); - - var dispatcher = new TestDispatcher(); - var cd = pcd.instantiate(dispatcher); - cd.hydrate(new TestData('value'), null, null); - - expect(() => cd.checkNoChanges()).toThrowError(new RegExp( - "Expression 'a in location' has changed after it was checked.")); - - cd.detectChanges(); - - expect(dispatcher.loggedValues).toEqual(['value']); - }); - }); - - //TODO vsavkin: implement it - describe("error handling", () => { - xit("should wrap exceptions into ChangeDetectionError", () => { - var pcd = createProtoChangeDetector(); - var cd = pcd.instantiate(new TestDispatcher(), [ - BindingRecord.createForElement(ast("invalidProp"), 0, "a") - ], null, []); - cd.hydrate(null, null); - - try { - cd.detectChanges(); - - throw new BaseException("fail"); - } catch (e) { - expect(e).toBeAnInstanceOf(ChangeDetectionError); - expect(e.location).toEqual("invalidProp in someComponent"); - } - }); - }); - - describe("Locals", () => { - it('should read a value from locals', () => { - var locals = new Locals(null, - MapWrapper.createFromPairs([["key", "value"]])); - - expect(executeWatch('key', 'key', null, locals)) - .toEqual(['key=value']); - }); - - it('should invoke a function from local', () => { - var locals = new Locals(null, - MapWrapper.createFromPairs([["key", () => "value"]])); - - expect(executeWatch('key', 'key()', null, locals)) - .toEqual(['key=value']); - }); - - it('should handle nested locals', () => { - var nested = new Locals(null, - MapWrapper.createFromPairs([["key", "value"]])); - var locals = new Locals(nested, MapWrapper.create()); - - expect(executeWatch('key', 'key', null, locals)) - .toEqual(['key=value']); - }); - - it("should fall back to a regular field read when the locals map" + - "does not have the requested field", () => { - var locals = new Locals(null, - MapWrapper.createFromPairs([["key", "value"]])); - - expect(executeWatch('name', 'name', new Person("Jim"), locals)) - .toEqual(['name=Jim']); - }); - - it('should correctly handle nested properties', () => { - var address = new Address('Grenoble'); - var person = new Person('Victor', address); - var locals = new Locals(null, - MapWrapper.createFromPairs([['city', 'MTV']])); - expect(executeWatch('address.city', 'address.city', person, locals)) - .toEqual(['address.city=Grenoble']); - expect(executeWatch('city', 'city', person, locals)) - .toEqual(['city=MTV']); - }); - - }); - - describe("handle children", () => { - var parent, child; - - beforeEach(() => { - parent = createProtoChangeDetector([]).instantiate(null); - child = createProtoChangeDetector([]).instantiate(null); - }); - - it("should add light dom children", () => { - parent.addChild(child); - - expect(parent.lightDomChildren.length).toEqual(1); - expect(parent.lightDomChildren[0]).toBe(child); - }); - - it("should add shadow dom children", () => { - parent.addShadowDomChild(child); - - expect(parent.shadowDomChildren.length).toEqual(1); - expect(parent.shadowDomChildren[0]).toBe(child); - }); - - it("should remove light dom children", () => { - parent.addChild(child); - parent.removeChild(child); - - expect(parent.lightDomChildren).toEqual([]); - }); - - it("should remove shadow dom children", () => { - parent.addShadowDomChild(child); - parent.removeShadowDomChild(child); - - expect(parent.shadowDomChildren.length).toEqual(0); - }); - }); - }); - - describe("mode", () => { - it("should set the mode to CHECK_ALWAYS when the default change detection is used", () => { - var proto = createProtoChangeDetector([], [], [], null, DEFAULT); - var cd = proto.instantiate(null); - - expect(cd.mode).toEqual(null); - - cd.hydrate(null, null, null); - - expect(cd.mode).toEqual(CHECK_ALWAYS); - }); - - it("should set the mode to CHECK_ONCE when the push change detection is used", () => { - var proto = createProtoChangeDetector([], [], [], null, ON_PUSH); - var cd = proto.instantiate(null); - cd.hydrate(null, null, null); - - expect(cd.mode).toEqual(CHECK_ONCE); - }); - - it("should not check a detached change detector", () => { - var c = createChangeDetector('name', 'a', new TestData("value")); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.mode = DETACHED; - cd.detectChanges(); - - expect(dispatcher.log).toEqual([]); - }); - - it("should not check a checked change detector", () => { - var c = createChangeDetector('name', 'a', new TestData("value")); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.mode = CHECKED; - cd.detectChanges(); - - expect(dispatcher.log).toEqual([]); - }); - - it("should change CHECK_ONCE to CHECKED", () => { - var cd = createProtoChangeDetector([]).instantiate(null); - cd.mode = CHECK_ONCE; - - cd.detectChanges(); - - expect(cd.mode).toEqual(CHECKED); - }); - - it("should not change the CHECK_ALWAYS", () => { - var cd = createProtoChangeDetector([]).instantiate(null); - cd.mode = CHECK_ALWAYS; - - cd.detectChanges(); - - expect(cd.mode).toEqual(CHECK_ALWAYS); - }); - - describe("marking ON_PUSH detectors as CHECK_ONCE after an update", () => { - var checkedDetector; - var dirRecordWithOnPush; - var updateDirWithOnPushRecord; - var directives; - - beforeEach(() => { - var proto = createProtoChangeDetector([], [], [], null, ON_PUSH); - checkedDetector = proto.instantiate(null); - checkedDetector.hydrate(null, null, null); - checkedDetector.mode = CHECKED; - - // this directive is a component with ON_PUSH change detection - dirRecordWithOnPush = new DirectiveRecord(new DirectiveIndex(0, 0), false, false, ON_PUSH); - - // a record updating a component - updateDirWithOnPushRecord = - BindingRecord.createForDirective(ast("42"), "a", (o,v) => o.a = v, dirRecordWithOnPush); - - var targetDirective = new TestData(null); - directives = new FakeDirectives([targetDirective], [checkedDetector]); - }); - - it("should set the mode to CHECK_ONCE when a binding is updated", () => { - var proto = createProtoChangeDetector([updateDirWithOnPushRecord], [], [dirRecordWithOnPush]); - - var cd = proto.instantiate(null); - cd.hydrate(null, null, directives); - - expect(checkedDetector.mode).toEqual(CHECKED); - - // evaluate the record, update the targetDirective, and mark its detector as CHECK_ONCE - cd.detectChanges(); - - expect(checkedDetector.mode).toEqual(CHECK_ONCE); - }); - }); - }); - - describe("markPathToRootAsCheckOnce", () => { - function changeDetector(mode, parent) { - var cd = createProtoChangeDetector([]).instantiate(null); - cd.mode = mode; - if (isPresent(parent)) parent.addChild(cd); - return cd; - } - - it("should mark all checked detectors as CHECK_ONCE " + - "until reaching a detached one", () => { - - var root = changeDetector(CHECK_ALWAYS, null); - var disabled = changeDetector(DETACHED, root); - var parent = changeDetector(CHECKED, disabled); - var checkAlwaysChild = changeDetector(CHECK_ALWAYS, parent); - var checkOnceChild = changeDetector(CHECK_ONCE, checkAlwaysChild); - var checkedChild = changeDetector(CHECKED, checkOnceChild); - - checkedChild.markPathToRootAsCheckOnce(); - - expect(root.mode).toEqual(CHECK_ALWAYS); - expect(disabled.mode).toEqual(DETACHED); - expect(parent.mode).toEqual(CHECK_ONCE); - expect(checkAlwaysChild.mode).toEqual(CHECK_ALWAYS); - expect(checkOnceChild.mode).toEqual(CHECK_ONCE); - expect(checkedChild.mode).toEqual(CHECK_ONCE); - }); - }); - - describe("hydration", () => { - it("should be able to rehydrate a change detector", () => { - var c = createChangeDetector("memo", "name"); - var cd = c["changeDetector"]; - - cd.hydrate("some context", null, null); - expect(cd.hydrated()).toBe(true); - - cd.dehydrate(); - expect(cd.hydrated()).toBe(false); - - cd.hydrate("other context", null, null); - expect(cd.hydrated()).toBe(true); - }); - - it("should destroy all active pipes during dehyration", () => { - var pipe = new OncePipe(); - var registry = new FakePipeRegistry('pipe', () => pipe); - var c = createChangeDetector("memo", "name | pipe", new Person('bob'), null, registry); - var cd = c["changeDetector"]; - - cd.detectChanges(); - - cd.dehydrate(); - - expect(pipe.destroyCalled).toBe(true); - }); - }); - - describe("pipes", () => { - it("should support pipes", () => { - var registry = new FakePipeRegistry('pipe', () => new CountingPipe()); - var ctx = new Person("Megatron"); - - var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.detectChanges(); - - expect(dispatcher.log).toEqual(['memo=Megatron state:0']); - - dispatcher.clear(); - cd.detectChanges(); - - expect(dispatcher.log).toEqual(['memo=Megatron state:1']); - }); - - it("should lookup pipes in the registry when the context is not supported", () => { - var registry = new FakePipeRegistry('pipe', () => new OncePipe()); - var ctx = new Person("Megatron"); - - var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); - var cd = c["changeDetector"]; - - cd.detectChanges(); - - expect(registry.numberOfLookups).toEqual(1); - - ctx.name = "Optimus Prime"; - cd.detectChanges(); - - expect(registry.numberOfLookups).toEqual(2); - }); - - it("should invoke onDestroy on a pipe before switching to another one", () => { - var pipe = new OncePipe(); - var registry = new FakePipeRegistry('pipe', () => pipe); - var ctx = new Person("Megatron"); - - var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); - var cd = c["changeDetector"]; - - cd.detectChanges(); - ctx.name = "Optimus Prime"; - cd.detectChanges(); - - expect(pipe.destroyCalled).toEqual(true); - }); - - it("should inject the ChangeDetectorRef " + - "of the encompassing component into a pipe", () => { - - var registry = new FakePipeRegistry('pipe', () => new IdentityPipe()); - var c = createChangeDetector("memo", "name | pipe", new Person('bob'), null, registry); - var cd = c["changeDetector"]; - - cd.detectChanges(); - - expect(registry.cdRef).toBe(cd.ref); - }); - }); - - it("should do nothing when no change", () => { - var registry = new FakePipeRegistry('pipe', () => new IdentityPipe()) - var ctx = new Person("Megatron"); - - var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.detectChanges(); - - expect(dispatcher.log).toEqual(['memo=Megatron']); - - dispatcher.clear(); - cd.detectChanges(); - - expect(dispatcher.log).toEqual([]); - }); - - it("should unwrap the wrapped value", () => { - var registry = new FakePipeRegistry('pipe', () => new WrappedPipe()) - var ctx = new Person("Megatron"); - - var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); - var cd = c["changeDetector"]; - var dispatcher = c["dispatcher"]; - - cd.detectChanges(); - - expect(dispatcher.log).toEqual(['memo=Megatron']); - }); - }); + describe("PreGeneratedChangeDetection", () => { + it("sfs", () => { + + var rs = coalesce([ + r("user", [], 0, 1, true), + r("user", [], 0, 2, true) + ]); + + expect(rs[1]).toEqual(new ProtoRecord( + RECORD_TYPE_SELF, "self", null, + [], null, 1, null, 2, + null, null, + true, false) + ); + }); }); } - -class CountingPipe extends Pipe { - state:number; - - constructor() { - super(); - this.state = 0; - } - - supports(newValue) { - return true; - } - - transform(value) { - return `${value} state:${this.state ++}`; - } -} - -class OncePipe extends Pipe { - called:boolean; - destroyCalled:boolean; - - constructor() { - super(); - this.called = false; - this.destroyCalled = false; - } - - supports(newValue) { - return !this.called; - } - - onDestroy() { - this.destroyCalled = true; - } - - transform(value) { - this.called = true; - return value; - } -} - -class IdentityPipe extends Pipe { - transform(value) { - return value; - } -} - -class WrappedPipe extends Pipe { - transform(value) { - return WrappedValue.wrap(value); - } -} - -class FakePipeRegistry extends PipeRegistry { - numberOfLookups:number; - pipeType:string; - factory:Function; - cdRef:any; - - constructor(pipeType, factory) { - super({}); - this.pipeType = pipeType; - this.factory = factory; - this.numberOfLookups = 0; - } - - get(type:string, obj, cdRef) { - if (type != this.pipeType) return null; - this.numberOfLookups ++; - this.cdRef = cdRef; - return this.factory(); - } -} - -class TestDirective { - a; - b; - changes; - onChangesDoneCalled; - onChangesDoneSpy; - - constructor(onChangesDoneSpy = null) { - this.onChangesDoneCalled = false; - this.onChangesDoneSpy = onChangesDoneSpy; - this.a = null; - this.b = null; - this.changes = null; - } - - onChange(changes) { - var r = {}; - StringMapWrapper.forEach(changes, (c, key) => r[key] = c.currentValue); - this.changes = r; - } - - onAllChangesDone() { - this.onChangesDoneCalled = true; - if(isPresent(this.onChangesDoneSpy)) { - this.onChangesDoneSpy(); - } - } -} - -class Person { - name:string; - age:number; - address:Address; - constructor(name:string, address:Address = null) { - this.name = name; - this.address = address; - } - - sayHi(m) { - return `Hi, ${m}`; - } - - toString():string { - var address = this.address == null ? '' : ' address=' + this.address.toString(); - - return 'name=' + this.name + address; - } -} - -class Address { - city:string; - constructor(city:string) { - this.city = city; - } - - toString():string { - return this.city; - } -} - -class Uninitialized { - value:any; -} - -class TestData { - a; - - constructor(a) { - this.a = a; - } -} - -class FakeDirectives { - directives:List; - detectors:List; - - constructor(directives:List, detectors:List) { - this.directives = directives; - this.detectors = detectors; - } - - getDirectiveFor(di:DirectiveIndex) { - return this.directives[di.directiveIndex]; - } - - getDetectorFor(di:DirectiveIndex) { - return this.detectors[di.directiveIndex]; - } -} - -class TestDispatcher extends ChangeDispatcher { - log:List; - loggedValues:List; - - constructor() { - super(); - this.clear(); - } - - clear() { - this.log = ListWrapper.create(); - this.loggedValues = ListWrapper.create(); - } - - notifyOnBinding(binding, value) { - ListWrapper.push(this.log, `${binding.propertyName}=${this._asString(value)}`); - ListWrapper.push(this.loggedValues, value); - } - - _asString(value) { - return (isBlank(value) ? 'null' : value.toString()); - } -} diff --git a/modules/angular2/test/change_detection/change_detector_spec.js b/modules/angular2/test/change_detection/change_detector_spec.js new file mode 100644 index 0000000000..7a30b82832 --- /dev/null +++ b/modules/angular2/test/change_detection/change_detector_spec.js @@ -0,0 +1,987 @@ +import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, IS_DARTIUM} from 'angular2/test_lib'; + +import {isPresent, isBlank, isJsObject, BaseException, FunctionWrapper} from 'angular2/src/facade/lang'; +import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; + +import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, BindingRecord, DirectiveRecord, DirectiveIndex, + PipeRegistry, Pipe, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH, DEFAULT, WrappedValue, + JitProtoChangeDetector, DynamicProtoChangeDetector, ChangeDetectorDefinition, + Lexer, Parser, Locals} from 'angular2/change_detection'; + + +export function main() { + describe("change detection", () => { + StringMapWrapper.forEach( + { "dynamic": (bindingRecords, variableBindings = null, directiveRecords = null, registry = null, strategy = null) => + new DynamicProtoChangeDetector( + registry, new ChangeDetectorDefinition( + null, + strategy, + isBlank(variableBindings) ? [] : variableBindings, + isBlank(bindingRecords) ? [] : bindingRecords, + isBlank(directiveRecords) ? [] : directiveRecords)), + + "JIT": (bindingRecords, variableBindings = null, directiveRecords = null, registry = null, strategy = null) => + new JitProtoChangeDetector( + registry, new ChangeDetectorDefinition( + null, + strategy, + isBlank(variableBindings) ? [] : variableBindings, + isBlank(bindingRecords) ? [] : bindingRecords, + isBlank(directiveRecords) ? [] : directiveRecords)) + + }, (createProtoChangeDetector, name) => { + + if (name == "JIT" && IS_DARTIUM) return; + + var parser = new Parser(new Lexer()); + + function ast(exp:string, location:string = 'location') { + return parser.parseBinding(exp, location); + } + + function dirs(directives:List) { + return new FakeDirectives(directives, []); + } + + function convertLocalsToVariableBindings(locals) { + var variableBindings = []; + var loc = locals; + while(isPresent(loc)) { + MapWrapper.forEach(loc.current, (v, k) => ListWrapper.push(variableBindings, k)); + loc = loc.parent; + } + return variableBindings; + } + + function createChangeDetector(propName:string, exp:string, context = null, locals = null, registry = null) { + var dispatcher = new TestDispatcher(); + + var variableBindings = convertLocalsToVariableBindings(locals); + + var records = [BindingRecord.createForElement(ast(exp), 0, propName)]; + var pcd = createProtoChangeDetector(records, variableBindings, [], registry); + var cd = pcd.instantiate(dispatcher); + cd.hydrate(context, locals, null); + + return {"changeDetector" : cd, "dispatcher" : dispatcher}; + } + + function executeWatch(memo:string, exp:string, context = null, locals = null) { + var res = createChangeDetector(memo, exp, context, locals); + res["changeDetector"].detectChanges(); + return res["dispatcher"].log; + } + + describe(`${name} change detection`, () => { + var dispatcher; + + beforeEach(() => { + dispatcher = new TestDispatcher(); + }); + + it('should do simple watching', () => { + var person = new Person("misko"); + + var c = createChangeDetector('name', 'name', person); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.detectChanges(); + expect(dispatcher.log).toEqual(['name=misko']); + dispatcher.clear(); + + cd.detectChanges(); + expect(dispatcher.log).toEqual([]); + dispatcher.clear(); + + person.name = "Misko"; + cd.detectChanges(); + expect(dispatcher.log).toEqual(['name=Misko']); + }); + + it('should report all changes on the first run including uninitialized values', () => { + expect(executeWatch('value', 'value', new Uninitialized())).toEqual(['value=null']); + }); + + it('should report all changes on the first run including null values', () => { + var td = new TestData(null); + expect(executeWatch('a', 'a', td)).toEqual(['a=null']); + }); + + it("should support literals", () => { + expect(executeWatch('const', '10')).toEqual(['const=10']); + expect(executeWatch('const', '"str"')).toEqual(['const=str']); + expect(executeWatch('const', '"a\n\nb"')).toEqual(['const=a\n\nb']); + }); + + it('simple chained property access', () => { + var address = new Address('Grenoble'); + var person = new Person('Victor', address); + + expect(executeWatch('address.city', 'address.city', person)) + .toEqual(['address.city=Grenoble']); + }); + + it("should support method calls", () => { + var person = new Person('Victor'); + expect(executeWatch('m', 'sayHi("Jim")', person)).toEqual(['m=Hi, Jim']); + }); + + it("should support function calls", () => { + var td = new TestData(() => (a) => a); + expect(executeWatch('value', 'a()(99)', td)).toEqual(['value=99']); + }); + + it("should support chained method calls", () => { + var person = new Person('Victor'); + var td = new TestData(person); + expect(executeWatch('m', 'a.sayHi("Jim")', td)).toEqual(['m=Hi, Jim']); + }); + + it("should support literal array", () => { + var c = createChangeDetector('array', '[1,2]'); + c["changeDetector"].detectChanges(); + expect(c["dispatcher"].loggedValues).toEqual([[1, 2]]); + + c = createChangeDetector('array', '[1,a]', new TestData(2)); + c["changeDetector"].detectChanges(); + expect(c["dispatcher"].loggedValues).toEqual([[1, 2]]); + }); + + it("should support literal maps", () => { + var c = createChangeDetector('map', '{z:1}'); + c["changeDetector"].detectChanges(); + expect(c["dispatcher"].loggedValues[0]['z']).toEqual(1); + + c = createChangeDetector('map', '{z:a}', new TestData(1)); + c["changeDetector"].detectChanges(); + expect(c["dispatcher"].loggedValues[0]['z']).toEqual(1); + }); + + it("should support binary operations", () => { + expect(executeWatch('exp', '10 + 2')).toEqual(['exp=12']); + expect(executeWatch('exp', '10 - 2')).toEqual(['exp=8']); + + expect(executeWatch('exp', '10 * 2')).toEqual(['exp=20']); + expect(executeWatch('exp', '10 / 2')).toEqual([`exp=${5.0}`]); //dart exp=5.0, js exp=5 + expect(executeWatch('exp', '11 % 2')).toEqual(['exp=1']); + + expect(executeWatch('exp', '1 == 1')).toEqual(['exp=true']); + expect(executeWatch('exp', '1 != 1')).toEqual(['exp=false']); + + expect(executeWatch('exp', '1 < 2')).toEqual(['exp=true']); + expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']); + + expect(executeWatch('exp', '2 > 1')).toEqual(['exp=true']); + expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']); + + expect(executeWatch('exp', '1 <= 2')).toEqual(['exp=true']); + expect(executeWatch('exp', '2 <= 2')).toEqual(['exp=true']); + expect(executeWatch('exp', '2 <= 1')).toEqual(['exp=false']); + + expect(executeWatch('exp', '2 >= 1')).toEqual(['exp=true']); + expect(executeWatch('exp', '2 >= 2')).toEqual(['exp=true']); + expect(executeWatch('exp', '1 >= 2')).toEqual(['exp=false']); + + expect(executeWatch('exp', 'true && true')).toEqual(['exp=true']); + expect(executeWatch('exp', 'true && false')).toEqual(['exp=false']); + + expect(executeWatch('exp', 'true || false')).toEqual(['exp=true']); + expect(executeWatch('exp', 'false || false')).toEqual(['exp=false']); + }); + + it("should support negate", () => { + expect(executeWatch('exp', '!true')).toEqual(['exp=false']); + expect(executeWatch('exp', '!!true')).toEqual(['exp=true']); + }); + + it("should support conditionals", () => { + expect(executeWatch('m', '1 < 2 ? 1 : 2')).toEqual(['m=1']); + expect(executeWatch('m', '1 > 2 ? 1 : 2')).toEqual(['m=2']); + }); + + describe("keyed access", () => { + it("should support accessing a list item", () => { + expect(executeWatch('array[0]', '["foo", "bar"][0]')).toEqual(['array[0]=foo']); + }); + + it("should support accessing a map item", () => { + expect(executeWatch('map[foo]', '{"foo": "bar"}["foo"]')).toEqual(['map[foo]=bar']); + }); + }); + + it("should support interpolation", () => { + var ast = parser.parseInterpolation("B{{a}}A", "location"); + var pcd = createProtoChangeDetector([BindingRecord.createForElement(ast, 0, "memo")]); + + var cd = pcd.instantiate(dispatcher); + cd.hydrate(new TestData("value"), null, null); + + cd.detectChanges(); + + expect(dispatcher.log).toEqual(["memo=BvalueA"]); + }); + + describe("change notification", () => { + describe("simple checks", () => { + it("should pass a change record to the dispatcher", () => { + var person = new Person('bob'); + var c = createChangeDetector('name', 'name', person); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.detectChanges(); + + expect(dispatcher.loggedValues).toEqual(['bob']); + }); + }); + + describe("pipes", () => { + it("should pass a change record to the dispatcher", () => { + var registry = new FakePipeRegistry('pipe', () => new CountingPipe()); + + var person = new Person('bob'); + var c = createChangeDetector('name', 'name | pipe', person, null, registry); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.detectChanges(); + + expect(dispatcher.loggedValues).toEqual(['bob state:0']); + }); + }); + + describe("updating directives", () => { + var dirRecord1 = new DirectiveRecord(new DirectiveIndex(0, 0), true, true, DEFAULT); + var dirRecord2 = new DirectiveRecord(new DirectiveIndex(0, 1), true, true, DEFAULT); + var dirRecordNoCallbacks = new DirectiveRecord(new DirectiveIndex(0, 0), false, false, DEFAULT); + + function updateA(exp:string, dirRecord) { + return BindingRecord.createForDirective(ast(exp), "a", (o,v) => o.a = v, dirRecord); + } + + function updateB(exp:string, dirRecord) { + return BindingRecord.createForDirective(ast(exp), "b", (o,v) => o.b = v, dirRecord); + } + + var directive1; + var directive2; + + beforeEach(() => { + directive1 = new TestDirective(); + directive2 = new TestDirective(); + }); + + it("should happen directly, without invoking the dispatcher", () => { + var pcd = createProtoChangeDetector([updateA("42", dirRecord1)], [], [dirRecord1]); + + var cd = pcd.instantiate(dispatcher); + + cd.hydrate(null, null, dirs([directive1])); + + cd.detectChanges(); + + expect(dispatcher.loggedValues).toEqual([]); + expect(directive1.a).toEqual(42); + }); + + describe("onChange", () => { + it("should notify the directive when a group of records changes", () => { + var pcd = createProtoChangeDetector([ + updateA("1", dirRecord1), + updateB("2", dirRecord1), + updateA("3", dirRecord2) + ], [], [dirRecord1, dirRecord2]); + + var cd = pcd.instantiate(dispatcher); + + cd.hydrate(null, null, dirs([directive1, directive2])); + + cd.detectChanges(); + + expect(directive1.changes).toEqual({'a': 1, 'b': 2}); + expect(directive2.changes).toEqual({'a': 3}); + }); + + it("should not call onChange when callOnChange is false", () => { + var pcd = createProtoChangeDetector([ + updateA("1", dirRecordNoCallbacks) + ], [], [dirRecordNoCallbacks]); + + var cd = pcd.instantiate(dispatcher); + + cd.hydrate(null, null, dirs([directive1])); + + cd.detectChanges(); + + expect(directive1.changes).toEqual(null); + }); + }); + + describe("onAllChangesDone", () => { + it("should be called after processing all the children", () => { + var pcd = createProtoChangeDetector([], [], [dirRecord1, dirRecord2]); + + var cd = pcd.instantiate(dispatcher); + cd.hydrate(null, null, dirs([directive1, directive2])); + + cd.detectChanges(); + + expect(directive1.onChangesDoneCalled).toBe(true); + expect(directive2.onChangesDoneCalled).toBe(true); + }); + + + it("should not be called when onAllChangesDone is false", () => { + var pcd = createProtoChangeDetector([ + updateA("1", dirRecordNoCallbacks) + ], [], [dirRecordNoCallbacks]); + + var cd = pcd.instantiate(dispatcher); + + cd.hydrate(null, null, dirs([directive1])); + + cd.detectChanges(); + + expect(directive1.onChangesDoneCalled).toEqual(false); + }); + + it("should be called in reverse order so the child is always notified before the parent", () => { + var pcd = createProtoChangeDetector([], [], [dirRecord1, dirRecord2]); + var cd = pcd.instantiate(dispatcher); + + var onChangesDoneCalls = []; + var td1; + td1 = new TestDirective(() => ListWrapper.push(onChangesDoneCalls, td1)); + var td2; + td2 = new TestDirective(() => ListWrapper.push(onChangesDoneCalls, td2)); + cd.hydrate(null, null, dirs([td1, td2])); + + cd.detectChanges(); + + expect(onChangesDoneCalls).toEqual([td2, td1]); + }); + + it("should be called before processing shadow dom children", () => { + var pcd = createProtoChangeDetector([], null, [dirRecord1]); + var shadowDomChildPCD = createProtoChangeDetector([updateA("1", dirRecord1)], null, [dirRecord1]); + + var parent = pcd.instantiate(dispatcher); + + var child = shadowDomChildPCD.instantiate(dispatcher); + parent.addShadowDomChild(child); + + var directiveInShadowDom = new TestDirective(); + var parentDirective = new TestDirective(() => { + expect(directiveInShadowDom.a).toBe(null); + }); + + parent.hydrate(null, null, dirs([parentDirective])); + child.hydrate(null, null, dirs([directiveInShadowDom])); + + parent.detectChanges(); + }); + }); + }); + }); + + describe("reading directives", () => { + var index = new DirectiveIndex(0, 0); + var dirRecord = new DirectiveRecord(index, false, false, DEFAULT); + + it("should read directive properties", () => { + var directive = new TestDirective(); + directive.a = "aaa"; + + var pcd = createProtoChangeDetector([BindingRecord.createForHostProperty(index, ast("a"), "prop")], null, [dirRecord]); + var cd = pcd.instantiate(dispatcher); + cd.hydrate(null, null, dirs([directive])); + + cd.detectChanges(); + + expect(dispatcher.loggedValues).toEqual(['aaa']); + }); + }); + + describe("enforce no new changes", () => { + it("should throw when a record gets changed after it has been checked", () => { + var pcd = createProtoChangeDetector([ + BindingRecord.createForElement(ast("a"), 0, "a") + ]); + + var dispatcher = new TestDispatcher(); + var cd = pcd.instantiate(dispatcher); + cd.hydrate(new TestData('value'), null, null); + + expect(() => { + cd.checkNoChanges(); + }).toThrowError(new RegExp("Expression 'a in location' has changed after it was checked")); + }); + + it("should not break the next run", () => { + var pcd = createProtoChangeDetector([ + BindingRecord.createForElement(ast("a"), 0, "a") + ]); + + var dispatcher = new TestDispatcher(); + var cd = pcd.instantiate(dispatcher); + cd.hydrate(new TestData('value'), null, null); + + expect(() => cd.checkNoChanges()).toThrowError(new RegExp( + "Expression 'a in location' has changed after it was checked.")); + + cd.detectChanges(); + + expect(dispatcher.loggedValues).toEqual(['value']); + }); + }); + + //TODO vsavkin: implement it + describe("error handling", () => { + xit("should wrap exceptions into ChangeDetectionError", () => { + var pcd = createProtoChangeDetector(); + var cd = pcd.instantiate(new TestDispatcher(), [ + BindingRecord.createForElement(ast("invalidProp"), 0, "a") + ], null, []); + cd.hydrate(null, null); + + try { + cd.detectChanges(); + + throw new BaseException("fail"); + } catch (e) { + expect(e).toBeAnInstanceOf(ChangeDetectionError); + expect(e.location).toEqual("invalidProp in someComponent"); + } + }); + }); + + describe("Locals", () => { + it('should read a value from locals', () => { + var locals = new Locals(null, + MapWrapper.createFromPairs([["key", "value"]])); + + expect(executeWatch('key', 'key', null, locals)) + .toEqual(['key=value']); + }); + + it('should invoke a function from local', () => { + var locals = new Locals(null, + MapWrapper.createFromPairs([["key", () => "value"]])); + + expect(executeWatch('key', 'key()', null, locals)) + .toEqual(['key=value']); + }); + + it('should handle nested locals', () => { + var nested = new Locals(null, + MapWrapper.createFromPairs([["key", "value"]])); + var locals = new Locals(nested, MapWrapper.create()); + + expect(executeWatch('key', 'key', null, locals)) + .toEqual(['key=value']); + }); + + it("should fall back to a regular field read when the locals map" + + "does not have the requested field", () => { + var locals = new Locals(null, + MapWrapper.createFromPairs([["key", "value"]])); + + expect(executeWatch('name', 'name', new Person("Jim"), locals)) + .toEqual(['name=Jim']); + }); + + it('should correctly handle nested properties', () => { + var address = new Address('Grenoble'); + var person = new Person('Victor', address); + var locals = new Locals(null, + MapWrapper.createFromPairs([['city', 'MTV']])); + expect(executeWatch('address.city', 'address.city', person, locals)) + .toEqual(['address.city=Grenoble']); + expect(executeWatch('city', 'city', person, locals)) + .toEqual(['city=MTV']); + }); + + }); + + describe("handle children", () => { + var parent, child; + + beforeEach(() => { + parent = createProtoChangeDetector([]).instantiate(null); + child = createProtoChangeDetector([]).instantiate(null); + }); + + it("should add light dom children", () => { + parent.addChild(child); + + expect(parent.lightDomChildren.length).toEqual(1); + expect(parent.lightDomChildren[0]).toBe(child); + }); + + it("should add shadow dom children", () => { + parent.addShadowDomChild(child); + + expect(parent.shadowDomChildren.length).toEqual(1); + expect(parent.shadowDomChildren[0]).toBe(child); + }); + + it("should remove light dom children", () => { + parent.addChild(child); + parent.removeChild(child); + + expect(parent.lightDomChildren).toEqual([]); + }); + + it("should remove shadow dom children", () => { + parent.addShadowDomChild(child); + parent.removeShadowDomChild(child); + + expect(parent.shadowDomChildren.length).toEqual(0); + }); + }); + }); + + describe("mode", () => { + it("should set the mode to CHECK_ALWAYS when the default change detection is used", () => { + var proto = createProtoChangeDetector([], [], [], null, DEFAULT); + var cd = proto.instantiate(null); + + expect(cd.mode).toEqual(null); + + cd.hydrate(null, null, null); + + expect(cd.mode).toEqual(CHECK_ALWAYS); + }); + + it("should set the mode to CHECK_ONCE when the push change detection is used", () => { + var proto = createProtoChangeDetector([], [], [], null, ON_PUSH); + var cd = proto.instantiate(null); + cd.hydrate(null, null, null); + + expect(cd.mode).toEqual(CHECK_ONCE); + }); + + it("should not check a detached change detector", () => { + var c = createChangeDetector('name', 'a', new TestData("value")); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.mode = DETACHED; + cd.detectChanges(); + + expect(dispatcher.log).toEqual([]); + }); + + it("should not check a checked change detector", () => { + var c = createChangeDetector('name', 'a', new TestData("value")); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.mode = CHECKED; + cd.detectChanges(); + + expect(dispatcher.log).toEqual([]); + }); + + it("should change CHECK_ONCE to CHECKED", () => { + var cd = createProtoChangeDetector([]).instantiate(null); + cd.mode = CHECK_ONCE; + + cd.detectChanges(); + + expect(cd.mode).toEqual(CHECKED); + }); + + it("should not change the CHECK_ALWAYS", () => { + var cd = createProtoChangeDetector([]).instantiate(null); + cd.mode = CHECK_ALWAYS; + + cd.detectChanges(); + + expect(cd.mode).toEqual(CHECK_ALWAYS); + }); + + describe("marking ON_PUSH detectors as CHECK_ONCE after an update", () => { + var checkedDetector; + var dirRecordWithOnPush; + var updateDirWithOnPushRecord; + var directives; + + beforeEach(() => { + var proto = createProtoChangeDetector([], [], [], null, ON_PUSH); + checkedDetector = proto.instantiate(null); + checkedDetector.hydrate(null, null, null); + checkedDetector.mode = CHECKED; + + // this directive is a component with ON_PUSH change detection + dirRecordWithOnPush = new DirectiveRecord(new DirectiveIndex(0, 0), false, false, ON_PUSH); + + // a record updating a component + updateDirWithOnPushRecord = + BindingRecord.createForDirective(ast("42"), "a", (o,v) => o.a = v, dirRecordWithOnPush); + + var targetDirective = new TestData(null); + directives = new FakeDirectives([targetDirective], [checkedDetector]); + }); + + it("should set the mode to CHECK_ONCE when a binding is updated", () => { + var proto = createProtoChangeDetector([updateDirWithOnPushRecord], [], [dirRecordWithOnPush]); + + var cd = proto.instantiate(null); + cd.hydrate(null, null, directives); + + expect(checkedDetector.mode).toEqual(CHECKED); + + // evaluate the record, update the targetDirective, and mark its detector as CHECK_ONCE + cd.detectChanges(); + + expect(checkedDetector.mode).toEqual(CHECK_ONCE); + }); + }); + }); + + describe("markPathToRootAsCheckOnce", () => { + function changeDetector(mode, parent) { + var cd = createProtoChangeDetector([]).instantiate(null); + cd.mode = mode; + if (isPresent(parent)) parent.addChild(cd); + return cd; + } + + it("should mark all checked detectors as CHECK_ONCE " + + "until reaching a detached one", () => { + + var root = changeDetector(CHECK_ALWAYS, null); + var disabled = changeDetector(DETACHED, root); + var parent = changeDetector(CHECKED, disabled); + var checkAlwaysChild = changeDetector(CHECK_ALWAYS, parent); + var checkOnceChild = changeDetector(CHECK_ONCE, checkAlwaysChild); + var checkedChild = changeDetector(CHECKED, checkOnceChild); + + checkedChild.markPathToRootAsCheckOnce(); + + expect(root.mode).toEqual(CHECK_ALWAYS); + expect(disabled.mode).toEqual(DETACHED); + expect(parent.mode).toEqual(CHECK_ONCE); + expect(checkAlwaysChild.mode).toEqual(CHECK_ALWAYS); + expect(checkOnceChild.mode).toEqual(CHECK_ONCE); + expect(checkedChild.mode).toEqual(CHECK_ONCE); + }); + }); + + describe("hydration", () => { + it("should be able to rehydrate a change detector", () => { + var c = createChangeDetector("memo", "name"); + var cd = c["changeDetector"]; + + cd.hydrate("some context", null, null); + expect(cd.hydrated()).toBe(true); + + cd.dehydrate(); + expect(cd.hydrated()).toBe(false); + + cd.hydrate("other context", null, null); + expect(cd.hydrated()).toBe(true); + }); + + it("should destroy all active pipes during dehyration", () => { + var pipe = new OncePipe(); + var registry = new FakePipeRegistry('pipe', () => pipe); + var c = createChangeDetector("memo", "name | pipe", new Person('bob'), null, registry); + var cd = c["changeDetector"]; + + cd.detectChanges(); + + cd.dehydrate(); + + expect(pipe.destroyCalled).toBe(true); + }); + }); + + describe("pipes", () => { + it("should support pipes", () => { + var registry = new FakePipeRegistry('pipe', () => new CountingPipe()); + var ctx = new Person("Megatron"); + + var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.detectChanges(); + + expect(dispatcher.log).toEqual(['memo=Megatron state:0']); + + dispatcher.clear(); + cd.detectChanges(); + + expect(dispatcher.log).toEqual(['memo=Megatron state:1']); + }); + + it("should lookup pipes in the registry when the context is not supported", () => { + var registry = new FakePipeRegistry('pipe', () => new OncePipe()); + var ctx = new Person("Megatron"); + + var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); + var cd = c["changeDetector"]; + + cd.detectChanges(); + + expect(registry.numberOfLookups).toEqual(1); + + ctx.name = "Optimus Prime"; + cd.detectChanges(); + + expect(registry.numberOfLookups).toEqual(2); + }); + + it("should invoke onDestroy on a pipe before switching to another one", () => { + var pipe = new OncePipe(); + var registry = new FakePipeRegistry('pipe', () => pipe); + var ctx = new Person("Megatron"); + + var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); + var cd = c["changeDetector"]; + + cd.detectChanges(); + ctx.name = "Optimus Prime"; + cd.detectChanges(); + + expect(pipe.destroyCalled).toEqual(true); + }); + + it("should inject the ChangeDetectorRef " + + "of the encompassing component into a pipe", () => { + + var registry = new FakePipeRegistry('pipe', () => new IdentityPipe()); + var c = createChangeDetector("memo", "name | pipe", new Person('bob'), null, registry); + var cd = c["changeDetector"]; + + cd.detectChanges(); + + expect(registry.cdRef).toBe(cd.ref); + }); + }); + + it("should do nothing when no change", () => { + var registry = new FakePipeRegistry('pipe', () => new IdentityPipe()) + var ctx = new Person("Megatron"); + + var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.detectChanges(); + + expect(dispatcher.log).toEqual(['memo=Megatron']); + + dispatcher.clear(); + cd.detectChanges(); + + expect(dispatcher.log).toEqual([]); + }); + + it("should unwrap the wrapped value", () => { + var registry = new FakePipeRegistry('pipe', () => new WrappedPipe()) + var ctx = new Person("Megatron"); + + var c = createChangeDetector("memo", "name | pipe", ctx, null, registry); + var cd = c["changeDetector"]; + var dispatcher = c["dispatcher"]; + + cd.detectChanges(); + + expect(dispatcher.log).toEqual(['memo=Megatron']); + }); + }); + }); +} + +class CountingPipe extends Pipe { + state:number; + + constructor() { + super(); + this.state = 0; + } + + supports(newValue) { + return true; + } + + transform(value) { + return `${value} state:${this.state ++}`; + } +} + +class OncePipe extends Pipe { + called:boolean; + destroyCalled:boolean; + + constructor() { + super(); + this.called = false; + this.destroyCalled = false; + } + + supports(newValue) { + return !this.called; + } + + onDestroy() { + this.destroyCalled = true; + } + + transform(value) { + this.called = true; + return value; + } +} + +class IdentityPipe extends Pipe { + transform(value) { + return value; + } +} + +class WrappedPipe extends Pipe { + transform(value) { + return WrappedValue.wrap(value); + } +} + +class FakePipeRegistry extends PipeRegistry { + numberOfLookups:number; + pipeType:string; + factory:Function; + cdRef:any; + + constructor(pipeType, factory) { + super({}); + this.pipeType = pipeType; + this.factory = factory; + this.numberOfLookups = 0; + } + + get(type:string, obj, cdRef) { + if (type != this.pipeType) return null; + this.numberOfLookups ++; + this.cdRef = cdRef; + return this.factory(); + } +} + +class TestDirective { + a; + b; + changes; + onChangesDoneCalled; + onChangesDoneSpy; + + constructor(onChangesDoneSpy = null) { + this.onChangesDoneCalled = false; + this.onChangesDoneSpy = onChangesDoneSpy; + this.a = null; + this.b = null; + this.changes = null; + } + + onChange(changes) { + var r = {}; + StringMapWrapper.forEach(changes, (c, key) => r[key] = c.currentValue); + this.changes = r; + } + + onAllChangesDone() { + this.onChangesDoneCalled = true; + if(isPresent(this.onChangesDoneSpy)) { + this.onChangesDoneSpy(); + } + } +} + +class Person { + name:string; + age:number; + address:Address; + constructor(name:string, address:Address = null) { + this.name = name; + this.address = address; + } + + sayHi(m) { + return `Hi, ${m}`; + } + + toString():string { + var address = this.address == null ? '' : ' address=' + this.address.toString(); + + return 'name=' + this.name + address; + } +} + +class Address { + city:string; + constructor(city:string) { + this.city = city; + } + + toString():string { + return this.city; + } +} + +class Uninitialized { + value:any; +} + +class TestData { + a; + + constructor(a) { + this.a = a; + } +} + +class FakeDirectives { + directives:List; + detectors:List; + + constructor(directives:List, detectors:List) { + this.directives = directives; + this.detectors = detectors; + } + + getDirectiveFor(di:DirectiveIndex) { + return this.directives[di.directiveIndex]; + } + + getDetectorFor(di:DirectiveIndex) { + return this.detectors[di.directiveIndex]; + } +} + +class TestDispatcher extends ChangeDispatcher { + log:List; + loggedValues:List; + + constructor() { + super(); + this.clear(); + } + + clear() { + this.log = ListWrapper.create(); + this.loggedValues = ListWrapper.create(); + } + + notifyOnBinding(binding, value) { + ListWrapper.push(this.log, `${binding.propertyName}=${this._asString(value)}`); + ListWrapper.push(this.loggedValues, value); + } + + _asString(value) { + return (isBlank(value) ? 'null' : value.toString()); + } +} diff --git a/modules/benchmarks/src/change_detection/change_detection_benchmark.js b/modules/benchmarks/src/change_detection/change_detection_benchmark.js index 4c3863a2f2..90f1fa109e 100644 --- a/modules/benchmarks/src/change_detection/change_detection_benchmark.js +++ b/modules/benchmarks/src/change_detection/change_detection_benchmark.js @@ -10,6 +10,7 @@ import { ChangeDetection, DynamicChangeDetection, JitChangeDetection, + ChangeDetectorDefinition, BindingRecord, DirectiveRecord, DirectiveIndex, @@ -187,7 +188,7 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations, objec var dispatcher = new DummyDispatcher(); var parser = new Parser(new Lexer()); - var parentProto = changeDetection.createProtoChangeDetector('parent', [], [], []); + var parentProto = changeDetection.createProtoChangeDetector(new ChangeDetectorDefinition('parent', null, [], [], [])); var parentCd = parentProto.instantiate(dispatcher); var directiveRecord = new DirectiveRecord(new DirectiveIndex(0, 0), false, false, DEFAULT); @@ -204,7 +205,7 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations, objec BindingRecord.createForDirective(parser.parseBinding('field9', null), "field9", reflector.setter("field9"), directiveRecord) ]; - var proto = changeDetection.createProtoChangeDetector("proto", bindings, [], [directiveRecord]); + var proto = changeDetection.createProtoChangeDetector(new ChangeDetectorDefinition("proto", null, [], bindings, [directiveRecord])); var targetObj = new Obj(); for (var i = 0; i < iterations; ++i) {