@shivaedev/effect-test 0.1.1 → 0.1.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 +10 -1
- package/README.md +12 -2
- package/dist/any-test-layer.d.ts +3 -0
- package/dist/any-test-layer.d.ts.map +1 -0
- package/dist/any-test-layer.js +2 -0
- package/dist/any-test-layer.js.map +1 -0
- package/dist/eventually.d.ts.map +1 -1
- package/dist/eventually.js +19 -17
- package/dist/eventually.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/types.d.ts +4 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/vitest.d.ts +3 -2
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +8 -14
- package/dist/vitest.js.map +1 -1
- package/package.json +1 -1
- package/src/any-test-layer.ts +3 -0
- package/src/eventually.ts +24 -24
- package/src/index.ts +3 -3
- package/src/types.ts +10 -23
- package/src/vitest.ts +72 -171
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.1.2 - 2026-09-26
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Make `eventually` retry only typed failures under TestClock, matching the live
|
|
8
|
+
clock. Defects and interruption propagate without retrying.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Document the live worker-Layer clock and the scope of per-test clock overrides.
|
|
4
13
|
|
|
5
14
|
## 0.1.1 - 2026-09-04
|
|
6
15
|
|
package/README.md
CHANGED
|
@@ -58,10 +58,20 @@ effectApp("talks to a real timer", function* () {
|
|
|
58
58
|
}, { clock: "live" })
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
The worker Layer is acquired and released outside the per-test environment,
|
|
62
|
+
using the live clock unless the Layer supplies its own. Fibers started during
|
|
63
|
+
Layer acquisition keep that environment; advancing a test's TestClock does not
|
|
64
|
+
advance their timers. The selected per-test clock applies to `around`,
|
|
65
|
+
`makeHarness`, and the test body.
|
|
66
|
+
|
|
61
67
|
## `eventually`
|
|
62
68
|
|
|
63
|
-
Retries until the effect succeeds. Under TestClock it advances
|
|
64
|
-
`TestClock.adjust`. Under `clock: "live"` it uses `Schedule.spaced`.
|
|
69
|
+
Retries typed failures until the effect succeeds. Under TestClock it advances
|
|
70
|
+
time with `TestClock.adjust`. Under `clock: "live"` it uses `Schedule.spaced`.
|
|
71
|
+
`times` counts retries after the initial attempt; omitting it retries without a
|
|
72
|
+
limit. Defects (including thrown assertions) and interruption propagate without
|
|
73
|
+
retrying under either clock. Use `Effect.try(() => expect(...))` when an
|
|
74
|
+
assertion failure is intentionally a retryable condition.
|
|
65
75
|
|
|
66
76
|
```ts
|
|
67
77
|
import { eventually } from "@shivaedev/effect-test"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"any-test-layer.d.ts","sourceRoot":"","sources":["../src/any-test-layer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAEpC,MAAM,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"any-test-layer.js","sourceRoot":"","sources":["../src/any-test-layer.ts"],"names":[],"mappings":""}
|
package/dist/eventually.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eventually.d.ts","sourceRoot":"","sources":["../src/eventually.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"eventually.d.ts","sourceRoot":"","sources":["../src/eventually.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,EAAoB,MAAM,QAAQ,CAAC;AAGxE,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACxB;AAgCD,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,iBAAiB,KAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAMpH,CAAC"}
|
package/dist/eventually.js
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
import { Clock, Effect,
|
|
1
|
+
import { Clock, Effect, Result, Schedule } from "effect";
|
|
2
2
|
import * as TestClock from "effect/testing/TestClock";
|
|
3
3
|
const defaultInterval = "10 millis";
|
|
4
4
|
const isTestClock = (clock) => "adjust" in clock;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
times: options.times,
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
let retriesLeft = options?.times;
|
|
5
|
+
const retryOnLiveClock = (effect, interval, times) => times === undefined
|
|
6
|
+
? Effect.retry(effect, Schedule.spaced(interval))
|
|
7
|
+
: Effect.retry(effect, {
|
|
8
|
+
schedule: Schedule.spaced(interval),
|
|
9
|
+
times,
|
|
10
|
+
});
|
|
11
|
+
const retryOnTestClock = (effect, interval, times) => Effect.gen(function* () {
|
|
12
|
+
let retriesLeft = times;
|
|
17
13
|
for (;;) {
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
20
|
-
return
|
|
14
|
+
const result = yield* Effect.result(effect);
|
|
15
|
+
if (Result.isSuccess(result)) {
|
|
16
|
+
return result.success;
|
|
21
17
|
}
|
|
22
18
|
if (retriesLeft === 0) {
|
|
23
|
-
return yield*
|
|
19
|
+
return yield* Effect.fail(result.failure);
|
|
24
20
|
}
|
|
25
21
|
if (retriesLeft !== undefined) {
|
|
26
22
|
retriesLeft -= 1;
|
|
@@ -28,4 +24,10 @@ export const eventually = (effect, options) => Effect.gen(function* () {
|
|
|
28
24
|
yield* TestClock.adjust(interval);
|
|
29
25
|
}
|
|
30
26
|
});
|
|
27
|
+
export const eventually = (effect, options) => Effect.gen(function* () {
|
|
28
|
+
const interval = options?.interval ?? defaultInterval;
|
|
29
|
+
const clock = yield* Clock.Clock;
|
|
30
|
+
const retry = isTestClock(clock) ? retryOnTestClock : retryOnLiveClock;
|
|
31
|
+
return yield* retry(effect, interval, options?.times);
|
|
32
|
+
});
|
|
31
33
|
//# sourceMappingURL=eventually.js.map
|
package/dist/eventually.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eventually.js","sourceRoot":"","sources":["../src/eventually.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAiB,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"eventually.js","sourceRoot":"","sources":["../src/eventually.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAiB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACxE,OAAO,KAAK,SAAS,MAAM,0BAA0B,CAAC;AAOtD,MAAM,eAAe,GAAG,WAAW,CAAC;AAEpC,MAAM,WAAW,GAAG,CAAC,KAAkB,EAAgC,EAAE,CAAC,QAAQ,IAAI,KAAK,CAAC;AAE5F,MAAM,gBAAgB,GAAG,CAAU,MAA8B,EAAE,QAAwB,EAAE,KAAyB,EAA0B,EAAE,CACjJ,KAAK,KAAK,SAAS;IAClB,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACjD,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE;QACrB,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC;QACnC,KAAK;KACL,CAAC,CAAC;AAEN,MAAM,gBAAgB,GAAG,CAAU,MAA8B,EAAE,QAAwB,EAAE,KAAyB,EAA0B,EAAE,CACjJ,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,SAAS,CAAC;QACT,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9B,OAAO,MAAM,CAAC,OAAO,CAAC;QACvB,CAAC;QACD,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC3C,CAAC;QACD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC/B,WAAW,IAAI,CAAC,CAAC;QAClB,CAAC;QACD,KAAK,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;AACF,CAAC,CAAC,CAAC;AAEJ,MAAM,CAAC,MAAM,UAAU,GAAG,CAAU,MAA8B,EAAE,OAA2B,EAA0B,EAAE,CAC1H,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,eAAe,CAAC;IACtD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;IACjC,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,gBAAgB,CAAC;IACvE,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { type EventuallyOptions, eventually } from "./eventually.
|
|
2
|
-
export type { EffectClock, EffectTest, EffectTester, EffectTestOptions, MakeEffectItOptions, MakeEffectItResult, } from "./types.
|
|
3
|
-
export { makeEffectIt } from "./vitest.
|
|
1
|
+
export { type EventuallyOptions, eventually } from "./eventually.ts";
|
|
2
|
+
export type { EffectClock, EffectTest, EffectTester, EffectTestOptions, MakeEffectItOptions, MakeEffectItResult, } from "./types.ts";
|
|
3
|
+
export { makeEffectIt } from "./vitest.ts";
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TestContext, TestOptions } from "@effect/vitest";
|
|
2
2
|
import type { Effect, Layer } from "effect";
|
|
3
|
+
import type { AnyTestLayer } from "./any-test-layer.ts";
|
|
3
4
|
export type EffectClock = "test" | "live";
|
|
4
5
|
export type EffectTestOptions = TestOptions & {
|
|
5
6
|
readonly clock?: EffectClock;
|
|
@@ -13,11 +14,11 @@ export interface EffectTester<Harness, Provided> extends EffectTest<Harness, Pro
|
|
|
13
14
|
readonly each: <Item>(cases: ReadonlyArray<Item>) => <A, Eff extends Effect.Effect<unknown, unknown, Provided>>(name: string, body: (item: Item, harness: Harness, context: TestContext) => Generator<Eff, A, never>, options?: number | EffectTestOptions) => void;
|
|
14
15
|
readonly fails: EffectTest<Harness, Provided>;
|
|
15
16
|
}
|
|
16
|
-
export interface MakeEffectItOptions<Harness, TestLayer extends
|
|
17
|
+
export interface MakeEffectItOptions<Harness, TestLayer extends AnyTestLayer> {
|
|
17
18
|
readonly layer: TestLayer;
|
|
18
|
-
readonly around?: <A, E>(effect: Effect.Effect<A, E, Layer.Success<TestLayer>>) => Effect.Effect<A, unknown, Layer.Success<TestLayer
|
|
19
|
+
readonly around?: (<A, E>(effect: Effect.Effect<A, E, Layer.Success<TestLayer>>) => Effect.Effect<A, unknown, Layer.Success<TestLayer>>) | undefined;
|
|
19
20
|
readonly makeHarness: (context: TestContext) => Effect.Effect<Harness, unknown, Layer.Success<TestLayer>>;
|
|
20
|
-
readonly clock?: EffectClock;
|
|
21
|
+
readonly clock?: EffectClock | undefined;
|
|
21
22
|
}
|
|
22
23
|
export interface MakeEffectItResult<Harness, Provided> {
|
|
23
24
|
readonly effectApp: EffectTester<Harness, Provided>;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/vitest.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Layer } from "effect";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type { AnyTestLayer } from "./any-test-layer.ts";
|
|
3
|
+
import type { MakeEffectItOptions, MakeEffectItResult } from "./types.ts";
|
|
4
|
+
export declare const makeEffectIt: <Harness, TestLayer extends AnyTestLayer>(options: MakeEffectItOptions<Harness, TestLayer>) => MakeEffectItResult<Harness, Layer.Success<TestLayer>>;
|
|
4
5
|
//# sourceMappingURL=vitest.d.ts.map
|
package/dist/vitest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AACA,OAAO,EAAqC,KAAK,EAAS,MAAM,QAAQ,CAAC;AAGzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAA4D,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAkEpI,eAAO,MAAM,YAAY,GAAI,OAAO,EAAE,SAAS,SAAS,YAAY,EACnE,SAAS,mBAAmB,CAAC,OAAO,EAAE,SAAS,CAAC,KAC9C,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAyEtD,CAAC"}
|
package/dist/vitest.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { it as effectIt
|
|
1
|
+
import { it as effectIt } from "@effect/vitest";
|
|
2
2
|
import { Cause, Effect, Exit, Layer, Scope } from "effect";
|
|
3
3
|
import * as TestClock from "effect/testing/TestClock";
|
|
4
4
|
import * as TestConsole from "effect/testing/TestConsole";
|
|
5
5
|
const fixtureName = "__effectTestContext";
|
|
6
6
|
const TestClockEnvironment = Layer.mergeAll(TestConsole.layer, TestClock.layer());
|
|
7
7
|
const LiveClockEnvironment = TestConsole.layer;
|
|
8
|
-
const environmentFor = (clock) => clock === "live" ? LiveClockEnvironment : TestClockEnvironment;
|
|
8
|
+
const environmentFor = (clock) => (clock === "live" ? LiveClockEnvironment : TestClockEnvironment);
|
|
9
9
|
const restoreContext = (fixture, context) => ({
|
|
10
10
|
...context,
|
|
11
11
|
[fixtureName]: fixture,
|
|
12
12
|
});
|
|
13
|
-
const contextFrom = (context) => context[fixtureName];
|
|
14
13
|
const runEffectTest = (effect, context, clock) => Effect.runPromise(Effect.gen(function* () {
|
|
15
14
|
const exit = yield* Effect.exit(effect);
|
|
16
15
|
if (Exit.isFailure(exit)) {
|
|
@@ -30,9 +29,7 @@ const splitOptions = (options) => {
|
|
|
30
29
|
const { clock, ...vitest } = options;
|
|
31
30
|
return { clock, vitest };
|
|
32
31
|
};
|
|
33
|
-
const makeFixtureIt = (layer) => effectIt.extend(fixtureName, { scope: "worker" },
|
|
34
|
-
// biome-ignore lint/correctness/noEmptyPattern: Vitest fixtures require a destructured context parameter.
|
|
35
|
-
async ({}, { onCleanup }) => {
|
|
32
|
+
const makeFixtureIt = (layer) => effectIt.extend(fixtureName, { scope: "worker" }, async ({}, { onCleanup }) => {
|
|
36
33
|
const scope = Effect.runSync(Scope.make());
|
|
37
34
|
onCleanup(() => Effect.runPromise(Scope.close(scope, Exit.void)));
|
|
38
35
|
try {
|
|
@@ -47,20 +44,17 @@ export const makeEffectIt = (options) => {
|
|
|
47
44
|
const fixtureIt = makeFixtureIt(options.layer);
|
|
48
45
|
const defaultClock = options.clock ?? "test";
|
|
49
46
|
const run = (body, context) => {
|
|
50
|
-
const
|
|
47
|
+
const ready = Effect.gen(function* () {
|
|
51
48
|
const harness = yield* options.makeHarness(context);
|
|
52
49
|
return yield* Effect.gen(() => body(harness, context));
|
|
53
50
|
});
|
|
54
|
-
// The body constraint proves every yielded service is part of Provided.
|
|
55
|
-
// TypeScript cannot reduce that generic generator requirement here.
|
|
56
|
-
const ready = program;
|
|
57
51
|
const wrapped = options.around?.(ready) ?? ready;
|
|
58
|
-
return wrapped.pipe(Effect.provide(
|
|
52
|
+
return wrapped.pipe(Effect.provide(context[fixtureName]));
|
|
59
53
|
};
|
|
60
54
|
const register = (current) => (name, body, testOptions) => {
|
|
61
55
|
const split = splitOptions(testOptions);
|
|
62
56
|
const clock = split.clock ?? defaultClock;
|
|
63
|
-
current(name, split.vitest, ({ __effectTestContext, task, signal, onTestFailed, onTestFinished, skip, annotate, expect, _local
|
|
57
|
+
current(name, split.vitest, ({ __effectTestContext, task, signal, onTestFailed, onTestFinished, skip, annotate, expect, _local }) => {
|
|
64
58
|
const context = restoreContext(__effectTestContext, {
|
|
65
59
|
annotate,
|
|
66
60
|
expect,
|
|
@@ -82,7 +76,7 @@ export const makeEffectIt = (options) => {
|
|
|
82
76
|
each: (cases) => (name, body, testOptions) => {
|
|
83
77
|
const split = splitOptions(testOptions);
|
|
84
78
|
const clock = split.clock ?? defaultClock;
|
|
85
|
-
fixtureIt.for(cases)(name, split.vitest, (item, { __effectTestContext, task, signal, onTestFailed, onTestFinished, skip, annotate, expect, _local
|
|
79
|
+
fixtureIt.for(cases)(name, split.vitest, (item, { __effectTestContext, task, signal, onTestFailed, onTestFinished, skip, annotate, expect, _local }) => {
|
|
86
80
|
const context = restoreContext(__effectTestContext, {
|
|
87
81
|
annotate,
|
|
88
82
|
expect,
|
|
@@ -93,7 +87,7 @@ export const makeEffectIt = (options) => {
|
|
|
93
87
|
task,
|
|
94
88
|
_local,
|
|
95
89
|
});
|
|
96
|
-
return runEffectTest(run((harness, testContext) => body(item, harness, testContext), context), context, clock);
|
|
90
|
+
return runEffectTest(Effect.asVoid(run((harness, testContext) => body(item, harness, testContext), context)), context, clock);
|
|
97
91
|
});
|
|
98
92
|
},
|
|
99
93
|
fails: register(fixtureIt.fails),
|
package/dist/vitest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest.js","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"vitest.js","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/eventually.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Clock, type Duration, Effect,
|
|
1
|
+
import { Clock, type Duration, Effect, Result, Schedule } from "effect";
|
|
2
2
|
import * as TestClock from "effect/testing/TestClock";
|
|
3
3
|
|
|
4
4
|
export interface EventuallyOptions {
|
|
@@ -8,34 +8,26 @@ export interface EventuallyOptions {
|
|
|
8
8
|
|
|
9
9
|
const defaultInterval = "10 millis";
|
|
10
10
|
|
|
11
|
-
const isTestClock = (clock: Clock.Clock): clock is TestClock.TestClock =>
|
|
12
|
-
"adjust" in clock;
|
|
11
|
+
const isTestClock = (clock: Clock.Clock): clock is TestClock.TestClock => "adjust" in clock;
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
const retryOnLiveClock = <A, E, R>(effect: Effect.Effect<A, E, R>, interval: Duration.Input, times: number | undefined): Effect.Effect<A, E, R> =>
|
|
14
|
+
times === undefined
|
|
15
|
+
? Effect.retry(effect, Schedule.spaced(interval))
|
|
16
|
+
: Effect.retry(effect, {
|
|
17
|
+
schedule: Schedule.spaced(interval),
|
|
18
|
+
times,
|
|
19
|
+
});
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
: Effect.retry(effect, {
|
|
26
|
-
schedule: Schedule.spaced(interval),
|
|
27
|
-
times: options.times,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let retriesLeft = options?.times;
|
|
21
|
+
const retryOnTestClock = <A, E, R>(effect: Effect.Effect<A, E, R>, interval: Duration.Input, times: number | undefined): Effect.Effect<A, E, R> =>
|
|
22
|
+
Effect.gen(function* () {
|
|
23
|
+
let retriesLeft = times;
|
|
32
24
|
for (;;) {
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
35
|
-
return
|
|
25
|
+
const result = yield* Effect.result(effect);
|
|
26
|
+
if (Result.isSuccess(result)) {
|
|
27
|
+
return result.success;
|
|
36
28
|
}
|
|
37
29
|
if (retriesLeft === 0) {
|
|
38
|
-
return yield*
|
|
30
|
+
return yield* Effect.fail(result.failure);
|
|
39
31
|
}
|
|
40
32
|
if (retriesLeft !== undefined) {
|
|
41
33
|
retriesLeft -= 1;
|
|
@@ -43,3 +35,11 @@ export const eventually = <A, E, R>(
|
|
|
43
35
|
yield* TestClock.adjust(interval);
|
|
44
36
|
}
|
|
45
37
|
});
|
|
38
|
+
|
|
39
|
+
export const eventually = <A, E, R>(effect: Effect.Effect<A, E, R>, options?: EventuallyOptions): Effect.Effect<A, E, R> =>
|
|
40
|
+
Effect.gen(function* () {
|
|
41
|
+
const interval = options?.interval ?? defaultInterval;
|
|
42
|
+
const clock = yield* Clock.Clock;
|
|
43
|
+
const retry = isTestClock(clock) ? retryOnTestClock : retryOnLiveClock;
|
|
44
|
+
return yield* retry(effect, interval, options?.times);
|
|
45
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { type EventuallyOptions, eventually } from "./eventually.
|
|
1
|
+
export { type EventuallyOptions, eventually } from "./eventually.ts";
|
|
2
2
|
export type {
|
|
3
3
|
EffectClock,
|
|
4
4
|
EffectTest,
|
|
@@ -6,5 +6,5 @@ export type {
|
|
|
6
6
|
EffectTestOptions,
|
|
7
7
|
MakeEffectItOptions,
|
|
8
8
|
MakeEffectItResult,
|
|
9
|
-
} from "./types.
|
|
10
|
-
export { makeEffectIt } from "./vitest.
|
|
9
|
+
} from "./types.ts";
|
|
10
|
+
export { makeEffectIt } from "./vitest.ts";
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TestContext, TestOptions } from "@effect/vitest";
|
|
2
2
|
import type { Effect, Layer } from "effect";
|
|
3
|
+
import type { AnyTestLayer } from "./any-test-layer.ts";
|
|
3
4
|
|
|
4
5
|
export type EffectClock = "test" | "live";
|
|
5
6
|
|
|
@@ -7,17 +8,13 @@ export type EffectTestOptions = TestOptions & {
|
|
|
7
8
|
readonly clock?: EffectClock;
|
|
8
9
|
};
|
|
9
10
|
|
|
10
|
-
export type EffectTest<Harness, Provided> = <
|
|
11
|
-
A,
|
|
12
|
-
Eff extends Effect.Effect<unknown, unknown, Provided>,
|
|
13
|
-
>(
|
|
11
|
+
export type EffectTest<Harness, Provided> = <A, Eff extends Effect.Effect<unknown, unknown, Provided>>(
|
|
14
12
|
name: string,
|
|
15
13
|
body: (harness: Harness, context: TestContext) => Generator<Eff, A, never>,
|
|
16
14
|
options?: number | EffectTestOptions,
|
|
17
15
|
) => void;
|
|
18
16
|
|
|
19
|
-
export interface EffectTester<Harness, Provided>
|
|
20
|
-
extends EffectTest<Harness, Provided> {
|
|
17
|
+
export interface EffectTester<Harness, Provided> extends EffectTest<Harness, Provided> {
|
|
21
18
|
readonly skip: EffectTest<Harness, Provided>;
|
|
22
19
|
readonly skipIf: (condition: unknown) => EffectTest<Harness, Provided>;
|
|
23
20
|
readonly runIf: (condition: unknown) => EffectTest<Harness, Provided>;
|
|
@@ -26,29 +23,19 @@ export interface EffectTester<Harness, Provided>
|
|
|
26
23
|
cases: ReadonlyArray<Item>,
|
|
27
24
|
) => <A, Eff extends Effect.Effect<unknown, unknown, Provided>>(
|
|
28
25
|
name: string,
|
|
29
|
-
body: (
|
|
30
|
-
item: Item,
|
|
31
|
-
harness: Harness,
|
|
32
|
-
context: TestContext,
|
|
33
|
-
) => Generator<Eff, A, never>,
|
|
26
|
+
body: (item: Item, harness: Harness, context: TestContext) => Generator<Eff, A, never>,
|
|
34
27
|
options?: number | EffectTestOptions,
|
|
35
28
|
) => void;
|
|
36
29
|
readonly fails: EffectTest<Harness, Provided>;
|
|
37
30
|
}
|
|
38
31
|
|
|
39
|
-
export interface MakeEffectItOptions<
|
|
40
|
-
Harness,
|
|
41
|
-
// biome-ignore lint/suspicious/noExplicitAny: Layer output and error are recovered with Layer utility types
|
|
42
|
-
TestLayer extends Layer.Layer<any, any, never>,
|
|
43
|
-
> {
|
|
32
|
+
export interface MakeEffectItOptions<Harness, TestLayer extends AnyTestLayer> {
|
|
44
33
|
readonly layer: TestLayer;
|
|
45
|
-
readonly around?:
|
|
46
|
-
effect: Effect.Effect<A, E, Layer.Success<TestLayer
|
|
47
|
-
|
|
48
|
-
readonly makeHarness: (
|
|
49
|
-
|
|
50
|
-
) => Effect.Effect<Harness, unknown, Layer.Success<TestLayer>>;
|
|
51
|
-
readonly clock?: EffectClock;
|
|
34
|
+
readonly around?:
|
|
35
|
+
| (<A, E>(effect: Effect.Effect<A, E, Layer.Success<TestLayer>>) => Effect.Effect<A, unknown, Layer.Success<TestLayer>>)
|
|
36
|
+
| undefined;
|
|
37
|
+
readonly makeHarness: (context: TestContext) => Effect.Effect<Harness, unknown, Layer.Success<TestLayer>>;
|
|
38
|
+
readonly clock?: EffectClock | undefined;
|
|
52
39
|
}
|
|
53
40
|
|
|
54
41
|
export interface MakeEffectItResult<Harness, Provided> {
|
package/src/vitest.ts
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
it as effectIt,
|
|
3
|
-
type TestContext,
|
|
4
|
-
type TestOptions,
|
|
5
|
-
} from "@effect/vitest";
|
|
1
|
+
import { it as effectIt, type TestContext, type TestOptions } from "@effect/vitest";
|
|
6
2
|
import { Cause, type Context, Effect, Exit, Layer, Scope } from "effect";
|
|
7
3
|
import * as TestClock from "effect/testing/TestClock";
|
|
8
4
|
import * as TestConsole from "effect/testing/TestConsole";
|
|
9
|
-
import type {
|
|
10
|
-
|
|
11
|
-
EffectTest,
|
|
12
|
-
EffectTester,
|
|
13
|
-
EffectTestOptions,
|
|
14
|
-
MakeEffectItOptions,
|
|
15
|
-
MakeEffectItResult,
|
|
16
|
-
} from "./types.js";
|
|
5
|
+
import type { AnyTestLayer } from "./any-test-layer.ts";
|
|
6
|
+
import type { EffectClock, EffectTest, EffectTester, EffectTestOptions, MakeEffectItOptions, MakeEffectItResult } from "./types.ts";
|
|
17
7
|
|
|
18
8
|
const fixtureName = "__effectTestContext";
|
|
19
9
|
|
|
@@ -21,54 +11,22 @@ type EffectFixture<Provided> = TestContext & {
|
|
|
21
11
|
readonly [fixtureName]: Context.Context<Provided>;
|
|
22
12
|
};
|
|
23
13
|
|
|
24
|
-
|
|
25
|
-
(
|
|
26
|
-
name: string,
|
|
27
|
-
options: TestOptions,
|
|
28
|
-
body: (context: EffectFixture<Provided>) => Promise<unknown>,
|
|
29
|
-
): void;
|
|
30
|
-
readonly skip: FixtureTestApi<Provided>;
|
|
31
|
-
readonly skipIf: (condition: unknown) => FixtureTestApi<Provided>;
|
|
32
|
-
readonly runIf: (condition: unknown) => FixtureTestApi<Provided>;
|
|
33
|
-
readonly only: FixtureTestApi<Provided>;
|
|
34
|
-
readonly fails: FixtureTestApi<Provided>;
|
|
35
|
-
readonly for: <Item>(
|
|
36
|
-
cases: ReadonlyArray<Item>,
|
|
37
|
-
) => (
|
|
38
|
-
name: string,
|
|
39
|
-
options: TestOptions,
|
|
40
|
-
body: (item: Item, context: EffectFixture<Provided>) => Promise<unknown>,
|
|
41
|
-
) => void;
|
|
42
|
-
}
|
|
14
|
+
type FixtureTest<Provided> = (name: string, options: TestOptions, body: (context: EffectFixture<Provided>) => Promise<unknown>) => void;
|
|
43
15
|
|
|
44
|
-
const TestClockEnvironment = Layer.mergeAll(
|
|
45
|
-
TestConsole.layer,
|
|
46
|
-
TestClock.layer(),
|
|
47
|
-
);
|
|
16
|
+
const TestClockEnvironment = Layer.mergeAll(TestConsole.layer, TestClock.layer());
|
|
48
17
|
const LiveClockEnvironment = TestConsole.layer;
|
|
49
18
|
|
|
50
|
-
const environmentFor = (clock: EffectClock) =>
|
|
51
|
-
clock === "live" ? LiveClockEnvironment : TestClockEnvironment;
|
|
19
|
+
const environmentFor = (clock: EffectClock) => (clock === "live" ? LiveClockEnvironment : TestClockEnvironment);
|
|
52
20
|
|
|
53
21
|
const restoreContext = <Provided>(
|
|
54
22
|
fixture: Context.Context<Provided>,
|
|
55
23
|
context: Omit<EffectFixture<Provided>, typeof fixtureName>,
|
|
56
|
-
): EffectFixture<Provided> =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}) as EffectFixture<Provided>;
|
|
24
|
+
): EffectFixture<Provided> => ({
|
|
25
|
+
...context,
|
|
26
|
+
[fixtureName]: fixture,
|
|
27
|
+
});
|
|
61
28
|
|
|
62
|
-
const
|
|
63
|
-
context: TestContext,
|
|
64
|
-
): Context.Context<Provided> =>
|
|
65
|
-
(context as EffectFixture<Provided>)[fixtureName];
|
|
66
|
-
|
|
67
|
-
const runEffectTest = <A, E>(
|
|
68
|
-
effect: Effect.Effect<A, E, Scope.Scope>,
|
|
69
|
-
context: TestContext,
|
|
70
|
-
clock: EffectClock,
|
|
71
|
-
): Promise<A> =>
|
|
29
|
+
const runEffectTest = <A, E>(effect: Effect.Effect<A, E, Scope.Scope>, context: TestContext, clock: EffectClock): Promise<A> =>
|
|
72
30
|
Effect.runPromise(
|
|
73
31
|
Effect.gen(function* () {
|
|
74
32
|
const exit = yield* Effect.exit(effect);
|
|
@@ -98,149 +56,92 @@ const splitOptions = (
|
|
|
98
56
|
return { clock, vitest };
|
|
99
57
|
};
|
|
100
58
|
|
|
101
|
-
const makeFixtureIt = <Provided, LayerError>(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
fixtureName,
|
|
106
|
-
{ scope: "worker" },
|
|
107
|
-
// biome-ignore lint/correctness/noEmptyPattern: Vitest fixtures require a destructured context parameter.
|
|
108
|
-
async ({}, { onCleanup }) => {
|
|
109
|
-
const scope = Effect.runSync(Scope.make());
|
|
110
|
-
onCleanup(() => Effect.runPromise(Scope.close(scope, Exit.void)));
|
|
59
|
+
const makeFixtureIt = <Provided, LayerError>(layer: Layer.Layer<Provided, LayerError>) =>
|
|
60
|
+
effectIt.extend(fixtureName, { scope: "worker" }, async ({}, { onCleanup }) => {
|
|
61
|
+
const scope = Effect.runSync(Scope.make());
|
|
62
|
+
onCleanup(() => Effect.runPromise(Scope.close(scope, Exit.void)));
|
|
111
63
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
) as unknown as FixtureTestApi<Provided>;
|
|
64
|
+
try {
|
|
65
|
+
return await Effect.runPromise(Layer.buildWithScope(layer, scope));
|
|
66
|
+
} catch (error) {
|
|
67
|
+
await Effect.runPromise(Scope.close(scope, Exit.void));
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
120
71
|
|
|
121
|
-
export const makeEffectIt = <
|
|
122
|
-
Harness,
|
|
123
|
-
// biome-ignore lint/suspicious/noExplicitAny: Layer output and error are recovered with Layer utility types
|
|
124
|
-
TestLayer extends Layer.Layer<any, any, never>,
|
|
125
|
-
>(
|
|
72
|
+
export const makeEffectIt = <Harness, TestLayer extends AnyTestLayer>(
|
|
126
73
|
options: MakeEffectItOptions<Harness, TestLayer>,
|
|
127
74
|
): MakeEffectItResult<Harness, Layer.Success<TestLayer>> => {
|
|
128
75
|
type Provided = Layer.Success<TestLayer>;
|
|
129
76
|
const fixtureIt = makeFixtureIt(options.layer);
|
|
130
77
|
const defaultClock = options.clock ?? "test";
|
|
131
78
|
|
|
132
|
-
const run = <A
|
|
133
|
-
body: (harness: Harness, context: TestContext) => Generator<
|
|
134
|
-
context:
|
|
79
|
+
const run = <A>(
|
|
80
|
+
body: (harness: Harness, context: TestContext) => Generator<Effect.Effect<unknown, unknown, Provided>, A, never>,
|
|
81
|
+
context: EffectFixture<Provided>,
|
|
135
82
|
): Effect.Effect<A, unknown, Scope.Scope> => {
|
|
136
|
-
const
|
|
83
|
+
const ready: Effect.Effect<A, unknown, Provided> = Effect.gen(function* () {
|
|
137
84
|
const harness = yield* options.makeHarness(context);
|
|
138
85
|
return yield* Effect.gen(() => body(harness, context));
|
|
139
86
|
});
|
|
140
|
-
// The body constraint proves every yielded service is part of Provided.
|
|
141
|
-
// TypeScript cannot reduce that generic generator requirement here.
|
|
142
|
-
const ready = program as Effect.Effect<A, unknown, Provided>;
|
|
143
87
|
const wrapped = options.around?.(ready) ?? ready;
|
|
144
|
-
return wrapped.pipe(
|
|
145
|
-
Effect.provide(contextFrom<Provided>(context)),
|
|
146
|
-
) as Effect.Effect<A, unknown, Scope.Scope>;
|
|
88
|
+
return wrapped.pipe(Effect.provide(context[fixtureName]));
|
|
147
89
|
};
|
|
148
90
|
|
|
149
91
|
const register =
|
|
150
|
-
(current:
|
|
92
|
+
(current: FixtureTest<Provided>): EffectTest<Harness, Provided> =>
|
|
151
93
|
(name, body, testOptions) => {
|
|
152
94
|
const split = splitOptions(testOptions);
|
|
153
95
|
const clock = split.clock ?? defaultClock;
|
|
154
|
-
current(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
__effectTestContext,
|
|
159
|
-
task,
|
|
160
|
-
signal,
|
|
96
|
+
current(name, split.vitest, ({ __effectTestContext, task, signal, onTestFailed, onTestFinished, skip, annotate, expect, _local }) => {
|
|
97
|
+
const context = restoreContext(__effectTestContext, {
|
|
98
|
+
annotate,
|
|
99
|
+
expect,
|
|
161
100
|
onTestFailed,
|
|
162
101
|
onTestFinished,
|
|
102
|
+
signal,
|
|
163
103
|
skip,
|
|
164
|
-
|
|
165
|
-
expect,
|
|
104
|
+
task,
|
|
166
105
|
_local,
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
expect,
|
|
171
|
-
onTestFailed,
|
|
172
|
-
onTestFinished,
|
|
173
|
-
signal,
|
|
174
|
-
skip,
|
|
175
|
-
task,
|
|
176
|
-
_local,
|
|
177
|
-
});
|
|
178
|
-
return runEffectTest(run(body, context), context, clock);
|
|
179
|
-
},
|
|
180
|
-
);
|
|
106
|
+
});
|
|
107
|
+
return runEffectTest(run(body, context), context, clock);
|
|
108
|
+
});
|
|
181
109
|
};
|
|
182
110
|
|
|
183
|
-
const effectApp: EffectTester<Harness, Provided> = Object.assign(
|
|
184
|
-
register(fixtureIt),
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
_local,
|
|
218
|
-
},
|
|
219
|
-
) => {
|
|
220
|
-
const context = restoreContext(__effectTestContext, {
|
|
221
|
-
annotate,
|
|
222
|
-
expect,
|
|
223
|
-
onTestFailed,
|
|
224
|
-
onTestFinished,
|
|
225
|
-
signal,
|
|
226
|
-
skip,
|
|
227
|
-
task,
|
|
228
|
-
_local,
|
|
229
|
-
});
|
|
230
|
-
return runEffectTest(
|
|
231
|
-
run(
|
|
232
|
-
(harness, testContext) => body(item, harness, testContext),
|
|
233
|
-
context,
|
|
234
|
-
),
|
|
235
|
-
context,
|
|
236
|
-
clock,
|
|
237
|
-
);
|
|
238
|
-
},
|
|
239
|
-
);
|
|
240
|
-
},
|
|
241
|
-
fails: register(fixtureIt.fails),
|
|
242
|
-
},
|
|
243
|
-
);
|
|
111
|
+
const effectApp: EffectTester<Harness, Provided> = Object.assign(register(fixtureIt), {
|
|
112
|
+
skip: register(fixtureIt.skip),
|
|
113
|
+
skipIf: (condition: unknown) => register(fixtureIt.skipIf(condition)),
|
|
114
|
+
runIf: (condition: unknown) => register(fixtureIt.runIf(condition)),
|
|
115
|
+
only: register(fixtureIt.only),
|
|
116
|
+
each:
|
|
117
|
+
<Item>(cases: ReadonlyArray<Item>) =>
|
|
118
|
+
<A, Eff extends Effect.Effect<unknown, unknown, Provided>>(
|
|
119
|
+
name: string,
|
|
120
|
+
body: (item: Item, harness: Harness, context: TestContext) => Generator<Eff, A, never>,
|
|
121
|
+
testOptions?: number | EffectTestOptions,
|
|
122
|
+
) => {
|
|
123
|
+
const split = splitOptions(testOptions);
|
|
124
|
+
const clock = split.clock ?? defaultClock;
|
|
125
|
+
fixtureIt.for(cases)(
|
|
126
|
+
name,
|
|
127
|
+
split.vitest,
|
|
128
|
+
(item, { __effectTestContext, task, signal, onTestFailed, onTestFinished, skip, annotate, expect, _local }) => {
|
|
129
|
+
const context = restoreContext(__effectTestContext, {
|
|
130
|
+
annotate,
|
|
131
|
+
expect,
|
|
132
|
+
onTestFailed,
|
|
133
|
+
onTestFinished,
|
|
134
|
+
signal,
|
|
135
|
+
skip,
|
|
136
|
+
task,
|
|
137
|
+
_local,
|
|
138
|
+
});
|
|
139
|
+
return runEffectTest(Effect.asVoid(run((harness, testContext) => body(item, harness, testContext), context)), context, clock);
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
},
|
|
143
|
+
fails: register(fixtureIt.fails),
|
|
144
|
+
});
|
|
244
145
|
|
|
245
146
|
return { effectApp };
|
|
246
147
|
};
|