{"record":{"id":"f2bf65cbb5d6727e","repo":"vitest-dev/vitest","slug":"done-callback-is-deprecated-use-promise-instead","errorCode":null,"errorMessage":"done() callback is deprecated, use promise instead","messagePattern":"done\\(\\) callback is deprecated, use promise instead","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/runtime/runner/context.ts","lineNumber":163,"sourceCode":"const abortControllers = new WeakMap<TestContext, AbortController>()\n\nexport function abortIfTimeout([context]: [TestContext?, unknown?], error: Error): void {\n  if (context) {\n    abortContextSignal(context, error)\n  }\n}\n\nexport function abortContextSignal(context: TestContext, error: Error): void {\n  const abortController = abortControllers.get(context)\n  abortController?.abort(error)\n}\n\nexport function createTestContext(\n  test: Test,\n  runner: VitestRunner,\n): TestContext {\n  const context = function () {\n    throw new Error('done() callback is deprecated, use promise instead')\n  } as unknown as WriteableTestContext\n\n  let abortController = abortControllers.get(context)\n\n  if (!abortController) {\n    abortController = new AbortController()\n    abortControllers.set(context, abortController)\n  }\n\n  context.signal = abortController.signal\n  context.task = test\n\n  context.skip = (condition?: boolean | string, note?: string): never => {\n    if (condition === false) {\n      // do nothing\n      return undefined as never\n    }\n    test.result ??= { state: 'skip' }","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/vitest-dev/vitest/blob/1fa9837ec26533512fdcad8baebf249771bd340a/packages/vitest/src/runtime/runner/context.ts#L145-L181","documentation":"The TestContext object is itself callable in Vitest 4, but invoking it throws because the Mocha-style `test.cb(fn)` done-callback pattern was removed. Vitest now expects tests to signal completion via returned promises/async-await instead of a `done` argument. Calling the context as a function is the only path to this error.","triggerScenarios":"Writing `test('x', (done) => { ...; done() })` and then invoking the injected context as the done callback; porting Mocha callback-style tests verbatim; libraries that call the first function argument they receive.","commonSituations":"Migrating a Mocha codebase that relied on `done` for async control flow; using event-emitter callbacks that call their last argument as completion.","solutions":["Convert the test to async/await: `test('x', async () => { await something() })`.","If waiting on an event, wrap it in a Promise and `await` that instead of calling done.","Remove any `done` parameter from test callbacks entirely."],"exampleFix":"// before\ntest('waits', (done) => {\n  emitter.once('ready', done)\n})\n\n// after\ntest('waits', async () => {\n  await once(emitter, 'ready')\n})","handlingStrategy":"validation","validationCode":"// Static: ensure no test callback declares a `done` parameter.\n// grep for `test(.*,\\s*\\(done\\)` and remove.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always use async/await in test bodies; never declare a done parameter.","When migrating from Mocha, convert callback-style tests first.","Wrap event-based async in `util.promisify` or a manual Promise."],"tags":["test-context","async","migration","deprecated-api"],"backgroundTag":null,"analyzedSha":"1fa9837ec26533512fdcad8baebf249771bd340a","analyzedAt":"2026-08-11T16:11:39.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}