rexfect 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1756 -0
- package/dist/abortableContext.d.ts +3 -0
- package/dist/abortableContext.d.ts.map +1 -0
- package/dist/abortableContext.js +48 -0
- package/dist/abortableContext.js.map +1 -0
- package/dist/action.d.ts +64 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/action.js +208 -0
- package/dist/action.js.map +1 -0
- package/dist/action.test.d.ts +2 -0
- package/dist/action.test.d.ts.map +1 -0
- package/dist/action.test.js +189 -0
- package/dist/action.test.js.map +1 -0
- package/dist/async/abortable-guard.d.ts +25 -0
- package/dist/async/abortable-guard.d.ts.map +1 -0
- package/dist/async/abortable-guard.js +33 -0
- package/dist/async/abortable-guard.js.map +1 -0
- package/dist/async/abortable.d.ts +331 -0
- package/dist/async/abortable.d.ts.map +1 -0
- package/dist/async/abortable.js +410 -0
- package/dist/async/abortable.js.map +1 -0
- package/dist/async/abortable.test.d.ts +2 -0
- package/dist/async/abortable.test.d.ts.map +1 -0
- package/dist/async/abortable.test.js +535 -0
- package/dist/async/abortable.test.js.map +1 -0
- package/dist/async/abortable.typeCheck.d.ts +8 -0
- package/dist/async/abortable.typeCheck.d.ts.map +1 -0
- package/dist/async/abortable.typeCheck.js +138 -0
- package/dist/async/abortable.typeCheck.js.map +1 -0
- package/dist/async/async.d.ts +18 -0
- package/dist/async/async.d.ts.map +1 -0
- package/dist/async/async.js +20 -0
- package/dist/async/async.js.map +1 -0
- package/dist/async/index.d.ts +15 -0
- package/dist/async/index.d.ts.map +1 -0
- package/dist/async/index.js +13 -0
- package/dist/async/index.js.map +1 -0
- package/dist/async/loadable.d.ts +7 -0
- package/dist/async/loadable.d.ts.map +1 -0
- package/dist/async/loadable.js +52 -0
- package/dist/async/loadable.js.map +1 -0
- package/dist/async/loadable.test.d.ts +2 -0
- package/dist/async/loadable.test.d.ts.map +1 -0
- package/dist/async/loadable.test.js +322 -0
- package/dist/async/loadable.test.js.map +1 -0
- package/dist/async/promiseCache.d.ts +14 -0
- package/dist/async/promiseCache.d.ts.map +1 -0
- package/dist/async/promiseCache.js +29 -0
- package/dist/async/promiseCache.js.map +1 -0
- package/dist/async/read.d.ts +120 -0
- package/dist/async/read.d.ts.map +1 -0
- package/dist/async/read.js +286 -0
- package/dist/async/read.js.map +1 -0
- package/dist/async/read.test.d.ts +2 -0
- package/dist/async/read.test.d.ts.map +1 -0
- package/dist/async/read.test.js +419 -0
- package/dist/async/read.test.js.map +1 -0
- package/dist/async/read.typeCheck.d.ts +6 -0
- package/dist/async/read.typeCheck.d.ts.map +1 -0
- package/dist/async/read.typeCheck.js +101 -0
- package/dist/async/read.typeCheck.js.map +1 -0
- package/dist/async/safe.d.ts +230 -0
- package/dist/async/safe.d.ts.map +1 -0
- package/dist/async/safe.js +247 -0
- package/dist/async/safe.js.map +1 -0
- package/dist/async/safe.test.d.ts +2 -0
- package/dist/async/safe.test.d.ts.map +1 -0
- package/dist/async/safe.test.js +447 -0
- package/dist/async/safe.test.js.map +1 -0
- package/dist/async/utils.d.ts +17 -0
- package/dist/async/utils.d.ts.map +1 -0
- package/dist/async/utils.js +38 -0
- package/dist/async/utils.js.map +1 -0
- package/dist/async/wait.d.ts +120 -0
- package/dist/async/wait.d.ts.map +1 -0
- package/dist/async/wait.js +112 -0
- package/dist/async/wait.js.map +1 -0
- package/dist/async/wait.test.d.ts +2 -0
- package/dist/async/wait.test.d.ts.map +1 -0
- package/dist/async/wait.test.js +122 -0
- package/dist/async/wait.test.js.map +1 -0
- package/dist/async/wait.typeCheck.d.ts +6 -0
- package/dist/async/wait.typeCheck.d.ts.map +1 -0
- package/dist/async/wait.typeCheck.js +104 -0
- package/dist/async/wait.typeCheck.js.map +1 -0
- package/dist/atom.d.ts +46 -0
- package/dist/atom.d.ts.map +1 -0
- package/dist/atom.js +86 -0
- package/dist/atom.js.map +1 -0
- package/dist/atom.test.d.ts +2 -0
- package/dist/atom.test.d.ts.map +1 -0
- package/dist/atom.test.js +75 -0
- package/dist/atom.test.js.map +1 -0
- package/dist/batch.d.ts +15 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/batch.js +45 -0
- package/dist/batch.js.map +1 -0
- package/dist/defer.d.ts +56 -0
- package/dist/defer.d.ts.map +1 -0
- package/dist/defer.js +49 -0
- package/dist/defer.js.map +1 -0
- package/dist/effect.d.ts +91 -0
- package/dist/effect.d.ts.map +1 -0
- package/dist/effect.js +311 -0
- package/dist/effect.js.map +1 -0
- package/dist/effect.test.d.ts +2 -0
- package/dist/effect.test.d.ts.map +1 -0
- package/dist/effect.test.js +123 -0
- package/dist/effect.test.js.map +1 -0
- package/dist/emitter.d.ts +129 -0
- package/dist/emitter.d.ts.map +1 -0
- package/dist/emitter.js +164 -0
- package/dist/emitter.js.map +1 -0
- package/dist/emitter.test.d.ts +2 -0
- package/dist/emitter.test.d.ts.map +1 -0
- package/dist/emitter.test.js +259 -0
- package/dist/emitter.test.js.map +1 -0
- package/dist/equality.d.ts +66 -0
- package/dist/equality.d.ts.map +1 -0
- package/dist/equality.js +145 -0
- package/dist/equality.js.map +1 -0
- package/dist/event.d.ts +18 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/event.js +166 -0
- package/dist/event.js.map +1 -0
- package/dist/event.test.d.ts +2 -0
- package/dist/event.test.d.ts.map +1 -0
- package/dist/event.test.js +167 -0
- package/dist/event.test.js.map +1 -0
- package/dist/hooks.d.ts +152 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +122 -0
- package/dist/hooks.js.map +1 -0
- package/dist/hooks.test.d.ts +2 -0
- package/dist/hooks.test.d.ts.map +1 -0
- package/dist/hooks.test.js +99 -0
- package/dist/hooks.test.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/isPromiseLike.d.ts +10 -0
- package/dist/isPromiseLike.d.ts.map +1 -0
- package/dist/isPromiseLike.js +15 -0
- package/dist/isPromiseLike.js.map +1 -0
- package/dist/pick.d.ts +22 -0
- package/dist/pick.d.ts.map +1 -0
- package/dist/pick.js +46 -0
- package/dist/pick.js.map +1 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +8 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/useRx.d.ts +14 -0
- package/dist/react/useRx.d.ts.map +1 -0
- package/dist/react/useRx.js +110 -0
- package/dist/react/useRx.js.map +1 -0
- package/dist/react/useRx.test.d.ts +2 -0
- package/dist/react/useRx.test.d.ts.map +1 -0
- package/dist/react/useRx.test.js +457 -0
- package/dist/react/useRx.test.js.map +1 -0
- package/dist/strictModeTest.d.ts +11 -0
- package/dist/strictModeTest.d.ts.map +1 -0
- package/dist/strictModeTest.js +41 -0
- package/dist/strictModeTest.js.map +1 -0
- package/dist/types.d.ts +606 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/untrack.d.ts +14 -0
- package/dist/untrack.d.ts.map +1 -0
- package/dist/untrack.js +17 -0
- package/dist/untrack.js.map +1 -0
- package/dist/utils/withUse.d.ts +10 -0
- package/dist/utils/withUse.d.ts.map +1 -0
- package/dist/utils/withUse.js +21 -0
- package/dist/utils/withUse.js.map +1 -0
- package/dist/utils/withUse.test.d.ts +2 -0
- package/dist/utils/withUse.test.d.ts.map +1 -0
- package/dist/utils/withUse.test.js +233 -0
- package/dist/utils/withUse.test.js.map +1 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +7 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.test.d.ts +2 -0
- package/dist/utils.test.d.ts.map +1 -0
- package/dist/utils.test.js +119 -0
- package/dist/utils.test.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type check file for abortable functions and wrapper chaining.
|
|
3
|
+
* This file should compile without errors - no runtime tests needed.
|
|
4
|
+
*
|
|
5
|
+
* Run: npx tsc --noEmit src/async/abortable.typeCheck.ts
|
|
6
|
+
*/
|
|
7
|
+
import { abortable, } from "./abortable";
|
|
8
|
+
// Note: wrappers are not yet implemented, so this file is disabled
|
|
9
|
+
// import {
|
|
10
|
+
// retry,
|
|
11
|
+
// catchError,
|
|
12
|
+
// timeout,
|
|
13
|
+
// logging,
|
|
14
|
+
// debounce,
|
|
15
|
+
// throttle,
|
|
16
|
+
// } from "./wrappers";
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Basic abortable creation
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Simple function with no args
|
|
21
|
+
const noArgs = abortable(async () => "hello");
|
|
22
|
+
noArgs;
|
|
23
|
+
// Function with single arg
|
|
24
|
+
const singleArg = abortable(async ({}, id) => ({ id, name: "test" }));
|
|
25
|
+
singleArg;
|
|
26
|
+
// Function with multiple args
|
|
27
|
+
const multiArgs = abortable(async ({}, a, b, c) => a + b.length + (c ? 1 : 0));
|
|
28
|
+
multiArgs;
|
|
29
|
+
// Function using signal
|
|
30
|
+
const withSignal = abortable(async ({ signal }, url) => {
|
|
31
|
+
const res = await fetch(url, { signal });
|
|
32
|
+
return res.json();
|
|
33
|
+
});
|
|
34
|
+
withSignal;
|
|
35
|
+
// Function using safe
|
|
36
|
+
const withSafe = abortable(async ({ safe }, id) => {
|
|
37
|
+
const result = await safe(Promise.resolve({ id }));
|
|
38
|
+
return result;
|
|
39
|
+
});
|
|
40
|
+
withSafe;
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// Direct call and .withSignal() method
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Direct call - should accept the args
|
|
45
|
+
void noArgs();
|
|
46
|
+
void singleArg("123");
|
|
47
|
+
void multiArgs(1, "test", true);
|
|
48
|
+
// .withSignal() method - should accept signal + args
|
|
49
|
+
const controller = new AbortController();
|
|
50
|
+
void noArgs.withSignal(controller.signal);
|
|
51
|
+
void singleArg.withSignal(controller.signal, "123");
|
|
52
|
+
void multiArgs.withSignal(controller.signal, 1, "test", true);
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// Single wrapper chaining
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Note: Wrapper functions are not yet implemented
|
|
57
|
+
// These examples are commented out until wrappers are available
|
|
58
|
+
// // retry() preserves types
|
|
59
|
+
// const withRetry = singleArg.use(retry(3));
|
|
60
|
+
// withRetry satisfies Abortable<[string], { id: string; name: string }>;
|
|
61
|
+
// const _retryResult: Promise<{ id: string; name: string }> = withRetry("test");
|
|
62
|
+
// =============================================================================
|
|
63
|
+
// Multi-wrapper chaining
|
|
64
|
+
// =============================================================================
|
|
65
|
+
// Note: Wrapper chaining examples commented out until wrappers are available
|
|
66
|
+
// =============================================================================
|
|
67
|
+
// Custom wrapper creation
|
|
68
|
+
// =============================================================================
|
|
69
|
+
// Custom wrapper that doesn't change types
|
|
70
|
+
const customPassThrough = (next) => async (ctx, ...args) => {
|
|
71
|
+
console.log("before");
|
|
72
|
+
const result = await next(ctx, ...args);
|
|
73
|
+
console.log("after");
|
|
74
|
+
return result;
|
|
75
|
+
};
|
|
76
|
+
const withCustom = singleArg.use(customPassThrough);
|
|
77
|
+
withCustom;
|
|
78
|
+
// API service with abortable methods (used in commented-out examples)
|
|
79
|
+
const userApi = {
|
|
80
|
+
getUser: abortable(async ({ signal }, userId) => {
|
|
81
|
+
const res = await fetch(`/api/users/${userId}`, { signal });
|
|
82
|
+
return res.json();
|
|
83
|
+
}),
|
|
84
|
+
createUser: abortable(async ({ signal }, data) => {
|
|
85
|
+
const res = await fetch("/api/users", {
|
|
86
|
+
method: "POST",
|
|
87
|
+
body: JSON.stringify(data),
|
|
88
|
+
signal,
|
|
89
|
+
});
|
|
90
|
+
return res.json();
|
|
91
|
+
}),
|
|
92
|
+
getPost: abortable(async ({ signal }, postId) => {
|
|
93
|
+
const res = await fetch(`/api/posts/${postId}`, { signal });
|
|
94
|
+
return res.json();
|
|
95
|
+
}),
|
|
96
|
+
};
|
|
97
|
+
void userApi; // Mark as used for type checking
|
|
98
|
+
// Wrap with retry and error handling (commented out until wrappers available)
|
|
99
|
+
// const robustGetUser = userApi.getUser
|
|
100
|
+
// .use(retry({ retries: 3, delay: "backoff" }))
|
|
101
|
+
// .use(catchError((err) => console.error("Failed to get user:", err)))
|
|
102
|
+
// .use(timeout(10000));
|
|
103
|
+
// Use the wrapped function (commented out)
|
|
104
|
+
// async function fetchUserProfile(userId: string): Promise<User> {
|
|
105
|
+
// return robustGetUser(userId);
|
|
106
|
+
// }
|
|
107
|
+
// =============================================================================
|
|
108
|
+
// Type preservation tests - ensure types are NOT widened to `any`
|
|
109
|
+
// =============================================================================
|
|
110
|
+
// Type error tests commented out until wrappers are available
|
|
111
|
+
// =============================================================================
|
|
112
|
+
// Edge cases
|
|
113
|
+
// =============================================================================
|
|
114
|
+
// Void return type (used in commented-out examples)
|
|
115
|
+
const voidFn = abortable(async ({}, msg) => {
|
|
116
|
+
console.log(msg);
|
|
117
|
+
});
|
|
118
|
+
void voidFn; // Mark as used for type checking
|
|
119
|
+
const complexFn = abortable(async ({}, page, _limit) => {
|
|
120
|
+
return {
|
|
121
|
+
users: [],
|
|
122
|
+
meta: { total: 0, page, hasMore: false },
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
void complexFn; // Mark as used for type checking
|
|
126
|
+
// const wrappedComplex = complexFn
|
|
127
|
+
// .use(retry({ retries: 3, delay: 1000 }))
|
|
128
|
+
// .use(timeout(5000));
|
|
129
|
+
// wrappedComplex satisfies Abortable<[number, number], ComplexData>;
|
|
130
|
+
// Optional args (rest params style) - used in commented-out examples
|
|
131
|
+
const optionalArgsFn = abortable(async ({}, required, optional) => {
|
|
132
|
+
return `${required}-${optional ?? 0}`;
|
|
133
|
+
});
|
|
134
|
+
void optionalArgsFn; // Mark as used for type checking
|
|
135
|
+
// const wrappedOptional = optionalArgsFn.use(retry(3));
|
|
136
|
+
// wrappedOptional satisfies Abortable<[string, (number | undefined)?], string>;
|
|
137
|
+
console.log("All type checks passed!");
|
|
138
|
+
//# sourceMappingURL=abortable.typeCheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abortable.typeCheck.js","sourceRoot":"","sources":["../../src/async/abortable.typeCheck.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async utility functions.
|
|
3
|
+
* Re-exports from utils for convenience.
|
|
4
|
+
*/
|
|
5
|
+
import { toPromise } from "./utils";
|
|
6
|
+
export { toPromise };
|
|
7
|
+
/**
|
|
8
|
+
* Delay utility for async operations.
|
|
9
|
+
*/
|
|
10
|
+
export declare const delay: (ms: number) => Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Async utilities object.
|
|
13
|
+
*/
|
|
14
|
+
export declare const async: {
|
|
15
|
+
delay: (ms: number) => Promise<void>;
|
|
16
|
+
toPromise: typeof toPromise;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=async.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async.d.ts","sourceRoot":"","sources":["../../src/async/async.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB;;GAEG;AACH,eAAO,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,OAAO,CAAC,IAAI,CAE9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK;gBAPQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;;CAU/C,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async utility functions.
|
|
3
|
+
* Re-exports from utils for convenience.
|
|
4
|
+
*/
|
|
5
|
+
import { toPromise } from "./utils";
|
|
6
|
+
export { toPromise };
|
|
7
|
+
/**
|
|
8
|
+
* Delay utility for async operations.
|
|
9
|
+
*/
|
|
10
|
+
export const delay = (ms) => {
|
|
11
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Async utilities object.
|
|
15
|
+
*/
|
|
16
|
+
export const async = {
|
|
17
|
+
delay,
|
|
18
|
+
toPromise,
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=async.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async.js","sourceRoot":"","sources":["../../src/async/async.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE;IACjD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,KAAK;IACL,SAAS;CACV,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rexfect/async - Async utilities
|
|
3
|
+
*
|
|
4
|
+
* - read: Suspense mode for Signals (throws promise)
|
|
5
|
+
* - wait: Promise mode for Promise/Action (returns Promise)
|
|
6
|
+
* - loadable: Extract async state from Promise-valued signals
|
|
7
|
+
* - abortable: Wrap functions with cancellation support
|
|
8
|
+
*/
|
|
9
|
+
export { read } from "./read";
|
|
10
|
+
export { wait } from "./wait";
|
|
11
|
+
export { loadable } from "./loadable";
|
|
12
|
+
export { abortable, isAbortable } from "./abortable";
|
|
13
|
+
export type { LoadableResult, LoadableReturn } from "./loadable";
|
|
14
|
+
export type { Abortable, AbortableContext, AbortableResult } from "./abortable";
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/async/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAGrD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjE,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rexfect/async - Async utilities
|
|
3
|
+
*
|
|
4
|
+
* - read: Suspense mode for Signals (throws promise)
|
|
5
|
+
* - wait: Promise mode for Promise/Action (returns Promise)
|
|
6
|
+
* - loadable: Extract async state from Promise-valued signals
|
|
7
|
+
* - abortable: Wrap functions with cancellation support
|
|
8
|
+
*/
|
|
9
|
+
export { read } from "./read";
|
|
10
|
+
export { wait } from "./wait";
|
|
11
|
+
export { loadable } from "./loadable";
|
|
12
|
+
export { abortable, isAbortable } from "./abortable";
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/async/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Signal, LoadableResult, LoadableReturn } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Extracts async state from a Promise-valued signal.
|
|
4
|
+
*/
|
|
5
|
+
export declare function loadable<TValue extends Promise<any> | null | undefined>(signal: Signal<TValue>): LoadableReturn<TValue>;
|
|
6
|
+
export type { LoadableResult, LoadableReturn };
|
|
7
|
+
//# sourceMappingURL=loadable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadable.d.ts","sourceRoot":"","sources":["../../src/async/loadable.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAgBvE;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,SAAS,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,SAAS,EACrE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GACrB,cAAc,CAAC,MAAM,CAAC,CAoCxB;AAGD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { atomInstance } from "../atom";
|
|
2
|
+
import { getHooks } from "../hooks";
|
|
3
|
+
import { trackPromise } from "./promiseCache";
|
|
4
|
+
function getPromiseState(promise) {
|
|
5
|
+
const entry = trackPromise(promise);
|
|
6
|
+
switch (entry.status) {
|
|
7
|
+
case "pending":
|
|
8
|
+
return { loading: true, data: undefined, error: undefined };
|
|
9
|
+
case "fulfilled":
|
|
10
|
+
return { loading: false, data: entry.value, error: undefined };
|
|
11
|
+
case "rejected":
|
|
12
|
+
return { loading: false, data: undefined, error: entry.error };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Extracts async state from a Promise-valued signal.
|
|
17
|
+
*/
|
|
18
|
+
export function loadable(signal) {
|
|
19
|
+
const value = signal();
|
|
20
|
+
if (value === null || value === undefined) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const promise = value;
|
|
24
|
+
const state = getPromiseState(promise);
|
|
25
|
+
// If pending, set up reactivity to re-run when promise settles
|
|
26
|
+
if (state.loading) {
|
|
27
|
+
const track = getHooks().track;
|
|
28
|
+
let onFinally;
|
|
29
|
+
if (track) {
|
|
30
|
+
let disposed = false;
|
|
31
|
+
const { signal, setter, dispose } = atomInstance({});
|
|
32
|
+
track(signal, () => {
|
|
33
|
+
disposed = true;
|
|
34
|
+
dispose();
|
|
35
|
+
});
|
|
36
|
+
onFinally = () => {
|
|
37
|
+
if (disposed)
|
|
38
|
+
return;
|
|
39
|
+
// trigger re-run
|
|
40
|
+
setter({});
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Force re-evaluation when promise settles
|
|
44
|
+
// Use .then with both handlers to avoid unhandled rejection warnings
|
|
45
|
+
promise.then(onFinally, () => {
|
|
46
|
+
// Call onFinally but also suppress the unhandled rejection
|
|
47
|
+
onFinally?.();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return state;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=loadable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadable.js","sourceRoot":"","sources":["../../src/async/loadable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,SAAS,eAAe,CAAI,OAAmB;IAC7C,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IAEpC,QAAQ,KAAK,CAAC,MAAM,EAAE,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAC9D,KAAK,WAAW;YACd,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QACjE,KAAK,UAAU;YACb,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IACnE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CACtB,MAAsB;IAEtB,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;IAEvB,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,IAA8B,CAAC;IACxC,CAAC;IAED,MAAM,OAAO,GAAG,KAAqB,CAAC;IACtC,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAEvC,+DAA+D;IAC/D,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC,KAAK,CAAC;QAC/B,IAAI,SAAmC,CAAC;QACxC,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;YACrD,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE;gBACjB,QAAQ,GAAG,IAAI,CAAC;gBAChB,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;YACH,SAAS,GAAG,GAAG,EAAE;gBACf,IAAI,QAAQ;oBAAE,OAAO;gBACrB,iBAAiB;gBACjB,MAAM,CAAC,EAAE,CAAC,CAAC;YACb,CAAC,CAAC;QACJ,CAAC;QACD,2CAA2C;QAC3C,qEAAqE;QACrE,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;YAC3B,2DAA2D;YAC3D,SAAS,EAAE,EAAE,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,KAA+B,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadable.test.d.ts","sourceRoot":"","sources":["../../src/async/loadable.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { atom } from "../atom";
|
|
3
|
+
import { effect } from "../effect";
|
|
4
|
+
import { loadable } from "./loadable";
|
|
5
|
+
// Helper to create rejected promise without unhandled rejection warning
|
|
6
|
+
function rejectedPromise(error) {
|
|
7
|
+
const p = Promise.reject(error);
|
|
8
|
+
p.catch(() => { }); // Suppress warning
|
|
9
|
+
return p;
|
|
10
|
+
}
|
|
11
|
+
describe("loadable", () => {
|
|
12
|
+
it("should return null for null signal value", () => {
|
|
13
|
+
const [promise] = atom(null);
|
|
14
|
+
const result = loadable(promise);
|
|
15
|
+
expect(result).toBeNull();
|
|
16
|
+
});
|
|
17
|
+
it("should return null for undefined signal value", () => {
|
|
18
|
+
const [promise] = atom(undefined);
|
|
19
|
+
const result = loadable(promise);
|
|
20
|
+
expect(result).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
it("should return loading state for pending promise", () => {
|
|
23
|
+
const [promise] = atom(new Promise(() => { })); // never resolves
|
|
24
|
+
const result = loadable(promise);
|
|
25
|
+
expect(result).not.toBeNull();
|
|
26
|
+
expect(result.loading).toBe(true);
|
|
27
|
+
expect(result.data).toBeUndefined();
|
|
28
|
+
expect(result.error).toBeUndefined();
|
|
29
|
+
});
|
|
30
|
+
it("should return data for resolved promise", async () => {
|
|
31
|
+
const [promise] = atom(Promise.resolve("hello"));
|
|
32
|
+
// First call to initialize tracking
|
|
33
|
+
loadable(promise);
|
|
34
|
+
// Wait for promise to settle and tracking to update
|
|
35
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
36
|
+
const result = loadable(promise);
|
|
37
|
+
expect(result).not.toBeNull();
|
|
38
|
+
expect(result.loading).toBe(false);
|
|
39
|
+
expect(result.data).toBe("hello");
|
|
40
|
+
expect(result.error).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
it("should return error for rejected promise", async () => {
|
|
43
|
+
const error = new Error("test error");
|
|
44
|
+
const [promise] = atom(rejectedPromise(error));
|
|
45
|
+
// First call to initialize tracking
|
|
46
|
+
loadable(promise);
|
|
47
|
+
// Wait for promise to settle and tracking to update
|
|
48
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
49
|
+
const result = loadable(promise);
|
|
50
|
+
expect(result).not.toBeNull();
|
|
51
|
+
expect(result.loading).toBe(false);
|
|
52
|
+
expect(result.data).toBeUndefined();
|
|
53
|
+
expect(result.error).toBe(error);
|
|
54
|
+
});
|
|
55
|
+
it("should handle promise that resolves later", async () => {
|
|
56
|
+
let resolve;
|
|
57
|
+
const [promise] = atom(new Promise((r) => {
|
|
58
|
+
resolve = r;
|
|
59
|
+
}));
|
|
60
|
+
// Initially loading
|
|
61
|
+
let result = loadable(promise);
|
|
62
|
+
expect(result.loading).toBe(true);
|
|
63
|
+
// Resolve the promise
|
|
64
|
+
resolve("resolved");
|
|
65
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
66
|
+
// Now should have data
|
|
67
|
+
result = loadable(promise);
|
|
68
|
+
expect(result.loading).toBe(false);
|
|
69
|
+
expect(result.data).toBe("resolved");
|
|
70
|
+
});
|
|
71
|
+
it("should cache promise state", async () => {
|
|
72
|
+
const [promise] = atom(Promise.resolve(42));
|
|
73
|
+
// First call to initialize tracking
|
|
74
|
+
loadable(promise);
|
|
75
|
+
// Wait for promise to settle
|
|
76
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
77
|
+
// Multiple calls should return same cached state
|
|
78
|
+
const result1 = loadable(promise);
|
|
79
|
+
const result2 = loadable(promise);
|
|
80
|
+
expect(result1.data).toBe(42);
|
|
81
|
+
expect(result2.data).toBe(42);
|
|
82
|
+
});
|
|
83
|
+
it("should handle signal value change", async () => {
|
|
84
|
+
const [promise, setPromise] = atom(null);
|
|
85
|
+
// Initial null
|
|
86
|
+
expect(loadable(promise)).toBeNull();
|
|
87
|
+
// Set to pending promise
|
|
88
|
+
setPromise(Promise.resolve(1));
|
|
89
|
+
// First call to initialize tracking
|
|
90
|
+
loadable(promise);
|
|
91
|
+
// Wait for promise
|
|
92
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
93
|
+
expect(loadable(promise).data).toBe(1);
|
|
94
|
+
// Change to new promise
|
|
95
|
+
setPromise(Promise.resolve(2));
|
|
96
|
+
// Initialize tracking for new promise
|
|
97
|
+
loadable(promise);
|
|
98
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
99
|
+
expect(loadable(promise).data).toBe(2);
|
|
100
|
+
});
|
|
101
|
+
describe("reactive tracking", () => {
|
|
102
|
+
it("should re-run effect when promise resolves", async () => {
|
|
103
|
+
// Create a promise that we control
|
|
104
|
+
let resolve;
|
|
105
|
+
const [promise] = atom(new Promise((r) => {
|
|
106
|
+
resolve = r;
|
|
107
|
+
}));
|
|
108
|
+
// Track effect runs
|
|
109
|
+
const states = [];
|
|
110
|
+
const dispose = effect(() => {
|
|
111
|
+
const state = loadable(promise);
|
|
112
|
+
if (state) {
|
|
113
|
+
states.push({ loading: state.loading, data: state.data });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
// First run should be loading
|
|
117
|
+
expect(states.length).toBe(1);
|
|
118
|
+
expect(states[0].loading).toBe(true);
|
|
119
|
+
expect(states[0].data).toBeUndefined();
|
|
120
|
+
// Resolve the promise
|
|
121
|
+
resolve("hello world");
|
|
122
|
+
// Wait for promise to settle and effect to re-run
|
|
123
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
124
|
+
// Effect should have re-run with resolved data
|
|
125
|
+
expect(states.length).toBe(2);
|
|
126
|
+
expect(states[1].loading).toBe(false);
|
|
127
|
+
expect(states[1].data).toBe("hello world");
|
|
128
|
+
dispose();
|
|
129
|
+
});
|
|
130
|
+
it("should re-run effect when promise rejects", async () => {
|
|
131
|
+
// Create a promise that we control
|
|
132
|
+
let reject;
|
|
133
|
+
const p = new Promise((_, r) => {
|
|
134
|
+
reject = r;
|
|
135
|
+
});
|
|
136
|
+
// Suppress unhandled rejection warning
|
|
137
|
+
p.catch(() => { });
|
|
138
|
+
const [promise] = atom(p);
|
|
139
|
+
// Track effect runs
|
|
140
|
+
const states = [];
|
|
141
|
+
const dispose = effect(() => {
|
|
142
|
+
const state = loadable(promise);
|
|
143
|
+
if (state) {
|
|
144
|
+
states.push({ loading: state.loading, error: state.error });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
// First run should be loading
|
|
148
|
+
expect(states.length).toBe(1);
|
|
149
|
+
expect(states[0].loading).toBe(true);
|
|
150
|
+
// Reject the promise
|
|
151
|
+
const error = new Error("loadable test rejection");
|
|
152
|
+
reject(error);
|
|
153
|
+
// Wait for promise to settle and effect to re-run
|
|
154
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
155
|
+
// Effect should have re-run with error
|
|
156
|
+
expect(states.length).toBe(2);
|
|
157
|
+
expect(states[1].loading).toBe(false);
|
|
158
|
+
expect(states[1].error).toBe(error);
|
|
159
|
+
dispose();
|
|
160
|
+
});
|
|
161
|
+
it("should not trigger re-run after effect is disposed", async () => {
|
|
162
|
+
// Create a promise that we control
|
|
163
|
+
let resolve;
|
|
164
|
+
const [promise] = atom(new Promise((r) => {
|
|
165
|
+
resolve = r;
|
|
166
|
+
}));
|
|
167
|
+
// Track effect runs
|
|
168
|
+
const fn = vi.fn();
|
|
169
|
+
const dispose = effect(() => {
|
|
170
|
+
const state = loadable(promise);
|
|
171
|
+
fn(state);
|
|
172
|
+
});
|
|
173
|
+
// First run
|
|
174
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
175
|
+
// Dispose the effect BEFORE promise resolves
|
|
176
|
+
dispose();
|
|
177
|
+
// Resolve the promise
|
|
178
|
+
resolve("hello");
|
|
179
|
+
// Wait for promise to settle
|
|
180
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
181
|
+
// Effect should NOT have re-run (was disposed)
|
|
182
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
183
|
+
});
|
|
184
|
+
it("should handle multiple promises in same effect", async () => {
|
|
185
|
+
// Create two promises we control
|
|
186
|
+
let resolve1;
|
|
187
|
+
let resolve2;
|
|
188
|
+
const [promise1] = atom(new Promise((r) => {
|
|
189
|
+
resolve1 = r;
|
|
190
|
+
}));
|
|
191
|
+
const [promise2] = atom(new Promise((r) => {
|
|
192
|
+
resolve2 = r;
|
|
193
|
+
}));
|
|
194
|
+
// Track effect runs
|
|
195
|
+
let runCount = 0;
|
|
196
|
+
let state1;
|
|
197
|
+
let state2;
|
|
198
|
+
const dispose = effect(() => {
|
|
199
|
+
runCount++;
|
|
200
|
+
state1 = loadable(promise1);
|
|
201
|
+
state2 = loadable(promise2);
|
|
202
|
+
});
|
|
203
|
+
// Initial run - both loading
|
|
204
|
+
expect(runCount).toBe(1);
|
|
205
|
+
expect(state1.loading).toBe(true);
|
|
206
|
+
expect(state2.loading).toBe(true);
|
|
207
|
+
// Resolve first promise
|
|
208
|
+
resolve1(100);
|
|
209
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
210
|
+
// Effect should re-run, first resolved, second still loading
|
|
211
|
+
expect(runCount).toBe(2);
|
|
212
|
+
expect(state1.loading).toBe(false);
|
|
213
|
+
expect(state1.data).toBe(100);
|
|
214
|
+
expect(state2.loading).toBe(true);
|
|
215
|
+
// Resolve second promise
|
|
216
|
+
resolve2(200);
|
|
217
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
218
|
+
// Effect should re-run again, both resolved
|
|
219
|
+
expect(runCount).toBe(3);
|
|
220
|
+
expect(state1.data).toBe(100);
|
|
221
|
+
expect(state2.loading).toBe(false);
|
|
222
|
+
expect(state2.data).toBe(200);
|
|
223
|
+
dispose();
|
|
224
|
+
});
|
|
225
|
+
it("should handle promise signal change during loading", async () => {
|
|
226
|
+
// Create first promise (never resolves)
|
|
227
|
+
const [promise, setPromise] = atom(new Promise(() => { }));
|
|
228
|
+
let runCount = 0;
|
|
229
|
+
let lastData;
|
|
230
|
+
const dispose = effect(() => {
|
|
231
|
+
runCount++;
|
|
232
|
+
const state = loadable(promise);
|
|
233
|
+
if (state && !state.loading) {
|
|
234
|
+
lastData = state.data;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
// Initial run - loading
|
|
238
|
+
expect(runCount).toBe(1);
|
|
239
|
+
// Change to a new promise that resolves immediately
|
|
240
|
+
setPromise(Promise.resolve("new value"));
|
|
241
|
+
// Effect should re-run due to signal change
|
|
242
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
243
|
+
// Should have the new resolved value
|
|
244
|
+
expect(lastData).toBe("new value");
|
|
245
|
+
dispose();
|
|
246
|
+
});
|
|
247
|
+
it("should not re-run for already resolved promise", async () => {
|
|
248
|
+
// Use an already-resolved promise
|
|
249
|
+
const [promise] = atom(Promise.resolve("already done"));
|
|
250
|
+
// Wait for cache to be populated
|
|
251
|
+
loadable(promise);
|
|
252
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
253
|
+
// Now track effect runs
|
|
254
|
+
const fn = vi.fn();
|
|
255
|
+
const dispose = effect(() => {
|
|
256
|
+
const state = loadable(promise);
|
|
257
|
+
fn(state);
|
|
258
|
+
});
|
|
259
|
+
// Should only run once (data already available)
|
|
260
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
261
|
+
expect(fn).toHaveBeenCalledWith(expect.objectContaining({
|
|
262
|
+
loading: false,
|
|
263
|
+
data: "already done",
|
|
264
|
+
}));
|
|
265
|
+
// Wait a bit to ensure no extra runs
|
|
266
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
267
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
268
|
+
dispose();
|
|
269
|
+
});
|
|
270
|
+
it("should not trigger update after disposal when promise resolves", async () => {
|
|
271
|
+
// Create a promise that we control
|
|
272
|
+
let resolve;
|
|
273
|
+
const [promise] = atom(new Promise((r) => {
|
|
274
|
+
resolve = r;
|
|
275
|
+
}));
|
|
276
|
+
// Track effect runs
|
|
277
|
+
const fn = vi.fn();
|
|
278
|
+
const dispose = effect(() => {
|
|
279
|
+
const state = loadable(promise);
|
|
280
|
+
fn(state?.loading);
|
|
281
|
+
});
|
|
282
|
+
// First run should be loading
|
|
283
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
284
|
+
expect(fn).toHaveBeenLastCalledWith(true);
|
|
285
|
+
// Dispose BEFORE promise resolves - this tests the disposed check in onFinally (lines 80-81)
|
|
286
|
+
dispose();
|
|
287
|
+
// Now resolve the promise
|
|
288
|
+
resolve("resolved after dispose");
|
|
289
|
+
// Wait for promise to settle
|
|
290
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
291
|
+
// Effect should NOT have re-run since it was disposed
|
|
292
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
293
|
+
});
|
|
294
|
+
it("should not trigger update after disposal when promise rejects", async () => {
|
|
295
|
+
// Create a promise that we control
|
|
296
|
+
let reject;
|
|
297
|
+
const p = new Promise((_, r) => {
|
|
298
|
+
reject = r;
|
|
299
|
+
});
|
|
300
|
+
// Suppress unhandled rejection warning
|
|
301
|
+
p.catch(() => { });
|
|
302
|
+
const [promise] = atom(p);
|
|
303
|
+
// Track effect runs
|
|
304
|
+
const fn = vi.fn();
|
|
305
|
+
const dispose = effect(() => {
|
|
306
|
+
const state = loadable(promise);
|
|
307
|
+
fn(state?.loading);
|
|
308
|
+
});
|
|
309
|
+
// First run should be loading
|
|
310
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
311
|
+
// Dispose BEFORE promise rejects
|
|
312
|
+
dispose();
|
|
313
|
+
// Now reject the promise
|
|
314
|
+
reject(new Error("rejected after dispose"));
|
|
315
|
+
// Wait for promise to settle
|
|
316
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
317
|
+
// Effect should NOT have re-run since it was disposed
|
|
318
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
//# sourceMappingURL=loadable.test.js.map
|