Skip to content

Commit 46322fc

Browse files
committed
Add entry()
1 parent ccf5f64 commit 46322fc

2 files changed

Lines changed: 152 additions & 10 deletions

File tree

src/index.test.ts

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,107 @@
1-
import { call, on, start } from "./index";
1+
import { call, entry, on, start } from "./index";
22

33
const fetch = jest.fn();
4+
beforeEach(fetch.mockClear);
45

5-
describe("start()", () => {
6+
describe("Machine with entry", () => {
7+
function Loader() {
8+
function* idle() {
9+
yield on("FETCH", loading);
10+
}
11+
function* loading() {
12+
yield entry(fetchData);
13+
yield on("SUCCESS", success);
14+
yield on("FAILURE", failure);
15+
}
16+
function* success() {}
17+
function* failure() {
18+
yield on("RETRY", loading);
19+
}
20+
21+
return idle;
22+
}
23+
24+
const someURL = new URL("https://example.org/");
25+
function fetchData() {
26+
return fetch(someURL.toString());
27+
}
28+
29+
test("creating", () => {
30+
const loader = start(Loader);
31+
expect(loader).toBeDefined();
32+
});
33+
34+
describe("when fetch succeeds", () => {
35+
beforeEach(() => {
36+
fetch.mockResolvedValue(42);
37+
});
38+
39+
test("sending events", async () => {
40+
const loader = start(Loader);
41+
expect(loader.value).toEqual("idle");
42+
expect(loader.changeCount).toEqual(0);
43+
44+
loader.next("NOOP");
45+
expect(loader.value).toEqual("idle");
46+
expect(loader.changeCount).toEqual(0);
47+
48+
const { actions } = loader.next("FETCH");
49+
expect(fetch).toHaveBeenCalledWith("https://example.org/");
50+
expect(actions).toEqual([{ type: "entry", then: fetchData }]);
51+
expect(loader.value).toEqual("loading");
52+
expect(loader.changeCount).toEqual(1);
53+
54+
await expect(loader.promisedValue).resolves.toEqual([42]);
55+
expect(loader.changeCount).toEqual(2);
56+
expect(loader.value).toEqual("success");
57+
58+
loader.next("FETCH");
59+
expect(loader.changeCount).toEqual(2);
60+
expect(loader.value).toEqual("success");
61+
62+
await loader.promisedValue;
63+
});
64+
});
65+
66+
describe("when fetch fails", () => {
67+
beforeEach(() => {
68+
fetch.mockRejectedValueOnce(new Error("Failed!")).mockResolvedValue(42);
69+
});
70+
71+
test("sending events", async () => {
72+
const loader = start(Loader, [{ url: someURL }]);
73+
expect(loader.value).toEqual("idle");
74+
75+
const { actions } = loader.next("FETCH");
76+
expect(fetch).toHaveBeenCalledTimes(1);
77+
expect(fetch).toHaveBeenLastCalledWith("https://example.org/");
78+
expect(actions).toEqual([{ type: "entry", then: fetchData }]);
79+
expect(loader.value).toEqual("loading");
80+
expect(loader.changeCount).toEqual(1);
81+
82+
await expect(loader.promisedValue).rejects.toEqual(new Error("Failed!"));
83+
expect(loader.changeCount).toEqual(2);
84+
expect(loader.value).toEqual("failure");
85+
86+
loader.next("FETCH");
87+
expect(fetch).toHaveBeenCalledTimes(1);
88+
expect(loader.changeCount).toEqual(2);
89+
90+
loader.next("RETRY");
91+
expect(loader.value).toEqual("loading");
92+
expect(loader.changeCount).toEqual(3);
93+
94+
expect(fetch).toHaveBeenCalledTimes(2);
95+
expect(fetch).toHaveBeenLastCalledWith("https://example.org/");
96+
97+
await expect(loader.promisedValue).resolves.toEqual([42]);
98+
expect(loader.changeCount).toEqual(4);
99+
expect(loader.value).toEqual("success");
100+
});
101+
});
102+
});
103+
104+
describe("Machine with call", () => {
6105
function Loader({ url }: { url: URL }) {
7106
function* idle() {
8107
yield on("FETCH", loading);
@@ -72,20 +171,23 @@ describe("start()", () => {
72171
expect(loader.value).toEqual("loading");
73172
expect(loader.changeCount).toEqual(1);
74173

75-
expect(fetch).toHaveBeenNthCalledWith(1, "https://example.org/");
174+
expect(fetch).toHaveBeenCalledTimes(1);
175+
expect(fetch).toHaveBeenLastCalledWith("https://example.org/");
76176

77177
await expect(loader.promisedValue).rejects.toEqual(new Error("Failed!"));
78178
expect(loader.changeCount).toEqual(2);
79179
expect(loader.value).toEqual("failure");
80180

81181
loader.next("FETCH");
182+
expect(fetch).toHaveBeenCalledTimes(1);
82183
expect(loader.changeCount).toEqual(2);
83184

84185
loader.next("RETRY");
85186
expect(loader.value).toEqual("loading");
86187
expect(loader.changeCount).toEqual(3);
87188

88-
expect(fetch).toHaveBeenNthCalledWith(2, "https://example.org/");
189+
expect(fetch).toHaveBeenCalledTimes(2);
190+
expect(fetch).toHaveBeenLastCalledWith("https://example.org/");
89191

90192
await expect(loader.promisedValue).resolves.toEqual([42]);
91193
expect(loader.changeCount).toEqual(4);

src/index.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,26 @@ export interface Call<A extends Array<any>> {
44
args: A;
55
}
66

7+
export interface ActionBody {
8+
(): void;
9+
}
10+
11+
export interface EntryAction {
12+
type: "entry";
13+
then: ActionBody;
14+
}
15+
export interface ExitAction {
16+
type: "exit";
17+
then: ActionBody;
18+
}
19+
720
export interface On {
821
type: "on";
922
on: string;
1023
target: Function;
1124
}
1225

13-
export type Yielded = Call<any> | On;
26+
export type Yielded = EntryAction | On | Call<any>;
1427

1528
export function call<Arguments extends Array<any>>(
1629
f: (...args: Arguments) => void,
@@ -19,6 +32,14 @@ export function call<Arguments extends Array<any>>(
1932
return { type: "call", f, args };
2033
}
2134

35+
export function entry(then: ActionBody): EntryAction {
36+
return { type: "entry", then };
37+
}
38+
39+
export function exit(then: ActionBody): ExitAction {
40+
return { type: "exit", then };
41+
}
42+
2243
export function on<Event extends string>(event: Event, target: Function): On {
2344
return { type: "on", on: event, target };
2445
}
@@ -28,11 +49,16 @@ export interface MachineInstance extends Iterator<string, void, string> {
2849
value: string;
2950
promisedValue: null | Promise<Array<any>>;
3051
done: boolean;
52+
next(
53+
...args: [string]
54+
): IteratorResult<string, void> & {
55+
actions: Array<EntryAction | ExitAction>;
56+
};
3157
}
3258

3359
export function start<Arguments extends Array<any>>(
3460
machine: (...args: Arguments) => () => Generator<Yielded>,
35-
args: Arguments
61+
args?: Arguments
3662
): MachineInstance {
3763
const initialGenerator = machine.apply(null, args);
3864

@@ -41,6 +67,7 @@ export function start<Arguments extends Array<any>>(
4167
let state = {
4268
changeCount: -1,
4369
current: "",
70+
actions: [] as Array<EntryAction | ExitAction>,
4471
promisedValue: null as Promise<Array<any>> | null,
4572
};
4673

@@ -61,10 +88,19 @@ export function start<Arguments extends Array<any>>(
6188

6289
const results: Array<any> = [];
6390

64-
const iterable = stateGenerator()
91+
const iterable = stateGenerator();
6592
for (const value of iterable) {
66-
if (value.type === "call") {
67-
const result = new Promise(resolve => resolve(value.f.apply(null, value.args)));
93+
if (value.type === "entry") {
94+
state.actions.push(value);
95+
// const result = Promise.resolve(value);
96+
const result = new Promise((resolve) => {
97+
resolve(value.then())
98+
});
99+
results.push(result);
100+
} else if (value.type === "call") {
101+
const result = new Promise((resolve) =>
102+
resolve(value.f.apply(null, value.args))
103+
);
68104
results.push(result);
69105
} else if (value.type === "on") {
70106
eventsMap.set(value.on, value.target);
@@ -97,7 +133,11 @@ export function start<Arguments extends Array<any>>(
97133
},
98134
next(event: string) {
99135
receive(event, state.changeCount);
100-
return { value: state.current, done: false };
136+
return {
137+
value: state.current,
138+
actions: Array.from(state.actions),
139+
done: false,
140+
};
101141
},
102142
get done() {
103143
return false;

0 commit comments

Comments
 (0)