bupkis 0.2.0 → 0.4.0

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.
Files changed (187) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +35 -11
  3. package/dist/commonjs/assertion/assertion-async.d.ts +2 -1
  4. package/dist/commonjs/assertion/assertion-async.d.ts.map +1 -1
  5. package/dist/commonjs/assertion/assertion-async.js +84 -2
  6. package/dist/commonjs/assertion/assertion-async.js.map +1 -1
  7. package/dist/commonjs/assertion/assertion-sync.d.ts +1 -1
  8. package/dist/commonjs/assertion/assertion-sync.d.ts.map +1 -1
  9. package/dist/commonjs/assertion/assertion-sync.js +5 -1
  10. package/dist/commonjs/assertion/assertion-sync.js.map +1 -1
  11. package/dist/commonjs/assertion/assertion-types.d.ts +6 -2
  12. package/dist/commonjs/assertion/assertion-types.d.ts.map +1 -1
  13. package/dist/commonjs/assertion/assertion.d.ts +1 -1
  14. package/dist/commonjs/assertion/assertion.d.ts.map +1 -1
  15. package/dist/commonjs/assertion/assertion.js +1 -14
  16. package/dist/commonjs/assertion/assertion.js.map +1 -1
  17. package/dist/commonjs/assertion/impl/async.d.ts +122 -21
  18. package/dist/commonjs/assertion/impl/async.d.ts.map +1 -1
  19. package/dist/commonjs/assertion/impl/async.js +118 -90
  20. package/dist/commonjs/assertion/impl/async.js.map +1 -1
  21. package/dist/commonjs/assertion/impl/callback.d.ts +104 -0
  22. package/dist/commonjs/assertion/impl/callback.d.ts.map +1 -0
  23. package/dist/commonjs/assertion/impl/callback.js +694 -0
  24. package/dist/commonjs/assertion/impl/callback.js.map +1 -0
  25. package/dist/commonjs/assertion/impl/index.d.ts +1 -1
  26. package/dist/commonjs/assertion/impl/index.d.ts.map +1 -1
  27. package/dist/commonjs/assertion/impl/index.js.map +1 -1
  28. package/dist/commonjs/assertion/impl/sync-basic.d.ts.map +1 -1
  29. package/dist/commonjs/assertion/impl/sync-basic.js +1 -1
  30. package/dist/commonjs/assertion/impl/sync-basic.js.map +1 -1
  31. package/dist/commonjs/assertion/impl/sync-collection.d.ts +1 -1
  32. package/dist/commonjs/assertion/impl/sync-collection.js +3 -3
  33. package/dist/commonjs/assertion/impl/sync-collection.js.map +1 -1
  34. package/dist/commonjs/assertion/impl/sync-esoteric.js +1 -1
  35. package/dist/commonjs/assertion/impl/sync-esoteric.js.map +1 -1
  36. package/dist/commonjs/assertion/impl/sync-parametric.d.ts +22 -28
  37. package/dist/commonjs/assertion/impl/sync-parametric.d.ts.map +1 -1
  38. package/dist/commonjs/assertion/impl/sync-parametric.js +35 -50
  39. package/dist/commonjs/assertion/impl/sync-parametric.js.map +1 -1
  40. package/dist/commonjs/assertion/impl/sync.d.ts +68 -30
  41. package/dist/commonjs/assertion/impl/sync.d.ts.map +1 -1
  42. package/dist/commonjs/assertion/impl/sync.js +4 -1
  43. package/dist/commonjs/assertion/impl/sync.js.map +1 -1
  44. package/dist/commonjs/bootstrap.d.ts +147 -52
  45. package/dist/commonjs/bootstrap.d.ts.map +1 -1
  46. package/dist/commonjs/bootstrap.js +2 -3
  47. package/dist/commonjs/bootstrap.js.map +1 -1
  48. package/dist/commonjs/constant.d.ts +1 -1
  49. package/dist/commonjs/constant.d.ts.map +1 -1
  50. package/dist/commonjs/constant.js +8 -1
  51. package/dist/commonjs/constant.js.map +1 -1
  52. package/dist/commonjs/error.d.ts +22 -2
  53. package/dist/commonjs/error.d.ts.map +1 -1
  54. package/dist/commonjs/error.js +44 -4
  55. package/dist/commonjs/error.js.map +1 -1
  56. package/dist/commonjs/expect.d.ts.map +1 -1
  57. package/dist/commonjs/expect.js +1 -1
  58. package/dist/commonjs/expect.js.map +1 -1
  59. package/dist/commonjs/guards.d.ts +96 -5
  60. package/dist/commonjs/guards.d.ts.map +1 -1
  61. package/dist/commonjs/guards.js +104 -25
  62. package/dist/commonjs/guards.js.map +1 -1
  63. package/dist/commonjs/index.d.ts +146 -51
  64. package/dist/commonjs/index.d.ts.map +1 -1
  65. package/dist/commonjs/index.js.map +1 -1
  66. package/dist/commonjs/schema.d.ts +84 -18
  67. package/dist/commonjs/schema.d.ts.map +1 -1
  68. package/dist/commonjs/schema.js +107 -22
  69. package/dist/commonjs/schema.js.map +1 -1
  70. package/dist/commonjs/types.d.ts +171 -9
  71. package/dist/commonjs/types.d.ts.map +1 -1
  72. package/dist/commonjs/use.d.ts.map +1 -1
  73. package/dist/commonjs/use.js +15 -1
  74. package/dist/commonjs/use.js.map +1 -1
  75. package/dist/commonjs/util.d.ts +66 -50
  76. package/dist/commonjs/util.d.ts.map +1 -1
  77. package/dist/commonjs/util.js +169 -156
  78. package/dist/commonjs/util.js.map +1 -1
  79. package/dist/commonjs/value-to-schema.d.ts +122 -0
  80. package/dist/commonjs/value-to-schema.d.ts.map +1 -0
  81. package/dist/commonjs/value-to-schema.js +329 -0
  82. package/dist/commonjs/value-to-schema.js.map +1 -0
  83. package/dist/esm/assertion/assertion-async.d.ts +2 -1
  84. package/dist/esm/assertion/assertion-async.d.ts.map +1 -1
  85. package/dist/esm/assertion/assertion-async.js +85 -3
  86. package/dist/esm/assertion/assertion-async.js.map +1 -1
  87. package/dist/esm/assertion/assertion-sync.d.ts +1 -1
  88. package/dist/esm/assertion/assertion-sync.d.ts.map +1 -1
  89. package/dist/esm/assertion/assertion-sync.js +6 -2
  90. package/dist/esm/assertion/assertion-sync.js.map +1 -1
  91. package/dist/esm/assertion/assertion-types.d.ts +6 -2
  92. package/dist/esm/assertion/assertion-types.d.ts.map +1 -1
  93. package/dist/esm/assertion/assertion.d.ts +1 -1
  94. package/dist/esm/assertion/assertion.d.ts.map +1 -1
  95. package/dist/esm/assertion/assertion.js +1 -14
  96. package/dist/esm/assertion/assertion.js.map +1 -1
  97. package/dist/esm/assertion/impl/async.d.ts +122 -21
  98. package/dist/esm/assertion/impl/async.d.ts.map +1 -1
  99. package/dist/esm/assertion/impl/async.js +118 -90
  100. package/dist/esm/assertion/impl/async.js.map +1 -1
  101. package/dist/esm/assertion/impl/callback.d.ts +104 -0
  102. package/dist/esm/assertion/impl/callback.d.ts.map +1 -0
  103. package/dist/esm/assertion/impl/callback.js +691 -0
  104. package/dist/esm/assertion/impl/callback.js.map +1 -0
  105. package/dist/esm/assertion/impl/index.d.ts +1 -1
  106. package/dist/esm/assertion/impl/index.d.ts.map +1 -1
  107. package/dist/esm/assertion/impl/index.js +1 -1
  108. package/dist/esm/assertion/impl/index.js.map +1 -1
  109. package/dist/esm/assertion/impl/sync-basic.d.ts.map +1 -1
  110. package/dist/esm/assertion/impl/sync-basic.js +2 -2
  111. package/dist/esm/assertion/impl/sync-basic.js.map +1 -1
  112. package/dist/esm/assertion/impl/sync-collection.d.ts +1 -1
  113. package/dist/esm/assertion/impl/sync-collection.js +3 -3
  114. package/dist/esm/assertion/impl/sync-collection.js.map +1 -1
  115. package/dist/esm/assertion/impl/sync-esoteric.js +2 -2
  116. package/dist/esm/assertion/impl/sync-esoteric.js.map +1 -1
  117. package/dist/esm/assertion/impl/sync-parametric.d.ts +22 -28
  118. package/dist/esm/assertion/impl/sync-parametric.d.ts.map +1 -1
  119. package/dist/esm/assertion/impl/sync-parametric.js +36 -51
  120. package/dist/esm/assertion/impl/sync-parametric.js.map +1 -1
  121. package/dist/esm/assertion/impl/sync.d.ts +68 -30
  122. package/dist/esm/assertion/impl/sync.d.ts.map +1 -1
  123. package/dist/esm/assertion/impl/sync.js +3 -1
  124. package/dist/esm/assertion/impl/sync.js.map +1 -1
  125. package/dist/esm/bootstrap.d.ts +147 -52
  126. package/dist/esm/bootstrap.d.ts.map +1 -1
  127. package/dist/esm/bootstrap.js +1 -2
  128. package/dist/esm/bootstrap.js.map +1 -1
  129. package/dist/esm/constant.d.ts +1 -1
  130. package/dist/esm/constant.d.ts.map +1 -1
  131. package/dist/esm/constant.js +7 -0
  132. package/dist/esm/constant.js.map +1 -1
  133. package/dist/esm/error.d.ts +22 -2
  134. package/dist/esm/error.d.ts.map +1 -1
  135. package/dist/esm/error.js +43 -4
  136. package/dist/esm/error.js.map +1 -1
  137. package/dist/esm/expect.d.ts.map +1 -1
  138. package/dist/esm/expect.js +2 -2
  139. package/dist/esm/expect.js.map +1 -1
  140. package/dist/esm/guards.d.ts +96 -5
  141. package/dist/esm/guards.d.ts.map +1 -1
  142. package/dist/esm/guards.js +98 -21
  143. package/dist/esm/guards.js.map +1 -1
  144. package/dist/esm/index.d.ts +146 -51
  145. package/dist/esm/index.d.ts.map +1 -1
  146. package/dist/esm/index.js.map +1 -1
  147. package/dist/esm/schema.d.ts +84 -18
  148. package/dist/esm/schema.d.ts.map +1 -1
  149. package/dist/esm/schema.js +107 -22
  150. package/dist/esm/schema.js.map +1 -1
  151. package/dist/esm/types.d.ts +171 -9
  152. package/dist/esm/types.d.ts.map +1 -1
  153. package/dist/esm/use.d.ts.map +1 -1
  154. package/dist/esm/use.js +15 -1
  155. package/dist/esm/use.js.map +1 -1
  156. package/dist/esm/util.d.ts +66 -50
  157. package/dist/esm/util.d.ts.map +1 -1
  158. package/dist/esm/util.js +153 -154
  159. package/dist/esm/util.js.map +1 -1
  160. package/dist/esm/value-to-schema.d.ts +122 -0
  161. package/dist/esm/value-to-schema.d.ts.map +1 -0
  162. package/dist/esm/value-to-schema.js +325 -0
  163. package/dist/esm/value-to-schema.js.map +1 -0
  164. package/package.json +16 -13
  165. package/src/assertion/assertion-async.ts +113 -3
  166. package/src/assertion/assertion-sync.ts +5 -2
  167. package/src/assertion/assertion-types.ts +14 -4
  168. package/src/assertion/assertion.ts +2 -17
  169. package/src/assertion/impl/async.ts +137 -93
  170. package/src/assertion/impl/callback.ts +882 -0
  171. package/src/assertion/impl/index.ts +1 -1
  172. package/src/assertion/impl/sync-basic.ts +5 -2
  173. package/src/assertion/impl/sync-collection.ts +3 -3
  174. package/src/assertion/impl/sync-esoteric.ts +2 -2
  175. package/src/assertion/impl/sync-parametric.ts +47 -54
  176. package/src/assertion/impl/sync.ts +3 -0
  177. package/src/bootstrap.ts +1 -2
  178. package/src/constant.ts +10 -0
  179. package/src/error.ts +57 -3
  180. package/src/expect.ts +6 -2
  181. package/src/guards.ts +125 -18
  182. package/src/index.ts +3 -0
  183. package/src/schema.ts +121 -23
  184. package/src/types.ts +205 -10
  185. package/src/use.ts +22 -0
  186. package/src/util.ts +168 -223
  187. package/src/value-to-schema.ts +489 -0
@@ -0,0 +1,882 @@
1
+ /**
2
+ * Callback-based assertion implementations.
3
+ *
4
+ * For callback invocation, error handling, and value validation in both
5
+ * synchronous and asynchronous contexts.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import { inspect } from 'node:util';
11
+ import { z } from 'zod/v4';
12
+
13
+ import { isA, isNonNullObject, isString } from '../../guards.js';
14
+ import { ConstructibleSchema, FunctionSchema } from '../../schema.js';
15
+ import {
16
+ valueToSchema,
17
+ type ValueToSchemaOptions,
18
+ valueToSchemaOptionsForDeepEqual,
19
+ valueToSchemaOptionsForSatisfies,
20
+ } from '../../value-to-schema.js';
21
+ import { createAssertion, createAsyncAssertion } from '../create.js';
22
+
23
+ /**
24
+ * Creates a standardized error object for when a callback or nodeback is not
25
+ * called.
26
+ *
27
+ * @param type - The type of callback ('callback' or 'nodeback')
28
+ * @returns Error object with consistent structure for sync assertions
29
+ */
30
+ /* c8 ignore next */
31
+ const createNotCalledError = (type: 'callback' | 'nodeback') => ({
32
+ actual: 'not called',
33
+ expected: `${type} to be called`,
34
+ message: `Expected ${type} to be called, but it was not called`,
35
+ });
36
+
37
+ /**
38
+ * Creates a standardized error object for when a callback or nodeback is not
39
+ * called in async contexts. Uses consistent field naming for async assertions.
40
+ *
41
+ * @param type - The type of callback ('callback' or 'nodeback')
42
+ * @returns Error object with consistent structure for async assertions
43
+ */
44
+ /* c8 ignore next */
45
+ const createAsyncNotCalledError = (type: 'callback' | 'nodeback') => ({
46
+ actual: `${type} not called`,
47
+ expected: `${type} to be called`,
48
+ message: `Expected ${type} to be called, but it was not`,
49
+ });
50
+
51
+ /**
52
+ * Creates error objects for nodeback error state mismatches in sync contexts.
53
+ * Handles cases where an error is expected but not present, or vice versa.
54
+ *
55
+ * @param hasError - Whether the nodeback was called with an error
56
+ * @param expectError - Whether an error was expected
57
+ * @param error - The actual error value (if any)
58
+ * @returns Error object if there's a mismatch, undefined if the state matches
59
+ * expectations
60
+ */
61
+ const createErrorMismatchError = (
62
+ hasError: boolean,
63
+ expectError: boolean,
64
+ error?: unknown,
65
+ ) => {
66
+ if (hasError && !expectError) {
67
+ return {
68
+ actual: 'called with error',
69
+ expected: 'called without error',
70
+ message: `Expected nodeback to be called without error, but it was called with error: ${inspect(
71
+ error,
72
+ { depth: 2 },
73
+ )}`,
74
+ };
75
+ }
76
+ if (!hasError && expectError) {
77
+ return {
78
+ actual: 'called without error',
79
+ expected: 'called with error',
80
+ message:
81
+ 'Expected nodeback to be called with an error, but it was called without error',
82
+ };
83
+ }
84
+ return undefined;
85
+ };
86
+
87
+ /**
88
+ * Creates error objects for nodeback error state mismatches in async contexts.
89
+ * Handles cases where an error is expected but not present, or vice versa. Uses
90
+ * consistent field naming and messaging for async assertions.
91
+ *
92
+ * @param hasError - Whether the nodeback was called with an error
93
+ * @param expectError - Whether an error was expected
94
+ * @param error - The actual error value (if any)
95
+ * @returns Error object if there's a mismatch, undefined if the state matches
96
+ * expectations
97
+ */
98
+ const createAsyncErrorMismatchError = (
99
+ hasError: boolean,
100
+ expectError: boolean,
101
+ error?: unknown,
102
+ ) => {
103
+ if (hasError && !expectError) {
104
+ return {
105
+ actual: 'called with error',
106
+ expected: 'called without error',
107
+ message: `Expected nodeback to be called without error, but it was called with error: ${inspect(
108
+ error,
109
+ { depth: 2 },
110
+ )}`,
111
+ };
112
+ }
113
+ if (!hasError && expectError) {
114
+ return {
115
+ actual: 'called without error',
116
+ expected: 'called with error',
117
+ message:
118
+ 'Expected nodeback to be called with an error, but it was called without an error',
119
+ };
120
+ }
121
+ return undefined;
122
+ };
123
+
124
+ /**
125
+ * Creates a standardized error object for exact value comparison failures. Used
126
+ * when a callback/nodeback is called with a value that doesn't match the
127
+ * expected value using Object.is() strict equality.
128
+ *
129
+ * @param actual - The actual value the callback was called with
130
+ * @param expected - The expected value
131
+ * @param callbackType - The type of callback for error messaging
132
+ * @returns Error object with formatted comparison message
133
+ */
134
+ const createValueMismatchError = (
135
+ actual: unknown,
136
+ expected: unknown,
137
+ callbackType: 'callback' | 'nodeback',
138
+ ) => ({
139
+ actual,
140
+ expected,
141
+ message: `Expected ${callbackType} to be called with exactly ${inspect(
142
+ expected,
143
+ { depth: 2 },
144
+ )}, but it was called with ${inspect(actual, { depth: 2 })}`,
145
+ });
146
+
147
+ /**
148
+ * Creates a Zod schema for validating error parameters in callback assertions.
149
+ * Handles string literals, RegExp patterns, and object structures for error
150
+ * matching.
151
+ *
152
+ * @param param - The error parameter to create a schema for
153
+ * @returns Zod schema that can validate the error against the parameter
154
+ * @throws TypeError if the parameter type is not supported
155
+ */
156
+ const createErrorSchema = (param: unknown): z.ZodType => {
157
+ if (isString(param)) {
158
+ return z
159
+ .looseObject({
160
+ message: z.coerce.string().pipe(z.literal(param)),
161
+ })
162
+ .or(z.coerce.string().pipe(z.literal(param)));
163
+ }
164
+ if (isA(param, RegExp)) {
165
+ return z
166
+ .looseObject({
167
+ message: z.coerce.string().regex(param),
168
+ })
169
+ .or(z.coerce.string().regex(param));
170
+ }
171
+ if (isNonNullObject(param)) {
172
+ return valueToSchema(param, valueToSchemaOptionsForDeepEqual);
173
+ }
174
+ throw new TypeError(
175
+ `Invalid parameter schema: ${inspect(param, { depth: 2 })}`,
176
+ );
177
+ };
178
+
179
+ /**
180
+ * Validates a value against a parameter using valueToSchema with specified
181
+ * options. Centralizes the common pattern of schema creation and validation
182
+ * used across many callback assertions.
183
+ *
184
+ * @param value - The value to validate
185
+ * @param param - The parameter to validate against
186
+ * @param options - Options to pass to valueToSchema
187
+ * @returns Undefined if validation succeeds, ZodError if validation fails
188
+ */
189
+ const validateValue = (
190
+ value: unknown,
191
+ param: unknown,
192
+ options: ValueToSchemaOptions = {},
193
+ ) => {
194
+ const schema = valueToSchema(param, options);
195
+ const result = schema.safeParse(value);
196
+ return result.success ? undefined : result.error;
197
+ };
198
+
199
+ /**
200
+ * Traps the invocation of a single-parameter callback function for synchronous
201
+ * testing. Executes the provided function with a callback and captures whether
202
+ * it was called, what arguments it received, and any errors thrown during
203
+ * execution.
204
+ *
205
+ * @param fn - The function to execute that should call the provided callback
206
+ * @returns Object containing invocation details: args, called, error, and value
207
+ */
208
+ const trapCallbackInvocation = (fn: (...args: unknown[]) => unknown) => {
209
+ let called = false;
210
+ let error: unknown;
211
+ let value: unknown;
212
+ let args: unknown[] = [];
213
+
214
+ const callback = (...cbArgs: unknown[]) => {
215
+ called = true;
216
+ args = cbArgs;
217
+ if (cbArgs.length >= 1) {
218
+ value = cbArgs[0];
219
+ }
220
+ };
221
+
222
+ try {
223
+ fn(callback);
224
+ } catch (thrownError) {
225
+ error = thrownError;
226
+ }
227
+
228
+ return { args, called, error, value };
229
+ };
230
+
231
+ /**
232
+ * Traps the invocation of a nodeback (error-first callback) function for
233
+ * synchronous testing. Executes the provided function with a nodeback and
234
+ * captures whether it was called, what arguments it received, and any errors
235
+ * thrown during execution.
236
+ *
237
+ * @param fn - The function to execute that should call the provided nodeback
238
+ * @returns Object containing invocation details: args, called, error, and value
239
+ */
240
+ const trapNodebackInvocation = (fn: (...args: unknown[]) => unknown) => {
241
+ let called = false;
242
+ let error: unknown;
243
+ let value: unknown;
244
+ let args: unknown[] = [];
245
+
246
+ const nodeback = (err: unknown, val?: unknown) => {
247
+ called = true;
248
+ args = [err, val];
249
+ error = err;
250
+ value = val;
251
+ };
252
+
253
+ try {
254
+ fn(nodeback);
255
+ } catch (thrownError) {
256
+ error = thrownError;
257
+ }
258
+
259
+ return { args, called, error, value };
260
+ };
261
+
262
+ /**
263
+ * Traps the invocation of a single-parameter callback function for asynchronous
264
+ * testing. Returns a Promise that resolves with invocation details once the
265
+ * callback is called or an error occurs. Uses a Proxy to catch errors during
266
+ * function execution.
267
+ *
268
+ * @param fn - The function to execute that should call the provided callback
269
+ * @returns Promise resolving to object with invocation details: args, called,
270
+ * error, and value
271
+ */
272
+ const trapAsyncCallbackInvocation = async (
273
+ fn: (...args: unknown[]) => unknown,
274
+ ) => {
275
+ return new Promise<{
276
+ args: unknown[];
277
+ called: boolean;
278
+ error: unknown;
279
+ value: unknown;
280
+ }>((resolve, _reject) => {
281
+ let called = false;
282
+ let error: unknown;
283
+ let value: unknown;
284
+ let args: unknown[] = [];
285
+
286
+ const callback = (...cbArgs: unknown[]) => {
287
+ called = true;
288
+ args = cbArgs;
289
+ if (cbArgs.length >= 1) {
290
+ value = cbArgs[0];
291
+ }
292
+ resolve({ args, called, error, value });
293
+ };
294
+
295
+ const proxiedFn = new Proxy(fn, {
296
+ apply(target, thisArg, argumentsList) {
297
+ try {
298
+ return Reflect.apply(target, thisArg, argumentsList);
299
+ } catch (thrownError) {
300
+ error = thrownError;
301
+ resolve({ args, called, error, value });
302
+ }
303
+ },
304
+ });
305
+
306
+ try {
307
+ proxiedFn(callback);
308
+ } catch (thrownError) {
309
+ error = thrownError;
310
+ resolve({ args, called, error, value });
311
+ }
312
+ });
313
+ };
314
+
315
+ /**
316
+ * Traps the invocation of a nodeback (error-first callback) function for
317
+ * asynchronous testing. Returns a Promise that resolves with invocation details
318
+ * once the nodeback is called or an error occurs. Uses a Proxy to catch errors
319
+ * during function execution. Always resolves (never rejects) to allow assertion
320
+ * logic to handle error conditions.
321
+ *
322
+ * @param fn - The function to execute that should call the provided nodeback
323
+ * @returns Promise resolving to object with invocation details: args, called,
324
+ * error, and value
325
+ */
326
+ const trapAsyncNodebackInvocation = async (
327
+ fn: (...args: unknown[]) => unknown,
328
+ ) => {
329
+ return new Promise<{
330
+ args: unknown[];
331
+ called: boolean;
332
+ error: unknown;
333
+ value: unknown;
334
+ }>((resolve, _reject) => {
335
+ let called = false;
336
+ let error: unknown;
337
+ let value: unknown;
338
+ let args: unknown[] = [];
339
+
340
+ const nodeback = (err: unknown, val?: unknown) => {
341
+ called = true;
342
+ args = [err, val];
343
+ error = err;
344
+ value = val;
345
+
346
+ // Always resolve with the result data, don't reject on nodeback errors
347
+ // The assertion logic will check if error is truthy
348
+ resolve({ args, called, error, value });
349
+ };
350
+
351
+ const proxiedFn = new Proxy(fn, {
352
+ apply(target, thisArg, argumentsList) {
353
+ try {
354
+ return Reflect.apply(target, thisArg, argumentsList);
355
+ } catch (thrownError) {
356
+ error = thrownError;
357
+ resolve({ args, called, error, value });
358
+ }
359
+ },
360
+ });
361
+
362
+ try {
363
+ proxiedFn(nodeback);
364
+ } catch (thrownError) {
365
+ error = thrownError;
366
+ resolve({ args, called, error, value });
367
+ }
368
+ });
369
+ };
370
+
371
+ export const CallbackSyncAssertions = [
372
+ // Basic callback invocation - synchronous
373
+ createAssertion(
374
+ [FunctionSchema, ['to call callback', 'to invoke callback']],
375
+ (subject) => {
376
+ const { called } = trapCallbackInvocation(subject);
377
+ return called;
378
+ },
379
+ ),
380
+
381
+ createAssertion(
382
+ [FunctionSchema, ['to call nodeback', 'to invoke nodeback']],
383
+ (subject) => {
384
+ const { called } = trapNodebackInvocation(subject);
385
+ return called;
386
+ },
387
+ ),
388
+
389
+ // Callback with value - deep equality check
390
+ createAssertion(
391
+ [
392
+ FunctionSchema,
393
+ ['to call callback with', 'to invoke callback with'],
394
+ z.unknown(),
395
+ ],
396
+ (subject, param) => {
397
+ const { called, value } = trapCallbackInvocation(subject);
398
+ /* c8 ignore next */
399
+ if (!called) {
400
+ return createNotCalledError('callback');
401
+ }
402
+
403
+ return validateValue(value, param, valueToSchemaOptionsForDeepEqual);
404
+ },
405
+ ),
406
+
407
+ // Callback with exact value - strict equality check
408
+ createAssertion(
409
+ [
410
+ FunctionSchema,
411
+ [
412
+ 'to call callback with exactly',
413
+ 'to call callback with exact value',
414
+ 'to invoke callback with exactly',
415
+ 'to invoke callback with exact value',
416
+ ],
417
+ z.unknown(),
418
+ ],
419
+ (subject, expected) => {
420
+ const { called, value } = trapCallbackInvocation(subject);
421
+ /* c8 ignore next */
422
+ if (!called) {
423
+ return createNotCalledError('callback');
424
+ }
425
+
426
+ if (!Object.is(value, expected)) {
427
+ return createValueMismatchError(value, expected, 'callback');
428
+ }
429
+ },
430
+ ),
431
+
432
+ // Nodeback with value - deep equality check
433
+ createAssertion(
434
+ [
435
+ FunctionSchema,
436
+ ['to call nodeback with', 'to invoke nodeback with'],
437
+ z.unknown(),
438
+ ],
439
+ (subject, param) => {
440
+ const { called, error, value } = trapNodebackInvocation(subject);
441
+ /* c8 ignore next */
442
+ if (!called) {
443
+ return createNotCalledError('nodeback');
444
+ }
445
+
446
+ const errorResult = createErrorMismatchError(!!error, false, error);
447
+ if (errorResult) {
448
+ return errorResult;
449
+ }
450
+
451
+ return validateValue(value, param, valueToSchemaOptionsForDeepEqual);
452
+ },
453
+ ),
454
+
455
+ // Nodeback with exact value - strict equality check
456
+ createAssertion(
457
+ [
458
+ FunctionSchema,
459
+ [
460
+ 'to call nodeback with exactly',
461
+ 'to call nodeback with exact value',
462
+ 'to invoke nodeback with exactly',
463
+ 'to invoke nodeback with exact value',
464
+ ],
465
+ z.unknown(),
466
+ ],
467
+ (subject, expected) => {
468
+ const { called, error, value } = trapNodebackInvocation(subject);
469
+ /* c8 ignore next */
470
+ if (!called) {
471
+ return createNotCalledError('nodeback');
472
+ }
473
+
474
+ const errorResult = createErrorMismatchError(!!error, false, error);
475
+ if (errorResult) {
476
+ return errorResult;
477
+ }
478
+
479
+ if (!Object.is(value, expected)) {
480
+ return createValueMismatchError(value, expected, 'nodeback');
481
+ }
482
+ },
483
+ ),
484
+
485
+ // Nodeback with error - synchronous
486
+ createAssertion(
487
+ [
488
+ FunctionSchema,
489
+ ['to call nodeback with error', 'to invoke nodeback with error'],
490
+ ],
491
+ (subject) => {
492
+ const { called, error } = trapNodebackInvocation(subject);
493
+ /* c8 ignore next */
494
+ if (!called) {
495
+ return createNotCalledError('nodeback');
496
+ }
497
+
498
+ return createErrorMismatchError(!!error, true, error);
499
+ },
500
+ ),
501
+
502
+ // Nodeback with specific error class - synchronous
503
+ createAssertion(
504
+ [
505
+ FunctionSchema,
506
+ [
507
+ 'to call nodeback with a',
508
+ 'to call nodeback with an',
509
+ 'to invoke nodeback with a',
510
+ 'to invoke nodeback with an',
511
+ ],
512
+ ConstructibleSchema,
513
+ ],
514
+ (subject, ctor) => {
515
+ const { called, error } = trapNodebackInvocation(subject);
516
+ /* c8 ignore next */
517
+ if (!called) {
518
+ return createNotCalledError('nodeback');
519
+ }
520
+
521
+ const errorResult = createErrorMismatchError(!!error, true, error);
522
+ if (errorResult) {
523
+ return errorResult;
524
+ }
525
+
526
+ if (!isA(error, ctor)) {
527
+ return {
528
+ actual: error,
529
+ expected: `instance of ${ctor.name}`,
530
+ message: `Expected nodeback to be called with an instance of ${ctor.name}, but it was called with ${inspect(
531
+ error,
532
+ { depth: 2 },
533
+ )}`,
534
+ };
535
+ }
536
+ },
537
+ ),
538
+
539
+ // Nodeback with specific error pattern - synchronous
540
+ createAssertion(
541
+ [
542
+ FunctionSchema,
543
+ ['to call nodeback with error', 'to invoke nodeback with error'],
544
+ z.union([z.string(), z.instanceof(RegExp), z.looseObject({})]),
545
+ ],
546
+ (subject, param) => {
547
+ const { called, error } = trapNodebackInvocation(subject);
548
+ /* c8 ignore next */
549
+ if (!called) {
550
+ return createNotCalledError('nodeback');
551
+ }
552
+
553
+ const errorResult = createErrorMismatchError(!!error, true, error);
554
+ if (errorResult) {
555
+ return errorResult;
556
+ }
557
+
558
+ const schema = createErrorSchema(param);
559
+ const result = schema.safeParse(error);
560
+ if (!result.success) {
561
+ return result.error;
562
+ }
563
+ },
564
+ ),
565
+
566
+ // Callback satisfying pattern - partial matching
567
+ createAssertion(
568
+ [
569
+ FunctionSchema,
570
+ [
571
+ 'to call callback with value satisfying',
572
+ 'to invoke callback with value satisfying',
573
+ ],
574
+ z.union([z.string(), z.instanceof(RegExp), z.looseObject({})]),
575
+ ],
576
+ (subject, param) => {
577
+ const { called, value } = trapCallbackInvocation(subject);
578
+ /* c8 ignore next */
579
+ if (!called) {
580
+ return createNotCalledError('callback');
581
+ }
582
+
583
+ return validateValue(value, param, valueToSchemaOptionsForSatisfies);
584
+ },
585
+ ),
586
+
587
+ // Nodeback satisfying pattern - partial matching
588
+ createAssertion(
589
+ [
590
+ FunctionSchema,
591
+ [
592
+ 'to call nodeback with value satisfying',
593
+ 'to invoke nodeback with value satisfying',
594
+ ],
595
+ z.union([z.string(), z.instanceof(RegExp), z.looseObject({})]),
596
+ ],
597
+ (subject, param) => {
598
+ const { called, error, value } = trapNodebackInvocation(subject);
599
+ /* c8 ignore next */
600
+ if (!called) {
601
+ return createNotCalledError('nodeback');
602
+ }
603
+
604
+ const errorResult = createErrorMismatchError(!!error, false, error);
605
+ if (errorResult) {
606
+ return errorResult;
607
+ }
608
+
609
+ return validateValue(value, param, valueToSchemaOptionsForSatisfies);
610
+ },
611
+ ),
612
+ ] as const;
613
+
614
+ export const CallbackAsyncAssertions = [
615
+ // Async versions
616
+ createAsyncAssertion(
617
+ [
618
+ FunctionSchema,
619
+ ['to eventually call callback', 'to eventually invoke callback'],
620
+ ],
621
+ async (subject) => {
622
+ const { called } = await trapAsyncCallbackInvocation(subject);
623
+ /* c8 ignore next */
624
+ if (!called) {
625
+ return {
626
+ actual: 'callback not called',
627
+ expected: 'callback to be called',
628
+ message: `Expected callback to be called, but it was not`,
629
+ };
630
+ }
631
+ },
632
+ ),
633
+
634
+ createAsyncAssertion(
635
+ [
636
+ FunctionSchema,
637
+ ['to eventually call nodeback', 'to eventually invoke nodeback'],
638
+ ],
639
+ async (subject) => {
640
+ const { called } = await trapAsyncNodebackInvocation(subject);
641
+ /* c8 ignore next */
642
+ if (!called) {
643
+ return {
644
+ actual: 'nodeback not called',
645
+ expected: 'nodeback to be called',
646
+ message: `Expected nodeback to be called, but it was not`,
647
+ };
648
+ }
649
+ },
650
+ ),
651
+
652
+ // Async callback with value - deep equality check
653
+ createAsyncAssertion(
654
+ [
655
+ FunctionSchema,
656
+ [
657
+ 'to eventually call callback with',
658
+ 'to eventually invoke callback with',
659
+ ],
660
+ z.unknown(),
661
+ ],
662
+ async (subject, param) => {
663
+ const { called, value } = await trapAsyncCallbackInvocation(subject);
664
+ /* c8 ignore next */
665
+ if (!called) {
666
+ return createAsyncNotCalledError('callback');
667
+ }
668
+
669
+ return validateValue(value, param, valueToSchemaOptionsForDeepEqual);
670
+ },
671
+ ),
672
+
673
+ // Async callback with exact value - strict equality check
674
+ createAsyncAssertion(
675
+ [
676
+ FunctionSchema,
677
+ [
678
+ 'to eventually call callback with exactly',
679
+ 'to eventually call callback with exact value',
680
+ 'to eventually invoke callback with exactly',
681
+ 'to eventually invoke callback with exact value',
682
+ ],
683
+ z.unknown(),
684
+ ],
685
+ async (subject, expected) => {
686
+ const { called, value } = await trapAsyncCallbackInvocation(subject);
687
+ /* c8 ignore next */
688
+ if (!called) {
689
+ return createAsyncNotCalledError('callback');
690
+ }
691
+ return Object.is(value, expected);
692
+ },
693
+ ),
694
+
695
+ // Async nodeback with value - deep equality check
696
+ createAsyncAssertion(
697
+ [
698
+ FunctionSchema,
699
+ [
700
+ 'to eventually call nodeback with',
701
+ 'to eventually invoke nodeback with',
702
+ ],
703
+ z.unknown(),
704
+ ],
705
+ async (subject, param) => {
706
+ const { called, error, value } =
707
+ await trapAsyncNodebackInvocation(subject);
708
+ /* c8 ignore next */
709
+ if (!called) {
710
+ return createAsyncNotCalledError('nodeback');
711
+ }
712
+
713
+ const errorResult = createAsyncErrorMismatchError(!!error, false, error);
714
+ if (errorResult) {
715
+ return errorResult;
716
+ }
717
+
718
+ return validateValue(value, param, valueToSchemaOptionsForDeepEqual);
719
+ },
720
+ ),
721
+
722
+ // Async nodeback with exact value - strict equality check
723
+ createAsyncAssertion(
724
+ [
725
+ FunctionSchema,
726
+ [
727
+ 'to eventually call nodeback with exactly',
728
+ 'to eventually call nodeback with exact value',
729
+ 'to eventually invoke nodeback with exactly',
730
+ 'to eventually invoke nodeback with exact value',
731
+ ],
732
+ z.unknown(),
733
+ ],
734
+ async (subject, expected) => {
735
+ const { called, error, value } =
736
+ await trapAsyncNodebackInvocation(subject);
737
+ /* c8 ignore next */
738
+ if (!called) {
739
+ return createAsyncNotCalledError('nodeback');
740
+ }
741
+
742
+ const errorResult = createAsyncErrorMismatchError(!!error, false, error);
743
+ if (errorResult) {
744
+ return errorResult;
745
+ }
746
+
747
+ return Object.is(value, expected);
748
+ },
749
+ ),
750
+
751
+ createAsyncAssertion(
752
+ [
753
+ FunctionSchema,
754
+ [
755
+ 'to eventually call nodeback with error',
756
+ 'to eventually invoke nodeback with error',
757
+ ],
758
+ ],
759
+ async (subject) => {
760
+ const { called, error } = await trapAsyncNodebackInvocation(subject);
761
+ /* c8 ignore next */
762
+ if (!called) {
763
+ return createAsyncNotCalledError('nodeback');
764
+ }
765
+
766
+ return createAsyncErrorMismatchError(!!error, true, error);
767
+ },
768
+ ),
769
+
770
+ createAsyncAssertion(
771
+ [
772
+ FunctionSchema,
773
+ [
774
+ 'to eventually call nodeback with a',
775
+ 'to eventually call nodeback with an',
776
+ 'to eventually invoke nodeback with a',
777
+ 'to eventually invoke nodeback with an',
778
+ ],
779
+ ConstructibleSchema,
780
+ ],
781
+ async (subject, ctor) => {
782
+ const { called, error } = await trapAsyncNodebackInvocation(subject);
783
+ /* c8 ignore next */
784
+ if (!called) {
785
+ return createAsyncNotCalledError('nodeback');
786
+ }
787
+
788
+ const errorResult = createAsyncErrorMismatchError(!!error, true, error);
789
+ if (errorResult) {
790
+ return errorResult;
791
+ }
792
+
793
+ if (!isA(error, ctor)) {
794
+ return {
795
+ actual: error,
796
+ expected: `instance of ${ctor.name}`,
797
+ message: `Expected nodeback to be called with an instance of ${ctor.name}, but it was called with ${inspect(
798
+ error,
799
+ { depth: 2 },
800
+ )}`,
801
+ };
802
+ }
803
+ },
804
+ ),
805
+
806
+ createAsyncAssertion(
807
+ [
808
+ FunctionSchema,
809
+ [
810
+ 'to eventually call nodeback with error',
811
+ 'to eventually invoke nodeback with error',
812
+ ],
813
+ z.union([z.string(), z.instanceof(RegExp), z.looseObject({})]),
814
+ ],
815
+ async (subject, param) => {
816
+ const { called, error } = await trapAsyncNodebackInvocation(subject);
817
+ /* c8 ignore next */
818
+ if (!called) {
819
+ return createAsyncNotCalledError('nodeback');
820
+ }
821
+
822
+ const errorResult = createAsyncErrorMismatchError(!!error, true, error);
823
+ if (errorResult) {
824
+ return errorResult;
825
+ }
826
+
827
+ const schema = createErrorSchema(param);
828
+ const result = schema.safeParse(error);
829
+ if (!result.success) {
830
+ return result.error;
831
+ }
832
+ },
833
+ ),
834
+
835
+ // Async callback satisfying pattern - partial matching
836
+ createAsyncAssertion(
837
+ [
838
+ FunctionSchema,
839
+ [
840
+ 'to eventually call callback with value satisfying',
841
+ 'to eventually invoke callback with value satisfying',
842
+ ],
843
+ z.union([z.string(), z.instanceof(RegExp), z.looseObject({})]),
844
+ ],
845
+ async (subject, param) => {
846
+ const { called, value } = await trapAsyncCallbackInvocation(subject);
847
+ /* c8 ignore next */
848
+ if (!called) {
849
+ return createAsyncNotCalledError('callback');
850
+ }
851
+
852
+ return validateValue(value, param, valueToSchemaOptionsForSatisfies);
853
+ },
854
+ ),
855
+
856
+ // Async nodeback satisfying pattern - partial matching
857
+ createAsyncAssertion(
858
+ [
859
+ FunctionSchema,
860
+ [
861
+ 'to eventually call nodeback with value satisfying',
862
+ 'to eventually invoke nodeback with value satisfying',
863
+ ],
864
+ z.union([z.string(), z.instanceof(RegExp), z.looseObject({})]),
865
+ ],
866
+ async (subject, param) => {
867
+ const { called, error, value } =
868
+ await trapAsyncNodebackInvocation(subject);
869
+ /* c8 ignore next */
870
+ if (!called) {
871
+ return createAsyncNotCalledError('nodeback');
872
+ }
873
+
874
+ const errorResult = createAsyncErrorMismatchError(!!error, false, error);
875
+ if (errorResult) {
876
+ return errorResult;
877
+ }
878
+
879
+ return validateValue(value, param, valueToSchemaOptionsForSatisfies);
880
+ },
881
+ ),
882
+ ] as const;