Skip to content

Commit

Permalink
When output is merged streams are combined, error thrown on other col…
Browse files Browse the repository at this point in the history
…lisions
  • Loading branch information
paldepind committed Aug 21, 2019
1 parent 588a098 commit bc7f7e4
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 21 deletions.
27 changes: 16 additions & 11 deletions src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,17 @@ export abstract class Component<A, O> implements Monad<O> {
output(handler: any): Component<A, any> {
if (typeof handler === "function") {
return new HandleOutput(
(a, o) => ({ available: a, output: mergeObj(o, handler(a)) }),
(a, o) => ({
available: a,
output: mergeObj(mergeObj({}, handler(a)), o)
}),
this
);
} else {
return new HandleOutput(
(a, o) => ({
available: a,
output: mergeObj(o, copyRemaps(handler, a))
output: mergeObj(mergeObj({}, o), copyRemaps(handler, a))
}),
this
);
Expand Down Expand Up @@ -323,15 +326,17 @@ class MergeComponent<
O extends object,
B,
P extends object
> extends Component<O & P, O & P> {
> extends Component<{}, O & P> {
constructor(private c1: Component<A, O>, private c2: Component<B, P>) {
super();
}
run(parent: DomApi, destroyed: Future<boolean>): Out<O & P, O & P> {
const { output: o1 } = this.c1.run(parent, destroyed);
const { output: o2 } = this.c2.run(parent, destroyed);
const output = Object.assign({}, o1, o2);
return { available: output, output };
run(parent: DomApi, destroyed: Future<boolean>): Out<{}, O & P> {
const res1 = this.c1.run(parent, destroyed);
const res2 = this.c2.run(parent, destroyed);
return {
available: {},
output: mergeObj(mergeObj({}, res2.output), res1.output)
};
}
}

Expand All @@ -341,7 +346,7 @@ class MergeComponent<
export function merge<O extends object, A, P extends object, B>(
c1: Component<A, O>,
c2: Component<B, P>
): Component<O & P, O & P> {
): Component<{}, O & P> {
return new MergeComponent(c1, c2);
}

Expand Down Expand Up @@ -561,11 +566,11 @@ class ListComponent extends Component<any, any> {
}
}
run(parent: DomApi, destroyed: Future<boolean>): Out<any, any> {
const output: Record<string, any> = {};
let output: Record<string, any> = {};
for (let i = 0; i < this.components.length; ++i) {
const component = this.components[i];
const res = component.run(parent, destroyed);
Object.assign(output, res.output);
mergeObj(output, res.output);
}
return { available: output, output };
}
Expand Down
27 changes: 19 additions & 8 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,26 @@ function isObject(item: any): item is Object {
);
}

export function mergeObj<A, B>(a: A, b: B): A & B {
const c: { [key: string]: any } = {};
for (const key of Object.keys(a) as (keyof A & string)[]) {
c[key] = a[key];
}
for (const key of Object.keys(b) as (keyof B & string)[]) {
c[key] = b[key];
export function mergeObj(
a: Record<string, any>,
b: Record<string, any>
): Record<string, any> {
for (const key of Object.keys(b) as string[]) {
const valueA: any = a[key];
const valueB: any = b[key];
if (valueA !== undefined) {
if (isStream(valueA) && isStream(valueB)) {
a[key] = valueA.combine(valueB);
} else {
throw new Error(
`Components was merged with colliding output on key ${key}`
);
}
} else {
a[key] = valueB;
}
}
return <any>c;
return <any>a;
}

export type Merge<T> = { [K in keyof T]: T[K] };
Expand Down
27 changes: 25 additions & 2 deletions test/component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,34 @@ describe("component specs", () => {
const b2 = button().output({ click2: "click" });
const m = merge(b1, b2);
const { output, available } = testComponent(m);
expect(available).to.have.property("click1");
expect(available).to.have.property("click2");
assert.deepEqual(available, {});
expect(output).to.have.property("click1");
expect(output).to.have.property("click2");
});
it("merges colliding streams", () => {
const sink1 = H.sinkStream<number>();
const sink2 = H.sinkStream<number>();
const m = merge(
Component.of({ click: sink1 }),
Component.of({ click: sink2 })
);
const { output } = testComponent(m);
expect(output).to.have.property("click");
const result: number[] = [];
output.click.subscribe((n) => result.push(n));
sink1.push(0);
sink2.push(1);
assert.deepEqual(result, [0, 1]);
});
it("throws on all other collisions", () => {
assert.throws(() => {
const m = merge(
Component.of({ click: H.Behavior.of(0) }),
Component.of({ click: H.empty })
);
testComponent(m);
}, "colliding");
});
});
describe("empty component", () => {
it("creates no dom", () => {
Expand Down

0 comments on commit bc7f7e4

Please sign in to comment.