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