Skip to content

Commit

Permalink
Rename loop to component and handle two return values
Browse files Browse the repository at this point in the history
  • Loading branch information
paldepind committed Aug 20, 2019
1 parent 68f6039 commit bda242a
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 18 deletions.
42 changes: 32 additions & 10 deletions src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export abstract class Component<A, O> implements Monad<O> {
);
}
}
result<R>(o: R): Result<R, O> {
return { output: o, child: this };
}
view(): Component<O, {}> {
return view(this);
}
Expand Down Expand Up @@ -253,9 +256,17 @@ const placeholderProxyHandler = {
}
};

class LoopComponent<O> extends Component<O, {}> {
type Result<R, O> = { output: R; child: Child<O> };

function isLoopResult(r: any): r is Result<any, any> {
return typeof r === "object" && "child" in r;
}

class LoopComponent<L, O> extends Component<O, {}> {
constructor(
private f: (o: O) => Child<O> | Now<Child<O>>,
private f: (
o: L
) => Child<L> | Now<Child<L>> | Result<O, L> | Now<Result<O, L>>,
private placeholderNames?: string[]
) {
super();
Expand All @@ -272,28 +283,39 @@ class LoopComponent<O> extends Component<O, {}> {
}
}
const res = this.f(placeholderObject);
const child = Now.is(res) ? runNow(res) : res;
const { output } = toComponent(child).run(parent, destroyed);
const result = Now.is(res) ? runNow<Child<L> | Result<O, L>>(res) : res;
const { output, child } = isLoopResult(result)
? result
: { output: {} as O, child: result };
const { output: looped } = toComponent(child).run(parent, destroyed);
const needed = Object.keys(placeholderObject);
for (const name of needed) {
if (name === "destroyed") {
continue;
}
if (output[name] === undefined) {
if (looped[name] === undefined) {
throw new Error(`The property ${name} is missing.`);
}
placeholderObject[name].replaceWith(output[name]);
placeholderObject[name].replaceWith(looped[name]);
}
return { available: output, output: {} };
}
}

export function loop<O extends ReactivesObject>(
f: (o: O) => Child<O> | Now<Child<O>>,
export function component<L extends ReactivesObject>(
f: (l: L) => Child<L> | Now<Child<L>>,
placeholderNames?: string[]
): Component<{}, {}>;
export function component<L extends ReactivesObject, O>(
f: (l: L) => Result<O, L> | Now<Result<O, L>>,
placeholderNames?: string[]
): Component<O, {}>;
export function component<L, O extends ReactivesObject>(
f: (l: L) => Child<L> | Now<Child<L>> | Result<O, L> | Now<Result<O, L>>,
placeholderNames?: string[]
): Component<O, {}> {
const f2 = isGeneratorFunction(f) ? fgo<O>(f) : f;
return new LoopComponent<O>(f2, placeholderNames);
const f2 = isGeneratorFunction(f) ? fgo<L>(f) : f;
return new LoopComponent<L, O>(f2, placeholderNames);
}

class MergeComponent<
Expand Down
19 changes: 11 additions & 8 deletions test/component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
view,
emptyComponent,
elements,
loop,
component,
testComponent,
list,
runComponent,
Expand Down Expand Up @@ -221,23 +221,26 @@ describe("component specs", () => {
});
});

describe("loop", () => {
describe("component loop", () => {
type Looped = { name: H.Behavior<string>; destroyed: H.Future<boolean> };
it("passed selected output as argument", () => {
let b: H.Behavior<number> | undefined = undefined;
const comp = loop<{ foo: H.Behavior<number> }>((input) => {
const comp = component<
{ foo: H.Behavior<number> },
{ bar: H.Behavior<number> }
>((input) => {
b = input.foo;
return Component.of({
foo: H.Behavior.of(2)
});
}).result({ bar: H.Behavior.of(3) });
});
const { available, output } = testComponent(comp);
assert.deepEqual(Object.keys(output), []);
assert.deepEqual(Object.keys(available), ["foo"]);
assert.deepEqual(Object.keys(available), ["bar"]);
expect(H.at(b!)).to.equal(2);
});
it("works with selected fgo and looped behavior", () => {
const comp = loop(
const comp = component(
fgo(function*({ name }: Looped): IterableIterator<Component<any, any>> {
yield div(name);
({ name } = yield input({ props: { value: "Foo" } }).output({
Expand All @@ -252,7 +255,7 @@ describe("component specs", () => {
});
it("can be told to destroy", () => {
let toplevel = false;
const comp = loop(
const comp = component(
fgo(function*({
name,
destroyed
Expand All @@ -273,7 +276,7 @@ describe("component specs", () => {
expect(toplevel).to.equal(true);
});
it("throws helpful error is a reactive is missing", () => {
const c = loop((props: { foo: H.Behavior<string> }) => {
const c = component((props: { foo: H.Behavior<string> }) => {
// Access something that isn't there
(props as any).bar;
return div([dynamic(props.foo)]).output((_) => ({
Expand Down

0 comments on commit bda242a

Please sign in to comment.