Skip to content

Commit

Permalink
Add customEventAsyncGeneator
Browse files Browse the repository at this point in the history
  • Loading branch information
allevo committed Jun 20, 2024
1 parent 366ec0f commit 52beff5
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 20 deletions.
48 changes: 48 additions & 0 deletions packages/seqflow-js/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,51 @@ export async function* combineEvents<T>(
}
}
}

export type CustomEventAsyncGenerator<E> = EventAsyncGenerator<E> & {
push: (ev: E) => void;
};

export function createCustomEventAsyncGenerator<
E,
>(): CustomEventAsyncGenerator<E> {
const queue: E[] = [];

const pipeAbortController = new AbortController();
async function* iterOnEvents(abortController: AbortController) {
// If already aborted, throw immediately
abortController.signal.throwIfAborted();
abortController.signal.addEventListener("abort", () => {
pipeAbortController.abort();
});

while (true) {
pipeAbortController.signal.throwIfAborted();

if (queue.length > 0) {
const ev = queue.shift();
if (ev) {
yield ev;
}
} else {
await new Promise<void>((resolve) => {
pipeAbortController.signal.addEventListener(
wakeupEventName,
() => resolve(),
{
once: true,
signal: pipeAbortController.signal,
},
);
});
}
}
}

iterOnEvents.push = (ev: E) => {
queue.push(ev);
pipeAbortController.signal.dispatchEvent(WAKEUP_EVENT);
};

return iterOnEvents;
}
2 changes: 2 additions & 0 deletions packages/seqflow-js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
import { domEvent, domainEvent, navigationEvent } from "./typedEvents";

export { BrowserRouter, type Router, NavigationEvent, InMemoryRouter };
export type { EventAsyncGenerator, CustomEventAsyncGenerator } from "./events";
export { createCustomEventAsyncGenerator } from "./events";

// biome-ignore lint/suspicious/noEmptyInterface: This type is fulfilled by the user
export interface Domains {}
Expand Down
81 changes: 81 additions & 0 deletions packages/seqflow-js/tests/customAsyncGeneator.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { screen, waitFor } from "@testing-library/dom";
import { expect, test } from "vitest";

import {
type CustomEventAsyncGenerator,
type SeqflowFunctionContext,
createCustomEventAsyncGenerator,
start,
} from "../src/index";

class DeltaEvent extends CustomEvent<{ delta: number }> {
constructor(delta: number) {
super("delta", { detail: { delta } });
}
}

test("domain event - increment", async () => {
async function ChangeButton(
this: SeqflowFunctionContext,
{
delta,
text,
pipe,
}: {
delta: number;
text: string;
pipe: CustomEventAsyncGenerator<DeltaEvent>;
},
) {
this.renderSync(
<button key="button" type="button">
{text}
</button>,
);

const events = this.waitEvents(this.domEvent("click", "button"));
for await (const _ of events) {
pipe.push(new DeltaEvent(delta));
}
}
async function App(this: SeqflowFunctionContext) {
const pipe = createCustomEventAsyncGenerator<DeltaEvent>();

let counter = 0;
this.renderSync(
<div>
<ChangeButton delta={1} text="increment" pipe={pipe} />
<ChangeButton delta={-1} text="decrement" pipe={pipe} />
<div key="counter">{counter}</div>
</div>,
);

const events = this.waitEvents(pipe);
for await (const e of events) {
counter += e.detail.delta;
this.getChild("counter").textContent = `${counter}`;
}
}

start(document.body, App, {}, {});

const incrementButton = await screen.findByText(/increment/i);
const decrementButton = await screen.findByText(/decrement/i);

await screen.findByText(/0/i);
incrementButton.click();
await screen.findByText(/1/i);
incrementButton.click();
await screen.findByText(/2/i);
incrementButton.click();
await screen.findByText(/3/i);

for (let i = 0; i < 10; i++) {
incrementButton.click();
}

await screen.findByText(/13/i);

decrementButton.click();
await screen.findByText(/12/i);
});
56 changes: 36 additions & 20 deletions packages/seqflow-js/tests/domEvent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -299,27 +299,35 @@ test("onClick on element", async () => {
const changeCounter = (amount: number) => {
return () => {
counter += amount;
this.getChild('counter').innerHTML = `${counter}`;
}
}
this.getChild("counter").innerHTML = `${counter}`;
};
};
const replaceWrapper = () => {
this.replaceChild('wrapper', () => <div/>);
}
this.replaceChild("wrapper", () => <div />);
};
this.renderSync(
<>
<div key="wrapper">
<button onClick={changeCounter(-1)}>Decrement</button>
<button onClick={changeCounter(1)}>Increment</button>
<button type="button" onClick={changeCounter(-1)}>
Decrement
</button>
<button type="button" onClick={changeCounter(1)}>
Increment
</button>
</div>
<div key="counter">{counter}</div>
<button onClick={replaceWrapper}>Replace Child</button>
<button type="button" onClick={replaceWrapper}>
Replace Child
</button>
</>,
);
}

start(document.body, App, undefined, {});

const incrementButton = await screen.findByRole('button', { name: /increment/i });
const incrementButton = await screen.findByRole("button", {
name: /increment/i,
});

incrementButton.click();
incrementButton.click();
Expand All @@ -328,13 +336,15 @@ test("onClick on element", async () => {
// Wait for the counter to be updated
await screen.findByText(/3/i);

const decrementButton = await screen.findByRole('button', { name: /decrement/i });
const decrementButton = await screen.findByRole("button", {
name: /decrement/i,
});

decrementButton.click();

await screen.findByText(/2/i);

(await screen.findByRole('button', { name: /replace child/i })).click();
(await screen.findByRole("button", { name: /replace child/i })).click();

for (let i = 0; i < 10; i++) {
incrementButton.click();
Expand All @@ -354,27 +364,31 @@ test("onClick on component", async () => {
const changeCounter = (amount: number) => {
return () => {
counter += amount;
this.getChild('counter').innerHTML = `${counter}`;
}
}
this.getChild("counter").innerHTML = `${counter}`;
};
};
const replaceWrapper = () => {
this.replaceChild('wrapper', () => <div/>);
}
this.replaceChild("wrapper", () => <div />);
};
this.renderSync(
<>
<div key="wrapper">
<Button onClick={changeCounter(-1)} text="Decrement" />
<Button onClick={changeCounter(1)} text="Increment" />
</div>
<div key="counter">{counter}</div>
<button onClick={replaceWrapper}>Replace Child</button>
<button type="button" onClick={replaceWrapper}>
Replace Child
</button>
</>,
);
}

start(document.body, App, undefined, {});

const incrementButton = await screen.findByRole('button', { name: /increment/i });
const incrementButton = await screen.findByRole("button", {
name: /increment/i,
});

incrementButton.click();
incrementButton.click();
Expand All @@ -383,13 +397,15 @@ test("onClick on component", async () => {
// Wait for the counter to be updated
await screen.findByText(/3/i);

const decrementButton = await screen.findByRole('button', { name: /decrement/i });
const decrementButton = await screen.findByRole("button", {
name: /decrement/i,
});

decrementButton.click();

await screen.findByText(/2/i);

(await screen.findByRole('button', { name: /replace child/i })).click();
(await screen.findByRole("button", { name: /replace child/i })).click();

for (let i = 0; i < 10; i++) {
incrementButton.click();
Expand Down

0 comments on commit 52beff5

Please sign in to comment.