1import { Cause, Equal, Option, Predicate, Result, SchemaIssue } from "effect"
2import * as Exit from "effect/Exit"
3import * as assert from "node:assert"
4import { assert as vassert } from "vitest"
5
6// ----------------------------
7// Primitives
8// ----------------------------
9
10/**
11 * Throws an `AssertionError` with the provided error message.
12 */
13export function fail(message: string) {
14 assert.fail(message)
15}
16
17export function deepStrictEqual<A>(actual: A, expected: A, message?: string, ..._: Array<never>) {
18 if (message === undefined) {
19 assert.deepStrictEqual(actual, expected)
20 } else {
21 assert.deepStrictEqual(actual, expected, message)
22 }
23}
24
25export function notDeepStrictEqual<A>(actual: A, expected: A, message?: string, ..._: Array<never>) {
26 if (message !== undefined) {
27 assert.notDeepStrictEqual(actual, expected, message)
28 } else {
29 assert.notDeepStrictEqual(actual, expected)
30 }
31}
32
33export function strictEqual<A>(actual: A, expected: A, message?: string, ..._: Array<never>) {
34 if (message !== undefined) {
35 assert.strictEqual(actual, expected, message)
36 } else {
37 assert.strictEqual(actual, expected)
38 }
39}
40
41/**
42 * Asserts that `actual` is equal to `expected` using the `Equal.equals` trait.
43 */
44export function assertEquals<A>(actual: A, expected: A, message?: string, ..._: Array<never>) {
45 if (!Equal.equals(actual, expected)) {
46 deepStrictEqual(actual, expected, message) // show diff
47 fail(message ?? "Expected values to be Equal.equals")
48 }
49}
50
51export function doesNotThrow(thunk: () => void, message?: string, ..._: Array<never>) {
52 assert.doesNotThrow(thunk, message)
53}
54
55// ----------------------------
56// Derived
57// ----------------------------
58
59/**
60 * Asserts that `value` is an instance of `constructor`.
61 */
62export function assertInstanceOf<C extends new(...args: any) => any>(
63 value: unknown,
64 constructor: C,
65 message?: string,
66 ..._: Array<never>
67): asserts value is InstanceType<C> {
68 vassert.instanceOf(value, constructor, message)
69}
70
71export function assertTrue(self: unknown, message?: string, ..._: Array<never>): asserts self {
72 strictEqual(self, true, message)
73}
74
75export function assertFalse(self: boolean, message?: string, ..._: Array<never>) {
76 strictEqual(self, false, message)
77}
78
79export function assertSchemaIssueError(
80 self: unknown,
81 expectedIssueMessage: string,
82 ..._: Array<never>
83): asserts self is Error & { readonly cause: SchemaIssue.Issue } {
84 assertInstanceOf(self, Error)
85 strictEqual(self.message, "Schema validation failed")
86 assertTrue(SchemaIssue.isIssue(self.cause))
87 strictEqual(SchemaIssue.defaultFormatter(self.cause), expectedIssueMessage)
88}
89
90export function assertInclude(actual: string | undefined, expected: string, ..._: Array<never>) {
91 if (Predicate.isString(expected)) {
92 if (!actual?.includes(expected)) {
93 fail(`Expected\n\n${actual}\n\nto include\n\n${expected}`)
94 }
95 }
96}
97
98export function assertMatch(actual: string, regExp: RegExp, ..._: Array<never>) {
99 if (!regExp.test(actual)) {
100 fail(`Expected\n\n${actual}\n\nto match\n\n${regExp}`)
101 }
102}
103
104export function throws(thunk: () => void, error?: string | Error | ((u: unknown) => undefined), ..._: Array<never>) {
105 try {
106 thunk()
107 } catch (e) {
108 if (error !== undefined) {
109 if (Predicate.isString(error)) {
110 assertInstanceOf(e, Error)
111 strictEqual(e.message, error)
112 } else if (Predicate.isError(error)) {
113 deepStrictEqual(e, error)
114 } else {
115 error(e)
116 }
117 }
118 return
119 }
120 fail("Expected to throw an error")
121}
122
123export async function throwsAsync(
124 thunk: () => Promise<void>,
125 error?: Error | ((u: unknown) => undefined),
126 ..._: Array<never>
127) {
128 try {
129 await thunk()
130 } catch (e) {
131 if (error !== undefined) {
132 if (Predicate.isFunction(error)) {
133 error(e)
134 } else {
135 deepStrictEqual(e, error)
136 }
137 }
138 return
139 }
140 fail("Expected to throw an error")
141}
142
143// ----------------------------
144// Option
145// ----------------------------
146
147export function assertNone<A>(option: Option.Option<A>, ..._: Array<never>): asserts option is Option.None<never> {
148 deepStrictEqual(option, Option.none())
149}
150
151export function assertSome<A>(
152 option: Option.Option<A>,
153 expected: A,
154 ..._: Array<never>
155): asserts option is Option.Some<A> {
156 deepStrictEqual(option, Option.some(expected))
157}
158
159// ----------------------------
160// Result
161// ----------------------------
162
163export function assertFailure<A, E>(
164 result: Result.Result<A, E>,
165 expected: E,
166 ..._: Array<never>
167): asserts result is Result.Failure<never, E> {
168 deepStrictEqual(result, Result.fail(expected))
169}
170
171export function assertSuccess<A, E>(
172 result: Result.Result<A, E>,
173 expected: A,
174 ..._: Array<never>
175): asserts result is Result.Success<A, never> {
176 deepStrictEqual(result, Result.succeed(expected))
177}
178
179// ----------------------------
180// Exit
181// ----------------------------
182
183export function assertExitFailure<A, E>(
184 exit: Exit.Exit<A, E>,
185 expected: Cause.Cause<E>,
186 ..._: Array<never>
187): asserts exit is Exit.Failure<never, E> {
188 deepStrictEqual(exit, Exit.failCause(expected))
189}
190
191export function assertExitSuccess<A, E>(
192 exit: Exit.Exit<A, E>,
193 expected: A,
194 ..._: Array<never>
195): asserts exit is Exit.Success<A, never> {
196 deepStrictEqual(exit, Exit.succeed(expected))
197}
198
199export function assertCauseFail<E>(
200 cause: Cause.Cause<E>,
201 expected: E,
202 ..._: Array<never>
203) {
204 deepStrictEqual(cause, Cause.fail(expected))
205}