diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 17042b88bea9..9e698adf8d5a 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -401,11 +401,11 @@ export function spyOn>>( export function spyOn> | Methods>>( obj: T, methodName: M -): Required[M] extends -| { new (...args: infer A): infer R } -| ((...args: infer A) => infer R) +): Required[M] extends { new (...args: infer A): infer R } ? MockInstance<(this: R, ...args: A) => R> - : never + : T[M] extends Procedure + ? MockInstance + : never export function spyOn( obj: T, method: K, diff --git a/test/core/test/vi.spec.ts b/test/core/test/vi.spec.ts index 79f0164efc3d..292aa1c19e3a 100644 --- a/test/core/test/vi.spec.ts +++ b/test/core/test/vi.spec.ts @@ -2,7 +2,7 @@ * @vitest-environment jsdom */ -import type { Mock, MockedFunction, MockedObject } from 'vitest' +import type { Mock, MockInstance, MockedFunction, MockedObject } from 'vitest' import { describe, expect, expectTypeOf, test, vi } from 'vitest' import { getWorkerState } from '../../../packages/vitest/src/utils' @@ -118,6 +118,40 @@ describe('testing vi utils', () => { expect(someFn4).not.toBeCalled() }) + test(`vi.spyOn for function overload types`, () => { + class MyElement { + scrollTo(options?: ScrollToOptions): void + scrollTo(x: number, y: number): void + scrollTo() {} + } + + // verify `spyOn` is assignable to `MockInstance` with overload + const spy: MockInstance = vi.spyOn( + MyElement.prototype, + 'scrollTo', + ) + + // however `Parameters` only picks up the last overload + // due to typescript limitation + expectTypeOf(spy.mock.calls).toEqualTypeOf< + [x: number, y: number][] + >() + }) + + test(`mock.contexts types`, () => { + class TestClass { + f(this: TestClass) {} + g() {} + } + + const fSpy = vi.spyOn(TestClass.prototype, 'f') + const gSpy = vi.spyOn(TestClass.prototype, 'g') + + // contexts inferred only when `this` is explicitly annotated + expectTypeOf(fSpy.mock.contexts).toEqualTypeOf() + expectTypeOf(gSpy.mock.contexts).toEqualTypeOf() + }) + test('can change config', () => { const state = getWorkerState() expect(state.config.hookTimeout).toBe(10000)