feat(change_detection): add support for pipes in the template
This commit is contained in:
+18
-52
@@ -3,7 +3,6 @@ import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, IS_DAR
|
||||
import {isPresent, isBlank, isJsObject, BaseException, FunctionWrapper} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {Pipe} from 'angular2/src/change_detection/parser/ast';
|
||||
import {Parser} from 'angular2/src/change_detection/parser/parser';
|
||||
import {Lexer} from 'angular2/src/change_detection/parser/lexer';
|
||||
|
||||
@@ -29,24 +28,18 @@ export function main() {
|
||||
return parser.parseBinding(exp, location);
|
||||
}
|
||||
|
||||
function createChangeDetector(memo:string, exp:string, context = null, formatters = null,
|
||||
registry = null, pipeType:string = null) {
|
||||
function createChangeDetector(memo:string, exp:string, context = null, registry = null) {
|
||||
var pcd = createProtoChangeDetector(registry);
|
||||
var parsedAst = ast(exp);
|
||||
if (isPresent(pipeType)) {
|
||||
parsedAst = new Pipe(parsedAst, pipeType);
|
||||
}
|
||||
pcd.addAst(parsedAst, memo, memo);
|
||||
|
||||
pcd.addAst(ast(exp), memo, memo);
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, formatters);
|
||||
var cd = pcd.instantiate(dispatcher);
|
||||
cd.setContext(context);
|
||||
|
||||
return {"changeDetector" : cd, "dispatcher" : dispatcher};
|
||||
}
|
||||
|
||||
function executeWatch(memo:string, exp:string, context = null, formatters = null) {
|
||||
var res = createChangeDetector(memo, exp, context, formatters);
|
||||
function executeWatch(memo:string, exp:string, context = null) {
|
||||
var res = createChangeDetector(memo, exp, context);
|
||||
res["changeDetector"].detectChanges();
|
||||
return res["dispatcher"].log;
|
||||
}
|
||||
@@ -182,14 +175,6 @@ export function main() {
|
||||
});
|
||||
});
|
||||
|
||||
it("should support formatters", () => {
|
||||
var formatters = MapWrapper.createFromPairs([
|
||||
['uppercase', (v) => v.toUpperCase()],
|
||||
['wrap', (v, before, after) => `${before}${v}${after}`]]);
|
||||
expect(executeWatch('str', '"aBc" | uppercase', null, formatters)).toEqual(['str=ABC']);
|
||||
expect(executeWatch('str', '"b" | wrap:"a":"c"', null, formatters)).toEqual(['str=abc']);
|
||||
});
|
||||
|
||||
it("should support interpolation", () => {
|
||||
var parser = new Parser(new Lexer());
|
||||
var pcd = createProtoChangeDetector();
|
||||
@@ -197,7 +182,7 @@ export function main() {
|
||||
pcd.addAst(ast, "memo", "memo");
|
||||
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, MapWrapper.create());
|
||||
var cd = pcd.instantiate(dispatcher);
|
||||
cd.setContext(new TestData("value"));
|
||||
|
||||
cd.detectChanges();
|
||||
@@ -213,7 +198,7 @@ export function main() {
|
||||
pcd.addAst(ast("100 + 200"), "memo2", "2");
|
||||
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, null);
|
||||
var cd = pcd.instantiate(dispatcher);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
@@ -227,7 +212,7 @@ export function main() {
|
||||
pcd.addAst(ast("c()"), "c", "2");
|
||||
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, null);
|
||||
var cd = pcd.instantiate(dispatcher);
|
||||
|
||||
var tr = new TestRecord();
|
||||
tr.a = () => {
|
||||
@@ -256,7 +241,7 @@ export function main() {
|
||||
pcd.addAst(ast("a"), "a", 1);
|
||||
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, null);
|
||||
var cd = pcd.instantiate(dispatcher);
|
||||
cd.setContext(new TestData('value'));
|
||||
|
||||
expect(() => {
|
||||
@@ -271,7 +256,7 @@ export function main() {
|
||||
var pcd = createProtoChangeDetector();
|
||||
pcd.addAst(ast('invalidProp', 'someComponent'), "a", 1);
|
||||
|
||||
var cd = pcd.instantiate(new TestDispatcher(), null);
|
||||
var cd = pcd.instantiate(new TestDispatcher());
|
||||
cd.setContext(null);
|
||||
|
||||
try {
|
||||
@@ -318,10 +303,10 @@ export function main() {
|
||||
|
||||
beforeEach(() => {
|
||||
var protoParent = createProtoChangeDetector();
|
||||
parent = protoParent.instantiate(null, null);
|
||||
parent = protoParent.instantiate(null);
|
||||
|
||||
var protoChild = createProtoChangeDetector();
|
||||
child = protoChild.instantiate(null, null);
|
||||
child = protoChild.instantiate(null);
|
||||
});
|
||||
|
||||
it("should add children", () => {
|
||||
@@ -340,25 +325,6 @@ export function main() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("optimizations", () => {
|
||||
it("should not rerun formatters when args did not change", () => {
|
||||
var count = 0;
|
||||
var formatters = MapWrapper.createFromPairs([
|
||||
['count', (v) => {count ++; "value"}]]);
|
||||
|
||||
var c = createChangeDetector('a', 'a | count', new TestData(null), formatters);
|
||||
var cd = c["changeDetector"];
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(count).toEqual(1);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(count).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mode", () => {
|
||||
it("should not check a detached change detector", () => {
|
||||
var c = createChangeDetector('name', 'a', new TestData("value"));
|
||||
@@ -383,7 +349,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it("should change CHECK_ONCE to CHECKED", () => {
|
||||
var cd = createProtoChangeDetector().instantiate(null, null);
|
||||
var cd = createProtoChangeDetector().instantiate(null);
|
||||
cd.mode = CHECK_ONCE;
|
||||
|
||||
cd.detectChanges();
|
||||
@@ -392,7 +358,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it("should not change the CHECK_ALWAYS", () => {
|
||||
var cd = createProtoChangeDetector().instantiate(null, null);
|
||||
var cd = createProtoChangeDetector().instantiate(null);
|
||||
cd.mode = CHECK_ALWAYS;
|
||||
|
||||
cd.detectChanges();
|
||||
@@ -403,7 +369,7 @@ export function main() {
|
||||
|
||||
describe("markPathToRootAsCheckOnce", () => {
|
||||
function changeDetector(mode, parent) {
|
||||
var cd = createProtoChangeDetector().instantiate(null, null);
|
||||
var cd = createProtoChangeDetector().instantiate(null);
|
||||
cd.mode = mode;
|
||||
if (isPresent(parent)) parent.addChild(cd);
|
||||
return cd;
|
||||
@@ -435,7 +401,7 @@ export function main() {
|
||||
var registry = new FakePipeRegistry('pipe', () => new CountingPipe());
|
||||
var ctx = new Person("Megatron");
|
||||
|
||||
var c = createChangeDetector("memo", "name", ctx, null, registry, 'pipe');
|
||||
var c = createChangeDetector("memo", "name | pipe", ctx, registry);
|
||||
var cd = c["changeDetector"];
|
||||
var dispatcher = c["dispatcher"];
|
||||
|
||||
@@ -453,7 +419,7 @@ export function main() {
|
||||
var registry = new FakePipeRegistry('pipe', () => new OncePipe());
|
||||
var ctx = new Person("Megatron");
|
||||
|
||||
var c = createChangeDetector("memo", "name", ctx, null, registry, 'pipe');
|
||||
var c = createChangeDetector("memo", "name | pipe", ctx, registry);
|
||||
var cd = c["changeDetector"];
|
||||
|
||||
cd.detectChanges();
|
||||
@@ -471,7 +437,7 @@ export function main() {
|
||||
var registry = new FakePipeRegistry('pipe', () => new IdentityPipe())
|
||||
var ctx = new Person("Megatron");
|
||||
|
||||
var c = createChangeDetector("memo", "name", ctx, null, registry, 'pipe');
|
||||
var c = createChangeDetector("memo", "name | pipe", ctx, registry);
|
||||
var cd = c["changeDetector"];
|
||||
var dispatcher = c["dispatcher"];
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {Parser} from 'angular2/src/change_detection/parser/parser';
|
||||
import {Lexer} from 'angular2/src/change_detection/parser/lexer';
|
||||
import {ContextWithVariableBindings} from 'angular2/src/change_detection/parser/context_with_variable_bindings';
|
||||
import {Formatter, LiteralPrimitive} from 'angular2/src/change_detection/parser/ast';
|
||||
import {Pipe, LiteralPrimitive} from 'angular2/src/change_detection/parser/ast';
|
||||
|
||||
class TestData {
|
||||
a;
|
||||
@@ -340,8 +340,8 @@ export function main() {
|
||||
});
|
||||
});
|
||||
|
||||
it("should error when using formatters", () => {
|
||||
expectEvalError('x|blah').toThrowError(new RegExp('Cannot have a formatter'));
|
||||
it("should error when using pipes", () => {
|
||||
expectEvalError('x|blah').toThrowError(new RegExp('Cannot have a pipe'));
|
||||
});
|
||||
|
||||
it('should pass exceptions', () => {
|
||||
@@ -367,16 +367,16 @@ export function main() {
|
||||
});
|
||||
|
||||
describe("parseBinding", () => {
|
||||
describe("formatters", () => {
|
||||
it("should parse formatters", () => {
|
||||
describe("pipes", () => {
|
||||
it("should parse pipes", () => {
|
||||
var exp = parseBinding("'Foo'|uppercase").ast;
|
||||
expect(exp).toBeAnInstanceOf(Formatter);
|
||||
expect(exp).toBeAnInstanceOf(Pipe);
|
||||
expect(exp.name).toEqual("uppercase");
|
||||
});
|
||||
|
||||
it("should parse formatters with args", () => {
|
||||
it("should parse pipes with args", () => {
|
||||
var exp = parseBinding("1|increment:2").ast;
|
||||
expect(exp).toBeAnInstanceOf(Formatter);
|
||||
expect(exp).toBeAnInstanceOf(Pipe);
|
||||
expect(exp.name).toEqual("increment");
|
||||
expect(exp.args[0]).toBeAnInstanceOf(LiteralPrimitive);
|
||||
});
|
||||
|
||||
+71
-4
@@ -5,7 +5,8 @@ import {Map, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {Type, isPresent} from 'angular2/src/facade/lang';
|
||||
|
||||
import {Injector} from 'angular2/di';
|
||||
import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'angular2/change_detection';
|
||||
import {Lexer, Parser, ChangeDetector, dynamicChangeDetection,
|
||||
DynamicChangeDetection, Pipe, PipeRegistry} from 'angular2/change_detection';
|
||||
|
||||
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
|
||||
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
|
||||
@@ -24,9 +25,8 @@ export function main() {
|
||||
describe('integration tests', function() {
|
||||
var compiler, tplResolver;
|
||||
|
||||
beforeEach( () => {
|
||||
tplResolver = new FakeTemplateResolver();
|
||||
compiler = new Compiler(dynamicChangeDetection,
|
||||
function createCompiler(tplResolver, changedDetection) {
|
||||
return new Compiler(changedDetection,
|
||||
new TemplateLoader(null),
|
||||
new DirectiveMetadataReader(),
|
||||
new Parser(new Lexer()),
|
||||
@@ -34,6 +34,11 @@ export function main() {
|
||||
new NativeShadowDomStrategy(),
|
||||
tplResolver
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach( () => {
|
||||
tplResolver = new FakeTemplateResolver();
|
||||
compiler = createCompiler(tplResolver, dynamicChangeDetection);
|
||||
});
|
||||
|
||||
describe('react to record changes', function() {
|
||||
@@ -114,6 +119,33 @@ export function main() {
|
||||
});
|
||||
});
|
||||
|
||||
it("should support pipes in bindings and bind config", (done) => {
|
||||
tplResolver.setTemplate(MyComp,
|
||||
new Template({
|
||||
inline: '<component-with-pipes #comp [prop]="ctxProp | double"></component-with-pipes>',
|
||||
directives: [ComponentWithPipes]
|
||||
}));
|
||||
|
||||
|
||||
var registry = new PipeRegistry({
|
||||
"double" : [new DoublePipeFactory()]
|
||||
});
|
||||
var changeDetection = new DynamicChangeDetection(registry);
|
||||
var compiler = createCompiler(tplResolver, changeDetection);
|
||||
compiler.compile(MyComp).then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
ctx.ctxProp = 'a';
|
||||
cd.detectChanges();
|
||||
|
||||
var comp = view.contextWithLocals.get("comp");
|
||||
|
||||
// it is doubled twice: once in the binding, second time in the bind config
|
||||
expect(comp.prop).toEqual('aaaa');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should support nested components.', (done) => {
|
||||
tplResolver.setTemplate(MyComp, new Template({
|
||||
inline: '<child-cmp></child-cmp>',
|
||||
@@ -379,6 +411,20 @@ class MyComp {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'component-with-pipes',
|
||||
bind: {
|
||||
"prop": "prop | double"
|
||||
}
|
||||
})
|
||||
@Template({
|
||||
inline: ''
|
||||
})
|
||||
class ComponentWithPipes {
|
||||
prop:string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'child-cmp',
|
||||
componentServices: [MyService]
|
||||
@@ -468,3 +514,24 @@ class FakeTemplateResolver extends TemplateResolver {
|
||||
return super.resolve(component);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DoublePipe extends Pipe {
|
||||
supports(obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
transform(value) {
|
||||
return `${value}${value}`;
|
||||
}
|
||||
}
|
||||
|
||||
class DoublePipeFactory {
|
||||
supports(obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
create() {
|
||||
return new DoublePipe();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user