feat(pipes): changed .append to .extend

BREAKING CHANGE:
    Pipes.append has been renamed into Pipes.extend.
    Pipes.extend prepends pipe factories instead of appending them.
This commit is contained in:
vsavkin 2015-07-17 13:26:26 -07:00
parent e94270946a
commit 4c8ea12903
2 changed files with 86 additions and 53 deletions

View File

@ -40,18 +40,18 @@ export class Pipes {
}
/**
* Takes a {@link Pipes} config object and returns a binding used to append the
* provided config to an inherited {@link Pipes} instance and return a new
* Takes a {@link Pipes} config object and returns a binding used to extend the
* inherited {@link Pipes} instance with the provided config and return a new
* {@link Pipes} instance.
*
* If the provided config contains a key that is not yet present in the
* inherited {@link Pipes}' config, a new {@link PipeFactory} list will be created
* for that key. Otherwise, the provided config will be merged with the inherited
* {@link Pipes} instance by appending pipes to their respective keys, without mutating
* {@link Pipes} instance by prepending pipes to their respective keys, without mutating
* the inherited {@link Pipes}.
*
* The following example shows how to append a new {@link PipeFactory} to the
* existing list of `async` factories, which will only be applied to the injector
* The following example shows how to extend an existing list of `async` factories
* with a new {@link PipeFactory}, which will only be applied to the injector
* for this component and its children. This step is all that's required to make a new
* pipe available to this component's template.
*
@ -60,44 +60,42 @@ export class Pipes {
* ```
* @Component({
* viewInjector: [
* Pipes.append({
* Pipes.extend({
* async: [newAsyncPipe]
* })
* ]
* })
* ```
*/
static append(config): Binding {
static extend(config): Binding {
return new Binding(Pipes, {
toFactory: (pipes: Pipes) => {
if (!isPresent(pipes)) {
// Typically would occur when calling Pipe.append inside of dependencies passed to
// bootstrap(), which would override default pipes instead of append.
throw new BaseException('Cannot append to Pipes without a parent injector');
if (isBlank(pipes)) {
// Typically would occur when calling Pipe.extend inside of dependencies passed to
// bootstrap(), which would override default pipes instead of extending them.
throw new BaseException('Cannot extend Pipes without a parent injector');
}
var mergedConfig: StringMap<string, PipeFactory[]> = <StringMap<string, PipeFactory[]>>{};
// Manual deep copy of existing Pipes config,
// so that lists of PipeFactories don't get mutated.
StringMapWrapper.forEach(pipes.config, (v: PipeFactory[], k: string) => {
var localPipeList: PipeFactory[] = mergedConfig[k] = [];
v.forEach((p: PipeFactory) => { localPipeList.push(p); });
});
StringMapWrapper.forEach(config, (v: PipeFactory[], k: string) => {
if (isListLikeIterable(mergedConfig[k])) {
mergedConfig[k] = ListWrapper.concat(mergedConfig[k], config[k]);
} else {
mergedConfig[k] = config[k];
}
});
return new Pipes(mergedConfig);
return Pipes.create(config, pipes);
},
// Dependency technically isn't optional, but we can provide a better error message this way.
deps: [[Pipes, new UnboundedMetadata(), new OptionalMetadata()]]
});
}
static create(config, pipes: Pipes = null): Pipes {
if (isPresent(pipes)) {
StringMapWrapper.forEach(pipes.config, (v: PipeFactory[], k: string) => {
if (StringMapWrapper.contains(config, k)) {
var configFactories: PipeFactory[] = config[k];
config[k] = configFactories.concat(v);
} else {
config[k] = ListWrapper.clone(v);
}
});
}
return new Pipes(config);
}
private _getListOfFactories(type: string, obj: any): PipeFactory[] {
var listOfFactories = this.config[type];
if (isBlank(listOfFactories)) {

View File

@ -76,13 +76,47 @@ export function main() {
.toThrowError(`Cannot find 'type' pipe supporting object 'some object'`);
});
describe('.append()', () => {
it('should create a factory that appends new pipes to old', () => {
firstPipeFactory.spy("supports").andReturn(false);
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);
});
it("should prepend passed it config in existing registry", () => {
firstPipeFactory.spy("supports").andReturn(true);
secondPipeFactory.spy("supports").andReturn(true);
secondPipeFactory.spy("create").andReturn(secondPipe);
var pipes1 = Pipes.create({'async': [firstPipeFactory]});
var pipes2 = Pipes.create({'async': [secondPipeFactory]}, pipes1);
expect(pipes2.get("async", "first")).toBe(secondPipe);
});
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);
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);
});
});
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 originalPipes = new Pipes({'async': [firstPipeFactory]});
var binding = Pipes.append({'async':<PipeFactory[]>[secondPipeFactory]});
var binding = Pipes.extend({'async':<PipeFactory[]>[secondPipeFactory]});
var pipes: Pipes = binding.toFactory(originalPipes);
expect(pipes.config['async'].length).toBe(2);
@ -90,33 +124,34 @@ export function main() {
expect(pipes.get('async', 'second plz')).toBe(secondPipe);
});
it('should append to di-inherited pipes', () => {
firstPipeFactory.spy("supports").andReturn(false);
secondPipeFactory.spy("supports").andReturn(true);
secondPipeFactory.spy("create").andReturn(secondPipe);
var originalPipes: Pipes = new Pipes({'async': [firstPipeFactory]});
var injector: Injector = Injector.resolveAndCreate([bind(Pipes).toValue(originalPipes)]);
var childInjector: Injector =
injector.resolveAndCreateChild([Pipes.append({'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(secondPipe);
});
it('should throw if calling append when creating root injector', () => {
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.append({'async': [secondPipeFactory]})]);
Injector.resolveAndCreate([Pipes.extend({'async': [secondPipeFactory]})]);
expect(() => injector.get(Pipes))
.toThrowError(/Cannot append to Pipes without a parent injector/g);
.toThrowError(/Cannot extend Pipes without a parent injector/g);
});
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);
});
});
});