(
testOrHook: Circus.TestEntry | Circus.Hook,
testContext: Circus.TestContext,
{isHook, timeout}: {isHook: boolean; timeout: number},
)
| 194 | } |
| 195 | |
| 196 | export const callAsyncCircusFn = ( |
| 197 | testOrHook: Circus.TestEntry | Circus.Hook, |
| 198 | testContext: Circus.TestContext, |
| 199 | {isHook, timeout}: {isHook: boolean; timeout: number}, |
| 200 | ): Promise<unknown> => { |
| 201 | let timeoutID: NodeJS.Timeout; |
| 202 | let completed = false; |
| 203 | |
| 204 | const {fn, asyncError} = testOrHook; |
| 205 | const doneCallback = takesDoneCallback(fn); |
| 206 | |
| 207 | return new Promise<void>((resolve, reject) => { |
| 208 | timeoutID = setTimeout( |
| 209 | () => reject(_makeTimeoutMessage(timeout, isHook, doneCallback)), |
| 210 | timeout, |
| 211 | ); |
| 212 | |
| 213 | // If this fn accepts `done` callback we return a promise that fulfills as |
| 214 | // soon as `done` called. |
| 215 | if (doneCallback) { |
| 216 | let returnedValue: unknown = undefined; |
| 217 | |
| 218 | const done = (reason?: Error | string): void => { |
| 219 | // We need to keep a stack here before the promise tick |
| 220 | const errorAtDone = new ErrorWithStack(undefined, done); |
| 221 | |
| 222 | if (!completed && testOrHook.seenDone) { |
| 223 | errorAtDone.message = |
| 224 | 'Expected done to be called once, but it was called multiple times.'; |
| 225 | |
| 226 | if (reason) { |
| 227 | errorAtDone.message += ` Reason: ${prettyFormat(reason, { |
| 228 | maxDepth: 3, |
| 229 | })}`; |
| 230 | } |
| 231 | reject(errorAtDone); |
| 232 | throw errorAtDone; |
| 233 | } else { |
| 234 | testOrHook.seenDone = true; |
| 235 | } |
| 236 | |
| 237 | // Use `Promise.resolve` to allow the event loop to go a single tick in case `done` is called synchronously |
| 238 | Promise.resolve().then(() => { |
| 239 | if (returnedValue !== undefined) { |
| 240 | asyncError.message = dedent` |
| 241 | Test functions cannot both take a 'done' callback and return something. Either use a 'done' callback, or return a promise. |
| 242 | Returned value: ${prettyFormat(returnedValue, {maxDepth: 3})} |
| 243 | `; |
| 244 | return reject(asyncError); |
| 245 | } |
| 246 | |
| 247 | let errorAsErrorObject: Error; |
| 248 | if (checkIsError(reason)) { |
| 249 | errorAsErrorObject = reason; |
| 250 | } else { |
| 251 | errorAsErrorObject = errorAtDone; |
| 252 | errorAtDone.message = `Failed: ${prettyFormat(reason, { |
| 253 | maxDepth: 3, |
no test coverage detected