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
paldepind committed Aug 21, 2019
1 parent 588a098 commit 9616676
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
@@ -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
);
@@ -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)
};
}
}

@@ -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);
}

@@ -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 };
}
27 changes: 19 additions & 8 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -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 extends Record<string, any>,
B extends Record<string, any>
>(a: A, b: B): A & B {
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 as any)[key] = valueA.combine(valueB);
} else {
throw new Error(
`Components was merged with colliding output on key ${key}`
);
}
} else {
(a as any)[key] = valueB;
}
}
return <any>c;
return <any>a;
}

export type Merge<T> = { [K in keyof T]: T[K] };
27 changes: 25 additions & 2 deletions test/component.spec.ts
Original file line number Diff line number Diff line change
@@ -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", () => {

0 comments on commit 9616676

Please sign in to comment.