- commit
- 95bee2f
- parent
- 797ba54
- author
- Eric Bower
- date
- 2024-02-10 16:59:03 +0000 UTC
Wait for loader (#37)
13 files changed,
+241,
-110
+25,
-0
1@@ -1,5 +1,6 @@
2 import {
3 call,
4+ Callable,
5 createContext,
6 createSignal,
7 each,
8@@ -12,6 +13,7 @@ import {
9 import { ActionPattern, matcher } from "./matcher.ts";
10 import type { Action, ActionWithPayload, AnyAction } from "./types.ts";
11 import { createFilterQueue } from "./queue.ts";
12+import { ActionFnWithPayload } from "./types.ts";
13
14 export const ActionContext = createContext(
15 "starfx:action",
16@@ -103,6 +105,29 @@ export function* takeLeading<T>(
17 }
18 }
19
20+export function* waitFor(
21+ predicate: Callable<boolean>,
22+) {
23+ const init = yield* call(predicate as any);
24+ if (init) {
25+ return;
26+ }
27+
28+ while (true) {
29+ yield* take("*");
30+ const result = yield* call(() => predicate as any);
31+ if (result) {
32+ return;
33+ }
34+ }
35+}
36+
37+export function getIdFromAction(
38+ action: ActionWithPayload<{ key: string }> | ActionFnWithPayload,
39+): string {
40+ return typeof action === "function" ? action.toString() : action.payload.key;
41+}
42+
43 export const API_ACTION_PREFIX = "";
44 export const createAction = (type: string) => {
45 if (!type) throw new Error("createAction requires non-empty string");
+1,
-1
1@@ -11,7 +11,7 @@ export function superviseBackoff(attempt: number, max = 10): number {
2 }
3
4 /**
5- * {@link supvervise} will watch whatever {@link Operation} is provided
6+ * supvervise will watch whatever {@link Operation} is provided
7 * and it will automatically try to restart it when it exists. By
8 * default it uses a backoff pressure mechanism so if there is an
9 * error simply calling the {@link Operation} then it will exponentially
+2,
-1
1@@ -1,8 +1,9 @@
2 import { createThunks, type ThunksApi } from "./thunk.ts";
3+import * as mdw from "./mdw.ts";
4+
5 export * from "./api.ts";
6 export * from "./types.ts";
7 export * from "./create-key.ts";
8-import * as mdw from "./mdw.ts";
9
10 export { createThunks, mdw, ThunksApi };
11
M
react.ts
+8,
-9
1@@ -7,6 +7,8 @@ import {
2 import type { AnyState, LoaderState } from "./types.ts";
3 import type { ThunkAction } from "./query/mod.ts";
4 import { type FxSchema, type FxStore, PERSIST_LOADER_ID } from "./store/mod.ts";
5+import { ActionFn, ActionFnWithPayload } from "./types.ts";
6+import { getIdFromAction } from "./action.ts";
7
8 export { useDispatch, useSelector } from "./deps.ts";
9 export type { TypedUseSelectorHook } from "./deps.ts";
10@@ -19,16 +21,13 @@ const {
11 createElement: h,
12 } = React;
13
14-type ActionFn<P = any> = (p: P) => { toString: () => string };
15-type ActionFnSimple = () => { toString: () => string };
16-
17 export interface UseApiProps<P = any> extends LoaderState {
18 trigger: (p: P) => void;
19- action: ActionFn<P>;
20+ action: ActionFnWithPayload<P>;
21 }
22 export interface UseApiSimpleProps extends LoaderState {
23 trigger: () => void;
24- action: ActionFn;
25+ action: ActionFnWithPayload;
26 }
27 export interface UseApiAction<A extends ThunkAction = ThunkAction>
28 extends LoaderState {
29@@ -93,10 +92,10 @@ export function useSchema<S extends AnyState>() {
30 * ```
31 */
32 export function useLoader<S extends AnyState>(
33- action: ThunkAction | ActionFn,
34+ action: ThunkAction | ActionFnWithPayload,
35 ) {
36 const schema = useSchema();
37- const id = typeof action === "function" ? `${action}` : action.payload.key;
38+ const id = getIdFromAction(action);
39 return useSelector((s: S) => schema.loaders.selectById(s, { id }));
40 }
41
42@@ -131,10 +130,10 @@ export function useApi<P = any, A extends ThunkAction = ThunkAction<P>>(
43 action: A,
44 ): UseApiAction<A>;
45 export function useApi<P = any, A extends ThunkAction = ThunkAction<P>>(
46- action: ActionFn<P>,
47+ action: ActionFnWithPayload<P>,
48 ): UseApiProps<P>;
49 export function useApi<A extends ThunkAction = ThunkAction>(
50- action: ActionFnSimple,
51+ action: ActionFn,
52 ): UseApiSimpleProps;
53 export function useApi(action: any): any {
54 const dispatch = useDispatch();
+36,
-2
1@@ -1,9 +1,11 @@
2 import { Operation, Result } from "../deps.ts";
3-import type { AnyState } from "../types.ts";
4+import type { ActionFnWithPayload, AnyState } from "../types.ts";
5 import type { FxStore, StoreUpdater, UpdaterCtx } from "./types.ts";
6 import { StoreContext } from "./context.ts";
7 import { LoaderOutput } from "./slice/loader.ts";
8-import { safe } from "../fx/mod.ts";
9+import { parallel, safe } from "../fx/mod.ts";
10+import { ThunkAction } from "../query/mod.ts";
11+import { getIdFromAction, take } from "../action.ts";
12
13 export function* updateStore<S extends AnyState>(
14 updater: StoreUpdater<S> | StoreUpdater<S>[],
15@@ -28,6 +30,38 @@ export function* select<S, R, P>(
16 return selectorFn(store.getState() as S, p);
17 }
18
19+export function* waitForLoader<M extends AnyState>(
20+ loaders: LoaderOutput<M, AnyState>,
21+ action: ThunkAction | ActionFnWithPayload,
22+) {
23+ const id = getIdFromAction(action);
24+ const selector = (s: AnyState) => loaders.selectById(s, { id });
25+
26+ // check for done state on init
27+ let loader = yield* select(selector);
28+ if (loader.isSuccess || loader.isError) {
29+ return loader;
30+ }
31+
32+ while (true) {
33+ yield* take("*");
34+ loader = yield* select(selector);
35+ if (loader.isSuccess || loader.isError) {
36+ return loader;
37+ }
38+ }
39+}
40+
41+export function* waitForLoaders<M extends AnyState>(
42+ loaders: LoaderOutput<M, AnyState>,
43+ actions: (ThunkAction | ActionFnWithPayload)[],
44+) {
45+ const group = yield* parallel(
46+ actions.map((action) => waitForLoader(loaders, action)),
47+ );
48+ return yield* group;
49+}
50+
51 export function createTracker<T, M extends Record<string, unknown>>(
52 loader: LoaderOutput<M, AnyState>,
53 ) {
+1,
-1
1@@ -128,7 +128,7 @@ export function loaderApi<
2 }
3
4 if (!ctx.loader) {
5- ctx.loader || {};
6+ ctx.loader = {};
7 }
8
9 if (!ctx.response.ok) {
M
test.ts
+0,
-7
1@@ -8,13 +8,6 @@ export * as asserts from "https://deno.land/std@0.185.0/testing/asserts.ts";
2 export { expect } from "https://deno.land/x/expect@v0.3.0/mod.ts";
3 export { install, mock } from "https://deno.land/x/mock_fetch@0.3.0/mod.ts";
4
5-export const sleep = (n: number) =>
6- new Promise<void>((resolve) => {
7- setTimeout(() => {
8- resolve();
9- }, n);
10- });
11-
12 export function isLikeSelector(selector: unknown) {
13 return (
14 selector !== null &&
+14,
-11
1@@ -1,21 +1,25 @@
2 import { describe, expect, it } from "../test.ts";
3-import { sleep } from "../test.ts";
4 import {
5 configureStore,
6 createSchema,
7+ select,
8 slice,
9 storeMdw,
10 updateStore,
11+ waitForLoader,
12 } from "../store/mod.ts";
13 import {
14+ AnyState,
15 type ApiCtx,
16 call,
17 createApi,
18 createKey,
19 keepAlive,
20 mdw,
21+ Operation,
22 safe,
23 takeEvery,
24+ waitFor,
25 } from "../mod.ts";
26 import { useCache } from "../react.ts";
27
28@@ -44,9 +48,8 @@ const jsonBlob = (data: unknown) => {
29
30 const tests = describe("createApi()");
31
32-it(tests, "createApi - POST", async () => {
33+it(tests, "POST", async () => {
34 const query = createApi();
35-
36 query.use(mdw.queryCtx);
37 query.use(mdw.nameParser);
38 query.use(query.routes());
39@@ -102,7 +105,12 @@ it(tests, "createApi - POST", async () => {
40 store.run(query.bootup);
41
42 store.dispatch(createUser({ email: mockUser.email }));
43- await sleep(150);
44+
45+ await store.run(waitFor(function* (): Operation<boolean> {
46+ const res = yield* select((state: AnyState) => state.users["1"].id);
47+ return res !== "";
48+ }));
49+
50 expect(store.getState().users).toEqual({
51 "1": { id: "1", name: "test", email: "test@test.com" },
52 });
53@@ -292,7 +300,7 @@ it(tests, "with hash key on a large post", async () => {
54 const action = createUserDefaultKey({ email, largetext });
55 store.dispatch(action);
56
57- await sleep(150);
58+ await store.run(waitForLoader(schema.loaders, action));
59
60 const s = store.getState();
61 const expectedKey = createKey(action.payload.name, {
62@@ -306,18 +314,16 @@ it(tests, "with hash key on a large post", async () => {
63 });
64 });
65
66-it(tests, "createApi - two identical endpoints", async () => {
67+it(tests, "two identical endpoints", () => {
68 const actual: string[] = [];
69 const { store, schema } = testStore();
70 const api = createApi();
71 api.use(mdw.api());
72 api.use(storeMdw.store(schema));
73- api.use(mdw.nameParser);
74 api.use(api.routes());
75
76 const first = api.get(
77 "/health",
78- { supervisor: takeEvery },
79 function* (ctx, next) {
80 actual.push(ctx.req().url);
81 yield* next();
82@@ -326,7 +332,6 @@ it(tests, "createApi - two identical endpoints", async () => {
83
84 const second = api.get(
85 ["/health", "poll"],
86- { supervisor: takeEvery },
87 function* (ctx, next) {
88 actual.push(ctx.req().url);
89 yield* next();
90@@ -337,8 +342,6 @@ it(tests, "createApi - two identical endpoints", async () => {
91 store.dispatch(first());
92 store.dispatch(second());
93
94- await sleep(150);
95-
96 expect(actual).toEqual(["/health", "/health"]);
97 });
98
+117,
-63
1@@ -1,17 +1,19 @@
2 import { describe, expect, install, it, mock } from "../test.ts";
3-import { configureStore, createSchema, slice, storeMdw } from "../store/mod.ts";
4-import { createApi, mdw, takeEvery } from "../mod.ts";
5+import {
6+ configureStore,
7+ createSchema,
8+ slice,
9+ storeMdw,
10+ waitForLoader,
11+ waitForLoaders,
12+} from "../store/mod.ts";
13+import { ApiCtx, createApi, mdw, takeEvery } from "../mod.ts";
14
15 install();
16
17 const baseUrl = "https://starfx.com";
18 const mockUser = { id: "1", email: "test@starfx.com" };
19
20-const delay = (n = 200) =>
21- new Promise((resolve) => {
22- setTimeout(resolve, n);
23- });
24-
25 const testStore = () => {
26 const [schema, initialState] = createSchema({
27 loaders: slice.loader(),
28@@ -21,6 +23,10 @@ const testStore = () => {
29 return { schema, store };
30 };
31
32+const getTestData = (ctx: ApiCtx) => {
33+ return { request: { ...ctx.req() }, json: { ...ctx.json } };
34+};
35+
36 const tests = describe("mdw.fetch()");
37
38 it(
39@@ -57,11 +63,10 @@ it(
40 const action = fetchUsers();
41 store.dispatch(action);
42
43- await delay();
44+ await store.run(waitForLoader(schema.loaders, action));
45
46 const state = store.getState();
47 expect(state.cache[action.payload.key]).toEqual(mockUser);
48-
49 expect(actual).toEqual([{
50 url: `${baseUrl}/users`,
51 method: "GET",
52@@ -104,7 +109,8 @@ it(
53 const action = fetchUsers();
54 store.dispatch(action);
55
56- await delay();
57+ await store.run(waitForLoader(schema.loaders, action));
58+
59 const data = "this is some text";
60 expect(actual).toEqual({ ok: true, data, value: data });
61 },
62@@ -140,7 +146,7 @@ it(tests, "error handling", async () => {
63 const action = fetchUsers();
64 store.dispatch(action);
65
66- await delay();
67+ await store.run(waitForLoader(schema.loaders, action));
68
69 const state = store.getState();
70 expect(state.cache[action.payload.key]).toEqual(errMsg);
71@@ -180,7 +186,7 @@ it(tests, "status 204", async () => {
72 const action = fetchUsers();
73 store.dispatch(action);
74
75- await delay();
76+ await store.run(waitForLoader(schema.loaders, action));
77
78 const state = store.getState();
79 expect(state.cache[action.payload.key]).toEqual({});
80@@ -220,7 +226,7 @@ it(tests, "malformed json", async () => {
81 const action = fetchUsers();
82 store.dispatch(action);
83
84- await delay();
85+ await store.run(waitForLoader(schema.loaders, action));
86
87 const data = {
88 message: "Unexpected token 'o', \"not json\" is not valid JSON",
89@@ -255,16 +261,7 @@ it(tests, "POST", async () => {
90 });
91 yield* next();
92
93- expect(ctx.req()).toEqual({
94- url: `${baseUrl}/users`,
95- headers: {
96- "Content-Type": "application/json",
97- },
98- method: "POST",
99- body: JSON.stringify(mockUser),
100- });
101-
102- expect(ctx.json).toEqual({ ok: true, data: mockUser, value: mockUser });
103+ ctx.loader = { meta: getTestData(ctx) };
104 },
105 );
106
107@@ -272,7 +269,25 @@ it(tests, "POST", async () => {
108 const action = fetchUsers();
109 store.dispatch(action);
110
111- await delay();
112+ const loader = await store.run(waitForLoader(schema.loaders, action));
113+ if (!loader.ok) {
114+ throw loader.error;
115+ }
116+
117+ expect(loader.value.meta.request).toEqual({
118+ url: `${baseUrl}/users`,
119+ headers: {
120+ "Content-Type": "application/json",
121+ },
122+ method: "POST",
123+ body: JSON.stringify(mockUser),
124+ });
125+
126+ expect(loader.value.meta.json).toEqual({
127+ ok: true,
128+ data: mockUser,
129+ value: mockUser,
130+ });
131 });
132
133 it(tests, "POST multiple endpoints with same uri", async () => {
134@@ -296,16 +311,7 @@ it(tests, "POST multiple endpoints with same uri", async () => {
135 ctx.request = ctx.req({ body: JSON.stringify(mockUser) });
136 yield* next();
137
138- expect(ctx.req()).toEqual({
139- url: `${baseUrl}/users/1/something`,
140- headers: {
141- "Content-Type": "application/json",
142- },
143- method: "POST",
144- body: JSON.stringify(mockUser),
145- });
146-
147- expect(ctx.json).toEqual({ ok: true, data: mockUser, value: mockUser });
148+ ctx.loader = { meta: getTestData(ctx) };
149 },
150 );
151
152@@ -316,38 +322,74 @@ it(tests, "POST multiple endpoints with same uri", async () => {
153 ctx.cache = true;
154 ctx.request = ctx.req({ body: JSON.stringify(mockUser) });
155 yield* next();
156-
157- expect(ctx.req()).toEqual({
158- url: `${baseUrl}/users/1/something`,
159- headers: {
160- "Content-Type": "application/json",
161- },
162- method: "POST",
163- body: JSON.stringify(mockUser),
164- });
165-
166- expect(ctx.json).toEqual({ ok: true, data: mockUser, value: mockUser });
167+ ctx.loader = { meta: getTestData(ctx) };
168 },
169 );
170
171 store.run(api.bootup);
172
173- store.dispatch(fetchUsers({ id: "1" }));
174- store.dispatch(fetchUsersSecond({ id: "1" }));
175+ const action1 = fetchUsers({ id: "1" });
176+ const action2 = fetchUsersSecond({ id: "1" });
177+ store.dispatch(action1);
178+ store.dispatch(action2);
179+
180+ const results = await store.run(
181+ waitForLoaders(schema.loaders, [action1, action2]),
182+ );
183+ if (!results.ok) {
184+ throw results.error;
185+ }
186+ const result1 = results.value[0];
187+ if (!result1.ok) {
188+ throw result1.error;
189+ }
190+ const result2 = results.value[1];
191+ if (!result2.ok) {
192+ throw result2.error;
193+ }
194+
195+ expect(result1.value.meta.request).toEqual({
196+ url: `${baseUrl}/users/1/something`,
197+ headers: {
198+ "Content-Type": "application/json",
199+ },
200+ method: "POST",
201+ body: JSON.stringify(mockUser),
202+ });
203+
204+ expect(result1.value.meta.json).toEqual({
205+ ok: true,
206+ data: mockUser,
207+ value: mockUser,
208+ });
209
210- await delay();
211+ expect(result2.value.meta.request).toEqual({
212+ url: `${baseUrl}/users/1/something`,
213+ headers: {
214+ "Content-Type": "application/json",
215+ },
216+ method: "POST",
217+ body: JSON.stringify(mockUser),
218+ });
219+
220+ expect(result2.value.meta.json).toEqual({
221+ ok: true,
222+ data: mockUser,
223+ value: mockUser,
224+ });
225 });
226
227 it(
228 tests,
229 "slug in url but payload has empty string for slug value",
230- async () => {
231+ () => {
232 const { store, schema } = testStore();
233 const api = createApi();
234 api.use(mdw.api());
235 api.use(storeMdw.store(schema));
236 api.use(api.routes());
237 api.use(mdw.fetch({ baseUrl }));
238+ let actual = "";
239
240 const fetchUsers = api.post<{ id: string }>(
241 "/users/:id",
242@@ -357,14 +399,9 @@ it(
243 ctx.request = ctx.req({ body: JSON.stringify(mockUser) });
244
245 yield* next();
246-
247- const data =
248- "found :id in endpoint name (/users/:id [POST]) but payload has falsy value ()";
249- expect(ctx.json).toEqual({
250- ok: false,
251- data,
252- error: data,
253- });
254+ if (!ctx.json.ok) {
255+ actual = ctx.json.error;
256+ }
257 },
258 );
259
260@@ -372,7 +409,9 @@ it(
261 const action = fetchUsers({ id: "" });
262 store.dispatch(action);
263
264- await delay();
265+ const data =
266+ "found :id in endpoint name (/users/:id [POST]) but payload has falsy value ()";
267+ expect(actual).toEqual(data);
268 },
269 );
270
271@@ -418,7 +457,10 @@ it(
272 const action = fetchUsers();
273 store.dispatch(action);
274
275- await delay();
276+ const loader = await store.run(waitForLoader(schema.loaders, action));
277+ if (!loader.ok) {
278+ throw loader.error;
279+ }
280
281 const state = store.getState();
282 expect(state.cache[action.payload.key]).toEqual(mockUser);
283@@ -457,7 +499,10 @@ it(
284 const action = fetchUsers();
285 store.dispatch(action);
286
287- await delay();
288+ const loader = await store.run(waitForLoader(schema.loaders, action));
289+ if (!loader.ok) {
290+ throw loader.error;
291+ }
292 const data = { message: "error" };
293 expect(actual).toEqual({ ok: false, data, error: data });
294 },
295@@ -486,7 +531,10 @@ it(
296 store.run(api.bootup);
297 store.dispatch(fetchUsers());
298
299- await delay();
300+ const loader = await store.run(waitForLoader(schema.loaders, fetchUsers));
301+ if (!loader.ok) {
302+ throw loader.error;
303+ }
304 expect(actual).toEqual({ ok: true, data: mockUser, value: mockUser });
305 },
306 );
307@@ -514,12 +562,18 @@ it(tests, "should use dynamic mdw to mock response", async () => {
308 const dynamicUser = { id: "2", email: "dynamic@starfx.com" };
309 fetchUsers.use(mdw.response(new Response(JSON.stringify(dynamicUser))));
310 store.dispatch(fetchUsers());
311- await delay();
312+ let loader = await store.run(waitForLoader(schema.loaders, fetchUsers));
313+ if (!loader.ok) {
314+ throw loader.error;
315+ }
316 expect(actual).toEqual({ ok: true, data: dynamicUser, value: dynamicUser });
317
318 // reset dynamic mdw and try again
319 api.reset();
320 store.dispatch(fetchUsers());
321- await delay();
322+ loader = await store.run(waitForLoader(schema.loaders, fetchUsers));
323+ if (!loader.ok) {
324+ throw loader.error;
325+ }
326 expect(actual).toEqual({ ok: true, data: mockUser, value: mockUser });
327 });
+15,
-6
1@@ -1,18 +1,21 @@
2-import { assertLike, asserts, describe, expect, it, sleep } from "../test.ts";
3+import { assertLike, asserts, describe, expect, it } from "../test.ts";
4 import {
5 configureStore,
6 createSchema,
7 slice,
8 storeMdw,
9 updateStore,
10+ waitForLoader,
11 } from "../store/mod.ts";
12 import {
13 createApi,
14 createKey,
15 mdw,
16+ put,
17 safe,
18 takeEvery,
19 takeLatest,
20+ waitFor,
21 } from "../mod.ts";
22 import type { ApiCtx, Next, ThunkCtx } from "../mod.ts";
23
24@@ -405,7 +408,9 @@ it(tests, "createApi with own key", async () => {
25 store.run(query.bootup);
26
27 store.dispatch(createUserCustomKey({ email: newUEmail }));
28- await sleep(150);
29+
30+ await store.run(waitForLoader(schema.loaders, createUserCustomKey));
31+
32 const expectedKey = theTestKey
33 ? `/users [POST]|${theTestKey}`
34 : createKey("/users [POST]", { email: newUEmail });
35@@ -473,9 +478,10 @@ it(tests, "createApi with custom key but no payload", async () => {
36 );
37
38 store.run(query.bootup);
39-
40 store.dispatch(getUsers());
41- await sleep(150);
42+
43+ await store.run(waitForLoader(schema.loaders, getUsers));
44+
45 const expectedKey = theTestKey
46 ? `/users [GET]|${theTestKey}`
47 : createKey("/users [GET]", null);
48@@ -547,7 +553,7 @@ it(tests, "errorHandler", () => {
49 });
50
51 it(tests, "stub predicate", async () => {
52- let actual = null;
53+ let actual: { ok: boolean } = { ok: false };
54 const api = createApi();
55 api.use(function* (ctx, next) {
56 ctx.stub = true;
57@@ -563,6 +569,7 @@ it(tests, "stub predicate", async () => {
58 function* (ctx, next) {
59 yield* next();
60 actual = ctx.json;
61+ yield* put({ type: "DONE" });
62 },
63 stub(function* (ctx, next) {
64 ctx.response = new Response(JSON.stringify({ frodo: "shire" }));
65@@ -575,7 +582,9 @@ it(tests, "stub predicate", async () => {
66 });
67 store.run(api.bootup);
68 store.dispatch(fetchUsers());
69- await sleep(150);
70+
71+ await store.run(waitFor(() => actual.ok));
72+
73 expect(actual).toEqual({
74 ok: true,
75 value: { frodo: "shire" },
+3,
-3
1@@ -68,7 +68,7 @@ it(
2 "should not cause stack overflow when puts are emitted while dispatching saga",
3 async () => {
4 function* root() {
5- for (let i = 0; i < 40_000; i += 1) {
6+ for (let i = 0; i < 10_000; i += 1) {
7 yield* put({ type: "test" });
8 }
9 yield* sleep(0);
10@@ -88,7 +88,7 @@ it(
11
12 function* root() {
13 yield* spawn(function* firstspawn() {
14- yield* sleep(1000);
15+ yield* sleep(10);
16 yield* put({ type: "c" });
17 yield* put({ type: "do not miss" });
18 });
19@@ -103,7 +103,7 @@ it(
20 }
21
22 const store = configureStore({ initialState: {} });
23- await store.run(() => root());
24+ await store.run(root);
25 const expected = ["didn't get missed"];
26 expect(actual).toEqual(expected);
27 },
+16,
-6
1@@ -1,6 +1,13 @@
2-import { assertLike, asserts, describe, it, sleep } from "../test.ts";
3+import { assertLike, asserts, describe, it } from "../test.ts";
4 import { configureStore, updateStore } from "../store/mod.ts";
5-import { call, createThunks, put, sleep as delay, takeEvery } from "../mod.ts";
6+import {
7+ call,
8+ createThunks,
9+ put,
10+ sleep as delay,
11+ takeEvery,
12+ waitFor,
13+} from "../mod.ts";
14 import type { Next, ThunkCtx } from "../mod.ts";
15
16 // deno-lint-ignore no-explicit-any
17@@ -397,6 +404,7 @@ it(tests, "middleware order of execution", async () => {
18 acc += "a";
19 yield* next();
20 acc += "g";
21+ yield* put({ type: "DONE" });
22 },
23 );
24
25@@ -404,7 +412,7 @@ it(tests, "middleware order of execution", async () => {
26 store.run(api.bootup);
27 store.dispatch(action());
28
29- await sleep(150);
30+ await store.run(waitFor(() => acc === "abcdefg"));
31 asserts.assert(acc === "abcdefg");
32 });
33
34@@ -417,11 +425,13 @@ it(tests, "retry with actionFn", async () => {
35
36 const action = api.create(
37 "/api",
38- { supervisor: takeEvery },
39 function* (ctx, next) {
40 acc += "a";
41 yield* next();
42 acc += "g";
43+ if (acc === "agag") {
44+ yield* put({ type: "DONE" });
45+ }
46
47 if (!called) {
48 called = true;
49@@ -434,7 +444,7 @@ it(tests, "retry with actionFn", async () => {
50 store.run(api.bootup);
51 store.dispatch(action());
52
53- await sleep(150);
54+ await store.run(waitFor(() => acc === "agag"));
55 asserts.assertEquals(acc, "agag");
56 });
57
58@@ -464,7 +474,7 @@ it(tests, "retry with actionFn with payload", async () => {
59 store.run(api.bootup);
60 store.dispatch(action({ page: 1 }));
61
62- await sleep(150);
63+ await store.run(waitFor(() => acc === "agag"));
64 asserts.assertEquals(acc, "agag");
65 });
66
M
types.ts
+3,
-0
1@@ -46,6 +46,9 @@ export interface Action {
2 type: string;
3 }
4
5+export type ActionFn = () => { toString: () => string };
6+export type ActionFnWithPayload<P = any> = (p: P) => { toString: () => string };
7+
8 // https://github.com/redux-utilities/flux-standard-action
9 export interface AnyAction extends Action {
10 payload?: any;