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 *
10 * @stability unstable
11 * @since 4.0.0
12 */
13import type * as Cause from "effect/Cause"
14import * as Equal from "effect/Equal"
15import * as Exit from "effect/Exit"
16import * as Option from "effect/Option"
17import * as Predicate from "effect/Predicate"
18import * as Result from "effect/Result"
19import * as assert from "node:assert"
20import { assert as vassert } from "vitest"
21
22// ----------------------------
23// Primitives
24// ----------------------------
25
26/**
27 * Fails the current test with the provided error message.
28 *
29 * @stability unstable
30 * @category testing
31 * @since 4.0.0
32 */
33export function fail(message: string) {
34 assert.fail(message)
35}
36
37/**
38 * Asserts that `actual` is deeply strictly equal to `expected` using Node's `assert.deepStrictEqual`.
39 *
40 * @stability unstable
41 * @category testing
42 * @since 4.0.0
43 */
44export function deepStrictEqual<A>(actual: A, expected: A, message?: string, ..._: Array<never>) {
45 if (message !== undefined) {
46 assert.deepStrictEqual(actual, expected, message)
47 } else {
48 assert.deepStrictEqual(actual, expected)
49 }
50}
51
52/**
53 * Asserts that `actual` is not deeply strictly equal to `expected` using Node's `assert.notDeepStrictEqual`.
54 *
55 * @stability unstable
56 * @category testing
57 * @since 4.0.0
58 */
59export function notDeepStrictEqual<A>(actual: A, expected: A, message?: string, ..._: Array<never>) {
60 if (message !== undefined) {
61 assert.notDeepStrictEqual(actual, expected, message)
62 } else {
63 assert.notDeepStrictEqual(actual, expected)
64 }
65}
66
67/**
68 * Asserts that `actual` is strictly equal to `expected` using Node's `assert.strictEqual`.
69 *
70 * @stability unstable
71 * @category testing
72 * @since 4.0.0
73 */
74export function strictEqual<A>(actual: A, expected: A, message?: string, ..._: Array<never>) {
75 if (message !== undefined) {
76 assert.strictEqual(actual, expected, message)
77 } else {
78 assert.strictEqual(actual, expected)
79 }
80}
81
82/**
83 * Asserts that `actual` is equal to `expected` using the `Equal.equals` trait.
84 *
85 * @stability unstable
86 * @category testing
87 * @since 4.0.0
88 */
89export function assertEquals<A>(actual: A, expected: A, message?: string, ..._: Array<never>) {
90 if (!Equal.equals(actual, expected)) {
91 deepStrictEqual(actual, expected, message) // show diff
92 fail(message ?? "Expected values to be Equal.equals")
93 }
94}
95
96/**
97 * Asserts that `thunk` does not throw an error.
98 *
99 * @stability unstable
100 * @category testing
101 * @since 4.0.0
102 */
103export function doesNotThrow(thunk: () => void, message?: string, ..._: Array<never>) {
104 assert.doesNotThrow(thunk, message)
105}
106
107// ----------------------------
108// Derived
109// ----------------------------
110
111/**
112 * Asserts that `value` is an instance of `constructor`.
113 *
114 * @stability unstable
115 * @category testing
116 * @since 4.0.0
117 */
118export function assertInstanceOf<C extends abstract new(...args: any) => any>(
119 value: unknown,
120 constructor: C,
121 message?: string,
122 ..._: Array<never>
123): asserts value is InstanceType<C> {
124 vassert.instanceOf(value, constructor as any, message)
125}
126
127/**
128 * Asserts that `self` is `true`.
129 *
130 * @stability unstable
131 * @category testing
132 * @since 4.0.0
133 */
134export function assertTrue(self: unknown, message?: string, ..._: Array<never>): asserts self {
135 strictEqual(self, true, message)
136}
137
138/**
139 * Asserts that `self` is `false`.
140 *
141 * @stability unstable
142 * @category testing
143 * @since 4.0.0
144 */
145export function assertFalse(self: boolean, message?: string, ..._: Array<never>) {
146 strictEqual(self, false, message)
147}
148
149/**
150 * Asserts that `actual` includes `expected`.
151 *
152 * @stability unstable
153 * @category testing
154 * @since 4.0.0
155 */
156export function assertInclude(actual: string | undefined, expected: string, ..._: Array<never>) {
157 if (typeof expected === "string") {
158 if (!actual?.includes(expected)) {
159 fail(`Expected\n\n${actual}\n\nto include\n\n${expected}`)
160 }
161 }
162}
163
164/**
165 * Asserts that `actual` matches `regExp`.
166 *
167 * @stability unstable
168 * @category testing
169 * @since 4.0.0
170 */
171export function assertMatch(actual: string, regExp: RegExp, ..._: Array<never>) {
172 if (!regExp.test(actual)) {
173 fail(`Expected\n\n${actual}\n\nto match\n\n${regExp}`)
174 }
175}
176
177/**
178 * Asserts that `thunk` throws, optionally checking the thrown value against an expected `Error` or validation function.
179 *
180 * @stability unstable
181 * @category testing
182 * @since 4.0.0
183 */
184export function throws(thunk: () => void, error?: Error | ((u: unknown) => undefined), ..._: Array<never>) {
185 try {
186 thunk()
187 } catch (e) {
188 if (error !== undefined) {
189 if (Predicate.isFunction(error)) {
190 error(e)
191 } else if (error) {
192 deepStrictEqual(e, error)
193 } else {
194 throw e
195 }
196 }
197 return
198 }
199 fail("Expected to throw an error")
200}
201
202/**
203 * Asserts that `thunk` throws or returns a rejected promise, optionally checking the failure value against an expected `Error` or validation function.
204 *
205 * @stability unstable
206 * @category testing
207 * @since 4.0.0
208 */
209export async function throwsAsync(
210 thunk: () => Promise<void>,
211 error?: Error | ((u: unknown) => undefined),
212 ..._: Array<never>
213) {
214 try {
215 await thunk()
216 } catch (e) {
217 if (error !== undefined) {
218 if (Predicate.isFunction(error)) {
219 error(e)
220 } else {
221 deepStrictEqual(e, error)
222 }
223 }
224 return
225 }
226 fail("Expected to throw an error")
227}
228
229// ----------------------------
230// Option
231// ----------------------------
232
233/**
234 * Asserts that `option` is `None`.
235 *
236 * @stability unstable
237 * @category testing
238 * @since 4.0.0
239 */
240export function assertNone<A>(option: Option.Option<A>, ..._: Array<never>): asserts option is Option.None<never> {
241 deepStrictEqual(option, Option.none())
242}
243
244/**
245 * Asserts that `a` is not `undefined`.
246 *
247 * @stability unstable
248 * @category testing
249 * @since 4.0.0
250 */
251export function assertDefined<A>(
252 a: A | undefined,
253 ..._: Array<never>
254): asserts a is Exclude<A, undefined> {
255 if (a === undefined) {
256 fail("Expected value to be defined")
257 }
258}
259
260/**
261 * Asserts that `a` is `undefined`.
262 *
263 * @stability unstable
264 * @category testing
265 * @since 4.0.0
266 */
267export function assertUndefined<A>(
268 a: A | undefined,
269 ..._: Array<never>
270): asserts a is undefined {
271 if (a !== undefined) {
272 fail("Expected value to be undefined")
273 }
274}
275
276/**
277 * Asserts that `option` is `Some` and contains a value equal to `expected`.
278 *
279 * @stability unstable
280 * @category testing
281 * @since 4.0.0
282 */
283export function assertSome<A>(
284 option: Option.Option<A>,
285 expected: A,
286 ..._: Array<never>
287): asserts option is Option.Some<A> {
288 deepStrictEqual(option, Option.some(expected))
289}
290
291// ----------------------------
292// Result
293// ----------------------------
294
295/**
296 * Asserts that `result` is `Success` and contains a value equal to `expected`.
297 *
298 * @stability unstable
299 * @category testing
300 * @since 4.0.0
301 */
302export function assertSuccess<A, E>(
303 result: Result.Result<A, E>,
304 expected: A,
305 ..._: Array<never>
306): asserts result is Result.Success<A, never> {
307 deepStrictEqual(result, Result.succeed(expected))
308}
309
310/**
311 * Asserts that `result` is `Failure` and contains an error equal to `expected`.
312 *
313 * @stability unstable
314 * @category testing
315 * @since 4.0.0
316 */
317export function assertFailure<A, E>(
318 result: Result.Result<A, E>,
319 expected: E,
320 ..._: Array<never>
321): asserts result is Result.Failure<never, E> {
322 deepStrictEqual(result, Result.fail(expected))
323}
324
325// ----------------------------
326// Exit
327// ----------------------------
328
329/**
330 * Asserts that `exit` is a failure with a cause equal to `expected`.
331 *
332 * @stability unstable
333 * @category testing
334 * @since 4.0.0
335 */
336export function assertExitFailure<A, E>(
337 exit: Exit.Exit<A, E>,
338 expected: Cause.Cause<E>,
339 ..._: Array<never>
340): asserts exit is Exit.Failure<never, E> {
341 deepStrictEqual(exit, Exit.failCause(expected))
342}
343
344/**
345 * Asserts that `exit` is a success with a value equal to `expected`.
346 *
347 * @stability unstable
348 * @category testing
349 * @since 4.0.0
350 */
351export function assertExitSuccess<A, E>(
352 exit: Exit.Exit<A, E>,
353 expected: A,
354 ..._: Array<never>
355): asserts exit is Exit.Success<A, never> {
356 deepStrictEqual(exit, Exit.succeed(expected))
357}