refactor(pipes): use Injector instead of pipe factories for pipe instantiation

BREAKING CHANGE
    - Pipe factories have been removed.
    - PIpe names to pipe implementations are 1-to-1  instead of 1-to-*

 Before:
     class DateFormatter {
         transform(date, args){}
     }

     class DateFormatterFactory {
       supporst(obj) { return true; }
       create(cdRef) { return new DateFormatter(); }
     }
     new Pipes({date: [new DateFormatterFactory()]})

After
    class DateFormatter {
      transform(date, args){}
    }
    new Pipes({date: DateFormatter})
This commit is contained in:
vsavkin
2015-08-06 10:39:02 -07:00
parent 06da60f4b7
commit 2dcf714d2b
28 changed files with 249 additions and 524 deletions

View File

@ -768,7 +768,7 @@ export function main() {
});
it('should destroy all active pipes during dehyration', () => {
var pipe = new OncePipe();
var pipe = new PipeWithOnDestroy();
var registry = new FakePipes('pipe', () => pipe);
var cd = _createChangeDetector('name | pipe', new Person('bob'), registry).changeDetector;
@ -810,36 +810,6 @@ export function main() {
expect(val.dispatcher.log).toEqual(['propName=Megatron state:1']);
});
it('should lookup pipes in the registry when the context is not supported', () => {
var registry = new FakePipes('pipe', () => new OncePipe());
var ctx = new Person('Megatron');
var cd = _createChangeDetector('name | pipe', ctx, registry).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 FakePipes('pipe', () => pipe);
var ctx = new Person('Megatron');
var cd = _createChangeDetector('name | pipe', ctx, registry).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',
() => {
@ -886,41 +856,24 @@ export function main() {
class CountingPipe implements Pipe {
state: number = 0;
onDestroy() {}
supports(newValue) { return true; }
transform(value, args = null) { return `${value} state:${this.state ++}`; }
}
class OncePipe implements Pipe {
called: boolean = false;
class PipeWithOnDestroy implements Pipe {
destroyCalled: boolean = false;
supports(newValue) { return !this.called; }
onDestroy() { this.destroyCalled = true; }
transform(value, args = null) {
this.called = true;
return value;
}
transform(value, args = null) { return null; }
}
class IdentityPipe implements Pipe {
supports(obj): boolean { return true; }
onDestroy() {}
transform(value, args = null) { return value; }
}
class WrappedPipe implements Pipe {
supports(obj): boolean { return true; }
onDestroy() {}
transform(value, args = null) { return WrappedValue.wrap(value); }
}
@ -931,24 +884,16 @@ class MultiArgPipe implements Pipe {
var arg3 = args.length > 2 ? args[2] : 'default';
return `${value} ${arg1} ${arg2} ${arg3}`;
}
supports(obj): boolean { return true; }
onDestroy(): void {}
}
class FakePipes extends Pipes {
numberOfLookups: number;
pipeType: string;
factory: Function;
numberOfLookups = 0;
cdRef: any;
constructor(pipeType, factory) {
super({});
this.pipeType = pipeType;
this.factory = factory;
this.numberOfLookups = 0;
}
constructor(public pipeType: string, public factory: Function) { super(null, null); }
get(type: string, obj, cdRef?, existingPipe?) {
get(type: string, cdRef?) {
if (type != this.pipeType) return null;
this.numberOfLookups++;
this.cdRef = cdRef;

View File

@ -20,10 +20,8 @@ import {JsonPipe} from 'angular2/src/change_detection/pipes/json_pipe';
export function main() {
describe("JsonPipe", () => {
var regNewLine = '\n';
var canHasUndefined; // because Dart doesn't like undefined;
var inceptionObj;
var inceptionObjString;
var catString;
var pipe;
var collection: number[];
@ -35,27 +33,10 @@ export function main() {
" \"dream\": \"Limbo\"\n" + " }\n" + " }\n" + "}";
catString = 'Inception Cat';
pipe = new JsonPipe();
collection = [];
});
describe("supports", () => {
it("should support objects", () => { expect(pipe.supports(inceptionObj)).toBe(true); });
it("should support strings", () => { expect(pipe.supports(catString)).toBe(true); });
it("should support null", () => { expect(pipe.supports(null)).toBe(true); });
it("should support NaN", () => { expect(pipe.supports(NumberWrapper.NaN)).toBe(true); });
if (!IS_DARTIUM) {
it("should support undefined",
() => { expect(pipe.supports(canHasUndefined)).toBe(true); });
}
});
describe("transform", () => {
it("should return JSON-formatted string",
() => { expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); });

View File

@ -4,29 +4,17 @@ import {LowerCasePipe} from 'angular2/src/change_detection/pipes/lowercase_pipe'
export function main() {
describe("LowerCasePipe", () => {
var str;
var upper;
var lower;
var pipe;
beforeEach(() => {
str = 'something';
lower = 'something';
upper = 'SOMETHING';
pipe = new LowerCasePipe();
});
describe("supports", () => {
it("should support strings", () => { expect(pipe.supports(str)).toBe(true); });
it("should not support other objects", () => {
expect(pipe.supports(new Object())).toBe(false);
expect(pipe.supports(null)).toBe(false);
});
});
describe("transform", () => {
it("should return lowercase", () => {
var val = pipe.transform(upper);
expect(val).toEqual(lower);
@ -39,6 +27,8 @@ export function main() {
expect(val2).toEqual('wat');
});
it("should not support other objects",
() => { expect(() => pipe.transform(new Object())).toThrowError(); });
});
});

View File

@ -12,18 +12,8 @@ export function main() {
beforeEach(() => { pipe = new DecimalPipe(); });
describe("supports", () => {
it("should support numbers", () => { expect(pipe.supports(123.0)).toBe(true); });
it("should not support other objects", () => {
expect(pipe.supports(new Object())).toBe(false);
expect(pipe.supports('str')).toBe(false);
expect(pipe.supports(null)).toBe(false);
});
});
describe("transform", () => {
it('should return correct value', () => {
it('should return correct value for numbers', () => {
expect(pipe.transform(12345, [])).toEqual('12,345');
expect(pipe.transform(123, ['.2'])).toEqual('123.00');
expect(pipe.transform(1, ['3.'])).toEqual('001');
@ -31,6 +21,9 @@ export function main() {
expect(pipe.transform(1.123456, ['3.4-5'])).toEqual('001.12346');
expect(pipe.transform(1.1234, [])).toEqual('1.123');
});
it("should not support other objects",
() => { expect(() => pipe.transform(new Object(), [])).toThrowError(); });
});
});
@ -39,21 +32,14 @@ export function main() {
beforeEach(() => { pipe = new PercentPipe(); });
describe("supports", () => {
it("should support numbers", () => { expect(pipe.supports(123.0)).toBe(true); });
it("should not support other objects", () => {
expect(pipe.supports(new Object())).toBe(false);
expect(pipe.supports('str')).toBe(false);
expect(pipe.supports(null)).toBe(false);
});
});
describe("transform", () => {
it('should return correct value', () => {
it('should return correct value for numbers', () => {
expect(pipe.transform(1.23, [])).toEqual('123%');
expect(pipe.transform(1.2, ['.2'])).toEqual('120.00%');
});
it("should not support other objects",
() => { expect(() => pipe.transform(new Object(), [])).toThrowError(); });
});
});
@ -62,21 +48,14 @@ export function main() {
beforeEach(() => { pipe = new CurrencyPipe(); });
describe("supports", () => {
it("should support numbers", () => { expect(pipe.supports(123.0)).toBe(true); });
it("should not support other objects", () => {
expect(pipe.supports(new Object())).toBe(false);
expect(pipe.supports('str')).toBe(false);
expect(pipe.supports(null)).toBe(false);
});
});
describe("transform", () => {
it('should return correct value', () => {
it('should return correct value for numbers', () => {
expect(pipe.transform(123, [])).toEqual('USD123');
expect(pipe.transform(12, ['EUR', false, '.2'])).toEqual('EUR12.00');
});
it("should not support other objects",
() => { expect(() => pipe.transform(new Object(), [])).toThrowError(); });
});
});
}

View File

@ -1,158 +1,88 @@
import {
ddescribe,
xdescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach,
SpyPipe,
SpyPipeFactory
afterEach
} from 'angular2/test_lib';
import {Injector, bind} from 'angular2/di';
import {Pipes} from 'angular2/src/change_detection/pipes/pipes';
import {PipeFactory} from 'angular2/src/change_detection/pipes/pipe';
import {Pipe} from 'angular2/src/change_detection/pipes/pipe';
class APipe implements Pipe {
transform(a, b) {}
onDestroy() {}
}
class AnotherPipe implements Pipe {
transform(a, b) {}
onDestroy() {}
}
export function main() {
describe("pipe registry", () => {
var firstPipe;
var secondPipe;
var injector;
var firstPipeFactory;
var secondPipeFactory;
beforeEach(() => { injector = Injector.resolveAndCreate([]); });
beforeEach(() => {
firstPipe = <any>new SpyPipe();
secondPipe = <any>new SpyPipe();
firstPipeFactory = <any>new SpyPipeFactory();
secondPipeFactory = <any>new SpyPipeFactory();
it("should instantiate a pipe", () => {
var r = new Pipes({"type": APipe}, injector);
expect(r.get("type", null)).toBeAnInstanceOf(APipe);
});
it("should return an existing pipe if it can support the passed in object", () => {
var r = new Pipes({"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 Pipes({"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", () => {
firstPipeFactory.spy("supports").andReturn(false);
firstPipeFactory.spy("create").andReturn(firstPipe);
secondPipeFactory.spy("supports").andReturn(true);
secondPipeFactory.spy("create").andReturn(secondPipe);
var r = new Pipes({"type": [firstPipeFactory, secondPipeFactory]});
expect(r.get("type", "some object")).toBe(secondPipe);
it("should instantiate a new pipe every time", () => {
var r = new Pipes({"type": APipe}, injector);
var p1 = r.get("type", null);
var p2 = r.get("type", null);
expect(p1).not.toBe(p2);
});
it("should throw when no matching type", () => {
var r = new Pipes({});
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 Pipes({"type": []});
expect(() => r.get("type", "some object"))
.toThrowError(`Cannot find 'type' pipe supporting object 'some object'`);
var r = new Pipes({}, null);
expect(() => r.get("unknown", null)).toThrowError(`Cannot find pipe 'unknown'.`);
});
describe('.create()', () => {
it("should create a new Pipes object", () => {
firstPipeFactory.spy("supports").andReturn(true);
firstPipeFactory.spy("create").andReturn(firstPipe);
var pipes = Pipes.create({'async': [firstPipeFactory]});
expect(pipes.get("async", "first")).toBe(firstPipe);
var pipes = Pipes.create({'pipe': APipe}, null);
expect(pipes.config).toEqual({'pipe': APipe});
});
it("should prepend passed it config in existing registry", () => {
firstPipeFactory.spy("supports").andReturn(true);
secondPipeFactory.spy("supports").andReturn(true);
secondPipeFactory.spy("create").andReturn(secondPipe);
it("should merge pipes config", () => {
var pipes1 = Pipes.create({'pipe': APipe, 'pipe1': APipe}, null);
var pipes2 = Pipes.create({'pipe': AnotherPipe, 'pipe2': AnotherPipe}, null, pipes1);
var pipes1 = Pipes.create({'async': [firstPipeFactory]});
var pipes2 = Pipes.create({'async': [secondPipeFactory]}, pipes1);
expect(pipes2.get("async", "first")).toBe(secondPipe);
expect(pipes2.config).toEqual({'pipe': AnotherPipe, 'pipe1': APipe, 'pipe2': AnotherPipe});
});
it("should use inherited pipes when no overrides support the provided object", () => {
firstPipeFactory.spy("supports").andReturn(true);
firstPipeFactory.spy("create").andReturn(firstPipe);
secondPipeFactory.spy("supports").andReturn(false);
it("should not change parent's config", () => {
var pipes1 = Pipes.create({'pipe': APipe, 'pipe1': APipe}, null);
Pipes.create({'pipe': AnotherPipe, 'pipe2': AnotherPipe}, null, pipes1);
var pipes1 = Pipes.create({'async': [firstPipeFactory], 'date': [firstPipeFactory]});
var pipes2 = Pipes.create({'async': [secondPipeFactory]}, pipes1);
expect(pipes2.get("async", "first")).toBe(firstPipe);
expect(pipes2.get("date", "first")).toBe(firstPipe);
expect(pipes1.config).toEqual({'pipe': APipe, 'pipe1': APipe});
});
});
describe(".extend()", () => {
it('should create a factory that prepend new pipes to old', () => {
firstPipeFactory.spy("supports").andReturn(true);
secondPipeFactory.spy("supports").andReturn(true);
secondPipeFactory.spy("create").andReturn(secondPipe);
var pipes1 = Pipes.create({'pipe': APipe, 'pipe1': APipe}, null);
var binding = Pipes.extend({'pipe': AnotherPipe, 'pipe2': AnotherPipe});
var pipes: Pipes = binding.toFactory(pipes1, injector);
var originalPipes = new Pipes({'async': [firstPipeFactory]});
var binding = Pipes.extend({'async':<PipeFactory[]>[secondPipeFactory]});
var pipes: Pipes = binding.toFactory(originalPipes);
expect(pipes.config['async'].length).toBe(2);
expect(originalPipes.config['async'].length).toBe(1);
expect(pipes.get('async', 'second plz')).toBe(secondPipe);
expect(pipes.config).toEqual({'pipe': AnotherPipe, 'pipe1': APipe, 'pipe2': AnotherPipe});
});
it('should throw if calling extend when creating root injector', () => {
secondPipeFactory.spy("supports").andReturn(true);
secondPipeFactory.spy("create").andReturn(secondPipe);
var injector: Injector =
Injector.resolveAndCreate([Pipes.extend({'async': [secondPipeFactory]})]);
var injector = Injector.resolveAndCreate([Pipes.extend({'pipe': APipe})]);
expect(() => injector.get(Pipes))
.toThrowErrorWith("Cannot extend Pipes without a parent injector");
});
it('should extend di-inherited pipes', () => {
firstPipeFactory.spy("supports").andReturn(true);
firstPipeFactory.spy("create").andReturn(firstPipe);
secondPipeFactory.spy("supports").andReturn(false);
var originalPipes: Pipes = new Pipes({'async': [firstPipeFactory]});
var injector: Injector = Injector.resolveAndCreate([bind(Pipes).toValue(originalPipes)]);
var childInjector: Injector =
injector.resolveAndCreateChild([Pipes.extend({'async': [secondPipeFactory]})]);
var parentPipes: Pipes = injector.get(Pipes);
var childPipes: Pipes = childInjector.get(Pipes);
expect(childPipes.config['async'].length).toBe(2);
expect(parentPipes.config['async'].length).toBe(1);
expect(childPipes.get('async', 'second plz')).toBe(firstPipe);
});
});
});
}

View File

@ -4,27 +4,16 @@ import {UpperCasePipe} from 'angular2/src/change_detection/pipes/uppercase_pipe'
export function main() {
describe("UpperCasePipe", () => {
var str;
var upper;
var lower;
var pipe;
beforeEach(() => {
str = 'something';
lower = 'something';
upper = 'SOMETHING';
pipe = new UpperCasePipe();
});
describe("supports", () => {
it("should support strings", () => { expect(pipe.supports(str)).toBe(true); });
it("should not support other objects", () => {
expect(pipe.supports(new Object())).toBe(false);
expect(pipe.supports(null)).toBe(false);
});
});
describe("transform", () => {
it("should return uppercase", () => {
@ -39,6 +28,8 @@ export function main() {
expect(val2).toEqual('WAT');
});
it("should not support other objects",
() => { expect(() => pipe.transform(new Object())).toThrowError(); });
});
});