@effect-app/vue 4.0.0 → 4.0.2
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/CHANGELOG.md +26 -0
- package/dist/commander.d.ts +11 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +38 -15
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +1 -1
- package/dist/makeIntl.js +3 -3
- package/dist/mutate.d.ts +1 -1
- package/dist/mutate.d.ts.map +1 -1
- package/dist/mutate.js +5 -5
- package/dist/routeParams.js +4 -4
- package/package.json +6 -6
- package/src/commander.ts +43 -16
- package/src/errorReporter.ts +3 -1
- package/src/makeIntl.ts +2 -2
- package/src/mutate.ts +4 -4
- package/src/routeParams.ts +4 -4
- package/test/dist/suspendWaiting.test.d.ts.map +1 -0
- package/test/suspendWaiting.test.ts +142 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-app/vue",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -15,17 +15,17 @@
|
|
|
15
15
|
"@vueuse/core": "^14.3.0",
|
|
16
16
|
"change-case": "^5.4.4",
|
|
17
17
|
"query-string": "^9.4.0",
|
|
18
|
-
"effect-app": "4.0.
|
|
18
|
+
"effect-app": "4.0.2"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
|
-
"@effect/atom-vue": "^4.0.
|
|
22
|
-
"@effect/platform-browser": "^4.0.
|
|
21
|
+
"@effect/atom-vue": "^4.0.2",
|
|
22
|
+
"@effect/platform-browser": "^4.0.2",
|
|
23
23
|
"@sentry/browser": "^10.55.0",
|
|
24
|
-
"effect": "^4.0.
|
|
24
|
+
"effect": "^4.0.2",
|
|
25
25
|
"vue": "^3.5.35"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@effect/vitest": "4.0.
|
|
28
|
+
"@effect/vitest": "4.0.2",
|
|
29
29
|
"@formatjs/icu-messageformat-parser": "^3.5.10",
|
|
30
30
|
"@types/node": "25.9.1",
|
|
31
31
|
"@vitejs/plugin-vue": "^6.0.7",
|
package/src/commander.ts
CHANGED
|
@@ -10,6 +10,7 @@ import * as Layer from "effect-app/Layer"
|
|
|
10
10
|
import * as Option from "effect-app/Option"
|
|
11
11
|
import * as S from "effect-app/Schema"
|
|
12
12
|
import { isGeneratorFunction, wrapEffect } from "effect-app/utils"
|
|
13
|
+
import { makeWaitingGate, suspendWaiting, WaitingGate } from "effect-app/waitingGate"
|
|
13
14
|
import * as Cause from "effect/Cause"
|
|
14
15
|
import type * as Exit from "effect/Exit"
|
|
15
16
|
import type * as Fiber from "effect/Fiber"
|
|
@@ -2114,6 +2115,18 @@ export const CommanderStatic = {
|
|
|
2114
2115
|
updateProgress: (progress: Progress | undefined): Effect.Effect<void> =>
|
|
2115
2116
|
CommandProgress.use((s) => s.update(progress)),
|
|
2116
2117
|
|
|
2118
|
+
/**
|
|
2119
|
+
* Park on an effect without the spinner or the waiting toast.
|
|
2120
|
+
* The button stays disabled. Chrome returns when the effect finishes.
|
|
2121
|
+
* Stream commands pause the spinner only. `withDefaultToastStream` does not.
|
|
2122
|
+
*
|
|
2123
|
+
* ```ts
|
|
2124
|
+
* const decision = yield* Command.suspendWaiting(ask(pickupDate))
|
|
2125
|
+
* yield* close(orderId)
|
|
2126
|
+
* ```
|
|
2127
|
+
*/
|
|
2128
|
+
suspendWaiting,
|
|
2129
|
+
|
|
2117
2130
|
/** Version of @see confirmOrInterrupt that automatically includes the action name in the default messages */
|
|
2118
2131
|
confirmOrInterrupt: Effect.fnUntraced(function*(
|
|
2119
2132
|
message?: string
|
|
@@ -2509,6 +2522,30 @@ const unregisterWait = (id: string) => {
|
|
|
2509
2522
|
}
|
|
2510
2523
|
}
|
|
2511
2524
|
|
|
2525
|
+
/**
|
|
2526
|
+
* `waiting` is the spinner. It drops while `suspendWaiting` is parked.
|
|
2527
|
+
* `blocked` stays on for the whole run, including that pause.
|
|
2528
|
+
*/
|
|
2529
|
+
const commandChrome = (
|
|
2530
|
+
ownRunning: () => boolean,
|
|
2531
|
+
waitId: string | undefined,
|
|
2532
|
+
blockId: string | undefined
|
|
2533
|
+
) => {
|
|
2534
|
+
const quiet = ref(0)
|
|
2535
|
+
const gate = makeWaitingGate((depth) => {
|
|
2536
|
+
quiet.value = depth
|
|
2537
|
+
})
|
|
2538
|
+
const running = computed(() => ownRunning() || (waitId !== undefined && (waitState.value[waitId] ?? 0) > 0))
|
|
2539
|
+
const waiting = computed(() => running.value && quiet.value === 0)
|
|
2540
|
+
const blocked = computed(() => running.value || (blockId !== undefined && (waitState.value[blockId] ?? 0) > 0))
|
|
2541
|
+
const provide = <A, E, R>(self: Effect.Effect<A, E, R>) =>
|
|
2542
|
+
Effect.gen(function*() {
|
|
2543
|
+
if (gate.depth !== 0) yield* gate.add(-gate.depth)
|
|
2544
|
+
return yield* self.pipe(Effect.provideService(WaitingGate, gate))
|
|
2545
|
+
})
|
|
2546
|
+
return { waiting, blocked, provide }
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2512
2549
|
const getStateValues = <
|
|
2513
2550
|
const Id extends string,
|
|
2514
2551
|
const I18nKey extends string,
|
|
@@ -2658,6 +2695,7 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2658
2695
|
const blockId = options?.blockKey ? options.blockKey(id) : undefined
|
|
2659
2696
|
|
|
2660
2697
|
const [result, exec_] = asResult(theHandler)
|
|
2698
|
+
const chrome = commandChrome(() => result.value.waiting, waitId, blockId)
|
|
2661
2699
|
|
|
2662
2700
|
const exec = Effect
|
|
2663
2701
|
.fnUntraced(
|
|
@@ -2666,7 +2704,7 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2666
2704
|
if (blockId !== undefined && blockId !== waitId) {
|
|
2667
2705
|
registerWait(blockId)
|
|
2668
2706
|
}
|
|
2669
|
-
return yield* exec_(...args)
|
|
2707
|
+
return yield* chrome.provide(exec_(...args))
|
|
2670
2708
|
},
|
|
2671
2709
|
Effect.onExit(() =>
|
|
2672
2710
|
Effect.sync(() => {
|
|
@@ -2678,13 +2716,7 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2678
2716
|
)
|
|
2679
2717
|
)
|
|
2680
2718
|
|
|
2681
|
-
const waiting =
|
|
2682
|
-
? computed(() => result.value.waiting || (waitState.value[waitId] ?? 0) > 0)
|
|
2683
|
-
: computed(() => result.value.waiting)
|
|
2684
|
-
|
|
2685
|
-
const blocked = blockId !== undefined
|
|
2686
|
-
? computed(() => waiting.value || (waitState.value[blockId] ?? 0) > 0)
|
|
2687
|
-
: computed(() => waiting.value)
|
|
2719
|
+
const { blocked, waiting } = chrome
|
|
2688
2720
|
|
|
2689
2721
|
const computeAllowed = options?.allowed
|
|
2690
2722
|
const allowed = computeAllowed ? computed(() => computeAllowed(id, state)) : true
|
|
@@ -2992,6 +3024,7 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2992
3024
|
const blockId = options?.blockKey ? options.blockKey(id) : undefined
|
|
2993
3025
|
|
|
2994
3026
|
const [result, exec_] = asStreamResult(theStreamHandler)
|
|
3027
|
+
const chrome = commandChrome(() => result.value.waiting, waitId, blockId)
|
|
2995
3028
|
|
|
2996
3029
|
const exec = Effect
|
|
2997
3030
|
.fnUntraced(
|
|
@@ -3000,7 +3033,7 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
3000
3033
|
if (blockId !== undefined && blockId !== waitId) {
|
|
3001
3034
|
registerWait(blockId)
|
|
3002
3035
|
}
|
|
3003
|
-
return yield* exec_(...args)
|
|
3036
|
+
return yield* chrome.provide(exec_(...args))
|
|
3004
3037
|
},
|
|
3005
3038
|
Effect.onExit(() =>
|
|
3006
3039
|
Effect.sync(() => {
|
|
@@ -3012,13 +3045,7 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
3012
3045
|
)
|
|
3013
3046
|
)
|
|
3014
3047
|
|
|
3015
|
-
const waiting =
|
|
3016
|
-
? computed(() => result.value.waiting || (waitState.value[waitId] ?? 0) > 0)
|
|
3017
|
-
: computed(() => result.value.waiting)
|
|
3018
|
-
|
|
3019
|
-
const blocked = blockId !== undefined
|
|
3020
|
-
? computed(() => waiting.value || (waitState.value[blockId] ?? 0) > 0)
|
|
3021
|
-
: computed(() => waiting.value)
|
|
3048
|
+
const { blocked, waiting } = chrome
|
|
3022
3049
|
|
|
3023
3050
|
const computeAllowed = options?.allowed
|
|
3024
3051
|
const allowed = computeAllowed ? computed(() => computeAllowed(id, state)) : true
|
package/src/errorReporter.ts
CHANGED
|
@@ -71,7 +71,9 @@ export function logError<E>(name: string) {
|
|
|
71
71
|
return Effect.fnUntraced(
|
|
72
72
|
function*(cause: Cause.Cause<E>, extras?: Record<string, unknown>) {
|
|
73
73
|
if (Cause.hasInterruptsOnly(cause)) {
|
|
74
|
-
yield* Effect.logDebug("Interrupted").pipe(
|
|
74
|
+
yield* Effect.logDebug("Interrupted").pipe(
|
|
75
|
+
Effect.annotateLogs(dropUndefined({ extras }))
|
|
76
|
+
)
|
|
75
77
|
return
|
|
76
78
|
}
|
|
77
79
|
yield* Effect
|
package/src/makeIntl.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
2
2
|
import { createIntl, createIntlCache, type Formatters, type IntlFormatters, type MessageDescriptor, type ResolvedIntlConfig } from "@formatjs/intl"
|
|
3
|
-
import
|
|
3
|
+
import * as Struct from "effect/Struct"
|
|
4
4
|
import { type FormatXMLElementFn, type Options as IntlMessageFormatOptions, type PrimitiveType } from "intl-messageformat"
|
|
5
5
|
import { type Ref, watch } from "vue"
|
|
6
6
|
import { translate } from "./form.ts"
|
|
@@ -20,7 +20,7 @@ export const makeIntl = <Locale extends string>(
|
|
|
20
20
|
) => {
|
|
21
21
|
const intlCache = createIntlCache()
|
|
22
22
|
|
|
23
|
-
const intls =
|
|
23
|
+
const intls = Struct.keys(messages).reduce(
|
|
24
24
|
(acc, cur) => {
|
|
25
25
|
acc[cur] = createIntl<Locale>(
|
|
26
26
|
{
|
package/src/mutate.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { DataDependencies, type InvalidationKey, InvalidationKeysFromServer, mak
|
|
|
3
3
|
import type { ClientForOptions, RequestHandlerWithInput } from "effect-app/client/clientFor"
|
|
4
4
|
import type { InvalidateQueryInstruction } from "effect-app/client/makeClient"
|
|
5
5
|
import * as Effect from "effect-app/Effect"
|
|
6
|
-
import { tuple } from "effect-app/Function"
|
|
7
6
|
import * as Option from "effect-app/Option"
|
|
8
7
|
import { isReadonlyArrayNonEmpty } from "effect/Array"
|
|
9
8
|
import type * as Cause from "effect/Cause"
|
|
@@ -12,6 +11,7 @@ import * as AsyncResult from "effect/reactivity/AsyncResult"
|
|
|
12
11
|
import type * as Reactivity from "effect/reactivity/Reactivity"
|
|
13
12
|
import * as Ref from "effect/Ref"
|
|
14
13
|
import * as Stream from "effect/Stream"
|
|
14
|
+
import * as Tuple from "effect/Tuple"
|
|
15
15
|
import { computed, type ComputedRef, shallowRef } from "vue"
|
|
16
16
|
import { invalidateAndAwait, invalidateSoft } from "./atomQuery.ts"
|
|
17
17
|
import { getDerivedInvalidationKeys, partitionInvalidationKeys } from "./dependencyMetadata.ts"
|
|
@@ -102,7 +102,7 @@ export function make<A, E, R>(self: Effect.Effect<A, E, R>) {
|
|
|
102
102
|
|
|
103
103
|
const latestSuccess = computed(() => Option.getOrUndefined(AsyncResult.value(result.value)))
|
|
104
104
|
|
|
105
|
-
return
|
|
105
|
+
return Tuple.make(result, latestSuccess, execute)
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
@@ -221,7 +221,7 @@ export const asResult = <Args extends readonly any[], A, E, R>(
|
|
|
221
221
|
))
|
|
222
222
|
)
|
|
223
223
|
|
|
224
|
-
return
|
|
224
|
+
return Tuple.make(computed(() => state.value), act)
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
/**
|
|
@@ -268,7 +268,7 @@ export const asStreamResult = <Args extends readonly any[], A, E, R>(
|
|
|
268
268
|
|
|
269
269
|
const act = (...args: Args) => runStream(handler(...args))
|
|
270
270
|
|
|
271
|
-
return
|
|
271
|
+
return Tuple.make(computed(() => state.value), act)
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
const buildInvalidateCache = <RInvalidator>(
|
package/src/routeParams.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Option from "effect-app/Option"
|
|
2
2
|
import * as S from "effect-app/Schema"
|
|
3
|
-
import { typedKeysOf } from "effect-app/utils"
|
|
4
3
|
import { flow } from "effect/Function"
|
|
4
|
+
import * as Struct from "effect/Struct"
|
|
5
5
|
import type { ParsedQuery } from "query-string"
|
|
6
6
|
|
|
7
7
|
export function getQueryParam(search: ParsedQuery, param: string) {
|
|
@@ -18,10 +18,10 @@ export function parseRouteParamsOption<NER extends Record<string, S.Codec<any, a
|
|
|
18
18
|
): {
|
|
19
19
|
[K in keyof NER]: Option.Option<NER[K]["Type"]>
|
|
20
20
|
} {
|
|
21
|
-
return
|
|
21
|
+
return Struct.keys(t).reduce(
|
|
22
22
|
(prev, cur) => {
|
|
23
23
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
24
|
-
prev[cur] = getQueryParamO(query, cur
|
|
24
|
+
prev[cur] = getQueryParamO(query, cur).pipe(
|
|
25
25
|
Option.flatMap(S.decodeUnknownOption(t[cur]!))
|
|
26
26
|
)
|
|
27
27
|
|
|
@@ -39,7 +39,7 @@ export function parseRouteParams<NER extends Record<string, S.Codec<any, any>>>(
|
|
|
39
39
|
): {
|
|
40
40
|
[K in keyof NER]: NER[K]["Type"]
|
|
41
41
|
} {
|
|
42
|
-
return
|
|
42
|
+
return Struct.keys(t).reduce(
|
|
43
43
|
(prev, cur) => {
|
|
44
44
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
45
45
|
prev[cur] = S.decodeUnknownSync(t[cur]!)(
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suspendWaiting.test.d.ts","sourceRoot":"","sources":["../suspendWaiting.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { it } from "@effect/vitest"
|
|
2
|
+
import * as Effect from "effect-app/Effect"
|
|
3
|
+
import * as Deferred from "effect/Deferred"
|
|
4
|
+
import * as Exit from "effect/Exit"
|
|
5
|
+
import * as Fiber from "effect/Fiber"
|
|
6
|
+
import { TestClock } from "effect/testing"
|
|
7
|
+
import { DefaultIntl } from "../src/commander.js"
|
|
8
|
+
import { useExperimentalE } from "./stubs.js"
|
|
9
|
+
|
|
10
|
+
const unwrap = <A, E>(r: Fiber.Fiber<Exit.Exit<A, E>, never>) => Fiber.join(r).pipe(Effect.flatten)
|
|
11
|
+
|
|
12
|
+
const infoToasts = (toasts: ReadonlyArray<{ type: string }>) => toasts.filter((toast) => toast.type === "info")
|
|
13
|
+
|
|
14
|
+
describe("Command.suspendWaiting", () => {
|
|
15
|
+
it.effect("hides the spinner and the waiting toast until work resumes", () =>
|
|
16
|
+
Effect.gen(function*() {
|
|
17
|
+
const toasts: Array<{ type: string; message: string }> = []
|
|
18
|
+
const Command = yield* useExperimentalE({ toasts, messages: DefaultIntl.en })
|
|
19
|
+
const toastVisible = yield* Deferred.make<void>()
|
|
20
|
+
const continueToDialog = yield* Deferred.make<void>()
|
|
21
|
+
const suspended = yield* Deferred.make<void>()
|
|
22
|
+
const release = yield* Deferred.make<void>()
|
|
23
|
+
const resumed = yield* Deferred.make<void>()
|
|
24
|
+
|
|
25
|
+
const command = Command.fn("Test Action")(
|
|
26
|
+
function*() {
|
|
27
|
+
yield* Effect.sleep("1100 millis")
|
|
28
|
+
yield* Deferred.succeed(toastVisible, undefined)
|
|
29
|
+
yield* Deferred.await(continueToDialog)
|
|
30
|
+
yield* Command.suspendWaiting(Effect.gen(function*() {
|
|
31
|
+
yield* Deferred.succeed(suspended, undefined)
|
|
32
|
+
return yield* Deferred.await(release)
|
|
33
|
+
}))
|
|
34
|
+
yield* Deferred.succeed(resumed, undefined)
|
|
35
|
+
expect(infoToasts(toasts)).toHaveLength(0)
|
|
36
|
+
yield* Effect.sleep("1100 millis")
|
|
37
|
+
expect(infoToasts(toasts)).toHaveLength(1)
|
|
38
|
+
return "done"
|
|
39
|
+
},
|
|
40
|
+
Command.withDefaultToast()
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const fiber = command.handle(undefined)
|
|
44
|
+
yield* TestClock.adjust("1100 millis")
|
|
45
|
+
yield* Deferred.await(toastVisible)
|
|
46
|
+
expect(command.waiting).toBe(true)
|
|
47
|
+
expect(command.blocked).toBe(true)
|
|
48
|
+
expect(infoToasts(toasts)).toHaveLength(1)
|
|
49
|
+
|
|
50
|
+
yield* Deferred.succeed(continueToDialog, undefined)
|
|
51
|
+
yield* Deferred.await(suspended)
|
|
52
|
+
expect(command.waiting).toBe(false)
|
|
53
|
+
expect(command.blocked).toBe(true)
|
|
54
|
+
expect(infoToasts(toasts)).toHaveLength(0)
|
|
55
|
+
|
|
56
|
+
yield* TestClock.adjust("2 seconds")
|
|
57
|
+
expect(infoToasts(toasts)).toHaveLength(0)
|
|
58
|
+
expect(command.waiting).toBe(false)
|
|
59
|
+
|
|
60
|
+
yield* Deferred.succeed(release, undefined)
|
|
61
|
+
yield* Deferred.await(resumed)
|
|
62
|
+
expect(command.waiting).toBe(true)
|
|
63
|
+
expect(command.blocked).toBe(true)
|
|
64
|
+
|
|
65
|
+
yield* TestClock.adjust("1100 millis")
|
|
66
|
+
expect(yield* unwrap(fiber)).toBe("done")
|
|
67
|
+
expect(toasts.map((toast) => toast.type)).toEqual(["success"])
|
|
68
|
+
}))
|
|
69
|
+
|
|
70
|
+
it.effect("stays quiet for a suspension that starts the command", () =>
|
|
71
|
+
Effect.gen(function*() {
|
|
72
|
+
const toasts: Array<{ type: string; message: string }> = []
|
|
73
|
+
const Command = yield* useExperimentalE({ toasts, messages: DefaultIntl.en })
|
|
74
|
+
const suspended = yield* Deferred.make<void>()
|
|
75
|
+
const release = yield* Deferred.make<string>()
|
|
76
|
+
const resumed = yield* Deferred.make<void>()
|
|
77
|
+
|
|
78
|
+
const command = Command.fn("Test Action")(
|
|
79
|
+
function*() {
|
|
80
|
+
const answer = yield* Command.suspendWaiting(Effect.gen(function*() {
|
|
81
|
+
yield* Deferred.succeed(suspended, undefined)
|
|
82
|
+
return yield* Deferred.await(release)
|
|
83
|
+
}))
|
|
84
|
+
yield* Deferred.succeed(resumed, undefined)
|
|
85
|
+
expect(infoToasts(toasts)).toHaveLength(0)
|
|
86
|
+
yield* Effect.sleep("1100 millis")
|
|
87
|
+
expect(infoToasts(toasts)).toHaveLength(1)
|
|
88
|
+
return answer
|
|
89
|
+
},
|
|
90
|
+
Command.withDefaultToast()
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
const fiber = command.handle(undefined)
|
|
94
|
+
yield* Deferred.await(suspended)
|
|
95
|
+
expect(command.waiting).toBe(false)
|
|
96
|
+
expect(command.blocked).toBe(true)
|
|
97
|
+
|
|
98
|
+
yield* TestClock.adjust("2 seconds")
|
|
99
|
+
expect(infoToasts(toasts)).toHaveLength(0)
|
|
100
|
+
|
|
101
|
+
yield* Deferred.succeed(release, "yes")
|
|
102
|
+
yield* Deferred.await(resumed)
|
|
103
|
+
expect(command.waiting).toBe(true)
|
|
104
|
+
|
|
105
|
+
yield* TestClock.adjust("1100 millis")
|
|
106
|
+
expect(yield* unwrap(fiber)).toBe("yes")
|
|
107
|
+
expect(toasts.map((toast) => toast.type)).toEqual(["success"])
|
|
108
|
+
}))
|
|
109
|
+
|
|
110
|
+
it.effect("drops the block when the suspension is interrupted", () =>
|
|
111
|
+
Effect.gen(function*() {
|
|
112
|
+
const toasts: Array<{ type: string }> = []
|
|
113
|
+
const Command = yield* useExperimentalE({ toasts, messages: DefaultIntl.en })
|
|
114
|
+
const suspended = yield* Deferred.make<void>()
|
|
115
|
+
|
|
116
|
+
const release = yield* Deferred.make<void>()
|
|
117
|
+
|
|
118
|
+
const command = Command.fn("Test Action")(
|
|
119
|
+
function*() {
|
|
120
|
+
return yield* Command.suspendWaiting(Effect.gen(function*() {
|
|
121
|
+
yield* Deferred.succeed(suspended, undefined)
|
|
122
|
+
yield* Deferred.await(release)
|
|
123
|
+
return yield* Effect.interrupt
|
|
124
|
+
}))
|
|
125
|
+
},
|
|
126
|
+
Command.withDefaultToast()
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
const fiber = command.handle(undefined)
|
|
130
|
+
yield* Deferred.await(suspended)
|
|
131
|
+
expect(command.waiting).toBe(false)
|
|
132
|
+
expect(command.blocked).toBe(true)
|
|
133
|
+
expect(toasts).toHaveLength(0)
|
|
134
|
+
|
|
135
|
+
yield* Deferred.succeed(release, undefined)
|
|
136
|
+
const exit = yield* Fiber.join(fiber)
|
|
137
|
+
expect(Exit.hasInterrupts(exit)).toBe(true)
|
|
138
|
+
expect(command.waiting).toBe(false)
|
|
139
|
+
expect(command.blocked).toBe(false)
|
|
140
|
+
expect(toasts).toHaveLength(0)
|
|
141
|
+
}))
|
|
142
|
+
})
|