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 c6ff629935..2b87dcb68a 100644 --- a/modules/angular2/src/change_detection/change_detection_jit_generator.ts +++ b/modules/angular2/src/change_detection/change_detection_jit_generator.ts @@ -145,7 +145,7 @@ export class ChangeDetectorJITGenerator { _getNonNullPipeNames(): List { var pipes = []; this.records.forEach((r) => { - if (r.mode === RecordType.PIPE || r.mode === RecordType.BINDING_PIPE) { + if (r.isPipeRecord()) { pipes.push(this._pipeNames[r.selfIndex]); } }); @@ -245,7 +245,7 @@ export class ChangeDetectorJITGenerator { var change = this._changeNames[r.selfIndex]; var pipe = this._pipeNames[r.selfIndex]; - var cdRef = r.mode === RecordType.BINDING_PIPE ? "this.ref" : "null"; + var cdRef = "this.ref"; var protoIndex = r.selfIndex - 1; var pipeType = r.name; diff --git a/modules/angular2/src/change_detection/dynamic_change_detector.ts b/modules/angular2/src/change_detection/dynamic_change_detector.ts index 214f398201..3fd7ff5722 100644 --- a/modules/angular2/src/change_detection/dynamic_change_detector.ts +++ b/modules/angular2/src/change_detection/dynamic_change_detector.ts @@ -269,14 +269,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { if (isPresent(storedPipe)) { storedPipe.onDestroy(); } - - // Currently, only pipes that used in bindings in the template get - // the changeDetectorRef of the encompassing component. - // - // In the future, pipes declared in the bind configuration should - // be able to access the changeDetectorRef of that component. - var cdr = proto.mode === RecordType.BINDING_PIPE ? this.ref : null; - var pipe = this.pipeRegistry.get(proto.name, context, cdr); + var pipe = this.pipeRegistry.get(proto.name, context, this.ref); this._writePipe(proto, pipe); return pipe; } diff --git a/modules/angular2/src/change_detection/parser/ast.ts b/modules/angular2/src/change_detection/parser/ast.ts index 0aee6b9a48..91c75bed4a 100644 --- a/modules/angular2/src/change_detection/parser/ast.ts +++ b/modules/angular2/src/change_detection/parser/ast.ts @@ -141,11 +141,8 @@ export class KeyedAccess extends AST { visit(visitor: AstVisitor) { return visitor.visitKeyedAccess(this); } } -export class Pipe extends AST { - constructor(public exp: AST, public name: string, public args: List, - public inBinding: boolean) { - super(); - } +export class BindingPipe extends AST { + constructor(public exp: AST, public name: string, public args: List) { super(); } visit(visitor: AstVisitor) { return visitor.visitPipe(this); } } @@ -336,7 +333,7 @@ export interface AstVisitor { visitChain(ast: Chain): any; visitConditional(ast: Conditional): any; visitIf(ast: If): any; - visitPipe(ast: Pipe): any; + visitPipe(ast: BindingPipe): any; visitFunctionCall(ast: FunctionCall): any; visitImplicitReceiver(ast: ImplicitReceiver): any; visitInterpolation(ast: Interpolation): any; @@ -394,8 +391,8 @@ export class AstTransformer implements AstVisitor { ast.falseExp.visit(this)); } - visitPipe(ast: Pipe) { - return new Pipe(ast.exp.visit(this), ast.name, this.visitAll(ast.args), ast.inBinding); + visitPipe(ast: BindingPipe) { + return new BindingPipe(ast.exp.visit(this), ast.name, this.visitAll(ast.args)); } visitKeyedAccess(ast: KeyedAccess) { diff --git a/modules/angular2/src/change_detection/parser/parser.ts b/modules/angular2/src/change_detection/parser/parser.ts index 6399cba26a..70480a5557 100644 --- a/modules/angular2/src/change_detection/parser/parser.ts +++ b/modules/angular2/src/change_detection/parser/parser.ts @@ -34,7 +34,7 @@ import { PrefixNot, Conditional, If, - Pipe, + BindingPipe, Assignment, Chain, KeyedAccess, @@ -73,15 +73,6 @@ export class Parser { return new ASTWithSource(ast, input, location); } - addPipes(bindingAst: ASTWithSource, pipes: List): ASTWithSource { - if (ListWrapper.isEmpty(pipes)) return bindingAst; - - var res: AST = ListWrapper.reduce( - pipes, (result, currentPipeName) => new Pipe(result, currentPipeName, [], false), - bindingAst.ast); - return new ASTWithSource(res, bindingAst.source, bindingAst.location); - } - parseTemplateBindings(input: string, location: any): List { var tokens = this._lexer.tokenize(input); return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings(); @@ -224,7 +215,7 @@ class _ParseAST { while (this.optionalCharacter($COLON)) { args.push(this.parsePipe()); } - result = new Pipe(result, name, args, true); + result = new BindingPipe(result, name, args); } while (this.optionalOperator("|")); } diff --git a/modules/angular2/src/change_detection/pipes/iterable_changes.ts b/modules/angular2/src/change_detection/pipes/iterable_changes.ts index 757a48106e..18b419651a 100644 --- a/modules/angular2/src/change_detection/pipes/iterable_changes.ts +++ b/modules/angular2/src/change_detection/pipes/iterable_changes.ts @@ -15,12 +15,10 @@ import { isArray } from 'angular2/src/facade/lang'; -import {WrappedValue, Pipe, PipeFactory} from './pipe'; +import {WrappedValue, Pipe, BasePipe, PipeFactory} from './pipe'; @CONST() -export class IterableChangesFactory extends PipeFactory { - constructor() { super(); } - +export class IterableChangesFactory implements PipeFactory { supports(obj): boolean { return IterableChanges.supportsObj(obj); } create(cdRef): Pipe { return new IterableChanges(); } @@ -29,7 +27,7 @@ export class IterableChangesFactory extends PipeFactory { /** * @exportedAs angular2/pipes */ -export class IterableChanges extends Pipe { +export class IterableChanges extends BasePipe { private _collection = null; private _length: int = null; // Keeps track of the used records at any point in time (during & across `_check()` calls) @@ -95,7 +93,7 @@ export class IterableChanges extends Pipe { if (this.check(collection)) { return WrappedValue.wrap(this); } else { - return this; + return null; } } diff --git a/modules/angular2/src/change_detection/pipes/json_pipe.ts b/modules/angular2/src/change_detection/pipes/json_pipe.ts index f6ec672ae9..8d379b9246 100644 --- a/modules/angular2/src/change_detection/pipes/json_pipe.ts +++ b/modules/angular2/src/change_detection/pipes/json_pipe.ts @@ -1,5 +1,5 @@ import {isBlank, isPresent, Json} from 'angular2/src/facade/lang'; -import {Pipe, PipeFactory} from './pipe'; +import {Pipe, BasePipe, PipeFactory} from './pipe'; /** * Implements json transforms to any object. @@ -26,9 +26,7 @@ import {Pipe, PipeFactory} from './pipe'; * * @exportedAs angular2/pipes */ -export class JsonPipe extends Pipe { - supports(obj): boolean { return true; } - +export class JsonPipe extends BasePipe { transform(value): string { return Json.stringify(value); } create(cdRef): Pipe { return this } diff --git a/modules/angular2/src/change_detection/pipes/keyvalue_changes.ts b/modules/angular2/src/change_detection/pipes/keyvalue_changes.ts index f8d6b6e329..f1ee23d343 100644 --- a/modules/angular2/src/change_detection/pipes/keyvalue_changes.ts +++ b/modules/angular2/src/change_detection/pipes/keyvalue_changes.ts @@ -1,15 +1,13 @@ import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {stringify, looseIdentical, isJsObject, CONST} from 'angular2/src/facade/lang'; -import {WrappedValue, Pipe, PipeFactory} from './pipe'; +import {WrappedValue, BasePipe, Pipe, PipeFactory} from './pipe'; /** * @exportedAs angular2/pipes */ @CONST() -export class KeyValueChangesFactory extends PipeFactory { - constructor() { super(); } - +export class KeyValueChangesFactory implements PipeFactory { supports(obj): boolean { return KeyValueChanges.supportsObj(obj); } create(cdRef): Pipe { return new KeyValueChanges(); } @@ -18,7 +16,7 @@ export class KeyValueChangesFactory extends PipeFactory { /** * @exportedAs angular2/pipes */ -export class KeyValueChanges extends Pipe { +export class KeyValueChanges extends BasePipe { private _records: Map = new Map(); private _mapHead: KVChangeRecord = null; private _previousMapHead: KVChangeRecord = null; @@ -37,7 +35,7 @@ export class KeyValueChanges extends Pipe { if (this.check(map)) { return WrappedValue.wrap(this); } else { - return this; + return null; } } diff --git a/modules/angular2/src/change_detection/pipes/lowercase_pipe.ts b/modules/angular2/src/change_detection/pipes/lowercase_pipe.ts index 578102508c..28a0fb72e0 100644 --- a/modules/angular2/src/change_detection/pipes/lowercase_pipe.ts +++ b/modules/angular2/src/change_detection/pipes/lowercase_pipe.ts @@ -23,7 +23,7 @@ import {Pipe} from './pipe'; * * @exportedAs angular2/pipes */ -export class LowerCasePipe extends Pipe { +export class LowerCasePipe implements Pipe { _latestValue: string = null; supports(str): boolean { return isString(str); } diff --git a/modules/angular2/src/change_detection/pipes/null_pipe.ts b/modules/angular2/src/change_detection/pipes/null_pipe.ts index e7e037ac7e..a0e95db44e 100644 --- a/modules/angular2/src/change_detection/pipes/null_pipe.ts +++ b/modules/angular2/src/change_detection/pipes/null_pipe.ts @@ -1,13 +1,11 @@ import {isBlank, CONST} from 'angular2/src/facade/lang'; -import {Pipe, WrappedValue, PipeFactory} from './pipe'; +import {Pipe, BasePipe, WrappedValue, PipeFactory} from './pipe'; /** * @exportedAs angular2/pipes */ @CONST() -export class NullPipeFactory extends PipeFactory { - constructor() { super(); } - +export class NullPipeFactory implements PipeFactory { supports(obj): boolean { return NullPipe.supportsObj(obj); } create(cdRef): Pipe { return new NullPipe(); } @@ -16,7 +14,7 @@ export class NullPipeFactory extends PipeFactory { /** * @exportedAs angular2/pipes */ -export class NullPipe extends Pipe { +export class NullPipe extends BasePipe { called: boolean = false; static supportsObj(obj): boolean { return isBlank(obj); } diff --git a/modules/angular2/src/change_detection/pipes/observable_pipe.ts b/modules/angular2/src/change_detection/pipes/observable_pipe.ts index 2e775ed6a1..db02f1e130 100644 --- a/modules/angular2/src/change_detection/pipes/observable_pipe.ts +++ b/modules/angular2/src/change_detection/pipes/observable_pipe.ts @@ -29,14 +29,14 @@ import {ChangeDetectorRef} from '../change_detector_ref'; * * @exportedAs angular2/pipes */ -export class ObservablePipe extends Pipe { +export class ObservablePipe implements Pipe { _latestValue: Object = null; _latestReturnedValue: Object = null; _subscription: Object = null; _observable: Observable = null; - constructor(public _ref: ChangeDetectorRef) { super(); } + constructor(public _ref: ChangeDetectorRef) {} supports(obs): boolean { return ObservableWrapper.isObservable(obs); } @@ -91,9 +91,7 @@ export class ObservablePipe extends Pipe { * @exportedAs angular2/pipes */ @CONST() -export class ObservablePipeFactory extends PipeFactory { - constructor() { super(); } - +export class ObservablePipeFactory implements PipeFactory { supports(obs): boolean { return ObservableWrapper.isObservable(obs); } create(cdRef): Pipe { return new ObservablePipe(cdRef); } diff --git a/modules/angular2/src/change_detection/pipes/pipe.ts b/modules/angular2/src/change_detection/pipes/pipe.ts index 9593ef6829..dd575c1c42 100644 --- a/modules/angular2/src/change_detection/pipes/pipe.ts +++ b/modules/angular2/src/change_detection/pipes/pipe.ts @@ -36,11 +36,13 @@ var _wrappedIndex = 0; * #Example * * ``` - * class DoublePipe extends Pipe { + * class DoublePipe implements Pipe { * supports(obj) { * return true; * } * + * onDestroy() {} + * * transform(value) { * return `${value}${value}`; * } @@ -49,24 +51,34 @@ var _wrappedIndex = 0; * * @exportedAs angular2/pipes */ -export class Pipe { - supports(obj): boolean { return false; } - onDestroy() {} - transform(value: any): any { return null; } +export interface Pipe { + supports(obj): boolean; + onDestroy(): void; + transform(value: any): any; } -// TODO: vsavkin: make it an interface -@CONST() -export class PipeFactory { - supports(obs): boolean { - _abstract(); - return false; - } +/** + * Provides default implementation of supports and onDestroy. + * + * #Example + * + * ``` + * class DoublePipe extends BasePipe {* + * transform(value) { + * return `${value}${value}`; + * } + * } + * ``` + */ +export class BasePipe implements Pipe { + supports(obj): boolean { return true; } + onDestroy(): void {} + transform(value: any): any { return _abstract(); } +} - create(cdRef): Pipe { - _abstract(); - return null; - } +export interface PipeFactory { + supports(obs): boolean; + create(cdRef): Pipe; } function _abstract() { diff --git a/modules/angular2/src/change_detection/pipes/pipe_registry.ts b/modules/angular2/src/change_detection/pipes/pipe_registry.ts index da54a6add2..c278c0f851 100644 --- a/modules/angular2/src/change_detection/pipes/pipe_registry.ts +++ b/modules/angular2/src/change_detection/pipes/pipe_registry.ts @@ -1,6 +1,6 @@ import {List, ListWrapper} from 'angular2/src/facade/collection'; import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang'; -import {Pipe} from './pipe'; +import {Pipe, PipeFactory} from './pipe'; import {Injectable} from 'angular2/src/di/decorators'; import {ChangeDetectorRef} from '../change_detector_ref'; @@ -8,18 +8,31 @@ import {ChangeDetectorRef} from '../change_detector_ref'; export class PipeRegistry { constructor(public config) {} - get(type: string, obj, cdRef: ChangeDetectorRef): Pipe { - var listOfConfigs = this.config[type]; - if (isBlank(listOfConfigs)) { + get(type: string, obj, cdRef?: ChangeDetectorRef, existingPipe?: Pipe): Pipe { + if (isPresent(existingPipe) && existingPipe.supports(obj)) return existingPipe; + + if (isPresent(existingPipe)) existingPipe.onDestroy(); + + var factories = this._getListOfFactories(type, obj); + var factory = this._getMatchingFactory(factories, type, obj); + + return factory.create(cdRef); + } + + private _getListOfFactories(type: string, obj: any): PipeFactory[] { + var listOfFactories = this.config[type]; + if (isBlank(listOfFactories)) { throw new BaseException(`Cannot find '${type}' pipe supporting object '${obj}'`); } + return listOfFactories; + } - var matchingConfig = ListWrapper.find(listOfConfigs, (pipeConfig) => pipeConfig.supports(obj)); - - if (isBlank(matchingConfig)) { + private _getMatchingFactory(listOfFactories: PipeFactory[], type: string, obj: any): PipeFactory { + var matchingFactory = + ListWrapper.find(listOfFactories, pipeFactory => pipeFactory.supports(obj)); + if (isBlank(matchingFactory)) { throw new BaseException(`Cannot find '${type}' pipe supporting object '${obj}'`); } - - return matchingConfig.create(cdRef); + return matchingFactory; } } diff --git a/modules/angular2/src/change_detection/pipes/promise_pipe.ts b/modules/angular2/src/change_detection/pipes/promise_pipe.ts index 95b0956f3c..00ffaf10a5 100644 --- a/modules/angular2/src/change_detection/pipes/promise_pipe.ts +++ b/modules/angular2/src/change_detection/pipes/promise_pipe.ts @@ -28,12 +28,12 @@ import {ChangeDetectorRef} from '../change_detector_ref'; * * @exportedAs angular2/pipes */ -export class PromisePipe extends Pipe { +export class PromisePipe implements Pipe { _latestValue: Object = null; _latestReturnedValue: Object = null; _sourcePromise: Promise; - constructor(public _ref: ChangeDetectorRef) { super(); } + constructor(public _ref: ChangeDetectorRef) {} supports(promise): boolean { return isPromise(promise); } diff --git a/modules/angular2/src/change_detection/pipes/uppercase_pipe.ts b/modules/angular2/src/change_detection/pipes/uppercase_pipe.ts index 58b193a4b2..8c9da28d70 100644 --- a/modules/angular2/src/change_detection/pipes/uppercase_pipe.ts +++ b/modules/angular2/src/change_detection/pipes/uppercase_pipe.ts @@ -23,7 +23,7 @@ import {Pipe} from './pipe'; * * @exportedAs angular2/pipes */ -export class UpperCasePipe extends Pipe { +export class UpperCasePipe implements Pipe { _latestValue: string = null; supports(str): boolean { return isString(str); } diff --git a/modules/angular2/src/change_detection/proto_change_detector.ts b/modules/angular2/src/change_detection/proto_change_detector.ts index a06e00a1ba..03811f6d89 100644 --- a/modules/angular2/src/change_detection/proto_change_detector.ts +++ b/modules/angular2/src/change_detection/proto_change_detector.ts @@ -11,7 +11,7 @@ import { Chain, Conditional, If, - Pipe, + BindingPipe, FunctionCall, ImplicitReceiver, Interpolation, @@ -179,10 +179,9 @@ class _ConvertAstIntoProtoRecords implements AstVisitor { null, 0); } - visitPipe(ast: Pipe) { + visitPipe(ast: BindingPipe) { var value = ast.exp.visit(this); - var type = ast.inBinding ? RecordType.BINDING_PIPE : RecordType.PIPE; - return this._addRecord(type, ast.name, ast.name, [], null, value); + return this._addRecord(RecordType.PIPE, ast.name, ast.name, [], null, value); } visitKeyedAccess(ast: KeyedAccess) { diff --git a/modules/angular2/src/change_detection/proto_record.ts b/modules/angular2/src/change_detection/proto_record.ts index 976fc06b21..df6458cf84 100644 --- a/modules/angular2/src/change_detection/proto_record.ts +++ b/modules/angular2/src/change_detection/proto_record.ts @@ -12,7 +12,6 @@ export enum RecordType { INVOKE_CLOSURE, KEYED_ACCESS, PIPE, - BINDING_PIPE, INTERPOLATE, SAFE_PROPERTY, SAFE_INVOKE_METHOD, @@ -30,9 +29,7 @@ export class ProtoRecord { return this.mode === RecordType.INTERPOLATE || this.mode === RecordType.PRIMITIVE_OP; } - isPipeRecord(): boolean { - return this.mode === RecordType.PIPE || this.mode === RecordType.BINDING_PIPE; - } + isPipeRecord(): boolean { return this.mode === RecordType.PIPE; } isLifeCycleRecord(): boolean { return this.mode === RecordType.DIRECTIVE_LIFECYCLE; } } diff --git a/modules/angular2/src/directives/class.ts b/modules/angular2/src/directives/class.ts index 22b8654d2e..6255b71b80 100644 --- a/modules/angular2/src/directives/class.ts +++ b/modules/angular2/src/directives/class.ts @@ -8,12 +8,17 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; export class CSSClass { _domEl; _pipe; - _prevRawClass; - rawClass; + _rawClass; + constructor(private _pipeRegistry: PipeRegistry, ngEl: ElementRef) { this._domEl = ngEl.domElement; } + set rawClass(v) { + this._rawClass = v; + this._pipe = this._pipeRegistry.get('keyValDiff', this._rawClass); + } + _toggleClass(className, enabled): void { if (enabled) { DOM.addClass(this._domEl, className); @@ -23,19 +28,15 @@ export class CSSClass { } onCheck() { - if (this.rawClass != this._prevRawClass) { - this._prevRawClass = this.rawClass; - this._pipe = isPresent(this.rawClass) ? - this._pipeRegistry.get('keyValDiff', this.rawClass, null) : - null; - } + var diff = this._pipe.transform(this._rawClass); + if (isPresent(diff)) this._applyChanges(diff.wrapped); + } - if (isPresent(this._pipe) && this._pipe.check(this.rawClass)) { - this._pipe.forEachAddedItem( - (record) => { this._toggleClass(record.key, record.currentValue); }); - this._pipe.forEachChangedItem( - (record) => { this._toggleClass(record.key, record.currentValue); }); - this._pipe.forEachRemovedItem((record) => { + private _applyChanges(diff) { + if (isPresent(diff)) { + diff.forEachAddedItem((record) => { this._toggleClass(record.key, record.currentValue); }); + diff.forEachChangedItem((record) => { this._toggleClass(record.key, record.currentValue); }); + diff.forEachRemovedItem((record) => { if (record.previousValue) { DOM.removeClass(this._domEl, record.key); } diff --git a/modules/angular2/src/directives/ng_for.ts b/modules/angular2/src/directives/ng_for.ts index 011aa38895..e4a982a575 100644 --- a/modules/angular2/src/directives/ng_for.ts +++ b/modules/angular2/src/directives/ng_for.ts @@ -1,5 +1,12 @@ import {Directive} from 'angular2/annotations'; -import {ViewContainerRef, ViewRef, ProtoViewRef} from 'angular2/core'; +import { + ViewContainerRef, + ViewRef, + ProtoViewRef, + PipeRegistry, + onCheck, + Pipe +} from 'angular2/angular2'; import {isPresent, isBlank} from 'angular2/src/facade/lang'; /** @@ -34,17 +41,25 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang'; * * @exportedAs angular2/directives */ -@Directive( - {selector: '[ng-for][ng-for-of]', properties: ['iterableChanges: ngForOf | iterableDiff']}) +@Directive({selector: '[ng-for][ng-for-of]', properties: ['ngForOf'], lifecycle: [onCheck]}) export class NgFor { - viewContainer: ViewContainerRef; - protoViewRef: ProtoViewRef; - constructor(viewContainer: ViewContainerRef, protoViewRef: ProtoViewRef) { - this.viewContainer = viewContainer; - this.protoViewRef = protoViewRef; + _ngForOf: any; + _pipe: Pipe; + + constructor(private viewContainer: ViewContainerRef, private protoViewRef: ProtoViewRef, + private pipes: PipeRegistry) {} + + set ngForOf(value: any) { + this._ngForOf = value; + this._pipe = this.pipes.get("iterableDiff", value, null, this._pipe); } - set iterableChanges(changes) { + onCheck() { + var diff = this._pipe.transform(this._ngForOf); + if (isPresent(diff)) this._applyChanges(diff.wrapped); + } + + private _applyChanges(changes) { if (isBlank(changes)) { this.viewContainer.clear(); return; @@ -67,11 +82,11 @@ export class NgFor { NgFor.bulkInsert(insertTuples, this.viewContainer, this.protoViewRef); for (var i = 0; i < insertTuples.length; i++) { - this.perViewChange(insertTuples[i].view, insertTuples[i].record); + this._perViewChange(insertTuples[i].view, insertTuples[i].record); } } - perViewChange(view, record) { + private _perViewChange(view, record) { view.setLocal('\$implicit', record.item); view.setLocal('index', record.currentIndex); } diff --git a/modules/angular2/src/render/dom/compiler/directive_parser.ts b/modules/angular2/src/render/dom/compiler/directive_parser.ts index 628f755309..7bcb499ebf 100644 --- a/modules/angular2/src/render/dom/compiler/directive_parser.ts +++ b/modules/angular2/src/render/dom/compiler/directive_parser.ts @@ -147,8 +147,7 @@ export class DirectiveParser implements CompileStep { // Bindings are optional, so this binding only needs to be set up if an expression is given. if (isPresent(bindingAst)) { - var fullExpAstWithBindPipes = this._parser.addPipes(bindingAst, pipes); - directiveBinderBuilder.bindProperty(dirProperty, fullExpAstWithBindPipes); + directiveBinderBuilder.bindProperty(dirProperty, bindingAst); } } diff --git a/modules/angular2/src/test_lib/spies.dart b/modules/angular2/src/test_lib/spies.dart new file mode 100644 index 0000000000..c1723247ea --- /dev/null +++ b/modules/angular2/src/test_lib/spies.dart @@ -0,0 +1,24 @@ +library test_lib.spies; + +import 'package:angular2/change_detection.dart'; +import './test_lib.dart'; + +@proxy +class SpyChangeDetector extends SpyObject implements ChangeDetector { + noSuchMethod(m) => super.noSuchMethod(m); +} + +@proxy +class SpyProtoChangeDetector extends SpyObject implements ProtoChangeDetector { + noSuchMethod(m) => super.noSuchMethod(m); +} + +@proxy +class SpyPipe extends SpyObject implements Pipe { + noSuchMethod(m) => super.noSuchMethod(m); +} + +@proxy +class SpyPipeFactory extends SpyObject implements PipeFactory { + noSuchMethod(m) => super.noSuchMethod(m); +} \ No newline at end of file diff --git a/modules/angular2/src/test_lib/spies.ts b/modules/angular2/src/test_lib/spies.ts index cbff71a2d4..b707ad3c63 100644 --- a/modules/angular2/src/test_lib/spies.ts +++ b/modules/angular2/src/test_lib/spies.ts @@ -3,45 +3,19 @@ import { ProtoChangeDetector, DynamicChangeDetector } from 'angular2/change_detection'; +import {BasePipe} from 'angular2/src/change_detection/pipes/pipe'; import {SpyObject, proxy} from './test_lib'; -// Remove dummy methods after https://github.com/angular/ts2dart/issues/209 is fixed. -@proxy -export class SpyChangeDetector extends SpyObject implements ChangeDetector { - parent: ChangeDetector; - mode: string; - - constructor() { super(DynamicChangeDetector, true); } - - addChild(cd: ChangeDetector): void { return this.spy("addChild")(cd); } - - addShadowDomChild(cd: ChangeDetector): void { return this.spy("addShadowDomChild")(cd); } - - removeChild(cd: ChangeDetector): void { return this.spy("removeChild")(cd); } - - removeShadowDomChild(cd: ChangeDetector): void { return this.spy("removeShadowDomChild")(cd); } - - remove(): void { return this.spy("remove")(); } - - hydrate(context: any, locals: any, directives: any): void { - return this.spy("hydrate")(context, locals, directives); - } - - dehydrate(): void { return this.spy("dehydrate")(); } - - markPathToRootAsCheckOnce(): void { return this.spy("markPathToRootAsCheckOnce")(); } - - detectChanges(): void { return this.spy("detectChanges")(); } - - checkNoChanges(): void { return this.spy("checkNoChanges")(); } - - noSuchMethod(m) { return super.noSuchMethod(m) } +export class SpyChangeDetector extends SpyObject { + constructor() { super(DynamicChangeDetector); } } -// Remove dummy methods after https://github.com/angular/ts2dart/issues/209 is fixed. -@proxy -export class SpyProtoChangeDetector extends SpyObject implements ProtoChangeDetector { - constructor() { super(DynamicChangeDetector, true); } +export class SpyProtoChangeDetector extends SpyObject { + constructor() { super(DynamicChangeDetector); } +} - instantiate(v: any): any { return this.spy("instantiate")(v); } -} \ No newline at end of file +export class SpyPipe extends SpyObject { + constructor() { super(BasePipe); } +} + +export class SpyPipeFactory extends SpyObject {} \ No newline at end of file diff --git a/modules/angular2/src/test_lib/test_lib.dart b/modules/angular2/src/test_lib/test_lib.dart index aac0a380e9..9ff6092f72 100644 --- a/modules/angular2/src/test_lib/test_lib.dart +++ b/modules/angular2/src/test_lib/test_lib.dart @@ -187,7 +187,7 @@ class SpyFunction extends gns.SpyFunction { class SpyObject extends gns.SpyObject { final Map _spyFuncs = {}; - SpyObject([arg, arg2]) {} + SpyObject([arg]) {} SpyFunction spy(String funcName) => _spyFuncs.putIfAbsent(funcName, () => new SpyFunction(funcName)); diff --git a/modules/angular2/src/test_lib/test_lib.ts b/modules/angular2/src/test_lib/test_lib.ts index 5195806d3a..c5e597a8b0 100644 --- a/modules/angular2/src/test_lib/test_lib.ts +++ b/modules/angular2/src/test_lib/test_lib.ts @@ -274,7 +274,7 @@ export interface GuinessCompatibleSpy extends jasmine.Spy { } export class SpyObject { - constructor(type = null, forceSpyCreation: boolean = false) { + constructor(type = null) { if (type) { for (var prop in type.prototype) { var m = null; @@ -287,11 +287,7 @@ export class SpyObject { // should not matter. } if (typeof m === 'function') { - if (forceSpyCreation) { - this.createSpy(prop); - } else { - this.spy(prop); - } + this.spy(prop); } } } @@ -301,14 +297,8 @@ export class SpyObject { spy(name) { if (!this[name]) { - return this.createSpy(name); - } else { - return this[name]; + this[name] = this._createGuinnessCompatibleSpy(name); } - } - - createSpy(name) { - this[name] = this._createGuinnessCompatibleSpy(name); return this[name]; } 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 a205bc4449..d0b9cbedda 100644 --- a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart +++ b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart @@ -223,7 +223,7 @@ class _CodegenState { List _getNonNullPipeNames() { return _records .where((r) => - r.mode == RecordType.PIPE || r.mode == RecordType.BINDING_PIPE) + r.mode == RecordType.PIPE) .map((r) => _pipeNames[r.selfIndex]) .toList(); } @@ -310,7 +310,7 @@ class _CodegenState { var change = _changeNames[r.selfIndex]; var pipe = _pipeNames[r.selfIndex]; - var cdRef = r.mode == RecordType.BINDING_PIPE ? 'this.ref' : 'null'; + var cdRef = 'this.ref'; var protoIndex = r.selfIndex - 1; var pipeType = r.name; diff --git a/modules/angular2/test/change_detection/change_detector_spec.ts b/modules/angular2/test/change_detection/change_detector_spec.ts index 29a43ecbac..8b3edd5a53 100644 --- a/modules/angular2/test/change_detection/change_detector_spec.ts +++ b/modules/angular2/test/change_detection/change_detector_spec.ts @@ -1,3 +1,4 @@ +/// import { ddescribe, describe, @@ -853,28 +854,19 @@ export function main() { }); } -class CountingPipe extends Pipe { - state: number; +class CountingPipe implements Pipe { + state: number = 0; - constructor() { - super(); - this.state = 0; - } + onDestroy() {} 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; - } +class OncePipe implements Pipe { + called: boolean = false; + destroyCalled: boolean = false; supports(newValue) { return !this.called; } @@ -886,11 +878,19 @@ class OncePipe extends Pipe { } } -class IdentityPipe extends Pipe { +class IdentityPipe implements Pipe { + supports(obj): boolean { return true; } + + onDestroy() {} + transform(value) { return value; } } -class WrappedPipe extends Pipe { +class WrappedPipe implements Pipe { + supports(obj): boolean { return true; } + + onDestroy() {} + transform(value) { return WrappedValue.wrap(value); } } @@ -907,7 +907,7 @@ class FakePipeRegistry extends PipeRegistry { this.numberOfLookups = 0; } - get(type: string, obj, cdRef) { + get(type: string, obj, cdRef?, existingPipe?) { if (type != this.pipeType) return null; this.numberOfLookups++; this.cdRef = cdRef; diff --git a/modules/angular2/test/change_detection/parser/parser_spec.ts b/modules/angular2/test/change_detection/parser/parser_spec.ts index fac5bdfbfb..d00a651e55 100644 --- a/modules/angular2/test/change_detection/parser/parser_spec.ts +++ b/modules/angular2/test/change_detection/parser/parser_spec.ts @@ -6,7 +6,7 @@ import {Parser} from 'angular2/src/change_detection/parser/parser'; import {Unparser} from './unparser'; import {Lexer} from 'angular2/src/change_detection/parser/lexer'; import {Locals} from 'angular2/src/change_detection/parser/locals'; -import {Pipe, LiteralPrimitive} from 'angular2/src/change_detection/parser/ast'; +import {BindingPipe, LiteralPrimitive} from 'angular2/src/change_detection/parser/ast'; class TestData { constructor(public a?: any, public b?: any, public fnReturnValue?: any) {} @@ -39,8 +39,6 @@ export function main() { return createParser().parseInterpolation(text, location); } - function addPipes(ast, pipes): any { return createParser().addPipes(ast, pipes); } - function emptyLocals() { return new Locals(null, new Map()); } function evalAction(text, passedInContext = null, passedInLocals = null) { @@ -412,7 +410,7 @@ export function main() { it("should parse pipes", () => { var originalExp = '"Foo" | uppercase'; var ast = parseBinding(originalExp).ast; - expect(ast).toBeAnInstanceOf(Pipe); + expect(ast).toBeAnInstanceOf(BindingPipe); expect(new Unparser().unparse(ast)).toEqual(`(${originalExp})`); }); @@ -600,7 +598,7 @@ export function main() { it('should parse pipes', () => { var bindings = parseTemplateBindings('key value|pipe'); var ast = bindings[0].expression.ast; - expect(ast).toBeAnInstanceOf(Pipe); + expect(ast).toBeAnInstanceOf(BindingPipe); }); }); @@ -622,29 +620,6 @@ export function main() { }); }); - describe('addPipes', () => { - it('should return the given ast whe the list of pipes is empty', () => { - var ast = parseBinding("1 + 1", "Location"); - var transformedAst = addPipes(ast, []); - expect(transformedAst).toBe(ast); - }); - - it('should append pipe ast nodes', () => { - var ast = parseBinding("1 + 1", "Location"); - var transformedAst = addPipes(ast, ['one', 'two']); - expect(transformedAst.ast.name).toEqual("two"); - expect(transformedAst.ast.exp.name).toEqual("one"); - expect(transformedAst.ast.exp.exp.operation).toEqual("+"); - }); - - it('should preserve location and source', () => { - var ast = parseBinding("1 + 1", "Location"); - var transformedAst = addPipes(ast, ['one', 'two']); - expect(transformedAst.source).toEqual("1 + 1"); - expect(transformedAst.location).toEqual("Location"); - }); - }); - describe('wrapLiteralPrimitive', () => { it('should wrap a literal primitive', () => { expect(createParser().wrapLiteralPrimitive("foo", null).eval(null, emptyLocals())) diff --git a/modules/angular2/test/change_detection/parser/unparser.ts b/modules/angular2/test/change_detection/parser/unparser.ts index f7d399d1e5..47ef2e52e7 100644 --- a/modules/angular2/test/change_detection/parser/unparser.ts +++ b/modules/angular2/test/change_detection/parser/unparser.ts @@ -8,7 +8,7 @@ import { Conditional, EmptyExpr, If, - Pipe, + BindingPipe, FunctionCall, ImplicitReceiver, Interpolation, @@ -81,7 +81,7 @@ export class Unparser implements AstVisitor { } } - visitPipe(ast: Pipe) { + visitPipe(ast: BindingPipe) { this._expression += '('; this._visit(ast.exp); this._expression += ` | ${ast.name}`; diff --git a/modules/angular2/test/change_detection/parser/unparser_spec.ts b/modules/angular2/test/change_detection/parser/unparser_spec.ts index 120758e455..d10970ff07 100644 --- a/modules/angular2/test/change_detection/parser/unparser_spec.ts +++ b/modules/angular2/test/change_detection/parser/unparser_spec.ts @@ -10,7 +10,7 @@ import { Conditional, EmptyExpr, If, - Pipe, + BindingPipe, ImplicitReceiver, Interpolation, KeyedAccess, @@ -68,7 +68,7 @@ export function main() { it('should support Pipe', () => { var originalExp = '(a | b)'; var ast = parseBinding(originalExp).ast; - expect(ast).toBeAnInstanceOf(Pipe); + expect(ast).toBeAnInstanceOf(BindingPipe); expect(unparser.unparse(ast)).toEqual(originalExp); }); diff --git a/modules/angular2/test/change_detection/pipes/pipe_registry_spec.ts b/modules/angular2/test/change_detection/pipes/pipe_registry_spec.ts index fe5a936dae..f505de9987 100644 --- a/modules/angular2/test/change_detection/pipes/pipe_registry_spec.ts +++ b/modules/angular2/test/change_detection/pipes/pipe_registry_spec.ts @@ -1,45 +1,77 @@ -import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; +import { + ddescribe, + describe, + it, + iit, + xit, + expect, + beforeEach, + afterEach, + SpyPipe, + SpyPipeFactory +} from 'angular2/test_lib'; import {PipeRegistry} from 'angular2/src/change_detection/pipes/pipe_registry'; -import {Pipe} from 'angular2/src/change_detection/pipes/pipe'; export function main() { describe("pipe registry", () => { - var firstPipe = new Pipe(); - var secondPipe = new Pipe(); + var firstPipe; + var secondPipe; + + var firstPipeFactory; + var secondPipeFactory; + + beforeEach(() => { + firstPipe = new SpyPipe(); + secondPipe = new SpyPipe(); + + firstPipeFactory = new SpyPipeFactory(); + secondPipeFactory = new SpyPipeFactory(); + }); + + it("should return an existing pipe if it can support the passed in object", () => { + var r = new PipeRegistry({"type": []}); + + firstPipe.spy("supports").andReturn(true); + + expect(r.get("type", "some object", null, firstPipe)).toEqual(firstPipe); + }); + + it("should call onDestroy on the provided pipe if it cannot support the provided object", + () => { + firstPipe.spy("supports").andReturn(false); + firstPipeFactory.spy("supports").andReturn(true); + firstPipeFactory.spy("create").andReturn(secondPipe); + + var r = new PipeRegistry({"type": [firstPipeFactory]}); + + expect(r.get("type", "some object", null, firstPipe)).toEqual(secondPipe); + expect(firstPipe.spy("onDestroy")).toHaveBeenCalled(); + }); it("should return the first pipe supporting the data type", () => { - var r = new PipeRegistry( - {"type": [new PipeFactory(false, firstPipe), new PipeFactory(true, secondPipe)]}); + firstPipeFactory.spy("supports").andReturn(false); + firstPipeFactory.spy("create").andReturn(firstPipe); - expect(r.get("type", "some object", null)).toBe(secondPipe); + secondPipeFactory.spy("supports").andReturn(true); + secondPipeFactory.spy("create").andReturn(secondPipe); + + var r = new PipeRegistry({"type": [firstPipeFactory, secondPipeFactory]}); + + expect(r.get("type", "some object")).toBe(secondPipe); }); it("should throw when no matching type", () => { var r = new PipeRegistry({}); - expect(() => r.get("unknown", "some object", null)) + expect(() => r.get("unknown", "some object")) .toThrowError(`Cannot find 'unknown' pipe supporting object 'some object'`); }); it("should throw when no matching pipe", () => { var r = new PipeRegistry({"type": []}); - expect(() => r.get("type", "some object", null)) + expect(() => r.get("type", "some object")) .toThrowError(`Cannot find 'type' pipe supporting object 'some object'`); }); }); -} - -class PipeFactory { - shouldSupport: boolean; - pipe: any; - - constructor(shouldSupport: boolean, pipe: any) { - this.shouldSupport = shouldSupport; - this.pipe = pipe; - } - - supports(obj): boolean { return this.shouldSupport; } - - create(cdRef): Pipe { return this.pipe; } -} +} \ No newline at end of file diff --git a/modules/angular2/test/core/compiler/integration_spec.ts b/modules/angular2/test/core/compiler/integration_spec.ts index 7f3061c371..9da5d0e568 100644 --- a/modules/angular2/test/core/compiler/integration_spec.ts +++ b/modules/angular2/test/core/compiler/integration_spec.ts @@ -36,6 +36,7 @@ import {PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/faca import {Injector, bind, Injectable, Binding, forwardRef, OpaqueToken, Inject} from 'angular2/di'; import { + PipeFactory, PipeRegistry, defaultPipeRegistry, ChangeDetection, @@ -243,12 +244,11 @@ export function main() { ]; }); - it("should support pipes in bindings and bind config", + it("should support pipes in bindings", inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => { tb.overrideView(MyComp, new viewAnn.View({ - template: - '', - directives: [ComponentWithPipes] + template: '
', + directives: [MyDir] })); tb.createView(MyComp, {context: ctx}) @@ -256,10 +256,8 @@ export function main() { ctx.ctxProp = 'a'; view.detectChanges(); - var comp = view.rawView.locals.get("comp"); - - // it is doubled twice: once in the binding, second time in the bind config - expect(comp.prop).toEqual('aaaa'); + var dir = view.rawView.locals.get("dir"); + expect(dir.dirProp).toEqual('aa'); async.done(); }); })); @@ -1292,7 +1290,7 @@ class DynamicViewport { } } -@Directive({selector: '[my-dir]', properties: ['dirProp: elprop']}) +@Directive({selector: '[my-dir]', properties: ['dirProp: elprop'], exportAs: 'mydir'}) @Injectable() class MyDir { dirProp: string; @@ -1349,7 +1347,7 @@ class MyComp { } } -@Component({selector: 'component-with-pipes', properties: ["prop: prop | double"]}) +@Component({selector: 'component-with-pipes', properties: ["prop"]}) @View({template: ''}) @Injectable() class ComponentWithPipes { @@ -1430,14 +1428,16 @@ class SomeViewport { } @Injectable() -class DoublePipe extends Pipe { +class DoublePipe implements Pipe { + onDestroy() {} + supports(obj) { return true; } transform(value) { return `${value}${value}`; } } @Injectable() -class DoublePipeFactory { +class DoublePipeFactory implements PipeFactory { supports(obj) { return true; } create(cdRef) { return new DoublePipe(); } diff --git a/modules/angular2/test/render/dom/compiler/directive_parser_spec.ts b/modules/angular2/test/render/dom/compiler/directive_parser_spec.ts index e5edf160c0..8e87972dcd 100644 --- a/modules/angular2/test/render/dom/compiler/directive_parser_spec.ts +++ b/modules/angular2/test/render/dom/compiler/directive_parser_spec.ts @@ -86,17 +86,6 @@ export function main() { expect(directiveBinding.propertyBindings.get('dirProp').source).toEqual('someExpr'); }); - it('should bind directive properties with pipes', () => { - var results = process(el('
'), - {'elProp': parser.parseBinding('someExpr', '')}); - var directiveBinding = results[0].directives[0]; - var pipedProp = directiveBinding.propertyBindings.get('doubleProp'); - var simpleProp = directiveBinding.propertyBindings.get('dirProp'); - expect(pipedProp.ast.name).toEqual('double'); - expect(pipedProp.ast.exp).toEqual(simpleProp.ast); - expect(simpleProp.source).toEqual('someExpr'); - }); - it('should bind directive properties from attribute values', () => { var results = process(el('
')); var directiveBinding = results[0].directives[0]; @@ -237,7 +226,7 @@ var decoratorWithMultipleAttrs = DirectiveMetadata.create({ var someDirectiveWithProps = DirectiveMetadata.create({ selector: '[some-decor-props]', - properties: ['dirProp: elProp', 'doubleProp: elProp | double'], + properties: ['dirProp: elProp'], readAttributes: ['some-attr'] });