Skip to content

Commit 504d0dc

Browse files
committed
Support symbol messages
1 parent a302382 commit 504d0dc

File tree

2 files changed

+164
-7
lines changed

2 files changed

+164
-7
lines changed

src/index.test.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,130 @@ describe("Machine with entry and exit actions", () => {
123123
});
124124
});
125125

126+
describe.skip("Fetch with abort signal", () => {
127+
const someURL = new URL("https://example.org/");
128+
function fetchData() {
129+
return fetch(someURL.toString());
130+
}
131+
132+
function Loader() {
133+
const aborterKey = Symbol('aborter');
134+
// yield register(aborterKey, () => new AbortController());
135+
// yield register(function aborter() { return new AbortController() });
136+
137+
function* idle() {
138+
yield on("FETCH", loading);
139+
}
140+
function* loading() {
141+
// yield entry(aborterKey);
142+
143+
yield entry(fetchData);
144+
yield exit(finishedLoading);
145+
yield on("SUCCESS", success);
146+
yield on("FAILURE", failure);
147+
// yield forward(AbortController.prototype.abort, aborter);
148+
// yield forward("abort", aborter);
149+
// yield forward("CANCEL", aborter);
150+
// yield forward("CANCEL", aborterKey);
151+
}
152+
function* success() {
153+
yield entry(succeeded);
154+
}
155+
function* failure() {
156+
yield on("RETRY", loading);
157+
}
158+
159+
return idle;
160+
}
161+
162+
test("creating", () => {
163+
const loader = start(Loader);
164+
expect(loader).toBeDefined();
165+
});
166+
167+
describe("when fetch succeeds", () => {
168+
beforeEach(() => {
169+
fetch.mockResolvedValue(42);
170+
});
171+
172+
test("sending events", async () => {
173+
const loader = start(Loader);
174+
expect(loader.current).toEqual("idle");
175+
expect(loader.changeCount).toEqual(0);
176+
177+
loader.next("NOOP");
178+
expect(loader.current).toEqual("idle");
179+
expect(loader.changeCount).toEqual(0);
180+
181+
const transitionResult = loader.next("FETCH");
182+
expect(fetch).toHaveBeenCalledWith("https://example.org/");
183+
expect(transitionResult.actions).toEqual([
184+
{ type: "entry", f: fetchData },
185+
]);
186+
expect(loader.current).toEqual("loading");
187+
expect(loader.changeCount).toEqual(1);
188+
expect(finishedLoading).toHaveBeenCalledTimes(0);
189+
190+
await expect(loader.results).resolves.toEqual({ fetchData: 42 });
191+
await expect(Promise.resolve(transitionResult)).resolves.toEqual({ fetchData: 42 });
192+
expect(finishedLoading).toHaveBeenCalledTimes(1);
193+
expect(loader.changeCount).toEqual(2);
194+
expect(loader.current).toEqual("success");
195+
expect(succeeded).toHaveBeenCalledTimes(1);
196+
197+
const transitionResult2 = loader.next("FETCH");
198+
// expect(transitionResult2.actions).toEqual([]);
199+
expect(loader.changeCount).toEqual(2);
200+
expect(loader.current).toEqual("success");
201+
expect(succeeded).toHaveBeenCalledTimes(1);
202+
203+
await loader.results;
204+
});
205+
});
206+
207+
describe("when fetch fails", () => {
208+
beforeEach(() => {
209+
fetch.mockRejectedValueOnce(new Error("Failed!")).mockResolvedValue(42);
210+
});
211+
212+
test("sending events", async () => {
213+
const loader = start(Loader);
214+
expect(loader.current).toEqual("idle");
215+
216+
const transitionResult = loader.next("FETCH");
217+
expect(fetch).toHaveBeenCalledTimes(1);
218+
expect(fetch).toHaveBeenLastCalledWith("https://example.org/");
219+
expect(transitionResult.actions).toEqual([
220+
{ type: "entry", f: fetchData },
221+
]);
222+
expect(loader.current).toEqual("loading");
223+
expect(loader.changeCount).toEqual(1);
224+
225+
await expect(loader.results).rejects.toEqual(new Error("Failed!"));
226+
await expect(Promise.resolve(transitionResult)).rejects.toEqual(
227+
new Error("Failed!")
228+
);
229+
expect(loader.changeCount).toEqual(2);
230+
expect(loader.current).toEqual("failure");
231+
232+
loader.next("FETCH");
233+
expect(fetch).toHaveBeenCalledTimes(1);
234+
expect(loader.changeCount).toEqual(2);
235+
236+
loader.next("RETRY");
237+
expect(loader.current).toEqual("loading");
238+
expect(loader.changeCount).toEqual(3);
239+
240+
expect(fetch).toHaveBeenCalledTimes(2);
241+
expect(fetch).toHaveBeenLastCalledWith("https://example.org/");
242+
243+
await expect(loader.results).resolves.toEqual({ fetchData: 42 });
244+
expect(loader.changeCount).toEqual(4);
245+
expect(loader.current).toEqual("success");
246+
});
247+
});
248+
});
249+
126250
describe("Form Field Machine with always()", () => {
127251
const isValid = jest.fn();
128252
beforeEach(isValid.mockClear);
@@ -294,6 +418,39 @@ describe("Switch", () => {
294418
});
295419
});
296420

421+
describe("Switch with symbol messages", () => {
422+
const FLICK = Symbol('FLICK');
423+
424+
function* Switch() {
425+
function* OFF() {
426+
yield on(FLICK, ON);
427+
}
428+
function* ON() {
429+
yield on(FLICK, OFF);
430+
}
431+
432+
return OFF;
433+
}
434+
435+
test("sending events", () => {
436+
const machine = start(Switch);
437+
expect(machine).toBeDefined();
438+
expect(machine.current).toEqual("OFF");
439+
440+
machine.next(FLICK);
441+
expect(machine.current).toEqual("ON");
442+
expect(machine.changeCount).toEqual(1);
443+
444+
machine.next(FLICK);
445+
expect(machine.current).toEqual("OFF");
446+
expect(machine.changeCount).toEqual(2);
447+
448+
machine.next(Symbol('will be ignored'));
449+
expect(machine.current).toEqual("OFF");
450+
expect(machine.changeCount).toEqual(2);
451+
});
452+
});
453+
297454
/*describe("Counter", () => {
298455
function* Counter() {
299456
function* initial() {

src/index.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export interface Compound {
3030
export type Target = StateDefinition | Cond | Compound;
3131
export interface On {
3232
type: "on";
33-
on: string;
33+
on: string | symbol;
3434
target: Target;
3535
}
3636
export interface Always {
@@ -55,7 +55,7 @@ export function exit(f: ActionBody): ExitAction {
5555
return { type: "exit", f };
5656
}
5757

58-
export function on<Event extends string>(event: Event, target: Target): On {
58+
export function on<Event extends string | symbol>(event: Event, target: Target): On {
5959
return { type: "on", on: event, target };
6060
}
6161

@@ -77,15 +77,15 @@ export interface MachineInstance extends Iterator<null | string | Record<string,
7777
results: null | Promise<Array<any>>;
7878
done: boolean;
7979
next(
80-
...args: [string]
80+
...args: [string | symbol]
8181
): IteratorResult<null | string | Record<string, string>> &
8282
PromiseLike<any> & {
8383
actions: Array<EntryAction | ExitAction>;
8484
};
8585
}
8686

8787
class Handlers {
88-
private eventsMap = new Map<string, Target>();
88+
private eventsMap = new Map<string | symbol, Target>();
8989
private alwaysArray = new Array<Target>();
9090
private entryActions = [] as Array<EntryAction>;
9191
private exitActions = [] as Array<ExitAction>;
@@ -147,7 +147,7 @@ class Handlers {
147147
this.alwaysArray.some(process);
148148
}
149149

150-
targetForEvent(event: string) {
150+
targetForEvent(event: string | symbol) {
151151
return this.eventsMap.get(event);
152152
}
153153
}
@@ -324,7 +324,7 @@ class InternalInstance {
324324
return false;
325325
}
326326

327-
receive(event: string) {
327+
receive(event: string | symbol) {
328328
this.child?.receive(event);
329329

330330
const target = this.globalHandlers.targetForEvent(event);
@@ -367,7 +367,7 @@ export function start(
367367
get results() {
368368
return instance.results;
369369
},
370-
next(event: string) {
370+
next(event: string | symbol) {
371371
instance.receive(event);
372372
const promise = instance.results;
373373
return {

0 commit comments

Comments
 (0)