diff --git a/modules/angular2/src/change_detection/change_detection_jit_generator.ts b/modules/angular2/src/change_detection/change_detection_jit_generator.ts index ed212a7c6e..6d2548981a 100644 --- a/modules/angular2/src/change_detection/change_detection_jit_generator.ts +++ b/modules/angular2/src/change_detection/change_detection_jit_generator.ts @@ -71,7 +71,6 @@ export class ChangeDetectorJITGenerator { return new ${this._typeName}(dispatcher, protos, directiveRecords); } `; - return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos', 'directiveRecords', classDefinition)( AbstractChangeDetector, ChangeDetectionUtil, this.records, this.directiveRecords); @@ -171,7 +170,6 @@ export class ChangeDetectorJITGenerator { var oldValue = this._names.getFieldName(r.selfIndex); var newValue = this._names.getLocalName(r.selfIndex); - var change = this._names.getChangeName(r.selfIndex); var pipe = this._names.getPipeName(r.selfIndex); var cdRef = "this.ref"; @@ -179,7 +177,7 @@ export class ChangeDetectorJITGenerator { var protoIndex = r.selfIndex - 1; var pipeType = r.name; - return ` + var read = ` ${this._names.getCurrentProtoName()} = ${this._names.getProtosName()}[${protoIndex}]; if (${pipe} === ${UTIL}.uninitialized) { ${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef}); @@ -187,16 +185,20 @@ export class ChangeDetectorJITGenerator { ${pipe}.onDestroy(); ${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef}); } - ${newValue} = ${pipe}.transform(${context}, [${argString}]); + `; + + var check = ` if (${oldValue} !== ${newValue}) { - ${newValue} = ${UTIL}.unwrapValue(${newValue}); - ${change} = true; + ${newValue} = ${UTIL}.unwrapValue(${newValue}) + ${this._genChangeMarker(r)} ${this._genUpdateDirectiveOrElement(r)} ${this._genAddToChanges(r)} ${oldValue} = ${newValue}; } `; + + return r.shouldBeChecked() ? `${read}${check}` : read; } _genReferenceCheck(r: ProtoRecord): string { @@ -204,22 +206,32 @@ export class ChangeDetectorJITGenerator { var newValue = this._names.getLocalName(r.selfIndex); var protoIndex = r.selfIndex - 1; - var check = ` + + var read = ` ${this._names.getCurrentProtoName()} = ${this._names.getProtosName()}[${protoIndex}]; ${this._genUpdateCurrentValue(r)} + `; + + var check = ` if (${newValue} !== ${oldValue}) { - ${this._names.getChangeName(r.selfIndex)} = true; + ${this._genChangeMarker(r)} ${this._genUpdateDirectiveOrElement(r)} ${this._genAddToChanges(r)} ${oldValue} = ${newValue}; } `; + var genCode = r.shouldBeChecked() ? `${read}${check}` : read; + if (r.isPureFunction()) { var condition = r.args.map((a) => this._names.getChangeName(a)).join(" || "); - return `if (${condition}) { ${check} } else { ${newValue} = ${oldValue}; }`; + if (r.isUsedByOtherRecord()) { + return `if (${condition}) { ${genCode} } else { ${newValue} = ${oldValue}; }`; + } else { + return `if (${condition}) { ${genCode} }`; + } } else { - return check; + return genCode; } } @@ -267,6 +279,10 @@ export class ChangeDetectorJITGenerator { rhs = `${UTIL}.${r.name}(${argString})`; break; + case RecordType.COLLECTION_LITERAL: + rhs = `${UTIL}.${r.name}(${argString})`; + break; + case RecordType.INTERPOLATE: rhs = this._genInterpolation(r); break; @@ -293,6 +309,10 @@ export class ChangeDetectorJITGenerator { return res; } + _genChangeMarker(r: ProtoRecord): string { + return r.argumentToPureFunction ? `${this._names.getChangeName(r.selfIndex)} = true` : ``; + } + _genUpdateDirectiveOrElement(r: ProtoRecord): string { if (!r.lastInBinding) return ""; diff --git a/modules/angular2/src/change_detection/coalesce.ts b/modules/angular2/src/change_detection/coalesce.ts index 50bd05bf0f..d2d7796437 100644 --- a/modules/angular2/src/change_detection/coalesce.ts +++ b/modules/angular2/src/change_detection/coalesce.ts @@ -12,7 +12,7 @@ import {RecordType, ProtoRecord} from './proto_record'; * Records that are last in bindings CANNOT be removed, and instead are * replaced with very cheap SELF records. */ -export function coalesce(records: List): List { +export function coalesce(records: ProtoRecord[]): ProtoRecord[] { var res: List = []; var indexMap: Map = new Map(); @@ -24,8 +24,13 @@ export function coalesce(records: List): List { if (isPresent(matchingRecord) && record.lastInBinding) { res.push(_selfRecord(record, matchingRecord.selfIndex, res.length + 1)); indexMap.set(r.selfIndex, matchingRecord.selfIndex); + matchingRecord.referencedBySelf = true; } else if (isPresent(matchingRecord) && !record.lastInBinding) { + if (record.argumentToPureFunction) { + matchingRecord.argumentToPureFunction = true; + } + indexMap.set(r.selfIndex, matchingRecord.selfIndex); } else { @@ -40,7 +45,7 @@ export function coalesce(records: List): List { function _selfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): ProtoRecord { return new ProtoRecord(RecordType.SELF, "self", null, [], r.fixedArgs, contextIndex, r.directiveIndex, selfIndex, r.bindingRecord, r.expressionAsString, - r.lastInBinding, r.lastInDirective); + r.lastInBinding, r.lastInDirective, false, false); } function _findMatching(r: ProtoRecord, rs: List) { @@ -66,7 +71,8 @@ function _replaceIndices(r: ProtoRecord, selfIndex: number, indexMap: Map, value: number) { diff --git a/modules/angular2/src/change_detection/codegen_name_util.ts b/modules/angular2/src/change_detection/codegen_name_util.ts index 84d658aadf..0efd100aba 100644 --- a/modules/angular2/src/change_detection/codegen_name_util.ts +++ b/modules/angular2/src/change_detection/codegen_name_util.ts @@ -83,9 +83,14 @@ export class CodegenNameUtil { if (i == CONTEXT_INDEX) { declarations.push(`${this.getLocalName(i)} = ${this.getFieldName(i)}`); } else { - var changeName = this.getChangeName(i); - declarations.push(`${this.getLocalName(i)},${changeName}`); - assignments.push(changeName); + var rec = this.records[i - 1]; + if (rec.argumentToPureFunction) { + var changeName = this.getChangeName(i); + declarations.push(`${this.getLocalName(i)},${changeName}`); + assignments.push(changeName); + } else { + declarations.push(`${this.getLocalName(i)}`); + } } } var assignmentsCode = @@ -100,14 +105,18 @@ export class CodegenNameUtil { getAllFieldNames(): List { var fieldList = []; for (var k = 0, kLen = this.getFieldCount(); k < kLen; ++k) { - fieldList.push(this.getFieldName(k)); + if (k === 0 || this.records[k - 1].shouldBeChecked()) { + fieldList.push(this.getFieldName(k)); + } } + for (var i = 0, iLen = this.records.length; i < iLen; ++i) { var rec = this.records[i]; if (rec.isPipeRecord()) { fieldList.push(this.getPipeName(rec.selfIndex)); } } + for (var j = 0, jLen = this.directiveRecords.length; j < jLen; ++j) { var dRec = this.directiveRecords[j]; fieldList.push(this.getDirectiveName(dRec.directiveIndex)); diff --git a/modules/angular2/src/change_detection/dynamic_change_detector.ts b/modules/angular2/src/change_detection/dynamic_change_detector.ts index 2c20e26f1e..eb20e58c56 100644 --- a/modules/angular2/src/change_detection/dynamic_change_detector.ts +++ b/modules/angular2/src/change_detection/dynamic_change_detector.ts @@ -150,26 +150,30 @@ export class DynamicChangeDetector extends AbstractChangeDetector { return null; } - var prevValue = this._readSelf(proto); var currValue = this._calculateCurrValue(proto); + if (proto.shouldBeChecked()) { + var prevValue = this._readSelf(proto); + if (!isSame(prevValue, currValue)) { + if (proto.lastInBinding) { + var change = ChangeDetectionUtil.simpleChange(prevValue, currValue); + if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change); - if (!isSame(prevValue, currValue)) { - if (proto.lastInBinding) { - var change = ChangeDetectionUtil.simpleChange(prevValue, currValue); - if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change); - - this._writeSelf(proto, currValue); - this._setChanged(proto, true); - - return change; - + this._writeSelf(proto, currValue); + this._setChanged(proto, true); + return change; + } else { + this._writeSelf(proto, currValue); + this._setChanged(proto, true); + return null; + } } else { - this._writeSelf(proto, currValue); - this._setChanged(proto, true); + this._setChanged(proto, false); return null; } + } else { - this._setChanged(proto, false); + this._writeSelf(proto, currValue); + this._setChanged(proto, true); return null; } } @@ -215,6 +219,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { case RecordType.INTERPOLATE: case RecordType.PRIMITIVE_OP: + case RecordType.COLLECTION_LITERAL: return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto)); default: @@ -227,28 +232,34 @@ export class DynamicChangeDetector extends AbstractChangeDetector { var args = this._readArgs(proto); var pipe = this._pipeFor(proto, context); - var prevValue = this._readSelf(proto); var currValue = pipe.transform(context, args); - if (!isSame(prevValue, currValue)) { - currValue = ChangeDetectionUtil.unwrapValue(currValue); + if (proto.shouldBeChecked()) { + var prevValue = this._readSelf(proto); + if (!isSame(prevValue, currValue)) { + currValue = ChangeDetectionUtil.unwrapValue(currValue); - if (proto.lastInBinding) { - var change = ChangeDetectionUtil.simpleChange(prevValue, currValue); - if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change); + if (proto.lastInBinding) { + var change = ChangeDetectionUtil.simpleChange(prevValue, currValue); + if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change); - this._writeSelf(proto, currValue); - this._setChanged(proto, true); + this._writeSelf(proto, currValue); + this._setChanged(proto, true); - return change; + return change; + } else { + this._writeSelf(proto, currValue); + this._setChanged(proto, true); + return null; + } } else { - this._writeSelf(proto, currValue); - this._setChanged(proto, true); + this._setChanged(proto, false); return null; } } else { - this._setChanged(proto, false); + this._writeSelf(proto, currValue); + this._setChanged(proto, true); return null; } } @@ -284,7 +295,9 @@ export class DynamicChangeDetector extends AbstractChangeDetector { _writePipe(proto: ProtoRecord, value) { this.localPipes[proto.selfIndex] = value; } - _setChanged(proto: ProtoRecord, value: boolean) { this.changes[proto.selfIndex] = value; } + _setChanged(proto: ProtoRecord, value: boolean) { + if (proto.argumentToPureFunction) this.changes[proto.selfIndex] = value; + } _pureFuncAndArgsDidNotChange(proto: ProtoRecord): boolean { return proto.isPureFunction() && !this._argsChanged(proto); diff --git a/modules/angular2/src/change_detection/proto_change_detector.ts b/modules/angular2/src/change_detection/proto_change_detector.ts index 7dac88eebd..6176cadd73 100644 --- a/modules/angular2/src/change_detection/proto_change_detector.ts +++ b/modules/angular2/src/change_detection/proto_change_detector.ts @@ -65,11 +65,23 @@ export class ProtoRecordBuilder { if (isPresent(oldLast) && oldLast.bindingRecord.directiveRecord == b.directiveRecord) { oldLast.lastInDirective = false; } + var numberOfRecordsBefore = this.records.length; this._appendRecords(b, variableNames); var newLast = ListWrapper.last(this.records); if (isPresent(newLast) && newLast !== oldLast) { newLast.lastInBinding = true; newLast.lastInDirective = true; + this._setArgumentToPureFunction(numberOfRecordsBefore); + } + } + + _setArgumentToPureFunction(startIndex: number): void { + for (var i = startIndex; i < this.records.length; ++i) { + var rec = this.records[i]; + if (rec.isPureFunction()) { + rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction = + true); + } } } @@ -77,7 +89,7 @@ export class ProtoRecordBuilder { if (b.isDirectiveLifecycle()) { this.records.push(new ProtoRecord(RecordType.DIRECTIVE_LIFECYCLE, b.lifecycleEvent, null, [], [], -1, null, this.records.length + 1, b, null, false, - false)); + false, false, false)); } else { _ConvertAstIntoProtoRecords.append(this.records, b, variableNames); } @@ -145,12 +157,13 @@ class _ConvertAstIntoProtoRecords implements AstVisitor { visitLiteralArray(ast: LiteralArray): number { var primitiveName = `arrayFn${ast.expressions.length}`; - return this._addRecord(RecordType.PRIMITIVE_OP, primitiveName, _arrayFn(ast.expressions.length), - this._visitAll(ast.expressions), null, 0); + return this._addRecord(RecordType.COLLECTION_LITERAL, primitiveName, + _arrayFn(ast.expressions.length), this._visitAll(ast.expressions), null, + 0); } visitLiteralMap(ast: LiteralMap): number { - return this._addRecord(RecordType.PRIMITIVE_OP, _mapPrimitiveName(ast.keys), + return this._addRecord(RecordType.COLLECTION_LITERAL, _mapPrimitiveName(ast.keys), ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null, 0); } @@ -208,11 +221,11 @@ class _ConvertAstIntoProtoRecords implements AstVisitor { if (context instanceof DirectiveIndex) { this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, -1, context, selfIndex, this._bindingRecord, this._expressionAsString, - false, false)); + false, false, false, false)); } else { this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, null, selfIndex, this._bindingRecord, this._expressionAsString, - false, false)); + false, false, false, false)); } return selfIndex; } diff --git a/modules/angular2/src/change_detection/proto_record.ts b/modules/angular2/src/change_detection/proto_record.ts index df6458cf84..25c1359837 100644 --- a/modules/angular2/src/change_detection/proto_record.ts +++ b/modules/angular2/src/change_detection/proto_record.ts @@ -14,6 +14,7 @@ export enum RecordType { PIPE, INTERPOLATE, SAFE_PROPERTY, + COLLECTION_LITERAL, SAFE_INVOKE_METHOD, DIRECTIVE_LIFECYCLE } @@ -23,10 +24,17 @@ export class ProtoRecord { public args: List, public fixedArgs: List, public contextIndex: number, public directiveIndex: DirectiveIndex, public selfIndex: number, public bindingRecord: BindingRecord, public expressionAsString: string, - public lastInBinding: boolean, public lastInDirective: boolean) {} + public lastInBinding: boolean, public lastInDirective: boolean, + public argumentToPureFunction: boolean, public referencedBySelf: boolean) {} isPureFunction(): boolean { - return this.mode === RecordType.INTERPOLATE || this.mode === RecordType.PRIMITIVE_OP; + return this.mode === RecordType.INTERPOLATE || this.mode === RecordType.COLLECTION_LITERAL; + } + + isUsedByOtherRecord(): boolean { return !this.lastInBinding || this.referencedBySelf; } + + shouldBeChecked(): boolean { + return this.argumentToPureFunction || this.lastInBinding || this.isPureFunction(); } isPipeRecord(): boolean { return this.mode === RecordType.PIPE; } diff --git a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart index 912f747f31..38e28dc2d9 100644 --- a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart +++ b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart @@ -246,14 +246,14 @@ class _CodegenState { var oldValue = _names.getFieldName(r.selfIndex); var newValue = _names.getLocalName(r.selfIndex); - var change = _names.getChangeName(r.selfIndex); var pipe = _names.getPipeName(r.selfIndex); var cdRef = 'this.ref'; var protoIndex = r.selfIndex - 1; var pipeType = r.name; - return ''' + + var read = ''' ${_names.getCurrentProtoName()} = ${_names.getProtosName()}[$protoIndex]; if ($_IDENTICAL_CHECK_FN($pipe, $_UTIL.uninitialized)) { $pipe = ${_names.getPipesAccessorName()}.get('$pipeType', $context, $cdRef); @@ -261,16 +261,20 @@ class _CodegenState { $pipe.onDestroy(); $pipe = ${_names.getPipesAccessorName()}.get('$pipeType', $context, $cdRef); } - $newValue = $pipe.transform($context, [$argString]); + '''; + + var check = ''' if ($_NOT_IDENTICAL_CHECK_FN($oldValue, $newValue)) { $newValue = $_UTIL.unwrapValue($newValue); - $change = true; + ${_genChangeMarker(r)} ${_genUpdateDirectiveOrElement(r)} ${_genAddToChanges(r)} $oldValue = $newValue; } '''; + + return r.shouldBeChecked() ? "${read}${check}" : read; } String _genReferenceCheck(ProtoRecord r) { @@ -278,22 +282,33 @@ class _CodegenState { var newValue = _names.getLocalName(r.selfIndex); var protoIndex = r.selfIndex - 1; - var check = ''' + + var read = ''' ${_names.getCurrentProtoName()} = ${_names.getProtosName()}[$protoIndex]; ${_genUpdateCurrentValue(r)} + '''; + + var check = ''' if ($_NOT_IDENTICAL_CHECK_FN($newValue, $oldValue)) { - ${_names.getChangeName(r.selfIndex)} = true; + ${_genChangeMarker(r)} ${_genUpdateDirectiveOrElement(r)} ${_genAddToChanges(r)} $oldValue = $newValue; } '''; + + var genCode = r.shouldBeChecked() ? "${read}${check}" : read; + if (r.isPureFunction()) { // Add an "if changed guard" var condition = r.args.map((a) => _names.getChangeName(a)).join(' || '); - return 'if ($condition) { $check } else { $newValue = $oldValue; }'; + if (r.isUsedByOtherRecord()) { + return 'if ($condition) { $genCode } else { $newValue = $oldValue; }'; + } else { + return 'if ($condition) { $genCode }'; + } } else { - return check; + return genCode; } } @@ -344,6 +359,10 @@ class _CodegenState { rhs = '$_UTIL.${r.name}($argString)'; break; + case RecordType.COLLECTION_LITERAL: + rhs = '$_UTIL.${r.name}($argString)'; + break; + case RecordType.INTERPOLATE: rhs = _genInterpolation(r); break; @@ -370,6 +389,10 @@ class _CodegenState { return '$res'; } + String _genChangeMarker(ProtoRecord r) { + return r.argumentToPureFunction ? "${this._names.getChangeName(r.selfIndex)} = true;" : ""; + } + String _genUpdateDirectiveOrElement(ProtoRecord r) { if (!r.lastInBinding) return ''; diff --git a/modules/angular2/test/change_detection/change_detector_config.ts b/modules/angular2/test/change_detection/change_detector_config.ts index 026a1a7b67..aeea49b83e 100644 --- a/modules/angular2/test/change_detection/change_detector_config.ts +++ b/modules/angular2/test/change_detection/change_detector_config.ts @@ -304,6 +304,7 @@ var _availableDefinitions = [ '{z: 1}', '{z: a}', 'name | pipe', + '(name | pipe).length', "name | pipe:'one':address.city", 'value', 'a', diff --git a/modules/angular2/test/change_detection/change_detector_spec.ts b/modules/angular2/test/change_detection/change_detector_spec.ts index 72c54c5758..b918e213fa 100644 --- a/modules/angular2/test/change_detection/change_detector_spec.ts +++ b/modules/angular2/test/change_detection/change_detector_spec.ts @@ -351,6 +351,14 @@ export function main() { val.changeDetector.detectChanges(); expect(val.dispatcher.loggedValues).toEqual(['value one two default']); }); + + it('should support pipes as arguments to pure functions', () => { + var registry = new FakePipes('pipe', () => new IdentityPipe()); + var person = new Person('bob'); + var val = _createChangeDetector('(name | pipe).length', person, registry); + val.changeDetector.detectChanges(); + expect(val.dispatcher.loggedValues).toEqual([3]); + }); }); it('should notify the dispatcher on all changes done', () => { diff --git a/modules/angular2/test/change_detection/coalesce_spec.ts b/modules/angular2/test/change_detection/coalesce_spec.ts index 6d3eb5c8a9..297ee59390 100644 --- a/modules/angular2/test/change_detection/coalesce_spec.ts +++ b/modules/angular2/test/change_detection/coalesce_spec.ts @@ -7,14 +7,22 @@ import {DirectiveIndex} from 'angular2/src/change_detection/directive_record'; export function main() { function r(funcOrValue, args, contextIndex, selfIndex, - {lastInBinding, mode, name, directiveIndex}: - {lastInBinding?: any, mode?: any, name?: any, directiveIndex?: any} = {}) { + {lastInBinding, mode, name, directiveIndex, argumentToPureFunction}: { + lastInBinding?: any, + mode?: any, + name?: any, + directiveIndex?: any, + argumentToPureFunction?: boolean + } = {}) { if (isBlank(lastInBinding)) lastInBinding = false; if (isBlank(mode)) mode = RecordType.PROPERTY; if (isBlank(name)) name = "name"; if (isBlank(directiveIndex)) directiveIndex = null; + if (isBlank(argumentToPureFunction)) argumentToPureFunction = false; + return new ProtoRecord(mode, name, funcOrValue, args, null, contextIndex, directiveIndex, - selfIndex, null, null, lastInBinding, false); + selfIndex, null, null, lastInBinding, false, argumentToPureFunction, + false); } describe("change detection - coalesce", () => { @@ -56,7 +64,14 @@ export function main() { [r("user", [], 0, 1, {lastInBinding: true}), r("user", [], 0, 2, {lastInBinding: true})]); expect(rs[1]).toEqual(new ProtoRecord(RecordType.SELF, "self", null, [], null, 1, null, 2, - null, null, true, false)); + null, null, true, false, false, false)); + }); + + it("should set referencedBySelf", () => { + var rs = coalesce( + [r("user", [], 0, 1, {lastInBinding: true}), r("user", [], 0, 2, {lastInBinding: true})]); + + expect(rs[0].referencedBySelf).toBeTruthy(); }); it("should not coalesce directive lifecycle records", () => { @@ -88,5 +103,26 @@ export function main() { ]); expect(rs.length).toEqual(4); }); + + it('should preserve the argumentToPureFunction property', () => { + var rs = coalesce([ + r("user", [], 0, 1), + r("user", [], 0, 2, {argumentToPureFunction: true}), + r("user", [], 0, 3), + r("name", [], 3, 4) + ]); + expect(rs) + .toEqual([r("user", [], 0, 1, {argumentToPureFunction: true}), r("name", [], 1, 2)]); + }); + + it('should preserve the argumentToPureFunction property (the original record)', () => { + var rs = coalesce([ + r("user", [], 0, 1, {argumentToPureFunction: true}), + r("user", [], 0, 2), + r("name", [], 2, 3) + ]); + expect(rs) + .toEqual([r("user", [], 0, 1, {argumentToPureFunction: true}), r("name", [], 1, 2)]); + }); }); } diff --git a/modules/angular2/test/change_detection/proto_record_builder_spec.ts b/modules/angular2/test/change_detection/proto_record_builder_spec.ts new file mode 100644 index 0000000000..06c27eadea --- /dev/null +++ b/modules/angular2/test/change_detection/proto_record_builder_spec.ts @@ -0,0 +1,38 @@ +import { + ddescribe, + describe, + it, + iit, + xit, + expect, + beforeEach, + afterEach, + inject +} from 'angular2/test_lib'; + +import {ProtoRecordBuilder} from 'angular2/src/change_detection/proto_change_detector'; +import {BindingRecord} from 'angular2/src/change_detection/binding_record'; +import {Parser} from 'angular2/src/change_detection/parser/parser'; + +export function main() { + describe("ProtoRecordBuilder", () => { + it('should set argumentToPureFunction flag', inject([Parser], (p: Parser) => { + var builder = new ProtoRecordBuilder(); + var ast = p.parseBinding("[1,2]", "location"); // collection literal is a pure function + builder.add(BindingRecord.createForElementProperty(ast, 0, "property"), []); + + var isPureFunc = builder.records.map(r => r.argumentToPureFunction); + expect(isPureFunc).toEqual([true, true, false]); + })); + + it('should not set argumentToPureFunction flag when not needed', + inject([Parser], (p: Parser) => { + var builder = new ProtoRecordBuilder(); + var ast = p.parseBinding("f(1,2)", "location"); + builder.add(BindingRecord.createForElementProperty(ast, 0, "property"), []); + + var isPureFunc = builder.records.map(r => r.argumentToPureFunction); + expect(isPureFunc).toEqual([false, false, false]); + })); + }); +} diff --git a/modules/angular2/test/change_detection/proto_record_spec.ts b/modules/angular2/test/change_detection/proto_record_spec.ts new file mode 100644 index 0000000000..3968b8e688 --- /dev/null +++ b/modules/angular2/test/change_detection/proto_record_spec.ts @@ -0,0 +1,58 @@ +import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; +import {isBlank} from 'angular2/src/facade/lang'; + +import {RecordType, ProtoRecord} from 'angular2/src/change_detection/proto_record'; + +export function main() { + function r({lastInBinding, mode, name, directiveIndex, argumentToPureFunction, referencedBySelf}: + { + lastInBinding?: any, + mode?: any, + name?: any, + directiveIndex?: any, + argumentToPureFunction?: boolean, + referencedBySelf?: boolean + } = {}) { + if (isBlank(lastInBinding)) lastInBinding = false; + if (isBlank(mode)) mode = RecordType.PROPERTY; + if (isBlank(name)) name = "name"; + if (isBlank(directiveIndex)) directiveIndex = null; + if (isBlank(argumentToPureFunction)) argumentToPureFunction = false; + if (isBlank(referencedBySelf)) referencedBySelf = false; + + return new ProtoRecord(mode, name, null, [], null, 0, directiveIndex, 0, null, null, + lastInBinding, false, argumentToPureFunction, referencedBySelf); + } + + describe("ProtoRecord", () => { + describe('shouldBeChecked', () => { + it('should be true for pure functions', () => { + expect(r({mode: RecordType.COLLECTION_LITERAL}).shouldBeChecked()).toBeTruthy(); + }); + + it('should be true for args of pure functions', () => { + expect(r({mode: RecordType.CONST, argumentToPureFunction: true}).shouldBeChecked()) + .toBeTruthy(); + }); + + it('should be true for last in binding records', () => { + expect(r({mode: RecordType.CONST, lastInBinding: true}).shouldBeChecked()).toBeTruthy(); + }); + + it('should be false otherwise', + () => { expect(r({mode: RecordType.CONST}).shouldBeChecked()).toBeFalsy(); }); + }); + + describe('isUsedByOtherRecord', () => { + it('should be false for lastInBinding records', + () => { expect(r({lastInBinding: true}).isUsedByOtherRecord()).toBeFalsy(); }); + + it('should be true for lastInBinding records that are referenced by self records', () => { + expect(r({lastInBinding: true, referencedBySelf: true}).isUsedByOtherRecord()).toBeTruthy(); + }); + + it('should be true for non lastInBinding records', + () => { expect(r({lastInBinding: false}).isUsedByOtherRecord()).toBeTruthy(); }); + }); + }); +} diff --git a/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart b/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart index a10e9cd167..cd9510846a 100644 --- a/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart +++ b/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart @@ -38,9 +38,8 @@ class _MyComponent_ChangeDetector0 var l_context = this.context, l_myNum0, c_myNum0, - l_interpolate1, - c_interpolate1; - c_myNum0 = c_interpolate1 = false; + l_interpolate1; + c_myNum0 = false; var isChanged = false; var changes = null; @@ -56,7 +55,6 @@ class _MyComponent_ChangeDetector0 l_interpolate1 = "Salad: " "${l_myNum0 == null ? "" : l_myNum0}" " is awesome"; if (_gen.looseNotIdentical(l_interpolate1, this.interpolate1)) { - c_interpolate1 = true; if (throwOnChange) { _gen.ChangeDetectionUtil.throwOnChange(this.currentProto, _gen.ChangeDetectionUtil.simpleChange( @@ -68,8 +66,6 @@ class _MyComponent_ChangeDetector0 this.interpolate1 = l_interpolate1; } - } else { - l_interpolate1 = this.interpolate1; } changes = null;