@effect/vitest 4.0.0-beta.1 → 4.0.0-beta.100

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.
@@ -1,7 +1,8 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * @since 4.0.0
3
3
  */
4
4
 
5
+ import { getCurrentSuite } from "@vitest/runner"
5
6
  import * as Cause from "effect/Cause"
6
7
  import * as Duration from "effect/Duration"
7
8
  import * as Effect from "effect/Effect"
@@ -48,6 +49,44 @@ export const addEqualityTesters = () => {
48
49
  /** @internal */
49
50
  const testOptions = (timeout?: number | V.TestOptions) => typeof timeout === "number" ? { timeout } : timeout ?? {}
50
51
 
52
+ const hookTimeout = (timeout?: Duration.Input) =>
53
+ timeout === undefined ? undefined : Duration.toMillis(Duration.fromInputUnsafe(timeout))
54
+
55
+ const makeItProxy = <Methods extends object>(
56
+ it: V.TestAPI,
57
+ overrides: Methods
58
+ ): Methods & V.TestAPI =>
59
+ new Proxy(it as Methods & V.TestAPI, {
60
+ apply(target, thisArg, argArray) {
61
+ return Reflect.apply(target, thisArg, argArray)
62
+ },
63
+ get(target, property, receiver) {
64
+ if (property in overrides) {
65
+ return Reflect.get(overrides, property)
66
+ }
67
+ // do not bind: binding would strip vitest's static helpers (e.g. `describe.each`)
68
+ return Reflect.get(target, property, receiver)
69
+ }
70
+ })
71
+
72
+ type CollectedTask = {
73
+ readonly type: string
74
+ readonly mode?: string
75
+ readonly tasks?: ReadonlyArray<CollectedTask>
76
+ }
77
+
78
+ const collectTasks = (tasks: ReadonlyArray<CollectedTask>, acc: Array<V.TestContext["task"]> = []) => {
79
+ for (let i = 0; i < tasks.length; i++) {
80
+ const task = tasks[i]
81
+ if (task.type === "test" && task.mode !== "skip" && task.mode !== "todo") {
82
+ acc.push(task as V.TestContext["task"])
83
+ } else if (task.tasks !== undefined) {
84
+ collectTasks(task.tasks, acc)
85
+ }
86
+ }
87
+ return acc
88
+ }
89
+
51
90
  /** @internal */
52
91
  const makeTester = <R>(
53
92
  mapEffect: <A, E>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, never>,
@@ -109,10 +148,7 @@ const makeTester = <R>(
109
148
  const arbs = fc.record(
110
149
  Object.keys(arbitraries).reduce(function(result, key) {
111
150
  const arb: any = arbitraries[key]
112
- if (Schema.isSchema(arb)) {
113
- result[key] = Schema.toArbitrary(arb)
114
- }
115
- result[key] = arb
151
+ result[key] = Schema.isSchema(arb) ? Schema.toArbitrary(arb) : arb
116
152
  return result
117
153
  }, {} as Record<string, fc.Arbitrary<any>>)
118
154
  )
@@ -176,7 +212,7 @@ export const layer = <R, E>(
176
212
  layer_: Layer.Layer<R, E>,
177
213
  options?: {
178
214
  readonly memoMap?: Layer.MemoMap
179
- readonly timeout?: Duration.DurationInput
215
+ readonly timeout?: Duration.Input
180
216
  readonly excludeTestServices?: boolean
181
217
  }
182
218
  ): {
@@ -207,9 +243,17 @@ export const layer = <R, E>(
207
243
  Effect.cached,
208
244
  Effect.runSync
209
245
  )
246
+ let closed = false
247
+ const closeScope = (ctx?: Vitest.TestContext) => {
248
+ if (closed) {
249
+ return Promise.resolve()
250
+ }
251
+ closed = true
252
+ return runPromise(Scope.close(scope, Exit.void), ctx)
253
+ }
210
254
 
211
255
  const makeIt = (it: V.TestAPI): Vitest.Vitest.MethodsNonLive<R> =>
212
- Object.assign(it, {
256
+ makeItProxy(it, {
213
257
  effect: makeTester<R | Scope.Scope>(
214
258
  (effect) =>
215
259
  Effect.flatMap(contextEffect, (context) =>
@@ -222,32 +266,60 @@ export const layer = <R, E>(
222
266
  prop,
223
267
  flakyTest,
224
268
  layer<R2, E2>(nestedLayer: Layer.Layer<R2, E2, R>, options?: {
225
- readonly timeout?: Duration.DurationInput
269
+ readonly timeout?: Duration.Input
226
270
  }) {
227
- return layer(Layer.provideMerge(nestedLayer, withTestEnv), { ...options, memoMap, excludeTestServices })
271
+ return layer(Layer.provideMerge(nestedLayer, withTestEnv), {
272
+ ...options,
273
+ memoMap: Layer.forkMemoMapUnsafe(memoMap),
274
+ excludeTestServices
275
+ })
228
276
  }
229
277
  })
230
278
 
231
279
  if (args.length === 1) {
232
- V.beforeAll(
233
- () => runPromise(Effect.asVoid(contextEffect)),
234
- options?.timeout ? Duration.toMillis(Duration.fromDurationInputUnsafe(options.timeout)) : undefined
280
+ const currentSuite = getCurrentSuite()
281
+ const previousTasks = new Set(currentSuite.tasks)
282
+
283
+ args[0](makeIt(V.it))
284
+
285
+ const blockTasks = collectTasks(
286
+ currentSuite.tasks.filter((task) => !previousTasks.has(task)) as ReadonlyArray<CollectedTask>
235
287
  )
236
- V.afterAll(
237
- () => runPromise(Scope.close(scope, Exit.void)),
238
- options?.timeout ? Duration.toMillis(Duration.fromDurationInputUnsafe(options.timeout)) : undefined
288
+ if (blockTasks.length === 0) {
289
+ V.afterAll(() => closeScope(), hookTimeout(options?.timeout))
290
+ return
291
+ }
292
+
293
+ const blockTaskSet = new Set(blockTasks)
294
+ let remaining = blockTasks.length
295
+
296
+ V.beforeEach(
297
+ (ctx) => {
298
+ if (!blockTaskSet.has(ctx.task)) {
299
+ return
300
+ }
301
+ ctx.onTestFinished(() => {
302
+ remaining--
303
+ if (remaining === 0) {
304
+ return closeScope(ctx)
305
+ }
306
+ })
307
+ return runPromise(Effect.asVoid(contextEffect), ctx)
308
+ },
309
+ hookTimeout(options?.timeout)
239
310
  )
240
- return args[0](makeIt(V.it))
311
+ V.afterAll(() => closeScope(), hookTimeout(options?.timeout))
312
+ return
241
313
  }
242
314
 
243
315
  return V.describe(args[0], () => {
244
316
  V.beforeAll(
245
317
  () => runPromise(Effect.asVoid(contextEffect)),
246
- options?.timeout ? Duration.toMillis(Duration.fromDurationInputUnsafe(options.timeout)) : undefined
318
+ hookTimeout(options?.timeout)
247
319
  )
248
320
  V.afterAll(
249
- () => runPromise(Scope.close(scope, Exit.void)),
250
- options?.timeout ? Duration.toMillis(Duration.fromDurationInputUnsafe(options.timeout)) : undefined
321
+ () => closeScope(),
322
+ hookTimeout(options?.timeout)
251
323
  )
252
324
  return args[1](makeIt(V.it))
253
325
  })
@@ -256,7 +328,7 @@ export const layer = <R, E>(
256
328
  /** @internal */
257
329
  export const flakyTest = <A, E, R>(
258
330
  self: Effect.Effect<A, E, R | Scope.Scope>,
259
- timeout: Duration.DurationInput = Duration.seconds(30)
331
+ timeout: Duration.Input = Duration.seconds(30)
260
332
  ) =>
261
333
  pipe(
262
334
  self,
@@ -267,8 +339,8 @@ export const flakyTest = <A, E, R>(
267
339
  Schedule.recurs(10),
268
340
  Schedule.while((_) =>
269
341
  Effect.succeed(Duration.isLessThanOrEqualTo(
270
- Duration.fromDurationInputUnsafe(_.elapsed),
271
- Duration.fromDurationInputUnsafe(timeout)
342
+ Duration.fromInputUnsafe(_.elapsed),
343
+ Duration.fromInputUnsafe(timeout)
272
344
  ))
273
345
  )
274
346
  )
@@ -278,7 +350,7 @@ export const flakyTest = <A, E, R>(
278
350
 
279
351
  /** @internal */
280
352
  export const makeMethods = (it: V.TestAPI): Vitest.Vitest.Methods =>
281
- Object.assign(it, {
353
+ makeItProxy(it, {
282
354
  effect: makeTester<Scope.Scope>(flow(Effect.scoped, Effect.provide(TestEnv)), it),
283
355
  live: makeTester<Scope.Scope>(Effect.scoped, it),
284
356
  flakyTest,
package/src/utils.ts CHANGED
@@ -1,4 +1,12 @@
1
1
  /**
2
+ * Provides assertion helpers used by `@effect/vitest` tests.
3
+ *
4
+ * This module defines small assertion functions built on Node's `assert`,
5
+ * Vitest's instance checks, and Effect's equality support. The helpers cover
6
+ * basic equality, thrown errors, defined and undefined values, strings, regular
7
+ * expressions, class instances, `Option`, `Result`, and `Exit`. Most helpers are
8
+ * synchronous; `throwsAsync` handles rejected promises.
9
+ *
2
10
  * @since 4.0.0
3
11
  */
4
12
  import type * as Cause from "effect/Cause"
@@ -15,8 +23,9 @@ import { assert as vassert } from "vitest"
15
23
  // ----------------------------
16
24
 
17
25
  /**
18
- * Throws an `AssertionError` with the provided error message.
26
+ * Fails the current test with the provided error message.
19
27
  *
28
+ * @category testing
20
29
  * @since 4.0.0
21
30
  */
22
31
  export function fail(message: string) {
@@ -24,35 +33,39 @@ export function fail(message: string) {
24
33
  }
25
34
 
26
35
  /**
27
- * Asserts that `actual` is equal to `expected` using the `Equal.equals` trait.
36
+ * Asserts that `actual` is deeply strictly equal to `expected` using Node's `assert.deepStrictEqual`.
28
37
  *
38
+ * @category testing
29
39
  * @since 4.0.0
30
40
  */
31
41
  export function deepStrictEqual<A>(actual: A, expected: A, message?: string, ..._: Array<never>) {
32
- assert.deepStrictEqual(actual, expected, message)
42
+ assert.deepStrictEqual(actual, expected, message as string)
33
43
  }
34
44
 
35
45
  /**
36
- * Asserts that `actual` is not equal to `expected` using the `Equal.equals` trait.
46
+ * Asserts that `actual` is not deeply strictly equal to `expected` using Node's `assert.notDeepStrictEqual`.
37
47
  *
48
+ * @category testing
38
49
  * @since 4.0.0
39
50
  */
40
51
  export function notDeepStrictEqual<A>(actual: A, expected: A, message?: string, ..._: Array<never>) {
41
- assert.notDeepStrictEqual(actual, expected, message)
52
+ assert.notDeepStrictEqual(actual, expected, message as string)
42
53
  }
43
54
 
44
55
  /**
45
- * Asserts that `actual` is equal to `expected` using the `Equal.equals` trait.
56
+ * Asserts that `actual` is strictly equal to `expected` using Node's `assert.strictEqual`.
46
57
  *
58
+ * @category testing
47
59
  * @since 4.0.0
48
60
  */
49
61
  export function strictEqual<A>(actual: A, expected: A, message?: string, ..._: Array<never>) {
50
- assert.strictEqual(actual, expected, message)
62
+ assert.strictEqual(actual, expected, message as string)
51
63
  }
52
64
 
53
65
  /**
54
66
  * Asserts that `actual` is equal to `expected` using the `Equal.equals` trait.
55
67
  *
68
+ * @category testing
56
69
  * @since 4.0.0
57
70
  */
58
71
  export function assertEquals<A>(actual: A, expected: A, message?: string, ..._: Array<never>) {
@@ -65,6 +78,7 @@ export function assertEquals<A>(actual: A, expected: A, message?: string, ..._:
65
78
  /**
66
79
  * Asserts that `thunk` does not throw an error.
67
80
  *
81
+ * @category testing
68
82
  * @since 4.0.0
69
83
  */
70
84
  export function doesNotThrow(thunk: () => void, message?: string, ..._: Array<never>) {
@@ -78,6 +92,7 @@ export function doesNotThrow(thunk: () => void, message?: string, ..._: Array<ne
78
92
  /**
79
93
  * Asserts that `value` is an instance of `constructor`.
80
94
  *
95
+ * @category testing
81
96
  * @since 4.0.0
82
97
  */
83
98
  export function assertInstanceOf<C extends abstract new(...args: any) => any>(
@@ -92,6 +107,7 @@ export function assertInstanceOf<C extends abstract new(...args: any) => any>(
92
107
  /**
93
108
  * Asserts that `self` is `true`.
94
109
  *
110
+ * @category testing
95
111
  * @since 4.0.0
96
112
  */
97
113
  export function assertTrue(self: unknown, message?: string, ..._: Array<never>): asserts self {
@@ -101,6 +117,7 @@ export function assertTrue(self: unknown, message?: string, ..._: Array<never>):
101
117
  /**
102
118
  * Asserts that `self` is `false`.
103
119
  *
120
+ * @category testing
104
121
  * @since 4.0.0
105
122
  */
106
123
  export function assertFalse(self: boolean, message?: string, ..._: Array<never>) {
@@ -110,6 +127,7 @@ export function assertFalse(self: boolean, message?: string, ..._: Array<never>)
110
127
  /**
111
128
  * Asserts that `actual` includes `expected`.
112
129
  *
130
+ * @category testing
113
131
  * @since 4.0.0
114
132
  */
115
133
  export function assertInclude(actual: string | undefined, expected: string, ..._: Array<never>) {
@@ -123,6 +141,7 @@ export function assertInclude(actual: string | undefined, expected: string, ..._
123
141
  /**
124
142
  * Asserts that `actual` matches `regExp`.
125
143
  *
144
+ * @category testing
126
145
  * @since 4.0.0
127
146
  */
128
147
  export function assertMatch(actual: string, regExp: RegExp, ..._: Array<never>) {
@@ -132,8 +151,9 @@ export function assertMatch(actual: string, regExp: RegExp, ..._: Array<never>)
132
151
  }
133
152
 
134
153
  /**
135
- * Asserts that `thunk` throws an error.
154
+ * Asserts that `thunk` throws, optionally checking the thrown value against an expected `Error` or validation function.
136
155
  *
156
+ * @category testing
137
157
  * @since 4.0.0
138
158
  */
139
159
  export function throws(thunk: () => void, error?: Error | ((u: unknown) => undefined), ..._: Array<never>) {
@@ -144,16 +164,19 @@ export function throws(thunk: () => void, error?: Error | ((u: unknown) => undef
144
164
  if (error !== undefined) {
145
165
  if (Predicate.isFunction(error)) {
146
166
  error(e)
147
- } else {
167
+ } else if (error) {
148
168
  deepStrictEqual(e, error)
169
+ } else {
170
+ throw e
149
171
  }
150
172
  }
151
173
  }
152
174
  }
153
175
 
154
176
  /**
155
- * Asserts that `thunk` throws an error.
177
+ * Asserts that `thunk` throws or returns a rejected promise, optionally checking the failure value against an expected `Error` or validation function.
156
178
  *
179
+ * @category testing
157
180
  * @since 4.0.0
158
181
  */
159
182
  export async function throwsAsync(
@@ -182,6 +205,7 @@ export async function throwsAsync(
182
205
  /**
183
206
  * Asserts that `option` is `None`.
184
207
  *
208
+ * @category testing
185
209
  * @since 4.0.0
186
210
  */
187
211
  export function assertNone<A>(option: Option.Option<A>, ..._: Array<never>): asserts option is Option.None<never> {
@@ -191,6 +215,7 @@ export function assertNone<A>(option: Option.Option<A>, ..._: Array<never>): ass
191
215
  /**
192
216
  * Asserts that `a` is not `undefined`.
193
217
  *
218
+ * @category testing
194
219
  * @since 4.0.0
195
220
  */
196
221
  export function assertDefined<A>(
@@ -205,6 +230,7 @@ export function assertDefined<A>(
205
230
  /**
206
231
  * Asserts that `a` is `undefined`.
207
232
  *
233
+ * @category testing
208
234
  * @since 4.0.0
209
235
  */
210
236
  export function assertUndefined<A>(
@@ -217,8 +243,9 @@ export function assertUndefined<A>(
217
243
  }
218
244
 
219
245
  /**
220
- * Asserts that `option` is `Some`.
246
+ * Asserts that `option` is `Some` and contains a value equal to `expected`.
221
247
  *
248
+ * @category testing
222
249
  * @since 4.0.0
223
250
  */
224
251
  export function assertSome<A>(
@@ -234,8 +261,9 @@ export function assertSome<A>(
234
261
  // ----------------------------
235
262
 
236
263
  /**
237
- * Asserts that `result` is `Success`.
264
+ * Asserts that `result` is `Success` and contains a value equal to `expected`.
238
265
  *
266
+ * @category testing
239
267
  * @since 4.0.0
240
268
  */
241
269
  export function assertSuccess<A, E>(
@@ -247,8 +275,9 @@ export function assertSuccess<A, E>(
247
275
  }
248
276
 
249
277
  /**
250
- * Asserts that `result` is `Failure`.
278
+ * Asserts that `result` is `Failure` and contains an error equal to `expected`.
251
279
  *
280
+ * @category testing
252
281
  * @since 4.0.0
253
282
  */
254
283
  export function assertFailure<A, E>(
@@ -264,8 +293,9 @@ export function assertFailure<A, E>(
264
293
  // ----------------------------
265
294
 
266
295
  /**
267
- * Asserts that `exit` is a failure.
296
+ * Asserts that `exit` is a failure with a cause equal to `expected`.
268
297
  *
298
+ * @category testing
269
299
  * @since 4.0.0
270
300
  */
271
301
  export function assertExitFailure<A, E>(
@@ -277,8 +307,9 @@ export function assertExitFailure<A, E>(
277
307
  }
278
308
 
279
309
  /**
280
- * Asserts that `exit` is a success.
310
+ * Asserts that `exit` is a success with a value equal to `expected`.
281
311
  *
312
+ * @category testing
282
313
  * @since 4.0.0
283
314
  */
284
315
  export function assertExitSuccess<A, E>(