@shivaedev/effect-test 0.1.1 → 0.1.3
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 +30 -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 +8 -7
- 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 +14 -20
- package/dist/vitest.js.map +1 -1
- package/package.json +37 -31
- package/src/any-test-layer.ts +3 -0
- package/src/eventually.ts +24 -24
- package/src/index.ts +3 -3
- package/src/types.ts +15 -28
- package/src/vitest.ts +73 -172
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.1.3 - 2026-10-04
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Imports that leave their folder go through `#` aliases declared in the `imports` field of `package.json`: `#*.ts` resolves to `src` under the `source` condition and to `dist` otherwise, and `#test/*` names a test file. The published modules and declarations name the package's own files through these aliases.
|
|
8
|
+
|
|
9
|
+
- Consolidate duplicate test cases around observable package behavior.
|
|
10
|
+
|
|
11
|
+
- Validate packed consumers and executable bins through the shared workspace gate.
|
|
12
|
+
|
|
13
|
+
- Build with the `@shivaedev/quality` tsconfig presets, which target ESNext and allow only erasable TypeScript syntax.
|
|
14
|
+
|
|
15
|
+
- Type-check with TypeScript 7 only. TypeScript 6 still builds the package.
|
|
16
|
+
|
|
17
|
+
- Lint and format with the `@shivaedev/quality` Biome preset through `quality lint` at the repository root, which replaces the package's `check` script.
|
|
18
|
+
|
|
19
|
+
- Run tests through the `@shivaedev/quality` Vitest projects, which take a test's environment from its file name. Type tests are named `*.typecheck.test.ts`.
|
|
20
|
+
|
|
21
|
+
- Keep `package.json` in sort-package-json key order, checked by `quality lint`.
|
|
22
|
+
|
|
23
|
+
## 0.1.2 - 2026-09-26
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Make `eventually` retry only typed failures under TestClock, matching the live
|
|
28
|
+
clock. Defects and interruption propagate without retrying.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Document the live worker-Layer clock and the scope of per-test clock overrides.
|
|
4
33
|
|
|
5
34
|
## 0.1.1 - 2026-09-04
|
|
6
35
|
|
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,23 +1,24 @@
|
|
|
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;
|
|
6
7
|
};
|
|
7
8
|
export type EffectTest<Harness, Provided> = <A, Eff extends Effect.Effect<unknown, unknown, Provided>>(name: string, body: (harness: Harness, context: TestContext) => Generator<Eff, A, never>, options?: number | EffectTestOptions) => void;
|
|
8
9
|
export interface EffectTester<Harness, Provided> extends EffectTest<Harness, Provided> {
|
|
10
|
+
readonly each: <Item>(cases: readonly 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;
|
|
11
|
+
readonly fails: EffectTest<Harness, Provided>;
|
|
12
|
+
readonly only: EffectTest<Harness, Provided>;
|
|
13
|
+
readonly runIf: (condition: unknown) => EffectTest<Harness, Provided>;
|
|
9
14
|
readonly skip: EffectTest<Harness, Provided>;
|
|
10
15
|
readonly skipIf: (condition: unknown) => EffectTest<Harness, Provided>;
|
|
11
|
-
readonly runIf: (condition: unknown) => EffectTest<Harness, Provided>;
|
|
12
|
-
readonly only: EffectTest<Harness, Provided>;
|
|
13
|
-
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
|
-
readonly fails: EffectTest<Harness, Provided>;
|
|
15
16
|
}
|
|
16
|
-
export interface MakeEffectItOptions<Harness, TestLayer extends
|
|
17
|
+
export interface MakeEffectItOptions<Harness, TestLayer extends AnyTestLayer> {
|
|
18
|
+
readonly around?: (<A, E>(effect: Effect.Effect<A, E, Layer.Success<TestLayer>>) => Effect.Effect<A, unknown, Layer.Success<TestLayer>>) | undefined;
|
|
19
|
+
readonly clock?: EffectClock | undefined;
|
|
17
20
|
readonly layer: TestLayer;
|
|
18
|
-
readonly around?: <A, E>(effect: Effect.Effect<A, E, Layer.Success<TestLayer>>) => Effect.Effect<A, unknown, Layer.Success<TestLayer>>;
|
|
19
21
|
readonly makeHarness: (context: TestContext) => Effect.Effect<Harness, unknown, Layer.Success<TestLayer>>;
|
|
20
|
-
readonly clock?: EffectClock;
|
|
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,21 +44,19 @@ 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, {
|
|
59
|
+
_local,
|
|
65
60
|
annotate,
|
|
66
61
|
expect,
|
|
67
62
|
onTestFailed,
|
|
@@ -69,21 +64,17 @@ export const makeEffectIt = (options) => {
|
|
|
69
64
|
signal,
|
|
70
65
|
skip,
|
|
71
66
|
task,
|
|
72
|
-
_local,
|
|
73
67
|
});
|
|
74
68
|
return runEffectTest(run(body, context), context, clock);
|
|
75
69
|
});
|
|
76
70
|
};
|
|
77
71
|
const effectApp = Object.assign(register(fixtureIt), {
|
|
78
|
-
skip: register(fixtureIt.skip),
|
|
79
|
-
skipIf: (condition) => register(fixtureIt.skipIf(condition)),
|
|
80
|
-
runIf: (condition) => register(fixtureIt.runIf(condition)),
|
|
81
|
-
only: register(fixtureIt.only),
|
|
82
72
|
each: (cases) => (name, body, testOptions) => {
|
|
83
73
|
const split = splitOptions(testOptions);
|
|
84
74
|
const clock = split.clock ?? defaultClock;
|
|
85
|
-
fixtureIt.for(cases)(name, split.vitest, (item, { __effectTestContext, task, signal, onTestFailed, onTestFinished, skip, annotate, expect, _local
|
|
75
|
+
fixtureIt.for(cases)(name, split.vitest, (item, { __effectTestContext, task, signal, onTestFailed, onTestFinished, skip, annotate, expect, _local }) => {
|
|
86
76
|
const context = restoreContext(__effectTestContext, {
|
|
77
|
+
_local,
|
|
87
78
|
annotate,
|
|
88
79
|
expect,
|
|
89
80
|
onTestFailed,
|
|
@@ -91,12 +82,15 @@ export const makeEffectIt = (options) => {
|
|
|
91
82
|
signal,
|
|
92
83
|
skip,
|
|
93
84
|
task,
|
|
94
|
-
_local,
|
|
95
85
|
});
|
|
96
|
-
return runEffectTest(run((harness, testContext) => body(item, harness, testContext), context), context, clock);
|
|
86
|
+
return runEffectTest(Effect.asVoid(run((harness, testContext) => body(item, harness, testContext), context)), context, clock);
|
|
97
87
|
});
|
|
98
88
|
},
|
|
99
89
|
fails: register(fixtureIt.fails),
|
|
90
|
+
only: register(fixtureIt.only),
|
|
91
|
+
runIf: (condition) => register(fixtureIt.runIf(condition)),
|
|
92
|
+
skip: register(fixtureIt.skip),
|
|
93
|
+
skipIf: (condition) => register(fixtureIt.skipIf(condition)),
|
|
100
94
|
});
|
|
101
95
|
return { effectApp };
|
|
102
96
|
};
|
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":"AAAA,OAAO,EAAE,EAAE,IAAI,QAAQ,EAAsC,MAAM,gBAAgB,CAAC;AACpF,OAAO,EAAE,KAAK,EAAgB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,KAAK,SAAS,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,WAAW,MAAM,4BAA4B,CAAC;AAI1D,MAAM,WAAW,GAAG,qBAAqB,CAAC;AAQ1C,MAAM,oBAAoB,GAAG,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;AAClF,MAAM,oBAAoB,GAAG,WAAW,CAAC,KAAK,CAAC;AAE/C,MAAM,cAAc,GAAG,CAAC,KAAkB,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC;AAEhH,MAAM,cAAc,GAAG,CACtB,OAAkC,EAClC,OAA0D,EAChC,EAAE,CAAC,CAAC;IAC9B,GAAG,OAAO;IACV,CAAC,WAAW,CAAC,EAAE,OAAO;CACtB,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,CAAO,MAAwC,EAAE,OAAoB,EAAE,KAAkB,EAAc,EAAE,CAC9H,MAAM,CAAC,UAAU,CAChB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACpD,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,IAAI,CAAC;AACpB,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,EAC7D,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAC1B,CAAC;AAEH,MAAM,YAAY,GAAG,CACpB,OAA+C,EAI9C,EAAE;IACH,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC;IAC3D,CAAC;IACD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACzC,CAAC;IACD,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;IACrC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAuB,KAAwC,EAAE,EAAE,CACxF,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;IAC7E,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3C,SAAS,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAElE,IAAI,CAAC;QACJ,OAAO,MAAM,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD,MAAM,KAAK,CAAC;IACb,CAAC;AACF,CAAC,CAAC,CAAC;AAEJ,MAAM,CAAC,MAAM,YAAY,GAAG,CAC3B,OAAgD,EACQ,EAAE;IAE1D,MAAM,SAAS,GAAG,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/C,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAAC;IAE7C,MAAM,GAAG,GAAG,CACX,IAAgH,EAChH,OAAgC,EACS,EAAE;QAC3C,MAAM,KAAK,GAAwC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YACtE,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YACpD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC;QACjD,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAC3D,CAAC,CAAC;IAEF,MAAM,QAAQ,GACb,CAAC,OAA8B,EAAiC,EAAE,CAClE,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE;QAC3B,MAAM,KAAK,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;QACxC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,YAAY,CAAC;QAC1C,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE;YACnI,MAAM,OAAO,GAAG,cAAc,CAAC,mBAAmB,EAAE;gBACnD,MAAM;gBACN,QAAQ;gBACR,MAAM;gBACN,YAAY;gBACZ,cAAc;gBACd,MAAM;gBACN,IAAI;gBACJ,IAAI;aACJ,CAAC,CAAC;YACH,OAAO,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC;IAEH,MAAM,SAAS,GAAoC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;QACrF,IAAI,EACH,CAAO,KAAsB,EAAE,EAAE,CACjC,CACC,IAAY,EACZ,IAAsF,EACtF,WAAwC,EACvC,EAAE;YACH,MAAM,KAAK,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;YACxC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,YAAY,CAAC;YAC1C,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CACnB,IAAI,EACJ,KAAK,CAAC,MAAM,EACZ,CAAC,IAAI,EAAE,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE;gBAC7G,MAAM,OAAO,GAAG,cAAc,CAAC,mBAAmB,EAAE;oBACnD,MAAM;oBACN,QAAQ;oBACR,MAAM;oBACN,YAAY;oBACZ,cAAc;oBACd,MAAM;oBACN,IAAI;oBACJ,IAAI;iBACJ,CAAC,CAAC;gBACH,OAAO,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;YAC/H,CAAC,CACD,CAAC;QACH,CAAC;QACF,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC;QAChC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC;QAC9B,KAAK,EAAE,CAAC,SAAkB,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACnE,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC;QAC9B,MAAM,EAAE,CAAC,SAAkB,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;KACrE,CAAC,CAAC;IAEH,OAAO,EAAE,SAAS,EAAE,CAAC;AACtB,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/effect-test",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Generic Effect Vitest runner with worker-scoped Layers and TestClock",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/ShivaeDev/platform.git",
|
|
10
|
-
"directory": "packages/effect-test"
|
|
11
|
-
},
|
|
12
5
|
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/effect-test#readme",
|
|
13
6
|
"bugs": {
|
|
14
7
|
"url": "https://github.com/ShivaeDev/platform/issues"
|
|
15
8
|
},
|
|
16
|
-
"
|
|
17
|
-
"
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/ShivaeDev/platform.git",
|
|
12
|
+
"directory": "packages/effect-test"
|
|
18
13
|
},
|
|
14
|
+
"license": "MIT",
|
|
19
15
|
"sideEffects": false,
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
"type": "module",
|
|
17
|
+
"imports": {
|
|
18
|
+
"#test/*": "./test/*",
|
|
19
|
+
"#*.ts": {
|
|
20
|
+
"source": "./src/*.ts",
|
|
21
|
+
"types": "./dist/*.d.ts",
|
|
22
|
+
"default": "./dist/*.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
26
25
|
"exports": {
|
|
27
26
|
".": {
|
|
28
27
|
"types": "./dist/index.d.ts",
|
|
@@ -32,9 +31,20 @@
|
|
|
32
31
|
},
|
|
33
32
|
"./package.json": "./package.json"
|
|
34
33
|
},
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"src",
|
|
37
|
+
"CHANGELOG.md",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@effect/vitest": "4.0.0-rc.112",
|
|
42
|
+
"@shivaedev/quality": "0.7.3",
|
|
43
|
+
"@types/node": "24.10.1",
|
|
44
|
+
"@typescript/native": "npm:typescript@7.0.2",
|
|
45
|
+
"effect": "4.0.0-rc.112",
|
|
46
|
+
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
47
|
+
"vitest": "4.1.9"
|
|
38
48
|
},
|
|
39
49
|
"peerDependencies": {
|
|
40
50
|
"@effect/vitest": "4.0.0-rc.112",
|
|
@@ -45,21 +55,17 @@
|
|
|
45
55
|
"optional": true
|
|
46
56
|
}
|
|
47
57
|
},
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"vitest": "4.1.9"
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=24"
|
|
60
|
+
},
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"access": "public",
|
|
63
|
+
"provenance": true
|
|
55
64
|
},
|
|
56
65
|
"scripts": {
|
|
57
66
|
"build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc6 --project tsconfig.build.json",
|
|
58
|
-
"
|
|
67
|
+
"ready": "pnpm typecheck && pnpm test && pnpm build",
|
|
59
68
|
"test": "vitest run",
|
|
60
|
-
"
|
|
61
|
-
"typecheck": "tsc --noEmit",
|
|
62
|
-
"typecheck:compat": "tsc6 --noEmit",
|
|
63
|
-
"ready": "pnpm check && pnpm typecheck && pnpm typecheck:compat && pnpm test && pnpm build && pnpm test:package"
|
|
69
|
+
"typecheck": "tsc --noEmit"
|
|
64
70
|
}
|
|
65
71
|
}
|
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,48 +8,34 @@ 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> {
|
|
21
|
-
readonly skip: EffectTest<Harness, Provided>;
|
|
22
|
-
readonly skipIf: (condition: unknown) => EffectTest<Harness, Provided>;
|
|
23
|
-
readonly runIf: (condition: unknown) => EffectTest<Harness, Provided>;
|
|
24
|
-
readonly only: EffectTest<Harness, Provided>;
|
|
17
|
+
export interface EffectTester<Harness, Provided> extends EffectTest<Harness, Provided> {
|
|
25
18
|
readonly each: <Item>(
|
|
26
|
-
cases:
|
|
19
|
+
cases: readonly Item[],
|
|
27
20
|
) => <A, Eff extends Effect.Effect<unknown, unknown, Provided>>(
|
|
28
21
|
name: string,
|
|
29
|
-
body: (
|
|
30
|
-
item: Item,
|
|
31
|
-
harness: Harness,
|
|
32
|
-
context: TestContext,
|
|
33
|
-
) => Generator<Eff, A, never>,
|
|
22
|
+
body: (item: Item, harness: Harness, context: TestContext) => Generator<Eff, A, never>,
|
|
34
23
|
options?: number | EffectTestOptions,
|
|
35
24
|
) => void;
|
|
36
25
|
readonly fails: EffectTest<Harness, Provided>;
|
|
26
|
+
readonly only: EffectTest<Harness, Provided>;
|
|
27
|
+
readonly runIf: (condition: unknown) => EffectTest<Harness, Provided>;
|
|
28
|
+
readonly skip: EffectTest<Harness, Provided>;
|
|
29
|
+
readonly skipIf: (condition: unknown) => EffectTest<Harness, Provided>;
|
|
37
30
|
}
|
|
38
31
|
|
|
39
|
-
export interface MakeEffectItOptions<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
export interface MakeEffectItOptions<Harness, TestLayer extends AnyTestLayer> {
|
|
33
|
+
readonly around?:
|
|
34
|
+
| (<A, E>(effect: Effect.Effect<A, E, Layer.Success<TestLayer>>) => Effect.Effect<A, unknown, Layer.Success<TestLayer>>)
|
|
35
|
+
| undefined;
|
|
36
|
+
readonly clock?: EffectClock | undefined;
|
|
44
37
|
readonly layer: TestLayer;
|
|
45
|
-
readonly
|
|
46
|
-
effect: Effect.Effect<A, E, Layer.Success<TestLayer>>,
|
|
47
|
-
) => Effect.Effect<A, unknown, Layer.Success<TestLayer>>;
|
|
48
|
-
readonly makeHarness: (
|
|
49
|
-
context: TestContext,
|
|
50
|
-
) => Effect.Effect<Harness, unknown, Layer.Success<TestLayer>>;
|
|
51
|
-
readonly clock?: EffectClock;
|
|
38
|
+
readonly makeHarness: (context: TestContext) => Effect.Effect<Harness, unknown, Layer.Success<TestLayer>>;
|
|
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
|
-
|
|
159
|
-
task,
|
|
160
|
-
signal,
|
|
96
|
+
current(name, split.vitest, ({ __effectTestContext, task, signal, onTestFailed, onTestFinished, skip, annotate, expect, _local }) => {
|
|
97
|
+
const context = restoreContext(__effectTestContext, {
|
|
98
|
+
_local,
|
|
99
|
+
annotate,
|
|
100
|
+
expect,
|
|
161
101
|
onTestFailed,
|
|
162
102
|
onTestFinished,
|
|
103
|
+
signal,
|
|
163
104
|
skip,
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const context = restoreContext(__effectTestContext, {
|
|
169
|
-
annotate,
|
|
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
|
-
);
|
|
105
|
+
task,
|
|
106
|
+
});
|
|
107
|
+
return runEffectTest(run(body, context), context, clock);
|
|
108
|
+
});
|
|
181
109
|
};
|
|
182
110
|
|
|
183
|
-
const effectApp: EffectTester<Harness, Provided> = Object.assign(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
context
|
|
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
|
+
each:
|
|
113
|
+
<Item>(cases: readonly Item[]) =>
|
|
114
|
+
<A, Eff extends Effect.Effect<unknown, unknown, Provided>>(
|
|
115
|
+
name: string,
|
|
116
|
+
body: (item: Item, harness: Harness, context: TestContext) => Generator<Eff, A, never>,
|
|
117
|
+
testOptions?: number | EffectTestOptions,
|
|
118
|
+
) => {
|
|
119
|
+
const split = splitOptions(testOptions);
|
|
120
|
+
const clock = split.clock ?? defaultClock;
|
|
121
|
+
fixtureIt.for(cases)(
|
|
122
|
+
name,
|
|
123
|
+
split.vitest,
|
|
124
|
+
(item, { __effectTestContext, task, signal, onTestFailed, onTestFinished, skip, annotate, expect, _local }) => {
|
|
125
|
+
const context = restoreContext(__effectTestContext, {
|
|
126
|
+
_local,
|
|
127
|
+
annotate,
|
|
128
|
+
expect,
|
|
129
|
+
onTestFailed,
|
|
130
|
+
onTestFinished,
|
|
131
|
+
signal,
|
|
132
|
+
skip,
|
|
133
|
+
task,
|
|
134
|
+
});
|
|
135
|
+
return runEffectTest(Effect.asVoid(run((harness, testContext) => body(item, harness, testContext), context)), context, clock);
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
},
|
|
139
|
+
fails: register(fixtureIt.fails),
|
|
140
|
+
only: register(fixtureIt.only),
|
|
141
|
+
runIf: (condition: unknown) => register(fixtureIt.runIf(condition)),
|
|
142
|
+
skip: register(fixtureIt.skip),
|
|
143
|
+
skipIf: (condition: unknown) => register(fixtureIt.skipIf(condition)),
|
|
144
|
+
});
|
|
244
145
|
|
|
245
146
|
return { effectApp };
|
|
246
147
|
};
|