@shivaedev/effect-test 0.3.1 → 0.3.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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2 - 2026-10-12
4
+
5
+ ### Added
6
+
7
+ - `makeEffectIt` fixture testers accept Effect-returning bodies as well as generators, including table cases and tester variants. Both forms retain harness, service, clock and per-test Scope behavior.
8
+ - Export `EffectTestBody<THarness, TProvided, A>` from `types.ts` to describe both fixture body forms.
9
+ - `makeEffectIt` harnesses, wrappers, generator bodies and table bodies can use the per-test `Scope` already supplied by the runner. The worker Context and inferred Layer services stay unchanged; unrelated services still require a matching Layer.
10
+
11
+ ### Changed
12
+
13
+ - Name descriptive generic type parameters with a `T` prefix, preserving test runner types and behavior.
14
+ - Verify conditional skipping in an isolated Vitest fixture, with passing suite assertions for registration and body non-execution instead of a skipped suite test.
15
+ - Explain the runner's purpose, service and test lifetimes, clock choices and typed readiness polling in the README, agent north star and package docs; publish `docs/` with the README.
16
+ - Build package JavaScript, declarations and source maps with TypeScript 7.
17
+
3
18
  ## 0.3.1 - 2026-10-06
4
19
 
5
20
  ### Fixed
package/README.md CHANGED
@@ -1,109 +1,233 @@
1
- # `@shivaedev/effect-test`
1
+ # @shivaedev/effect-test
2
2
 
3
- A Vitest runner for Effect programs. The test Layer is built once per worker.
4
- Each test gets a fresh Scope, a generator body, and TestClock unless you ask
5
- for the live clock.
3
+ Effect tests should spend their lines on the behavior they check. This package gives ordinary tests and reusable fixtures one Vitest runner, so a generator can use the application's services and a controlled clock without repeating the runner around every test.
6
4
 
7
- Prisma and tRPC testing helpers in this repository are thin wrappers around
8
- `makeEffectIt`. Use this package directly when those wrappers are the wrong
9
- shape.
5
+ ## Why you want this
10
6
 
11
- This package currently targets exact early-access versions of Effect v4. It is
12
- built for ShivaeDev applications and may change without a deprecation period.
7
+ Wrapping every body in `Effect.gen` hides the test's work, and copying a runner into each fixture makes service and clock setup drift. Pass the generator itself and read the test from top to bottom:
13
8
 
14
- ## Install
15
-
16
- ```sh
17
- pnpm add --save-dev @shivaedev/effect-test @effect/vitest effect vitest
9
+ ```ts
10
+ import { expect } from "@effect/vitest";
11
+ import { Clock } from "effect";
12
+ import * as TestClock from "effect/testing/TestClock";
13
+ import { it } from "@shivaedev/effect-test/it.ts";
14
+
15
+ it.effect("advances the test's time", function* () {
16
+ expect(yield* Clock.currentTimeMillis).toBe(0);
17
+ yield* TestClock.adjust("1 second");
18
+ expect(yield* Clock.currentTimeMillis).toBe(1000);
19
+ });
18
20
  ```
19
21
 
20
- ## `it`
22
+ That is a whole test. The body starts at time zero and advances its clock by one second. For a suite that needs services, the same package makes a tester from a Layer and a harness, so the suite defines its plumbing once and each spec keeps its own work.
23
+
24
+ ## Using it
25
+
26
+ ### How to think about it
27
+
28
+ There are two ways to enter the runner. `it.effect` and `it.live` are the ordinary testers: their body receives Vitest's test context and uses services it provides inside its own Effect. `makeEffectIt` defines a tester for a reusable fixture: its body receives a harness first, then the test context, and can yield the services its Layer provides.
21
29
 
22
- `@shivaedev/effect-test/it.ts` exports the `it` of `@effect/vitest`, whose `effect` and `live`
23
- testers also take the generator itself and run it with `Effect.gen`:
30
+ A **Layer** describes how to acquire the fixture's services. `makeEffectIt` builds it in a Vitest worker fixture and retains the resulting Effect **Context**, the map from service keys to service values. A **harness** is the value made for one test using those services. It can hold a service, a fixture identifier or the domain entry points the test needs. The **test context** is Vitest's context, with the test's name, assertion helpers and hooks; it is a different thing from Effect's Context.
31
+
32
+ Keep the two lifetimes separate. The worker owns the acquired Layer and its scope. Each test makes its harness and runs its body in a separate scope. Harnesses, wrappers, generator bodies and `each` bodies may acquire resources through that Scope; their finalizers run on success, failure and interruption. Scope is available per test and is not added to the worker Context. A shared service can retain mutable state across tests; a fresh harness alone does not reset it. Put application-state isolation in the fixture that owns that state, such as a database transaction wrapper.
33
+
34
+ The work usually splits into two parts:
35
+
36
+ 1. Once per fixture, define its Layer, `makeHarness` and, when needed, an `around` wrapper in test support.
37
+ 2. In every test, write the generator and the assertions. For a test with no reusable fixture, use `it.effect` directly.
38
+
39
+ ### 1. Once per fixture: services and the harness
24
40
 
25
41
  ```ts
26
- import { it } from "@shivaedev/effect-test/it.ts"
42
+ import { expect } from "@effect/vitest";
43
+ import { Clock, Context, Effect, Layer } from "effect";
44
+ import { makeEffectIt } from "@shivaedev/effect-test/vitest.ts";
27
45
 
28
- it.effect("starts at time zero", function* ({ task }) {
29
- expect(yield* Clock.currentTimeMillis).toBe(0)
30
- })
46
+ class Token extends Context.Service<Token, string>()("test/Token") {}
31
47
 
32
- it.live("waits on the real clock", function* () {
33
- yield* Effect.sleep("10 millis")
34
- })
48
+ const { effectApp } = makeEffectIt({
49
+ layer: Layer.succeed(Token, "from-layer"),
50
+ makeHarness: (context) => Effect.gen(function* () {
51
+ return {
52
+ name: context.task.name,
53
+ token: yield* Token,
54
+ };
55
+ }),
56
+ });
57
+
58
+ effectApp("reads the fixture's services", function* (harness, context) {
59
+ expect(harness.token).toBe("from-layer");
60
+ expect(harness.name).toBe(context.task.name);
61
+ expect(yield* Token).toBe("from-layer");
62
+ });
35
63
  ```
36
64
 
37
- The body receives the Vitest test context; destructure what it reads. `effect`
38
- installs TestClock and the other test services, `live` keeps the live ones, and
39
- each runs the body in a Scope. A function that returns an Effect works as well,
40
- as with `@effect/vitest`. Each tester supports `each`, `fails`, `only`, `runIf`,
41
- `skip` and `skipIf`; an `each` body receives the table case.
65
+ The Layer supplies `Token`. `makeHarness` runs for the test and reads that same service; the generator receives its result and can also yield `Token` directly. Harness fields and provided services keep their types. A body that requires a service the Layer does not provide does not type-check.
42
66
 
43
- ## `makeEffectIt`
67
+ A fixture that needs no services uses `Layer.empty`. This separate module
68
+ replaces the token fixture above:
44
69
 
45
70
  ```ts
46
- import { Layer } from "effect"
47
- import { makeEffectIt } from "@shivaedev/effect-test/vitest.ts"
71
+ import { expect } from "@effect/vitest";
72
+ import { Effect, Layer } from "effect";
73
+ import { makeEffectIt } from "@shivaedev/effect-test/vitest.ts";
48
74
 
49
75
  const { effectApp } = makeEffectIt({
50
- layer: TestLive,
51
- makeHarness: () => Effect.succeed({}),
52
- })
53
-
54
- effectApp("reads a service from the test Layer", function* (_harness, _context) {
55
- const movies = yield* Movies
56
- expect(yield* movies.list()).toEqual([])
57
- })
76
+ layer: Layer.empty,
77
+ makeHarness: () => Effect.succeed({ shelf: 0 }),
78
+ });
79
+
80
+ effectApp("uses a harness without services", function* (harness) {
81
+ expect(harness.shelf).toBe(0);
82
+ });
58
83
  ```
59
84
 
60
- `makeHarness` runs inside the worker Layer and any `around` wrapper, then the
61
- generator receives that value as its first argument.
85
+ There is no placeholder service. The harness is still inferred, and a body that tries to yield `Token` from this fixture does not compile. The Layer itself must have no unmet service requirements: compose its dependencies before passing it to the factory.
86
+
87
+ #### Wrapping the harness and body
88
+
89
+ This alternative token fixture adds a tracing wrapper:
62
90
 
63
91
  ```ts
92
+ import { expect } from "@effect/vitest";
93
+ import { Context, Effect, Layer } from "effect";
94
+ import { makeEffectIt } from "@shivaedev/effect-test/vitest.ts";
95
+
96
+ class Token extends Context.Service<Token, string>()("test/Token") {}
97
+
64
98
  const { effectApp } = makeEffectIt({
65
- layer: DatabaseLive,
66
- around: (effect) => withTestTransaction(Database, effect),
67
- makeHarness: () => Database,
68
- })
99
+ around: (effect) => Effect.withSpan(effect, "test.token"),
100
+ layer: Layer.succeed(Token, "from-layer"),
101
+ makeHarness: () => Effect.service(Token),
102
+ });
103
+
104
+ effectApp("reads the harness", function* (token) {
105
+ expect(token).toBe("from-layer");
106
+ });
107
+ ```
108
+
109
+ `around` receives the Effect that makes the harness and then runs the body. The wrapper also has the Layer's services. This is where a fixture composes its tracing or transaction boundary. The database package owns whether that transaction rolls back; this runner does not add a database policy. The later examples use the first token fixture, whose harness contains `token` and `name`.
110
+
111
+ `@shivaedev/effect-prisma` and `@shivaedev/effect-trpc` build their testing helpers on this factory. `@shivaedev/test-story` builds domain stories above it and owns traits, verbs, engine stepping and failure narration. Use those helpers when the test needs their vocabulary; use this runner when defining the fixture underneath. An application-owned story kit can lease PostgreSQL through its worker Layer, use `around` for real-commit resets or local rollback, and acquire servers, native RPC clients and React roots through per-test Scope. The [order feature kit](https://github.com/ShivaeDev/platform/tree/main/packages/effect-react/src/test-support/order-example) demonstrates that composition; [test-story’s kit guide](https://github.com/ShivaeDev/platform/tree/main/packages/test-story#whole-stack-application-kits) explains the application’s isolation, seed and screen responsibilities.
112
+
113
+ ### 2. In every test: the generator
114
+
115
+ ```ts
116
+ import { expect } from "@effect/vitest";
117
+ import { Effect } from "effect";
118
+ import { it } from "@shivaedev/effect-test/it.ts";
119
+
120
+ it.effect("reads an Effect result", function* ({ task }) {
121
+ expect(task.name).toBe("reads an Effect result");
122
+ expect(yield* Effect.succeed(3)).toBe(3);
123
+ });
124
+
125
+ it.effect("accepts an Effect-returning body", () =>
126
+ Effect.sync(() => expect(3).toBe(3)),
127
+ );
128
+
129
+ it.effect.each([1, 2])("passes the case %s", function* (count) {
130
+ expect(yield* Effect.succeed(count)).toBe(count);
131
+ });
132
+ ```
133
+
134
+ The ordinary body receives Vitest's context. An `each` body receives the table case instead. Both generator and Effect-returning bodies are accepted. `it` itself remains the plain Vitest tester.
135
+
136
+ For a fixture's `effectApp.each`, the arguments are the case, harness and context:
137
+
138
+ ```ts
139
+ effectApp.each(["alpha", "beta"])("checks %s", function* (item, harness, context) {
140
+ expect(context.task.name).toContain(item);
141
+ expect(harness.token).toBe("from-layer");
142
+ });
69
143
  ```
70
144
 
71
- `effectApp` supports `skip`, `skipIf`, `runIf`, `only`, `each`, and `fails`.
145
+ This uses the token fixture from the first example. Case and harness types are inferred. The fixture tester, `it.effect` and `it.live` accept generator bodies and Effect-returning functions. A synchronous DOM fixture test can return `Effect.sync(() => expect(harness.token).toBe("from-layer"))`; the harness, services and per-test Scope are the same for both body forms.
72
146
 
73
- ## Clock
147
+ ### Choosing which clock runs the test
74
148
 
75
- Tests install TestClock and TestConsole. Pass `clock: "live"` on the factory or
76
- on a single test when the program must use wall-clock time:
149
+ `it.effect` uses TestClock; `it.live` uses live time. A fixture defaults to `clock: "test"`. Its factory may choose `clock: "live"`, and a single test may override the factory in either direction:
77
150
 
78
151
  ```ts
79
- effectApp("talks to a real timer", function* () {
80
- yield* Effect.sleep("10 millis")
81
- }, { clock: "live" })
152
+ effectApp("reads live time", function* () {
153
+ expect(yield* Clock.currentTimeMillis).toBeGreaterThan(1_000_000);
154
+ }, { clock: "live" });
82
155
  ```
83
156
 
84
- The worker Layer is acquired and released outside the per-test environment,
85
- using the live clock unless the Layer supplies its own. Fibers started during
86
- Layer acquisition keep that environment; advancing a test's TestClock does not
87
- advance their timers. The selected per-test clock applies to `around`,
88
- `makeHarness`, and the test body.
157
+ The override changes the per-test environment used by the wrapper, harness and body. TestConsole is installed for both clock choices.
158
+
159
+ Worker acquisition is separate: a Layer that reads the default Clock while it is being built sees live time, even when its tests use TestClock. A fiber started during that acquisition retains its worker environment, so advancing a test's clock does not advance that fiber's timers. A Layer that supplies its own Clock also supplies that service through its Context; the test's clock option chooses the outer environment and does not replace services explicitly supplied by the fixture.
160
+
161
+ ### Waiting for a typed readiness condition
89
162
 
90
- ## `eventually`
163
+ `eventually(effect, options)` retries the Effect's typed failures until it succeeds or uses its retry budget. Under TestClock it advances time with `TestClock.adjust`; under live time it retries with `Schedule.spaced`.
91
164
 
92
- Retries typed failures until the effect succeeds. Under TestClock it advances
93
- time with `TestClock.adjust`. Under `clock: "live"` it uses `Schedule.spaced`.
94
- `times` counts retries after the initial attempt; omitting it retries without a
95
- limit. Defects (including thrown assertions) and interruption propagate without
96
- retrying under either clock. Use `Effect.try(() => expect(...))` when an
97
- assertion failure is intentionally a retryable condition.
165
+ ```ts
166
+ import { eventually } from "@shivaedev/effect-test/eventually.ts";
167
+
168
+ effectApp("reaches the selected time", function* () {
169
+ const readyAt = yield* eventually(
170
+ Effect.gen(function* () {
171
+ const now = yield* Clock.currentTimeMillis;
172
+ return now >= 50 ? now : yield* Effect.fail("too-early");
173
+ }),
174
+ { interval: "7 millis", times: 8 },
175
+ );
176
+
177
+ expect(readyAt).toBe(56);
178
+ });
179
+ ```
180
+
181
+ The attempts see 0, 7, 14 and subsequent multiples of 7 through 56 milliseconds. `times` counts retries after the initial attempt, so `times: 0` tries once. When the budget runs out, the final typed failure remains the failure.
182
+
183
+ A thrown assertion is a defect, so it escapes on the first attempt. Interruption also escapes immediately. Capture an assertion with `Effect.try` only when its failure means the state is not ready yet:
98
184
 
99
185
  ```ts
100
- import { eventually } from "@shivaedev/effect-test/eventually.ts"
101
-
102
- effectApp("sees a delayed write", function* ({ db }) {
103
- const row = yield* eventually(db.Job.where({ id }).first(), {
104
- interval: "10 millis",
105
- times: 50,
106
- })
107
- expect(row.status).toBe("ready")
108
- })
186
+ effectApp("retries a readiness assertion", function* () {
187
+ let attempts = 0;
188
+
189
+ yield* eventually(
190
+ Effect.try(() => {
191
+ attempts += 1;
192
+ expect(attempts).toBe(3);
193
+ }),
194
+ { interval: "1 millis", times: 3 },
195
+ );
196
+
197
+ expect(attempts).toBe(3);
198
+ });
109
199
  ```
200
+
201
+ This distinction applies under both clocks. The helper preserves the Effect's success, error and service types.
202
+
203
+ ### API
204
+
205
+ Import the module that defines the name; the package has no root entry.
206
+
207
+ | Module | Exports |
208
+ | --- | --- |
209
+ | `@shivaedev/effect-test/it.ts` | `it`, with generator-aware `effect` and `live` testers. |
210
+ | `@shivaedev/effect-test/vitest.ts` | `makeEffectIt({ layer, makeHarness, around?, clock? })`, returning `{ effectApp }`. |
211
+ | `@shivaedev/effect-test/eventually.ts` | `eventually(effect, options?)` and `EventuallyOptions`. |
212
+ | `@shivaedev/effect-test/any-test-layer.ts` | `AnyTestLayer` and `buildTestLayer(layer, scope)`, an Effect that builds the Layer's service Context in the supplied scope. |
213
+ | `@shivaedev/effect-test/types.ts` | `EffectClock`, `EffectTestOptions`, `EffectTestBody`, `EffectTest`, `EffectTester`, `MakeEffectItOptions`, `MakeEffectItResult`, `EffectItTest`, `EffectItTester` and `EffectIt`. |
214
+
215
+ `effectApp(name, body, options?)` takes a generator or Effect-returning body with `(harness, context)`. Its `each(cases)` takes `(item, harness, context)`. Options are a timeout in milliseconds or Vitest test options with an optional `clock`.
216
+
217
+ `it.effect` and `it.live` take `(name, body, options?)`; options are a timeout or Vitest test options. A regular body takes the context; an `each` body takes the case. These testers and `effectApp` expose `each`, `fails`, `only`, `runIf`, `skip` and `skipIf`.
218
+
219
+ `EventuallyOptions` has `interval?: Duration.Input` and `times?: number`. The default interval is `"10 millis"`; omitting `times` leaves retries unbounded.
220
+
221
+ ### Install, setup and limits
222
+
223
+ ```sh
224
+ pnpm add --save-dev @shivaedev/effect-test @effect/vitest@4.0.0-rc.112 effect@4.0.0-rc.112 vitest@4.1.11
225
+ ```
226
+
227
+ The package declares `effect` as a peer and `@effect/vitest` as an optional peer. The Vitest runner modules require `@effect/vitest`; install it when using them. The package requires Node.js 24 or later. Use peer versions compatible with the package's `package.json`.
228
+
229
+ - Define a fixture tester in test support and import it from Vitest tests. Calling `makeEffectIt` declares a Vitest fixture; it is not an application runtime factory.
230
+ - The Layer is shared within its worker fixture. Reset application state in the harness or compose the fixture's own isolation wrapper when tests need it.
231
+ - TestClock controls its Effect environment. It does not establish how external timers, real HTTP or a database behave.
232
+ - An unbounded readiness poll can run until Vitest ends the test. Set a retry budget when the condition may never become true.
233
+ - Compiler tests prove service requirements and inferred types; runtime tests prove the exercised clock and failure paths. Neither is evidence that a consumer's composed application has been validated.
@@ -1 +1 @@
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"}
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;AAoCD,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,YAAY,iBAAiB,KAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAMpH,CAAC"}
@@ -1,29 +1,35 @@
1
1
  import { Clock, Effect, Result, Schedule } from "effect";
2
2
  import * as TestClock from "effect/testing/TestClock";
3
3
  const defaultInterval = "10 millis";
4
- const isTestClock = (clock) => "adjust" in clock;
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;
13
- for (;;) {
14
- const result = yield* Effect.result(effect);
15
- if (Result.isSuccess(result)) {
16
- return result.success;
17
- }
18
- if (retriesLeft === 0) {
19
- return yield* Effect.fail(result.failure);
20
- }
21
- if (retriesLeft !== undefined) {
22
- retriesLeft -= 1;
4
+ function isTestClock(clock) {
5
+ return "adjust" in clock;
6
+ }
7
+ function retryOnLiveClock(effect, interval, times) {
8
+ return times === undefined
9
+ ? Effect.retry(effect, Schedule.spaced(interval))
10
+ : Effect.retry(effect, {
11
+ schedule: Schedule.spaced(interval),
12
+ times,
13
+ });
14
+ }
15
+ function retryOnTestClock(effect, interval, times) {
16
+ return Effect.gen(function* () {
17
+ let retriesLeft = times;
18
+ for (;;) {
19
+ const result = yield* Effect.result(effect);
20
+ if (Result.isSuccess(result)) {
21
+ return result.success;
22
+ }
23
+ if (retriesLeft === 0) {
24
+ return yield* Effect.fail(result.failure);
25
+ }
26
+ if (retriesLeft !== undefined) {
27
+ retriesLeft -= 1;
28
+ }
29
+ yield* TestClock.adjust(interval);
23
30
  }
24
- yield* TestClock.adjust(interval);
25
- }
26
- });
31
+ });
32
+ }
27
33
  export const eventually = (effect, options) => Effect.gen(function* () {
28
34
  const interval = options?.interval ?? defaultInterval;
29
35
  const clock = yield* Clock.Clock;
@@ -1 +1 @@
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"}
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,SAAS,WAAW,CAAC,KAAkB;IACtC,OAAO,QAAQ,IAAI,KAAK,CAAC;AAC1B,CAAC;AAED,SAAS,gBAAgB,CAAU,MAA8B,EAAE,QAAwB,EAAE,KAAyB;IACrH,OAAO,KAAK,KAAK,SAAS;QACzB,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACjD,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE;YACrB,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC;YACnC,KAAK;SACL,CAAC,CAAC;AACN,CAAC;AAED,SAAS,gBAAgB,CAAU,MAA8B,EAAE,QAAwB,EAAE,KAAyB;IACrH,OAAO,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC1B,IAAI,WAAW,GAAG,KAAK,CAAC;QACxB,SAAS,CAAC;YACT,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC5C,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC9B,OAAO,MAAM,CAAC,OAAO,CAAC;YACvB,CAAC;YACD,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;gBACvB,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3C,CAAC;YACD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;gBAC/B,WAAW,IAAI,CAAC,CAAC;YAClB,CAAC;YACD,KAAK,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACnC,CAAC;IACF,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,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/types.d.ts CHANGED
@@ -6,23 +6,24 @@ export type EffectClock = "test" | "live";
6
6
  export type EffectTestOptions = TestOptions & {
7
7
  readonly clock?: EffectClock;
8
8
  };
9
- 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;
10
- export interface EffectTester<Harness, Provided> extends EffectTest<Harness, Provided> {
11
- 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;
12
- readonly fails: EffectTest<Harness, Provided>;
13
- readonly only: EffectTest<Harness, Provided>;
14
- readonly runIf: (condition: unknown) => EffectTest<Harness, Provided>;
15
- readonly skip: EffectTest<Harness, Provided>;
16
- readonly skipIf: (condition: unknown) => EffectTest<Harness, Provided>;
9
+ export type EffectTestBody<THarness, TProvided, A = unknown> = (harness: THarness, context: TestContext) => Generator<Effect.Effect<unknown, unknown, TProvided | Scope.Scope>, A, never> | Effect.Effect<A, unknown, TProvided | Scope.Scope>;
10
+ export type EffectTest<THarness, TProvided> = <A, TEff extends Effect.Effect<unknown, unknown, TProvided | Scope.Scope>>(name: string, body: (harness: THarness, context: TestContext) => Generator<TEff, A, never> | Effect.Effect<A, unknown, TProvided | Scope.Scope>, options?: number | EffectTestOptions) => void;
11
+ export interface EffectTester<THarness, TProvided> extends EffectTest<THarness, TProvided> {
12
+ readonly each: <TItem>(cases: readonly TItem[]) => <A, TEff extends Effect.Effect<unknown, unknown, TProvided | Scope.Scope>>(name: string, body: (item: TItem, harness: THarness, context: TestContext) => Generator<TEff, A, never> | Effect.Effect<A, unknown, TProvided | Scope.Scope>, options?: number | EffectTestOptions) => void;
13
+ readonly fails: EffectTest<THarness, TProvided>;
14
+ readonly only: EffectTest<THarness, TProvided>;
15
+ readonly runIf: (condition: unknown) => EffectTest<THarness, TProvided>;
16
+ readonly skip: EffectTest<THarness, TProvided>;
17
+ readonly skipIf: (condition: unknown) => EffectTest<THarness, TProvided>;
17
18
  }
18
- export interface MakeEffectItOptions<Harness, TestLayer extends AnyTestLayer> {
19
- readonly around?: (<A, E>(effect: Effect.Effect<A, E, Layer.Success<TestLayer>>) => Effect.Effect<A, unknown, Layer.Success<TestLayer>>) | undefined;
19
+ export interface MakeEffectItOptions<THarness, TTestLayer extends AnyTestLayer> {
20
+ readonly around?: (<A, E>(effect: Effect.Effect<A, E, Layer.Success<TTestLayer> | Scope.Scope>) => Effect.Effect<A, unknown, Layer.Success<TTestLayer> | Scope.Scope>) | undefined;
20
21
  readonly clock?: EffectClock | undefined;
21
- readonly layer: TestLayer;
22
- readonly makeHarness: (context: TestContext) => Effect.Effect<Harness, unknown, Layer.Success<TestLayer>>;
22
+ readonly layer: TTestLayer;
23
+ readonly makeHarness: (context: TestContext) => Effect.Effect<THarness, unknown, Layer.Success<TTestLayer> | Scope.Scope>;
23
24
  }
24
- export interface MakeEffectItResult<Harness, Provided> {
25
- readonly effectApp: EffectTester<Harness, Provided>;
25
+ export interface MakeEffectItResult<THarness, TProvided> {
26
+ readonly effectApp: EffectTester<THarness, TProvided>;
26
27
  }
27
28
  export interface EffectItTest<R, TArgs extends readonly unknown[] = [context: TestContext]> {
28
29
  <TEffect extends Effect.Effect<unknown, unknown, R>, A>(name: string, body: (...args: TArgs) => Generator<TEffect, A, never>, options?: number | TestOptions): void;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
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,5 +1,5 @@
1
1
  import { Layer } from "effect";
2
2
  import { type AnyTestLayer } from "./any-test-layer.ts";
3
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
+ export declare const makeEffectIt: <THarness, TTestLayer extends AnyTestLayer>(options: MakeEffectItOptions<THarness, TTestLayer>) => MakeEffectItResult<THarness, Layer.Success<TTestLayer>>;
5
5
  //# sourceMappingURL=vitest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AACA,OAAO,EAAqC,KAAK,EAAS,MAAM,QAAQ,CAAC;AAGzE,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,qBAAqB,CAAC;AACxE,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"}
1
+ {"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AACA,OAAO,EAAqC,KAAK,EAAS,MAAM,QAAQ,CAAC;AAGzE,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,qBAAqB,CAAC;AACxE,OAAO,KAAK,EAA4E,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAqEpJ,eAAO,MAAM,YAAY,GAAI,QAAQ,EAAE,UAAU,SAAS,YAAY,WAC5D,mBAAmB,CAAC,QAAQ,EAAE,UAAU,CAAC,KAChD,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAuExD,CAAC"}
package/dist/vitest.js CHANGED
@@ -6,11 +6,15 @@ import { buildTestLayer } from "./any-test-layer.js";
6
6
  const fixtureName = "__effectTestContext";
7
7
  const TestClockEnvironment = Layer.mergeAll(TestConsole.layer, TestClock.layer());
8
8
  const LiveClockEnvironment = TestConsole.layer;
9
- const environmentFor = (clock) => (clock === "live" ? LiveClockEnvironment : TestClockEnvironment);
10
- const restoreContext = (fixture, context) => ({
11
- ...context,
12
- [fixtureName]: fixture,
13
- });
9
+ function environmentFor(clock) {
10
+ return clock === "live" ? LiveClockEnvironment : TestClockEnvironment;
11
+ }
12
+ function restoreContext(fixture, context) {
13
+ return {
14
+ ...context,
15
+ [fixtureName]: fixture,
16
+ };
17
+ }
14
18
  const runEffectTest = (effect, context, clock) => Effect.runPromise(Effect.gen(function* () {
15
19
  const exit = yield* Effect.exit(effect);
16
20
  if (Exit.isFailure(exit)) {
@@ -20,7 +24,7 @@ const runEffectTest = (effect, context, clock) => Effect.runPromise(Effect.gen(f
20
24
  }
21
25
  return yield* exit;
22
26
  }).pipe(Effect.scoped, Effect.provide(environmentFor(clock))), { signal: context.signal });
23
- const splitOptions = (options) => {
27
+ function splitOptions(options) {
24
28
  if (typeof options === "number") {
25
29
  return { clock: undefined, vitest: { timeout: options } };
26
30
  }
@@ -29,46 +33,51 @@ const splitOptions = (options) => {
29
33
  }
30
34
  const { clock, ...vitest } = options;
31
35
  return { clock, vitest };
32
- };
33
- const makeFixtureIt = (layer) => effectIt.extend(fixtureName, { scope: "worker" }, async ({}, { onCleanup }) => {
34
- const scope = Effect.runSync(Scope.make());
35
- onCleanup(() => Effect.runPromise(Scope.close(scope, Exit.void)));
36
- try {
37
- return await Effect.runPromise(buildTestLayer(layer, scope));
38
- }
39
- catch (error) {
40
- await Effect.runPromise(Scope.close(scope, Exit.void));
41
- throw error;
42
- }
43
- });
36
+ }
37
+ function makeFixtureIt(layer) {
38
+ return effectIt.extend(fixtureName, { scope: "worker" }, async ({}, { onCleanup }) => {
39
+ const scope = Effect.runSync(Scope.make());
40
+ onCleanup(() => Effect.runPromise(Scope.close(scope, Exit.void)));
41
+ try {
42
+ return await Effect.runPromise(buildTestLayer(layer, scope));
43
+ }
44
+ catch (error) {
45
+ await Effect.runPromise(Scope.close(scope, Exit.void));
46
+ throw error;
47
+ }
48
+ });
49
+ }
44
50
  export const makeEffectIt = (options) => {
45
51
  const fixtureIt = makeFixtureIt(options.layer);
46
52
  const defaultClock = options.clock ?? "test";
47
- const run = (body, context) => {
53
+ function run(body, context) {
48
54
  const ready = Effect.gen(function* () {
49
55
  const harness = yield* options.makeHarness(context);
50
- return yield* Effect.gen(() => body(harness, context));
56
+ const result = body(harness, context);
57
+ return yield* Effect.isEffect(result) ? result : Effect.gen(() => result);
51
58
  });
52
59
  const wrapped = options.around?.(ready) ?? ready;
53
60
  return wrapped.pipe(Effect.provide(context[fixtureName]));
54
- };
55
- const register = (current) => (name, body, testOptions) => {
56
- const split = splitOptions(testOptions);
57
- const clock = split.clock ?? defaultClock;
58
- current(name, split.vitest, ({ __effectTestContext, task, signal, onTestFailed, onTestFinished, skip, annotate, expect, _local }) => {
59
- const context = restoreContext(__effectTestContext, {
60
- _local,
61
- annotate,
62
- expect,
63
- onTestFailed,
64
- onTestFinished,
65
- signal,
66
- skip,
67
- task,
61
+ }
62
+ function register(current) {
63
+ return (name, body, testOptions) => {
64
+ const split = splitOptions(testOptions);
65
+ const clock = split.clock ?? defaultClock;
66
+ current(name, split.vitest, ({ __effectTestContext, task, signal, onTestFailed, onTestFinished, skip, annotate, expect, _local }) => {
67
+ const context = restoreContext(__effectTestContext, {
68
+ _local,
69
+ annotate,
70
+ expect,
71
+ onTestFailed,
72
+ onTestFinished,
73
+ signal,
74
+ skip,
75
+ task,
76
+ });
77
+ return runEffectTest(run(body, context), context, clock);
68
78
  });
69
- return runEffectTest(run(body, context), context, clock);
70
- });
71
- };
79
+ };
80
+ }
72
81
  const effectApp = Object.assign(register(fixtureIt), {
73
82
  each: (cases) => (name, body, testOptions) => {
74
83
  const split = splitOptions(testOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"vitest.js","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vitest.js","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ # North star
2
+
3
+ ## The problem
4
+
5
+ An Effect test needs more than a Promise runner. It needs the application's services, a scope for work that must be released, a clock it can control and a way to wait for a typed readiness condition. When each database or transport fixture writes that machinery, small differences become differences in correctness: a clock advances in one helper but waits in another, a retry catches a defect, or a service is acquired at the wrong lifetime.
6
+
7
+ The package gives those fixtures one runner. A test names its work as a generator, and a fixture supplies its Layer and the value the test receives. The test still uses Effect's services, failure channel and clock, and Vitest still declares and reports the test.
8
+
9
+ ## The ideal
10
+
11
+ A test contains the work specific to that test. Shared service acquisition, per-test harness construction and an optional wrapper are defined once in test support. A plain Effect test needs no custom fixture: its generator goes directly to `it.effect` or `it.live`.
12
+
13
+ The boundary between worker and test is explicit. A worker owns the acquired service Context, while a test owns its harness and execution scope. A shared database service does not imply fresh database contents, and a fresh harness does not imply a freshly acquired service. The fixture that owns application state chooses how to reset or transact it.
14
+
15
+ Time is part of the Effect environment. A controlled test clock makes a readiness poll cheap and inspectable; live time remains an explicit choice for work that needs it. A typed readiness failure means “try again”. A defect means something broke, and interruption means the run must stop. These are different outcomes and the runner must keep them different.
16
+
17
+ ## What good looks like
18
+
19
+ - A generator reaches the application's real services instead of converting them into a separate testing API.
20
+ - A Layer, a harness and a wrapper are enough to define a reusable fixture. Their types are inferred, and a body cannot silently ask for a service the fixture does not provide.
21
+ - Resource lifetimes are visible in the design and backed by tests at the boundaries where this package adds behavior.
22
+ - Clock selection does not imply control over resources that were acquired in a different environment.
23
+ - A failed assertion is retried only when the author explicitly makes it a typed, retryable failure.
24
+ - Downstream fixture authors reuse the runner and own their domain setup.
25
+
26
+ ## Trade-offs
27
+
28
+ When goals conflict, preserve native Effect semantics first, keep one runner second, preserve deliberate worker and test lifetimes third, prove readable behavior fourth, and keep the adapter small fifth.
29
+
30
+ **Shared acquisition costs less and requires state discipline.** A worker Layer avoids rebuilding expensive services for every test. That places application-state isolation in the fixture that understands the state. Adding a hidden reset would conceal the lifetime rather than solve it.
31
+
32
+ **Controlled time and live time solve different problems.** A test clock helps a test advance the Effects in its environment. It does not make external timers or a worker's already-running fibers deterministic. Prefer a precise limit over a broad promise of determinism.
33
+
34
+ **Composition keeps responsibility local.** An `around` wrapper can establish a transaction or tracing boundary for the harness and body. This package owns how the wrapper fits the runner; the package that supplies a transaction owns its rollback semantics.
35
+
36
+ **Types are a contract, not runtime evidence.** Compiler tests establish which requirements, harness fields and table cases a body may use. Runtime tests establish what runs, when it runs and which failure escapes. Neither substitutes for the other.
37
+
38
+ ## What it leaves out
39
+
40
+ - Database rollback, migrations, seeded application state and transport caller construction. Those belong to their integrations or application test support.
41
+ - Domain traits, verbs, engine stepping and story failure narration. `@shivaedev/test-story` owns that layer above this runner.
42
+ - A replacement for Vitest's reporting, selection or configuration, or for Effect's Context, Scope and scheduling.
43
+ - Evidence of real HTTP, PostgreSQL, browser or deployment behavior from a controlled-clock unit test. Composed examples and host-boundary validation belong to the framework.
@@ -0,0 +1,24 @@
1
+ # Roadmap
2
+
3
+ This roadmap owns the test runner, its public contracts and its package tests. The [framework roadmap](https://github.com/ShivaeDev/platform/blob/main/docs/framework/roadmap.md) owns composed fixtures, application adoption and host or deployment validation. The same status is recorded in one place.
4
+
5
+ ## Built
6
+
7
+ - [x] `it.effect` and `it.live` accept generator bodies and Effect-returning bodies over the upstream testers. Tests cover the controlled and live clocks, table cases, expected failure, conditional skipping and plain Vitest tests.
8
+ - [x] `makeEffectIt` supplies the Layer's services, an inferred harness and Vitest's test context to the generator. Tests cover wrapper entry, harness values and table cases.
9
+ - [x] Default TestClock, factory live-clock selection and per-test overrides in either direction. Acquisition-time tests distinguish the worker Layer's live clock from the harness and body's test clock.
10
+ - [x] `Layer.empty` runs without a placeholder service. Compiler tests reject a service the empty Layer does not provide and retain harness, service and table-case types.
11
+ - [x] `eventually` retries typed failures on both clock paths. Tests count initial attempts and retries, preserve the final typed failure, reject thrown assertion retries, accept assertions captured with `Effect.try`, and preserve interruption.
12
+ - [x] Packed-consumer compiler fixtures exercise the published runner modules and retained harness types.
13
+
14
+ ## Next
15
+
16
+ - [ ] Add direct acquisition-count and finalizer assertions for worker Layer reuse and release, including acquisition failure.
17
+ - [ ] Assert per-test scope cleanup and wrapper ordering around harness construction as well as the body.
18
+ - [ ] Cover a timer fiber started during worker acquisition and a fixture that explicitly supplies its own Clock, so the clock boundary has direct regression evidence.
19
+ - [ ] Cover the runner's TestConsole environment, tester variants and `buildTestLayer` with assertions dedicated to the behavior this package adds.
20
+
21
+ ## Open questions
22
+
23
+ - Should composed application fixtures gain any API here, or remain framework examples and downstream helpers? The runner keeps the composition points small; new fixture APIs need a repeated job that the existing Layer, harness and wrapper cannot express.
24
+ - Which lifecycle and clock properties need local regression tests, and which should rely on the upstream Vitest and Effect contracts? The next items identify the gaps in this package's own evidence without changing its public API.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/effect-test",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Generic Effect Vitest runner with worker-scoped Layers and TestClock",
5
5
  "homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/effect-test#readme",
6
6
  "bugs": {
@@ -37,13 +37,14 @@
37
37
  "!src/**/*.test.ts",
38
38
  "!src/**/*.spec.ts",
39
39
  "!src/**/test-support",
40
+ "docs",
40
41
  "CHANGELOG.md",
41
42
  "README.md"
42
43
  ],
43
44
  "devDependencies": {
44
45
  "@effect/vitest": "4.0.0-rc.112",
45
- "@shivaedev/quality": "0.17.0",
46
- "@types/node": "24.10.1",
46
+ "@shivaedev/quality": "0.19.0",
47
+ "@types/node": "24.19.1",
47
48
  "@typescript/native": "npm:typescript@7.0.2",
48
49
  "effect": "4.0.0-rc.112",
49
50
  "typescript": "npm:@typescript/typescript6@6.0.2",
@@ -66,7 +67,7 @@
66
67
  "provenance": true
67
68
  },
68
69
  "scripts": {
69
- "build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc6 --project tsconfig.build.json",
70
+ "build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc --project tsconfig.build.json",
70
71
  "ready": "pnpm typecheck && pnpm test && pnpm build",
71
72
  "test": "vitest run",
72
73
  "typecheck": "tsc --noEmit"
package/src/eventually.ts CHANGED
@@ -8,18 +8,21 @@ export interface EventuallyOptions {
8
8
 
9
9
  const defaultInterval = "10 millis";
10
10
 
11
- const isTestClock = (clock: Clock.Clock): clock is TestClock.TestClock => "adjust" in clock;
11
+ function isTestClock(clock: Clock.Clock): clock is TestClock.TestClock {
12
+ return "adjust" in clock;
13
+ }
12
14
 
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
+ function retryOnLiveClock<A, E, R>(effect: Effect.Effect<A, E, R>, interval: Duration.Input, times: number | undefined): Effect.Effect<A, E, R> {
16
+ return times === undefined
15
17
  ? Effect.retry(effect, Schedule.spaced(interval))
16
18
  : Effect.retry(effect, {
17
19
  schedule: Schedule.spaced(interval),
18
20
  times,
19
21
  });
22
+ }
20
23
 
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* () {
24
+ function retryOnTestClock<A, E, R>(effect: Effect.Effect<A, E, R>, interval: Duration.Input, times: number | undefined): Effect.Effect<A, E, R> {
25
+ return Effect.gen(function* () {
23
26
  let retriesLeft = times;
24
27
  for (;;) {
25
28
  const result = yield* Effect.result(effect);
@@ -35,6 +38,7 @@ const retryOnTestClock = <A, E, R>(effect: Effect.Effect<A, E, R>, interval: Dur
35
38
  yield* TestClock.adjust(interval);
36
39
  }
37
40
  });
41
+ }
38
42
 
39
43
  export const eventually = <A, E, R>(effect: Effect.Effect<A, E, R>, options?: EventuallyOptions): Effect.Effect<A, E, R> =>
40
44
  Effect.gen(function* () {
package/src/types.ts CHANGED
@@ -9,38 +9,45 @@ export type EffectTestOptions = TestOptions & {
9
9
  readonly clock?: EffectClock;
10
10
  };
11
11
 
12
- export type EffectTest<Harness, Provided> = <A, Eff extends Effect.Effect<unknown, unknown, Provided>>(
12
+ export type EffectTestBody<THarness, TProvided, A = unknown> = (
13
+ harness: THarness,
14
+ context: TestContext,
15
+ ) => Generator<Effect.Effect<unknown, unknown, TProvided | Scope.Scope>, A, never> | Effect.Effect<A, unknown, TProvided | Scope.Scope>;
16
+
17
+ export type EffectTest<THarness, TProvided> = <A, TEff extends Effect.Effect<unknown, unknown, TProvided | Scope.Scope>>(
13
18
  name: string,
14
- body: (harness: Harness, context: TestContext) => Generator<Eff, A, never>,
19
+ body: (harness: THarness, context: TestContext) => Generator<TEff, A, never> | Effect.Effect<A, unknown, TProvided | Scope.Scope>,
15
20
  options?: number | EffectTestOptions,
16
21
  ) => void;
17
22
 
18
- export interface EffectTester<Harness, Provided> extends EffectTest<Harness, Provided> {
19
- readonly each: <Item>(
20
- cases: readonly Item[],
21
- ) => <A, Eff extends Effect.Effect<unknown, unknown, Provided>>(
23
+ export interface EffectTester<THarness, TProvided> extends EffectTest<THarness, TProvided> {
24
+ readonly each: <TItem>(
25
+ cases: readonly TItem[],
26
+ ) => <A, TEff extends Effect.Effect<unknown, unknown, TProvided | Scope.Scope>>(
22
27
  name: string,
23
- body: (item: Item, harness: Harness, context: TestContext) => Generator<Eff, A, never>,
28
+ body: (item: TItem, harness: THarness, context: TestContext) => Generator<TEff, A, never> | Effect.Effect<A, unknown, TProvided | Scope.Scope>,
24
29
  options?: number | EffectTestOptions,
25
30
  ) => void;
26
- readonly fails: EffectTest<Harness, Provided>;
27
- readonly only: EffectTest<Harness, Provided>;
28
- readonly runIf: (condition: unknown) => EffectTest<Harness, Provided>;
29
- readonly skip: EffectTest<Harness, Provided>;
30
- readonly skipIf: (condition: unknown) => EffectTest<Harness, Provided>;
31
+ readonly fails: EffectTest<THarness, TProvided>;
32
+ readonly only: EffectTest<THarness, TProvided>;
33
+ readonly runIf: (condition: unknown) => EffectTest<THarness, TProvided>;
34
+ readonly skip: EffectTest<THarness, TProvided>;
35
+ readonly skipIf: (condition: unknown) => EffectTest<THarness, TProvided>;
31
36
  }
32
37
 
33
- export interface MakeEffectItOptions<Harness, TestLayer extends AnyTestLayer> {
38
+ export interface MakeEffectItOptions<THarness, TTestLayer extends AnyTestLayer> {
34
39
  readonly around?:
35
- | (<A, E>(effect: Effect.Effect<A, E, Layer.Success<TestLayer>>) => Effect.Effect<A, unknown, Layer.Success<TestLayer>>)
40
+ | (<A, E>(
41
+ effect: Effect.Effect<A, E, Layer.Success<TTestLayer> | Scope.Scope>,
42
+ ) => Effect.Effect<A, unknown, Layer.Success<TTestLayer> | Scope.Scope>)
36
43
  | undefined;
37
44
  readonly clock?: EffectClock | undefined;
38
- readonly layer: TestLayer;
39
- readonly makeHarness: (context: TestContext) => Effect.Effect<Harness, unknown, Layer.Success<TestLayer>>;
45
+ readonly layer: TTestLayer;
46
+ readonly makeHarness: (context: TestContext) => Effect.Effect<THarness, unknown, Layer.Success<TTestLayer> | Scope.Scope>;
40
47
  }
41
48
 
42
- export interface MakeEffectItResult<Harness, Provided> {
43
- readonly effectApp: EffectTester<Harness, Provided>;
49
+ export interface MakeEffectItResult<THarness, TProvided> {
50
+ readonly effectApp: EffectTester<THarness, TProvided>;
44
51
  }
45
52
 
46
53
  export interface EffectItTest<R, TArgs extends readonly unknown[] = [context: TestContext]> {
package/src/vitest.ts CHANGED
@@ -3,28 +3,32 @@ import { Cause, type Context, 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
  import { type AnyTestLayer, buildTestLayer } from "./any-test-layer.ts";
6
- import type { EffectClock, EffectTest, EffectTester, EffectTestOptions, MakeEffectItOptions, MakeEffectItResult } from "./types.ts";
6
+ import type { EffectClock, EffectTest, EffectTestBody, EffectTester, EffectTestOptions, MakeEffectItOptions, MakeEffectItResult } from "./types.ts";
7
7
 
8
8
  const fixtureName = "__effectTestContext";
9
9
 
10
- type EffectFixture<Provided> = TestContext & {
11
- readonly [fixtureName]: Context.Context<Provided>;
10
+ type EffectFixture<TProvided> = TestContext & {
11
+ readonly [fixtureName]: Context.Context<TProvided>;
12
12
  };
13
13
 
14
- type FixtureTest<Provided> = (name: string, options: TestOptions, body: (context: EffectFixture<Provided>) => Promise<unknown>) => void;
14
+ type FixtureTest<TProvided> = (name: string, options: TestOptions, body: (context: EffectFixture<TProvided>) => Promise<unknown>) => void;
15
15
 
16
16
  const TestClockEnvironment = Layer.mergeAll(TestConsole.layer, TestClock.layer());
17
17
  const LiveClockEnvironment = TestConsole.layer;
18
18
 
19
- const environmentFor = (clock: EffectClock) => (clock === "live" ? LiveClockEnvironment : TestClockEnvironment);
19
+ function environmentFor(clock: EffectClock) {
20
+ return clock === "live" ? LiveClockEnvironment : TestClockEnvironment;
21
+ }
20
22
 
21
- const restoreContext = <Provided>(
22
- fixture: Context.Context<Provided>,
23
- context: Omit<EffectFixture<Provided>, typeof fixtureName>,
24
- ): EffectFixture<Provided> => ({
25
- ...context,
26
- [fixtureName]: fixture,
27
- });
23
+ function restoreContext<TProvided>(
24
+ fixture: Context.Context<TProvided>,
25
+ context: Omit<EffectFixture<TProvided>, typeof fixtureName>,
26
+ ): EffectFixture<TProvided> {
27
+ return {
28
+ ...context,
29
+ [fixtureName]: fixture,
30
+ };
31
+ }
28
32
 
29
33
  const runEffectTest = <A, E>(effect: Effect.Effect<A, E, Scope.Scope>, context: TestContext, clock: EffectClock): Promise<A> =>
30
34
  Effect.runPromise(
@@ -40,12 +44,10 @@ const runEffectTest = <A, E>(effect: Effect.Effect<A, E, Scope.Scope>, context:
40
44
  { signal: context.signal },
41
45
  );
42
46
 
43
- const splitOptions = (
44
- options: number | EffectTestOptions | undefined,
45
- ): {
47
+ function splitOptions(options: number | EffectTestOptions | undefined): {
46
48
  readonly vitest: TestOptions;
47
49
  readonly clock: EffectClock | undefined;
48
- } => {
50
+ } {
49
51
  if (typeof options === "number") {
50
52
  return { clock: undefined, vitest: { timeout: options } };
51
53
  }
@@ -54,10 +56,10 @@ const splitOptions = (
54
56
  }
55
57
  const { clock, ...vitest } = options;
56
58
  return { clock, vitest };
57
- };
59
+ }
58
60
 
59
- const makeFixtureIt = <TLayer extends AnyTestLayer>(layer: TLayer) =>
60
- effectIt.extend(fixtureName, { scope: "worker" }, async ({}, { onCleanup }) => {
61
+ function makeFixtureIt<TLayer extends AnyTestLayer>(layer: TLayer) {
62
+ return effectIt.extend(fixtureName, { scope: "worker" }, async ({}, { onCleanup }) => {
61
63
  const scope = Effect.runSync(Scope.make());
62
64
  onCleanup(() => Effect.runPromise(Scope.close(scope, Exit.void)));
63
65
 
@@ -68,29 +70,27 @@ const makeFixtureIt = <TLayer extends AnyTestLayer>(layer: TLayer) =>
68
70
  throw error;
69
71
  }
70
72
  });
73
+ }
71
74
 
72
- export const makeEffectIt = <Harness, TestLayer extends AnyTestLayer>(
73
- options: MakeEffectItOptions<Harness, TestLayer>,
74
- ): MakeEffectItResult<Harness, Layer.Success<TestLayer>> => {
75
- type Provided = Layer.Success<TestLayer>;
75
+ export const makeEffectIt = <THarness, TTestLayer extends AnyTestLayer>(
76
+ options: MakeEffectItOptions<THarness, TTestLayer>,
77
+ ): MakeEffectItResult<THarness, Layer.Success<TTestLayer>> => {
78
+ type Provided = Layer.Success<TTestLayer>;
76
79
  const fixtureIt = makeFixtureIt(options.layer);
77
80
  const defaultClock = options.clock ?? "test";
78
81
 
79
- const run = <A>(
80
- body: (harness: Harness, context: TestContext) => Generator<Effect.Effect<unknown, unknown, Provided>, A, never>,
81
- context: EffectFixture<Provided>,
82
- ): Effect.Effect<A, unknown, Scope.Scope> => {
83
- const ready: Effect.Effect<A, unknown, Provided> = Effect.gen(function* () {
82
+ function run<A>(body: EffectTestBody<THarness, Provided, A>, context: EffectFixture<Provided>): Effect.Effect<A, unknown, Scope.Scope> {
83
+ const ready: Effect.Effect<A, unknown, Provided | Scope.Scope> = Effect.gen(function* () {
84
84
  const harness = yield* options.makeHarness(context);
85
- return yield* Effect.gen(() => body(harness, context));
85
+ const result = body(harness, context);
86
+ return yield* Effect.isEffect(result) ? result : Effect.gen(() => result);
86
87
  });
87
88
  const wrapped = options.around?.(ready) ?? ready;
88
89
  return wrapped.pipe(Effect.provide(context[fixtureName]));
89
- };
90
+ }
90
91
 
91
- const register =
92
- (current: FixtureTest<Provided>): EffectTest<Harness, Provided> =>
93
- (name, body, testOptions) => {
92
+ function register(current: FixtureTest<Provided>): EffectTest<THarness, Provided> {
93
+ return (name, body, testOptions) => {
94
94
  const split = splitOptions(testOptions);
95
95
  const clock = split.clock ?? defaultClock;
96
96
  current(name, split.vitest, ({ __effectTestContext, task, signal, onTestFailed, onTestFinished, skip, annotate, expect, _local }) => {
@@ -107,13 +107,14 @@ export const makeEffectIt = <Harness, TestLayer extends AnyTestLayer>(
107
107
  return runEffectTest(run(body, context), context, clock);
108
108
  });
109
109
  };
110
+ }
110
111
 
111
- const effectApp: EffectTester<Harness, Provided> = Object.assign(register(fixtureIt), {
112
+ const effectApp: EffectTester<THarness, Provided> = Object.assign(register(fixtureIt), {
112
113
  each:
113
- <Item>(cases: readonly Item[]) =>
114
- <A, Eff extends Effect.Effect<unknown, unknown, Provided>>(
114
+ <TItem>(cases: readonly TItem[]) =>
115
+ <A, TEff extends Effect.Effect<unknown, unknown, Provided | Scope.Scope>>(
115
116
  name: string,
116
- body: (item: Item, harness: Harness, context: TestContext) => Generator<Eff, A, never>,
117
+ body: (item: TItem, harness: THarness, context: TestContext) => Generator<TEff, A, never> | Effect.Effect<A, unknown, Provided | Scope.Scope>,
117
118
  testOptions?: number | EffectTestOptions,
118
119
  ) => {
119
120
  const split = splitOptions(testOptions);