From e2d6312ad6c8672f700607b029d827be22ffcc3f Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 20 Dec 2023 09:37:40 +0900 Subject: [PATCH 01/24] fix!(spy): align vi.fn and vi.Mock typings to jest --- packages/spy/src/index.ts | 116 ++++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index e4a3e94c8f4e..56bc0cfdbbb3 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -22,7 +22,7 @@ interface MockResultThrow { type MockResult = MockResultReturn | MockResultThrow | MockResultIncomplete -export interface MockContext { +export interface MockContext { /** * This is an array containing all arguments for each call. One item of the array is the arguments of that call. * @@ -37,11 +37,11 @@ export interface MockContext { * ['arg3'], // second call * ] */ - calls: TArgs[] + calls: Parameters[] /** * This is an array containing all instances that were instantiated when mock was called with a `new` keyword. Note that this is an actual context (`this`) of the function, not a return value. */ - instances: TReturns[] + instances: ReturnType[] /** * The order of mock's execution. This returns an array of numbers which are shared between all defined mocks. * @@ -85,14 +85,15 @@ export interface MockContext { * }, * ] */ - results: MockResult[] + results: MockResult>[] /** * This contains the arguments of the last call. If spy wasn't called, will return `undefined`. */ - lastCall: TArgs | undefined + lastCall: Parameters | undefined } type Procedure = (...args: any[]) => any +type UnknownProcedure = (...args: unknown[]) => unknown type Methods = keyof { [K in keyof T as T[K] extends Procedure ? K : never]: T[K]; @@ -107,9 +108,9 @@ type Classes = { /** * @deprecated Use MockInstance instead */ -export interface SpyInstance extends MockInstance {} +export interface SpyInstance extends MockInstance<(...args: TArgs) => TReturns> {} -export interface MockInstance { +export interface MockInstance { /** * Use it to return the name given to mock with method `.mockName(name)`. */ @@ -121,7 +122,7 @@ export interface MockInstance { /** * Current context of the mock. It stores information about all invocation calls, instances, and results. */ - mock: MockContext + mock: MockContext /** * Clears all information about every call. After calling it, all properties on `.mock` will return an empty state. This method does not reset implementations. * @@ -147,14 +148,14 @@ export interface MockInstance { * * If mock was created with `vi.spyOn`, it will return `undefined` unless a custom implementation was provided. */ - getMockImplementation(): ((...args: TArgs) => TReturns) | undefined + getMockImplementation(): (T) | undefined /** * Accepts a function that will be used as an implementation of the mock. * @example * const increment = vi.fn().mockImplementation(count => count + 1); * expect(increment(3)).toBe(4); */ - mockImplementation(fn: ((...args: TArgs) => TReturns) | (() => Promise)): this + mockImplementation(fn: T | (() => Promise>)): this /** * Accepts a function that will be used as a mock implementation during the next call. Can be chained so that multiple function calls produce different results. * @example @@ -162,7 +163,7 @@ export interface MockInstance { * expect(fn(3)).toBe(4); * expect(fn(3)).toBe(3); */ - mockImplementationOnce(fn: ((...args: TArgs) => TReturns) | (() => Promise)): this + mockImplementationOnce(fn: T | (() => Promise>)): this /** * Overrides the original mock implementation temporarily while the callback is being executed. * @example @@ -174,7 +175,7 @@ export interface MockInstance { * * myMockFn() // 'original' */ - withImplementation(fn: ((...args: TArgs) => TReturns), cb: () => T): T extends Promise ? Promise : this + withImplementation(fn: T, cb: () => T2): T2 extends Promise ? Promise : this /** * Use this if you need to return `this` context from the method without invoking actual implementation. */ @@ -182,7 +183,7 @@ export interface MockInstance { /** * Accepts a value that will be returned whenever the mock function is called. */ - mockReturnValue(obj: TReturns): this + mockReturnValue(obj: ReturnType): this /** * Accepts a value that will be returned during the next function call. If chained, every consecutive call will return the specified value. * @@ -197,14 +198,14 @@ export interface MockInstance { * // 'first call', 'second call', 'default' * console.log(myMockFn(), myMockFn(), myMockFn()) */ - mockReturnValueOnce(obj: TReturns): this + mockReturnValueOnce(obj: ReturnType): this /** * Accepts a value that will be resolved when async function is called. * @example * const asyncMock = vi.fn().mockResolvedValue(42) * asyncMock() // Promise<42> */ - mockResolvedValue(obj: Awaited): this + mockResolvedValue(obj: Awaited>): this /** * Accepts a value that will be resolved during the next function call. If chained, every consecutive call will resolve specified value. * @example @@ -217,7 +218,7 @@ export interface MockInstance { * // Promise<'first call'>, Promise<'second call'>, Promise<'default'> * console.log(myMockFn(), myMockFn(), myMockFn()) */ - mockResolvedValueOnce(obj: Awaited): this + mockResolvedValueOnce(obj: Awaited>): this /** * Accepts an error that will be rejected when async function is called. * @example @@ -239,28 +240,31 @@ export interface MockInstance { mockRejectedValueOnce(obj: any): this } -export interface Mock extends MockInstance { - new (...args: TArgs): TReturns - (...args: TArgs): TReturns +export interface Mock extends MockInstance { + new (...args: Parameters): ReturnType + (...args: Parameters): ReturnType } -export interface PartialMock extends MockInstance> ? Promise>> : Partial> { - new (...args: TArgs): TReturns - (...args: TArgs): TReturns + +type PartialMaybePromise = T extends Promise> ? Promise>> : Partial + +export interface PartialMock extends MockInstance<(...args: Parameters) => PartialMaybePromise>> { + new (...args: Parameters): ReturnType + (...args: Parameters): ReturnType } export type MaybeMockedConstructor = T extends new ( ...args: Array ) => infer R - ? Mock, R> + ? Mock<(...args: ConstructorParameters) => R> : T -export type MockedFunction = Mock, ReturnType> & { +export type MockedFunction = Mock & { [K in keyof T]: T[K]; } -export type PartiallyMockedFunction = PartialMock, ReturnType> & { +export type PartiallyMockedFunction = PartialMock & { [K in keyof T]: T[K]; } -export type MockedFunctionDeep = Mock, ReturnType> & MockedObjectDeep -export type PartiallyMockedFunctionDeep = PartialMock, ReturnType> & MockedObjectDeep +export type MockedFunctionDeep = Mock & MockedObjectDeep +export type PartiallyMockedFunctionDeep = PartialMock & MockedObjectDeep export type MockedObject = MaybeMockedConstructor & { [K in Methods]: T[K] extends Procedure ? MockedFunction @@ -300,23 +304,20 @@ interface Constructable { new (...args: any[]): any } -export type MockedClass = MockInstance< - T extends new (...args: infer P) => any ? P : never, - InstanceType -> & { +export type MockedClass = MockInstance<(...args: ConstructorParameters) => InstanceType> & { prototype: T extends { prototype: any } ? Mocked : never } & T export type Mocked = { - [P in keyof T]: T[P] extends (...args: infer Args) => infer Returns - ? MockInstance + [P in keyof T]: T[P] extends Procedure + ? MockInstance : T[P] extends Constructable ? MockedClass : T[P] } & T -export const mocks = new Set() +export const mocks = new Set>() export function isMockFunction(fn: any): fn is MockInstance { return typeof fn === 'function' @@ -328,16 +329,16 @@ export function spyOn>>( obj: T, methodName: S, accessType: 'get', -): MockInstance<[], T[S]> +): MockInstance<() => T[S]> export function spyOn>>( obj: T, methodName: G, accessType: 'set', -): MockInstance<[T[G]], void> +): MockInstance<(arg: T[G]) => void> export function spyOn> | Methods>)>( obj: T, methodName: M, -): Required[M] extends ({ new (...args: infer A): infer R }) | ((...args: infer A) => infer R) ? MockInstance : never +): Required[M] extends ({ new (...args: infer A): infer R }) | ((...args: infer A) => infer R) ? MockInstance<(...args: A) => R> : never export function spyOn( obj: T, method: K, @@ -356,12 +357,15 @@ export function spyOn( let callOrder = 0 -function enhanceSpy( - spy: SpyInternalImpl, -): MockInstance { - const stub = spy as unknown as MockInstance +function enhanceSpy( + spy: SpyInternalImpl, ReturnType>, +): MockInstance { + type TArgs = Parameters + type TReturns = ReturnType + + const stub = spy as unknown as MockInstance - let implementation: ((...args: TArgs) => TReturns) | undefined + let implementation: T | undefined let instances: any[] = [] let invocations: number[] = [] @@ -416,7 +420,7 @@ function enhanceSpy( stub.mockReset = () => { stub.mockClear() - implementation = () => undefined as unknown as TReturns + implementation = (() => undefined) as T onceImplementations = [] return stub } @@ -429,20 +433,20 @@ function enhanceSpy( } stub.getMockImplementation = () => implementation - stub.mockImplementation = (fn: (...args: TArgs) => TReturns) => { + stub.mockImplementation = (fn: T) => { implementation = fn state.willCall(mockCall) return stub } - stub.mockImplementationOnce = (fn: (...args: TArgs) => TReturns) => { + stub.mockImplementationOnce = (fn: T) => { onceImplementations.push(fn) return stub } - function withImplementation(fn: (...args: TArgs) => TReturns, cb: () => void): MockInstance - function withImplementation(fn: (...args: TArgs) => TReturns, cb: () => Promise): Promise> - function withImplementation(fn: (...args: TArgs) => TReturns, cb: () => void | Promise): MockInstance | Promise> { + function withImplementation(fn: T, cb: () => void): MockInstance + function withImplementation(fn: T, cb: () => Promise): Promise> + function withImplementation(fn: T, cb: () => void | Promise): MockInstance | Promise> { const originalImplementation = implementation implementation = fn @@ -501,16 +505,16 @@ function enhanceSpy( return stub as any } -export function fn(): Mock -export function fn( - implementation: (...args: TArgs) => R -): Mock -export function fn( - implementation?: (...args: TArgs) => R, -): Mock { +export function fn(): Mock +export function fn( + implementation: T +): Mock +export function fn( + implementation?: T, +): Mock { const enhancedSpy = enhanceSpy(tinyspy.internalSpyOn({ spy: implementation || (() => {}) }, 'spy')) if (implementation) enhancedSpy.mockImplementation(implementation) - return enhancedSpy as Mock + return enhancedSpy as Mock } From c5781fe361271bff5f6caf675e24f875f8a362f6 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 20 Dec 2023 09:57:13 +0900 Subject: [PATCH 02/24] fix: put back `any` for starter --- packages/spy/src/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 56bc0cfdbbb3..45d6070e5842 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -93,7 +93,10 @@ export interface MockContext { } type Procedure = (...args: any[]) => any -type UnknownProcedure = (...args: unknown[]) => unknown + +// TODO: jest uses stricter default based on `unknown` +// type UnknownProcedure = (...args: unknown[]) => unknown +type UnknownProcedure = (...args: any[]) => any type Methods = keyof { [K in keyof T as T[K] extends Procedure ? K : never]: T[K]; From 8a13a22fa052adaa756f9959e49a7f69e5f1bd96 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 20 Dec 2023 10:00:20 +0900 Subject: [PATCH 03/24] chore: lint --- packages/spy/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 45d6070e5842..5e39a2f078a6 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -151,7 +151,7 @@ export interface MockInstance { * * If mock was created with `vi.spyOn`, it will return `undefined` unless a custom implementation was provided. */ - getMockImplementation(): (T) | undefined + getMockImplementation(): T | undefined /** * Accepts a function that will be used as an implementation of the mock. * @example From a87d320cb5c4017270943804927ce0c1eb1d3abf Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 20 Dec 2023 10:33:03 +0900 Subject: [PATCH 04/24] chore: back to "unknown" --- packages/spy/src/index.ts | 6 +++--- test/core/test/spy.test.ts | 2 +- test/core/test/vi.spec.ts | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 5e39a2f078a6..994ce767e7d2 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -94,9 +94,9 @@ export interface MockContext { type Procedure = (...args: any[]) => any -// TODO: jest uses stricter default based on `unknown` -// type UnknownProcedure = (...args: unknown[]) => unknown -type UnknownProcedure = (...args: any[]) => any +// TODO: jest uses stricter default based on `unknown`, but vitest has been using `any`. +type UnknownProcedure = (...args: unknown[]) => unknown +// type UnknownProcedure = (...args: any[]) => any type Methods = keyof { [K in keyof T as T[K] extends Procedure ? K : never]: T[K]; diff --git a/test/core/test/spy.test.ts b/test/core/test/spy.test.ts index 35cddb90374a..719160540b01 100644 --- a/test/core/test/spy.test.ts +++ b/test/core/test/spy.test.ts @@ -15,7 +15,7 @@ describe('spyOn', () => { test('infers a class correctly', () => { vi.spyOn(mock, 'HelloWorld').mockImplementationOnce(() => { - const Mock = vi.fn() + const Mock = vi.fn() Mock.prototype.hello = vi.fn(() => 'hello world') return new Mock() }) diff --git a/test/core/test/vi.spec.ts b/test/core/test/vi.spec.ts index 79ea9ad5b910..cf2d9d10074f 100644 --- a/test/core/test/vi.spec.ts +++ b/test/core/test/vi.spec.ts @@ -52,14 +52,14 @@ describe('testing vi utils', () => { type FooBarFactory = () => FooBar - const mockFactory: FooBarFactory = vi.fn() + const mockFactory = vi.fn() vi.mocked(mockFactory, { partial: true }).mockReturnValue({ foo: vi.fn(), }) vi.mocked(mockFactory, { partial: true, deep: false }).mockReturnValue({ - bar: vi.fn(), + bar: vi.fn(), }) vi.mocked(mockFactory, { partial: true, deep: true }).mockReturnValue({ @@ -68,14 +68,14 @@ describe('testing vi utils', () => { type FooBarAsyncFactory = () => Promise - const mockFactoryAsync: FooBarAsyncFactory = vi.fn() + const mockFactoryAsync = vi.fn() vi.mocked(mockFactoryAsync, { partial: true }).mockResolvedValue({ foo: vi.fn(), }) vi.mocked(mockFactoryAsync, { partial: true, deep: false }).mockResolvedValue({ - bar: vi.fn(), + bar: vi.fn(), }) vi.mocked(mockFactoryAsync, { partial: true, deep: true }).mockResolvedValue({ From f79f1c8e8758998f0aadbb83419ae3ddc41eaa87 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 4 Jan 2024 08:17:18 +0900 Subject: [PATCH 05/24] chore: revert lock --- pnpm-lock.yaml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index efdc9b450406..ae3d4523d6e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -143,7 +143,7 @@ importers: version: 4.7.1 unocss: specifier: ^0.57.4 - version: 0.57.4(postcss@8.4.32)(rollup@2.79.1)(vite@5.0.2) + version: 0.57.4(postcss@8.4.31)(rollup@2.79.1)(vite@5.0.2) unplugin-vue-components: specifier: ^0.25.2 version: 0.25.2(rollup@2.79.1)(vue@3.3.8) @@ -155,7 +155,7 @@ importers: version: 0.16.7(vite@5.0.2)(workbox-build@7.0.0)(workbox-window@7.0.0) vitepress: specifier: ^1.0.0-rc.34 - version: 1.0.0-rc.34(@types/node@18.18.9)(postcss@8.4.32)(search-insights@2.9.0)(typescript@5.2.2) + version: 1.0.0-rc.34(@types/node@18.18.9)(postcss@8.4.31)(search-insights@2.9.0)(typescript@5.2.2) workbox-window: specifier: ^7.0.0 version: 7.0.0 @@ -1188,7 +1188,7 @@ importers: version: 3.1.5 unocss: specifier: ^0.57.4 - version: 0.57.4(postcss@8.4.32)(rollup@4.4.0)(vite@5.0.2) + version: 0.57.4(postcss@8.4.31)(rollup@4.4.0)(vite@5.0.2) unplugin-auto-import: specifier: ^0.16.7 version: 0.16.7(@vueuse/core@10.6.1)(rollup@4.4.0) @@ -6250,7 +6250,7 @@ packages: '@emotion/cache': 11.11.0 '@emotion/react': 11.10.4(@babel/core@7.23.3)(@types/react@18.2.37)(react@18.2.0) '@emotion/styled': 11.10.4(@babel/core@7.23.3)(@emotion/react@11.10.4)(@types/react@18.2.37)(react@18.2.0) - csstype: 3.1.3 + csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 dev: false @@ -6310,7 +6310,7 @@ packages: '@mui/utils': 5.14.14(@types/react@18.2.37)(react@18.2.0) '@types/react': 18.2.37 clsx: 2.0.0 - csstype: 3.1.3 + csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 dev: false @@ -9815,7 +9815,7 @@ packages: sirv: 2.0.4 dev: true - /@unocss/postcss@0.57.4(postcss@8.4.32): + /@unocss/postcss@0.57.4(postcss@8.4.31): resolution: {integrity: sha512-ggq8JS4rvgvW2QXjLGwg+m8e4YcmvOtbUS6C7UCrP8pmUqBCpbnTmLi6inpBbBuCN5WokecNZS5f3C4EwNMOMA==} engines: {node: '>=14'} peerDependencies: @@ -9827,7 +9827,7 @@ packages: css-tree: 2.3.1 fast-glob: 3.3.2 magic-string: 0.30.5 - postcss: 8.4.32 + postcss: 8.4.31 dev: true /@unocss/preset-attributify@0.57.4: @@ -13522,6 +13522,7 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dev: true /currently-unhandled@0.4.1: resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} @@ -25394,7 +25395,7 @@ packages: detect-node: 2.1.0 dev: false - /unocss@0.57.4(postcss@8.4.32)(rollup@2.79.1)(vite@5.0.2): + /unocss@0.57.4(postcss@8.4.31)(rollup@2.79.1)(vite@5.0.2): resolution: {integrity: sha512-rf5eiCVb8957rqzCyRxLzljeYguVMS70X322/Z1sYhosKhh8SBBMsC/TrZEf5o8LTn/MbFN9fVizEtbUKaFjUg==} engines: {node: '>=14'} peerDependencies: @@ -25410,7 +25411,7 @@ packages: '@unocss/cli': 0.57.4(rollup@2.79.1) '@unocss/core': 0.57.4 '@unocss/extractor-arbitrary-variants': 0.57.4 - '@unocss/postcss': 0.57.4(postcss@8.4.32) + '@unocss/postcss': 0.57.4(postcss@8.4.31) '@unocss/preset-attributify': 0.57.4 '@unocss/preset-icons': 0.57.4 '@unocss/preset-mini': 0.57.4 @@ -25433,7 +25434,7 @@ packages: - supports-color dev: true - /unocss@0.57.4(postcss@8.4.32)(rollup@4.4.0)(vite@5.0.2): + /unocss@0.57.4(postcss@8.4.31)(rollup@4.4.0)(vite@5.0.2): resolution: {integrity: sha512-rf5eiCVb8957rqzCyRxLzljeYguVMS70X322/Z1sYhosKhh8SBBMsC/TrZEf5o8LTn/MbFN9fVizEtbUKaFjUg==} engines: {node: '>=14'} peerDependencies: @@ -25449,7 +25450,7 @@ packages: '@unocss/cli': 0.57.4(rollup@4.4.0) '@unocss/core': 0.57.4 '@unocss/extractor-arbitrary-variants': 0.57.4 - '@unocss/postcss': 0.57.4(postcss@8.4.32) + '@unocss/postcss': 0.57.4(postcss@8.4.31) '@unocss/preset-attributify': 0.57.4 '@unocss/preset-icons': 0.57.4 '@unocss/preset-mini': 0.57.4 @@ -26030,7 +26031,7 @@ packages: vite: 5.0.2(@types/node@18.18.9)(less@4.1.3) dev: true - /vitepress@1.0.0-rc.34(@types/node@18.18.9)(postcss@8.4.32)(search-insights@2.9.0)(typescript@5.2.2): + /vitepress@1.0.0-rc.34(@types/node@18.18.9)(postcss@8.4.31)(search-insights@2.9.0)(typescript@5.2.2): resolution: {integrity: sha512-TUbTiSdAZFni2XlHlpx61KikgkQ5uG4Wtmw2R0SXhIOG6qGqzDJczAFjkMc4i45I9c3KyatwOYe8oEfCnzVYwQ==} hasBin: true peerDependencies: @@ -26053,7 +26054,7 @@ packages: mark.js: 8.11.1 minisearch: 6.3.0 mrmime: 2.0.0 - postcss: 8.4.32 + postcss: 8.4.31 shikiji: 0.9.17 shikiji-core: 0.9.17 shikiji-transformers: 0.9.17 From 5efee9510de7d6dd1ef9808e5a997c4826705f18 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 4 Jan 2024 08:21:37 +0900 Subject: [PATCH 06/24] chore: more any type --- packages/spy/src/index.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 41bce785fe78..d9065f86401e 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -478,24 +478,24 @@ function enhanceSpy( stub.withImplementation = withImplementation stub.mockReturnThis = () => - stub.mockImplementation(function (this: TReturns) { + stub.mockImplementation((function (this: TReturns) { return this - }) + }) as any) - stub.mockReturnValue = (val: TReturns) => stub.mockImplementation(() => val) - stub.mockReturnValueOnce = (val: TReturns) => stub.mockImplementationOnce(() => val) + stub.mockReturnValue = (val: TReturns) => stub.mockImplementation((() => val) as any) + stub.mockReturnValueOnce = (val: TReturns) => stub.mockImplementationOnce((() => val) as any) stub.mockResolvedValue = (val: Awaited) => - stub.mockImplementation(() => Promise.resolve(val as TReturns) as any) + stub.mockImplementation((() => Promise.resolve(val as TReturns)) as any) stub.mockResolvedValueOnce = (val: Awaited) => - stub.mockImplementationOnce(() => Promise.resolve(val as TReturns) as any) + stub.mockImplementationOnce((() => Promise.resolve(val as TReturns)) as any) stub.mockRejectedValue = (val: unknown) => - stub.mockImplementation(() => Promise.reject(val) as any) + stub.mockImplementation((() => Promise.reject(val)) as any) stub.mockRejectedValueOnce = (val: unknown) => - stub.mockImplementationOnce(() => Promise.reject(val) as any) + stub.mockImplementationOnce((() => Promise.reject(val)) as any) Object.defineProperty(stub, 'mock', { get: () => mockContext, From a3bdb7a5059c4a7fa4afdf749a06531f2d3e5ac5 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 23 Apr 2024 10:38:16 +0900 Subject: [PATCH 07/24] fix: fix types --- packages/spy/src/index.ts | 27 ++++++++++----------------- packages/vitest/src/types/index.ts | 1 - 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 7747f4dc4c08..136ad99e2cd5 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -92,11 +92,9 @@ export interface MockContext { lastCall: Parameters | undefined } -type Procedure = (...args: any[]) => any - // TODO: jest uses stricter default based on `unknown`, but vitest has been using `any`. -type UnknownProcedure = (...args: unknown[]) => unknown -// type UnknownProcedure = (...args: any[]) => any +// type Procedure = (...args: unknown[]) => unknown +type Procedure = (...args: any[]) => any type Methods = keyof { [K in keyof T as T[K] extends Procedure ? K : never]: T[K]; @@ -108,12 +106,7 @@ type Classes = { [K in keyof T]: T[K] extends new (...args: any[]) => any ? K : never }[keyof T] & (string | symbol) -/** - * @deprecated Use MockInstance instead - */ -export interface SpyInstance extends MockInstance<(...args: TArgs) => TReturns> {} - -export interface MockInstance { +export interface MockInstance { /** * Use it to return the name given to mock with method `.mockName(name)`. */ @@ -243,14 +236,14 @@ export interface MockInstance { mockRejectedValueOnce: (obj: any) => this } -export interface Mock extends MockInstance { +export interface Mock extends MockInstance { new (...args: Parameters): ReturnType (...args: Parameters): ReturnType } type PartialMaybePromise = T extends Promise> ? Promise>> : Partial -export interface PartialMock extends MockInstance<(...args: Parameters) => PartialMaybePromise>> { +export interface PartialMock extends MockInstance<(...args: Parameters) => PartialMaybePromise>> { new (...args: Parameters): ReturnType (...args: Parameters): ReturnType } @@ -503,21 +496,21 @@ function enhanceSpy( state.willCall(mockCall) - mocks.add(stub) + mocks.add(stub as MockInstance) return stub as any } -export function fn(): Mock -export function fn( +export function fn(): Mock +export function fn( implementation: T ): Mock -export function fn( +export function fn( implementation?: T, ): Mock { const enhancedSpy = enhanceSpy(tinyspy.internalSpyOn({ spy: implementation || (() => {}) }, 'spy')) if (implementation) enhancedSpy.mockImplementation(implementation) - return enhancedSpy as Mock + return enhancedSpy as any } diff --git a/packages/vitest/src/types/index.ts b/packages/vitest/src/types/index.ts index 3da526a29867..05eaef50742d 100644 --- a/packages/vitest/src/types/index.ts +++ b/packages/vitest/src/types/index.ts @@ -17,7 +17,6 @@ export type { DiffOptions } from '@vitest/utils/diff' export type { MockedFunction, MockedObject, - SpyInstance, MockInstance, Mock, MockContext, From e034a05709a19508481867fb84945c9b47891f4b Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 23 Apr 2024 11:03:25 +0900 Subject: [PATCH 08/24] test: fix and add --- test/config/test/resolution.test.ts | 2 +- test/core/test/mock-internals.test.ts | 4 +-- test/core/test/vi.spec.ts | 45 ++++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/test/config/test/resolution.test.ts b/test/config/test/resolution.test.ts index f047d7d44760..5b5c99fb2eaf 100644 --- a/test/config/test/resolution.test.ts +++ b/test/config/test/resolution.test.ts @@ -305,7 +305,7 @@ describe.each([ await expect(async () => { await config(rawConfig.options) }).rejects.toThrowError() - expect(error.mock.lastCall[0]).toEqual( + expect(error.mock.lastCall?.[0]).toEqual( expect.stringContaining(`Inspector host cannot be a URL. Use "host:port" instead of "${url}"`), ) }) diff --git a/test/core/test/mock-internals.test.ts b/test/core/test/mock-internals.test.ts index ea3ac225d36f..246c691093c1 100644 --- a/test/core/test/mock-internals.test.ts +++ b/test/core/test/mock-internals.test.ts @@ -1,6 +1,6 @@ import childProcess, { exec } from 'node:child_process' import timers from 'node:timers' -import { type SpyInstance, afterEach, beforeEach, describe, expect, test, vi } from 'vitest' +import { type MockInstance, afterEach, beforeEach, describe, expect, test, vi } from 'vitest' import { execDefault, execHelloWorld, execImportAll } from '../src/exec' import { dynamicImport } from '../src/dynamic-import' @@ -31,7 +31,7 @@ test('mocked dynamically imported packages', async () => { describe('Math.random', () => { describe('mock is restored', () => { - let spy: SpyInstance + let spy: MockInstance beforeEach(() => { spy = vi.spyOn(Math, 'random').mockReturnValue(0.1) diff --git a/test/core/test/vi.spec.ts b/test/core/test/vi.spec.ts index cf2d9d10074f..a8de4d38b488 100644 --- a/test/core/test/vi.spec.ts +++ b/test/core/test/vi.spec.ts @@ -2,8 +2,8 @@ * @vitest-environment jsdom */ -import type { MockedFunction, MockedObject } from 'vitest' -import { describe, expect, test, vi } from 'vitest' +import type { Mock, MockedFunction, MockedObject } from 'vitest' +import { describe, expect, expectTypeOf, test, vi } from 'vitest' import { getWorkerState } from '../../../packages/vitest/src/utils' function expectType(obj: T) { @@ -36,11 +36,14 @@ describe('testing vi utils', () => { }) test('vi mocked', () => { - expectType boolean }>>({ + // TODO: constant `true` isn't assignable `boolean`. + // check what Jest does. + expectType true }>>({ bar: vi.fn(() => true), }) - expectType boolean>>(vi.fn(() => true)) + expectType true>>(vi.fn(() => true)) expectType boolean>>(vi.fn()) + expectType true>>(vi.fn(() => true)) }) test('vi partial mocked', () => { @@ -83,6 +86,40 @@ describe('testing vi utils', () => { }) }) + test('vi.fn and Mock type', () => { + // use case from https://github.com/vitest-dev/vitest/issues/4723#issuecomment-1851034249 + + // library to be tested + type SomeFn = (v: string) => number + function acceptSomeFn(f: SomeFn) { + f('hi') + } + + // SETUP + + // no args are allowed even though it's not type safe + const someFn1: Mock = vi.fn() + + // argument type is infered + const someFn2: Mock = vi.fn((v) => { + expectTypeOf(v).toEqualTypeOf() + return 0 + }) + + // @ts-expect-error argument is required + const someFn3: Mock = vi.fn(() => 0) + + // @ts-expect-error wrong return type + const someFn4: Mock = vi.fn(_ => '0') + + // TEST + acceptSomeFn(someFn1) + expect(someFn1).toBeCalledWith('hi') + expect(someFn2).not.toBeCalled() + expect(someFn3).not.toBeCalled() + expect(someFn4).not.toBeCalled() + }) + test('can change config', () => { const state = getWorkerState() expect(state.config.hookTimeout).toBe(10000) From eb1c442793334c9acef945ec2e7aaf6f85c5d7f4 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 23 Apr 2024 14:05:22 +0900 Subject: [PATCH 09/24] fix: give up "ts/method-signature-style" --- packages/spy/src/index.ts | 41 ++++++++++++++++++++++++--------------- test/core/test/vi.spec.ts | 16 ++++++--------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 136ad99e2cd5..3227b5aafb57 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -92,8 +92,7 @@ export interface MockContext { lastCall: Parameters | undefined } -// TODO: jest uses stricter default based on `unknown`, but vitest has been using `any`. -// type Procedure = (...args: unknown[]) => unknown +// TODO: used `(...args: unknown[]) => unknown` for stricter default like jest? type Procedure = (...args: any[]) => any type Methods = keyof { @@ -106,6 +105,18 @@ type Classes = { [K in keyof T]: T[K] extends new (...args: any[]) => any ? K : never }[keyof T] & (string | symbol) +/* +cf. https://typescript-eslint.io/rules/method-signature-style/ + +Typescript assignability is different between + { foo: (f: T) => void } (this is "method-signature-style") +and + { foo(f: T): void } + +Jest uses the latter for `MockInstance.mockImplementation` etc... and it allows assignment such as: + const boolFn: Jest.Mock<() => boolean> = jest.fn(() => true) +*/ +/* eslint-disable ts/method-signature-style */ export interface MockInstance { /** * Use it to return the name given to mock with method `.mockName(name)`. @@ -151,7 +162,7 @@ export interface MockInstance { * const increment = vi.fn().mockImplementation(count => count + 1); * expect(increment(3)).toBe(4); */ - mockImplementation: (fn: T) => this + mockImplementation(fn: T): this /** * Accepts a function that will be used as a mock implementation during the next call. Can be chained so that multiple function calls produce different results. * @example @@ -159,7 +170,7 @@ export interface MockInstance { * expect(fn(3)).toBe(4); * expect(fn(3)).toBe(3); */ - mockImplementationOnce: (fn: T) => this + mockImplementationOnce(fn: T): this /** * Overrides the original mock implementation temporarily while the callback is being executed. * @example @@ -171,15 +182,16 @@ export interface MockInstance { * * myMockFn() // 'original' */ - withImplementation: (fn: T, cb: () => T2) => T2 extends Promise ? Promise : this + withImplementation(fn: T, cb: () => T2): T2 extends Promise ? Promise : this + /** * Use this if you need to return `this` context from the method without invoking actual implementation. */ - mockReturnThis: () => this + mockReturnThis(): this /** * Accepts a value that will be returned whenever the mock function is called. */ - mockReturnValue: (obj: ReturnType) => this + mockReturnValue(obj: ReturnType): this /** * Accepts a value that will be returned during the next function call. If chained, every consecutive call will return the specified value. * @@ -194,14 +206,14 @@ export interface MockInstance { * // 'first call', 'second call', 'default' * console.log(myMockFn(), myMockFn(), myMockFn()) */ - mockReturnValueOnce: (obj: ReturnType) => this + mockReturnValueOnce(obj: ReturnType): this /** * Accepts a value that will be resolved when async function is called. * @example * const asyncMock = vi.fn().mockResolvedValue(42) * asyncMock() // Promise<42> */ - mockResolvedValue: (obj: Awaited>) => this + mockResolvedValue(obj: Awaited>): this /** * Accepts a value that will be resolved during the next function call. If chained, every consecutive call will resolve specified value. * @example @@ -214,14 +226,14 @@ export interface MockInstance { * // Promise<'first call'>, Promise<'second call'>, Promise<'default'> * console.log(myMockFn(), myMockFn(), myMockFn()) */ - mockResolvedValueOnce: (obj: Awaited>) => this + mockResolvedValueOnce(obj: Awaited>): this /** * Accepts an error that will be rejected when async function is called. * @example * const asyncMock = vi.fn().mockRejectedValue(new Error('Async error')) * await asyncMock() // throws 'Async error' */ - mockRejectedValue: (obj: any) => this + mockRejectedValue(obj: any): this /** * Accepts a value that will be rejected during the next function call. If chained, every consecutive call will reject specified value. * @example @@ -233,8 +245,9 @@ export interface MockInstance { * await asyncMock() // first call * await asyncMock() // throws "Async error" */ - mockRejectedValueOnce: (obj: any) => this + mockRejectedValueOnce(obj: any): this } +/* eslint-enable ts/method-signature-style */ export interface Mock extends MockInstance { new (...args: Parameters): ReturnType @@ -501,10 +514,6 @@ function enhanceSpy( return stub as any } -export function fn(): Mock -export function fn( - implementation: T -): Mock export function fn( implementation?: T, ): Mock { diff --git a/test/core/test/vi.spec.ts b/test/core/test/vi.spec.ts index a8de4d38b488..193baaf39b8d 100644 --- a/test/core/test/vi.spec.ts +++ b/test/core/test/vi.spec.ts @@ -36,14 +36,11 @@ describe('testing vi utils', () => { }) test('vi mocked', () => { - // TODO: constant `true` isn't assignable `boolean`. - // check what Jest does. - expectType true }>>({ + expectType boolean }>>({ bar: vi.fn(() => true), }) - expectType true>>(vi.fn(() => true)) + expectType boolean>>(vi.fn(() => true)) expectType boolean>>(vi.fn()) - expectType true>>(vi.fn(() => true)) }) test('vi partial mocked', () => { @@ -89,28 +86,27 @@ describe('testing vi utils', () => { test('vi.fn and Mock type', () => { // use case from https://github.com/vitest-dev/vitest/issues/4723#issuecomment-1851034249 - // library to be tested + // hypotetical library to be tested type SomeFn = (v: string) => number function acceptSomeFn(f: SomeFn) { f('hi') } // SETUP - // no args are allowed even though it's not type safe const someFn1: Mock = vi.fn() - // argument type is infered + // argument types are infered const someFn2: Mock = vi.fn((v) => { expectTypeOf(v).toEqualTypeOf() return 0 }) - // @ts-expect-error argument is required + // arguments are not necessary const someFn3: Mock = vi.fn(() => 0) // @ts-expect-error wrong return type - const someFn4: Mock = vi.fn(_ => '0') + const someFn4: Mock = vi.fn(() => '0') // TEST acceptSomeFn(someFn1) From d0903b67844ca48394948d5086b006ef9c765616 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 23 Apr 2024 14:07:57 +0900 Subject: [PATCH 10/24] chore: comment --- packages/spy/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 3227b5aafb57..a84b4b7cff8d 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -109,9 +109,9 @@ type Classes = { cf. https://typescript-eslint.io/rules/method-signature-style/ Typescript assignability is different between - { foo: (f: T) => void } (this is "method-signature-style") + { foo: (f: T) => U } (this is "method-signature-style") and - { foo(f: T): void } + { foo(f: T): U } Jest uses the latter for `MockInstance.mockImplementation` etc... and it allows assignment such as: const boolFn: Jest.Mock<() => boolean> = jest.fn(() => true) From fef7e5f076e73313a5535e9a60f26d13bedac699 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 23 Apr 2024 14:13:27 +0900 Subject: [PATCH 11/24] chore: cleanup --- packages/spy/src/index.ts | 18 +++++++++--------- test/core/test/spy.test.ts | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index a84b4b7cff8d..147a195c2ab5 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -92,7 +92,7 @@ export interface MockContext { lastCall: Parameters | undefined } -// TODO: used `(...args: unknown[]) => unknown` for stricter default like jest? +// TODO: use `(...args: unknown[]) => unknown` for stricter default like jest? type Procedure = (...args: any[]) => any type Methods = keyof { @@ -121,11 +121,11 @@ export interface MockInstance { /** * Use it to return the name given to mock with method `.mockName(name)`. */ - getMockName: () => string + getMockName(): string /** * Sets internal mock name. Useful to see the name of the mock if an assertion fails. */ - mockName: (n: string) => this + mockName(n: string): this /** * Current context of the mock. It stores information about all invocation calls, instances, and results. */ @@ -135,19 +135,19 @@ export interface MockInstance { * * It is useful if you need to clean up mock between different assertions. */ - mockClear: () => this + mockClear(): this /** * Does what `mockClear` does and makes inner implementation an empty function (returning `undefined` when invoked). This also resets all "once" implementations. * * This is useful when you want to completely reset a mock to the default state. */ - mockReset: () => this + mockReset(): this /** * Does what `mockReset` does and restores inner implementation to the original function. * * Note that restoring mock from `vi.fn()` will set implementation to an empty function that returns `undefined`. Restoring a `vi.fn(impl)` will restore implementation to `impl`. */ - mockRestore: () => void + mockRestore(): void /** * Returns current mock implementation if there is one. * @@ -155,7 +155,7 @@ export interface MockInstance { * * If mock was created with `vi.spyOn`, it will return `undefined` unless a custom implementation was provided. */ - getMockImplementation: () => T | undefined + getMockImplementation(): T | undefined /** * Accepts a function that will be used as an implementation of the mock. * @example @@ -326,7 +326,7 @@ export type Mocked = { } & T -export const mocks = new Set>() +export const mocks = new Set() export function isMockFunction(fn: any): fn is MockInstance { return typeof fn === 'function' @@ -509,7 +509,7 @@ function enhanceSpy( state.willCall(mockCall) - mocks.add(stub as MockInstance) + mocks.add(stub) return stub as any } diff --git a/test/core/test/spy.test.ts b/test/core/test/spy.test.ts index 719160540b01..35cddb90374a 100644 --- a/test/core/test/spy.test.ts +++ b/test/core/test/spy.test.ts @@ -15,7 +15,7 @@ describe('spyOn', () => { test('infers a class correctly', () => { vi.spyOn(mock, 'HelloWorld').mockImplementationOnce(() => { - const Mock = vi.fn() + const Mock = vi.fn() Mock.prototype.hello = vi.fn(() => 'hello world') return new Mock() }) From cb777f14f2bfaef0da98e20058a6c658e92fc4ad Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 23 Apr 2024 14:20:36 +0900 Subject: [PATCH 12/24] chore: cleanup --- packages/spy/src/index.ts | 2 +- test/core/test/vi.spec.ts | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 147a195c2ab5..db22a60ccd3a 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -114,7 +114,7 @@ and { foo(f: T): U } Jest uses the latter for `MockInstance.mockImplementation` etc... and it allows assignment such as: - const boolFn: Jest.Mock<() => boolean> = jest.fn(() => true) + const boolFn: Jest.Mock<() => boolean> = jest.fn<() => true>(() => true) */ /* eslint-disable ts/method-signature-style */ export interface MockInstance { diff --git a/test/core/test/vi.spec.ts b/test/core/test/vi.spec.ts index 193baaf39b8d..86cacc412462 100644 --- a/test/core/test/vi.spec.ts +++ b/test/core/test/vi.spec.ts @@ -50,32 +50,28 @@ describe('testing vi utils', () => { baz: string } - type FooBarFactory = () => FooBar - - const mockFactory = vi.fn() + const mockFactory = vi.fn<() => FooBar>() vi.mocked(mockFactory, { partial: true }).mockReturnValue({ foo: vi.fn(), }) vi.mocked(mockFactory, { partial: true, deep: false }).mockReturnValue({ - bar: vi.fn(), + bar: vi.fn(), }) vi.mocked(mockFactory, { partial: true, deep: true }).mockReturnValue({ baz: 'baz', }) - type FooBarAsyncFactory = () => Promise - - const mockFactoryAsync = vi.fn() + const mockFactoryAsync = vi.fn<() => Promise>() vi.mocked(mockFactoryAsync, { partial: true }).mockResolvedValue({ foo: vi.fn(), }) vi.mocked(mockFactoryAsync, { partial: true, deep: false }).mockResolvedValue({ - bar: vi.fn(), + bar: vi.fn(), }) vi.mocked(mockFactoryAsync, { partial: true, deep: true }).mockResolvedValue({ @@ -105,7 +101,7 @@ describe('testing vi utils', () => { // arguments are not necessary const someFn3: Mock = vi.fn(() => 0) - // @ts-expect-error wrong return type + // @ts-expect-error wrong return type will be caught const someFn4: Mock = vi.fn(() => '0') // TEST From 7b59e10a8305acc4c31f31fa9ca063b94d860159 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 23 Apr 2024 16:13:32 +0900 Subject: [PATCH 13/24] feat: replace `any` with `unknown` for default types --- packages/spy/src/index.ts | 10 +++++----- test/core/test/spy.test.ts | 2 +- test/core/test/vi.spec.ts | 13 +++++++++++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index db22a60ccd3a..a9dfcc70fe04 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -92,8 +92,8 @@ export interface MockContext { lastCall: Parameters | undefined } -// TODO: use `(...args: unknown[]) => unknown` for stricter default like jest? type Procedure = (...args: any[]) => any +type UnknownProcedure = (...args: unknown[]) => unknown type Methods = keyof { [K in keyof T as T[K] extends Procedure ? K : never]: T[K]; @@ -117,7 +117,7 @@ Jest uses the latter for `MockInstance.mockImplementation` etc... and it allows const boolFn: Jest.Mock<() => boolean> = jest.fn<() => true>(() => true) */ /* eslint-disable ts/method-signature-style */ -export interface MockInstance { +export interface MockInstance { /** * Use it to return the name given to mock with method `.mockName(name)`. */ @@ -249,14 +249,14 @@ export interface MockInstance { } /* eslint-enable ts/method-signature-style */ -export interface Mock extends MockInstance { +export interface Mock extends MockInstance { new (...args: Parameters): ReturnType (...args: Parameters): ReturnType } type PartialMaybePromise = T extends Promise> ? Promise>> : Partial -export interface PartialMock extends MockInstance<(...args: Parameters) => PartialMaybePromise>> { +export interface PartialMock extends MockInstance<(...args: Parameters) => PartialMaybePromise>> { new (...args: Parameters): ReturnType (...args: Parameters): ReturnType } @@ -514,7 +514,7 @@ function enhanceSpy( return stub as any } -export function fn( +export function fn( implementation?: T, ): Mock { const enhancedSpy = enhanceSpy(tinyspy.internalSpyOn({ spy: implementation || (() => {}) }, 'spy')) diff --git a/test/core/test/spy.test.ts b/test/core/test/spy.test.ts index 35cddb90374a..719160540b01 100644 --- a/test/core/test/spy.test.ts +++ b/test/core/test/spy.test.ts @@ -15,7 +15,7 @@ describe('spyOn', () => { test('infers a class correctly', () => { vi.spyOn(mock, 'HelloWorld').mockImplementationOnce(() => { - const Mock = vi.fn() + const Mock = vi.fn() Mock.prototype.hello = vi.fn(() => 'hello world') return new Mock() }) diff --git a/test/core/test/vi.spec.ts b/test/core/test/vi.spec.ts index 86cacc412462..5de26f05f495 100644 --- a/test/core/test/vi.spec.ts +++ b/test/core/test/vi.spec.ts @@ -41,6 +41,15 @@ describe('testing vi utils', () => { }) expectType boolean>>(vi.fn(() => true)) expectType boolean>>(vi.fn()) + + expectType boolean>>(vi.fn<() => boolean>(() => true)) + expectType boolean>>(vi.fn<() => boolean>(() => true)) + expectType<() => boolean>(vi.fn(() => true)) + + // @ts-expect-error default unkonwn + expectType<(v: number) => boolean>(vi.fn()) + + expectType<(v: number) => boolean>(vi.fn()) }) test('vi partial mocked', () => { @@ -57,7 +66,7 @@ describe('testing vi utils', () => { }) vi.mocked(mockFactory, { partial: true, deep: false }).mockReturnValue({ - bar: vi.fn(), + bar: vi.fn(), }) vi.mocked(mockFactory, { partial: true, deep: true }).mockReturnValue({ @@ -71,7 +80,7 @@ describe('testing vi utils', () => { }) vi.mocked(mockFactoryAsync, { partial: true, deep: false }).mockResolvedValue({ - bar: vi.fn(), + bar: vi.fn(), }) vi.mocked(mockFactoryAsync, { partial: true, deep: true }).mockResolvedValue({ From 67b188762f0bc9f25a46142cc97713af1df21e9a Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 20 Jun 2024 17:11:10 +0900 Subject: [PATCH 14/24] fix: fix types --- packages/spy/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 5feb9f9d29a5..4a58ea19590a 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -129,7 +129,7 @@ export interface MockContext { * }, * ] */ - settledResults: MockSettledResult>[] + settledResults: MockSettledResult>>[] /** * This contains the arguments of the last call. If spy wasn't called, will return `undefined`. */ @@ -422,7 +422,7 @@ function enhanceSpy( const state = tinyspy.getInternalState(spy) - const mockContext: MockContext = { + const mockContext: MockContext = { get calls() { return state.calls }, From 8ff1526d4fb05c9ee861fd5b48ad45b3197ff943 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 20 Jun 2024 17:13:27 +0900 Subject: [PATCH 15/24] chore: fix format 1 --- packages/spy/src/index.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 4a58ea19590a..b4a37932ddf8 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -378,17 +378,21 @@ export function isMockFunction(fn: any): fn is MockInstance { export function spyOn>>( obj: T, methodName: S, - accessType: 'get', + accessType: 'get' ): MockInstance<() => T[S]> export function spyOn>>( obj: T, methodName: G, - accessType: 'set', + accessType: 'set' ): MockInstance<(arg: T[G]) => void> -export function spyOn> | Methods>)>( +export function spyOn> | Methods>>( obj: T, - methodName: M, -): Required[M] extends ({ new (...args: infer A): infer R }) | ((...args: infer A) => infer R) ? MockInstance<(...args: A) => R> : never + methodName: M +): Required[M] extends +| { new (...args: infer A): infer R } +| ((...args: infer A) => infer R) + ? MockInstance<(...args: A) => R> + : never export function spyOn( obj: T, method: K, From 35273422cf163ad97af899efffd95e8c3d85bfbf Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 20 Jun 2024 17:15:34 +0900 Subject: [PATCH 16/24] chore: fix format 2 --- packages/spy/src/index.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index b4a37932ddf8..e3f3d9b7c0d0 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -295,14 +295,20 @@ export interface MockInstance { } /* eslint-enable ts/method-signature-style */ -export interface Mock extends MockInstance { +export interface Mock + extends MockInstance { new (...args: Parameters): ReturnType (...args: Parameters): ReturnType } -type PartialMaybePromise = T extends Promise> ? Promise>> : Partial +type PartialMaybePromise = T extends Promise> + ? Promise>> + : Partial -export interface PartialMock extends MockInstance<(...args: Parameters) => PartialMaybePromise>> { +export interface PartialMock + extends MockInstance< + (...args: Parameters) => PartialMaybePromise> + > { new (...args: Parameters): ReturnType (...args: Parameters): ReturnType } @@ -318,8 +324,10 @@ export type MockedFunction = Mock & { export type PartiallyMockedFunction = PartialMock & { [K in keyof T]: T[K]; } -export type MockedFunctionDeep = Mock & MockedObjectDeep -export type PartiallyMockedFunctionDeep = PartialMock & MockedObjectDeep +export type MockedFunctionDeep = Mock & + MockedObjectDeep +export type PartiallyMockedFunctionDeep = PartialMock & + MockedObjectDeep export type MockedObject = MaybeMockedConstructor & { [K in Methods]: T[K] extends Procedure ? MockedFunction : T[K]; } & { [K in Properties]: T[K] } From 61f6ecca6f2f845d102730f44c2559a5eceda190 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 20 Jun 2024 17:16:30 +0900 Subject: [PATCH 17/24] chore: fix format 3 --- packages/spy/src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index e3f3d9b7c0d0..6c5f0a35d785 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -363,7 +363,9 @@ interface Constructable { new (...args: any[]): any } -export type MockedClass = MockInstance<(...args: ConstructorParameters) => InstanceType> & { +export type MockedClass = MockInstance< + (...args: ConstructorParameters) => InstanceType +> & { prototype: T extends { prototype: any } ? Mocked : never } & T From 0d47f57a78713711618d8fc872d7cd358f58f1bd Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 20 Jun 2024 17:24:38 +0900 Subject: [PATCH 18/24] docs: jest migration --- docs/guide/migration.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 5b3a6f11dfe7..96dfdee042b8 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -324,13 +324,11 @@ export default defineConfig({ Vitest doesn't have an equivalent to `jest` namespace, so you will need to import types directly from `vitest`: ```ts -let fn: jest.Mock // [!code --] +let fn: jest.Mock<(name: string) => number> // [!code --] import type { Mock } from 'vitest' // [!code ++] -let fn: Mock<[string], string> // [!code ++] +let fn: Mock<(name: string) => number> // [!code ++] ``` -Also, Vitest has `Args` type as a first argument instead of `Returns`, as you can see in diff. - ### Timers Vitest doesn't support Jest's legacy timers. From 5e8b508de9cd6e41be8968737d79dfd361494e00 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 20 Jun 2024 17:48:59 +0900 Subject: [PATCH 19/24] docs: v2 migration --- docs/guide/migration.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 96dfdee042b8..4c78f6e2f80b 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -78,6 +78,19 @@ This makes `.suite` optional; if the task is defined at the top level, it will n This change also removes the file from `expect.getState().currentTestName` and makes `expect.getState().testPath` required. +### Simplified generic types of mock functions (e.g. `vi.fn`, `Mock`) + +Previously `vi.fn` accepted two generic types separately for arguemnts and return value. This is changed to directly accept single function type `vi.fn` to improve the usability. + +```ts +import { type Mock, vi } from 'vitest' + +const add = (x: number, y: number): number => x + y + +const mockAdd = vi.fn, ReturnType>() // [!code --] +const mockAdd = vi.fn() // [!code ++] +``` + ## Migrating to Vitest 1.0 From 1a7a3c870dfee90d7a810354e2cc2e8d849f960f Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 20 Jun 2024 17:53:28 +0900 Subject: [PATCH 20/24] docs: tweak --- docs/guide/migration.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 4c78f6e2f80b..3196c577a7f6 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -80,15 +80,20 @@ This change also removes the file from `expect.getState().currentTestName` and m ### Simplified generic types of mock functions (e.g. `vi.fn`, `Mock`) -Previously `vi.fn` accepted two generic types separately for arguemnts and return value. This is changed to directly accept single function type `vi.fn` to improve the usability. +Previously `vi.fn` accepted two generic types separately for arguemnts and return value. This is changed to directly accept a function type `vi.fn` to simplify the usage. ```ts import { type Mock, vi } from 'vitest' const add = (x: number, y: number): number => x + y +// using vi.fn const mockAdd = vi.fn, ReturnType>() // [!code --] const mockAdd = vi.fn() // [!code ++] + +// using Mock +const mockAdd: Mock, ReturnType> = vi.fn() // [!code --] +const mockAdd: Mock = vi.fn() // [!code ++] ``` ## Migrating to Vitest 1.0 From 140c25d9d697cd33562619ddb7d092503a267459 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 20 Jun 2024 18:04:11 +0900 Subject: [PATCH 21/24] fix: more any types --- test/cli/test/create-vitest.test.ts | 2 +- test/core/__mocks__/zustand.ts | 2 +- test/core/test/task-collector.test.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/cli/test/create-vitest.test.ts b/test/cli/test/create-vitest.test.ts index a4719cc1d2e6..8c7a0caf22ba 100644 --- a/test/cli/test/create-vitest.test.ts +++ b/test/cli/test/create-vitest.test.ts @@ -2,7 +2,7 @@ import { expect, it, vi } from 'vitest' import { createVitest } from 'vitest/node' it(createVitest, async () => { - const onFinished = vi.fn() + const onFinished = vi.fn() const ctx = await createVitest('test', { watch: false, root: 'fixtures/create-vitest', diff --git a/test/core/__mocks__/zustand.ts b/test/core/__mocks__/zustand.ts index 6bfe4ce3bf86..3e79bb10ae99 100644 --- a/test/core/__mocks__/zustand.ts +++ b/test/core/__mocks__/zustand.ts @@ -2,7 +2,7 @@ import actualCreate from 'zustand' import { vi } from 'vitest' // when creating a store, we get its initial state, create a reset function and add it in the set -const create = vi.fn((createState) => { +const create = vi.fn((createState: any) => { const store = actualCreate(createState) return store }) diff --git a/test/core/test/task-collector.test.ts b/test/core/test/task-collector.test.ts index f37009f40c07..e30d7f1834c2 100644 --- a/test/core/test/task-collector.test.ts +++ b/test/core/test/task-collector.test.ts @@ -2,9 +2,9 @@ import { expect, test, vi } from 'vitest' import { createTaskCollector } from 'vitest/suite' test('collector keeps the order of arguments', () => { - const fn = vi.fn() + const fn = vi.fn() const collector = createTaskCollector(fn) - const cb = vi.fn() + const cb = vi.fn() const options = {} collector('a', cb, options) From 532684b017fcc21d2c8398a89681af11b7c00cfe Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 20 Jun 2024 18:18:21 +0900 Subject: [PATCH 22/24] docs: also call out `any` to `unknown` --- docs/guide/migration.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 3196c577a7f6..8ff2115a6258 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -96,6 +96,14 @@ const mockAdd: Mock, ReturnType> = vi.fn() // const mockAdd: Mock = vi.fn() // [!code ++] ``` +Also previously default generic types were `any`. However, they have been changed to `unknown` and thus strict in some cases. +To maintain the previous loose typing, you can use `vi.fn.` + +```ts +const anyFn = vi.fn() // [!code --] +const anyFn = vi.fn() // [!code ++] +``` + ## Migrating to Vitest 1.0 From e5c85c12e26cbe0cd61bbaafc4d7543bd4c60d3a Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 21 Jun 2024 10:19:03 +0900 Subject: [PATCH 23/24] chore: revert default UknownProcedure to AnyProcedure --- docs/guide/migration.md | 8 -------- packages/spy/src/index.ts | 9 ++++----- test/cli/test/create-vitest.test.ts | 2 +- test/core/test/spy.test.ts | 2 +- test/core/test/task-collector.test.ts | 4 ++-- test/core/test/vi.spec.ts | 3 --- 6 files changed, 8 insertions(+), 20 deletions(-) diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 8ff2115a6258..3196c577a7f6 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -96,14 +96,6 @@ const mockAdd: Mock, ReturnType> = vi.fn() // const mockAdd: Mock = vi.fn() // [!code ++] ``` -Also previously default generic types were `any`. However, they have been changed to `unknown` and thus strict in some cases. -To maintain the previous loose typing, you can use `vi.fn.` - -```ts -const anyFn = vi.fn() // [!code --] -const anyFn = vi.fn() // [!code ++] -``` - ## Migrating to Vitest 1.0 diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 6c5f0a35d785..98e8c4acb800 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -137,7 +137,6 @@ export interface MockContext { } type Procedure = (...args: any[]) => any -type UnknownProcedure = (...args: unknown[]) => unknown type Methods = keyof { [K in keyof T as T[K] extends Procedure ? K : never]: T[K]; @@ -163,7 +162,7 @@ Jest uses the latter for `MockInstance.mockImplementation` etc... and it allows const boolFn: Jest.Mock<() => boolean> = jest.fn<() => true>(() => true) */ /* eslint-disable ts/method-signature-style */ -export interface MockInstance { +export interface MockInstance { /** * Use it to return the name given to mock with method `.mockName(name)`. */ @@ -295,7 +294,7 @@ export interface MockInstance { } /* eslint-enable ts/method-signature-style */ -export interface Mock +export interface Mock extends MockInstance { new (...args: Parameters): ReturnType (...args: Parameters): ReturnType @@ -305,7 +304,7 @@ type PartialMaybePromise = T extends Promise> ? Promise>> : Partial -export interface PartialMock +export interface PartialMock extends MockInstance< (...args: Parameters) => PartialMaybePromise> > { @@ -582,7 +581,7 @@ function enhanceSpy( return stub as any } -export function fn( +export function fn( implementation?: T, ): Mock { const enhancedSpy = enhanceSpy(tinyspy.internalSpyOn({ spy: implementation || (() => {}) }, 'spy')) diff --git a/test/cli/test/create-vitest.test.ts b/test/cli/test/create-vitest.test.ts index 8c7a0caf22ba..a4719cc1d2e6 100644 --- a/test/cli/test/create-vitest.test.ts +++ b/test/cli/test/create-vitest.test.ts @@ -2,7 +2,7 @@ import { expect, it, vi } from 'vitest' import { createVitest } from 'vitest/node' it(createVitest, async () => { - const onFinished = vi.fn() + const onFinished = vi.fn() const ctx = await createVitest('test', { watch: false, root: 'fixtures/create-vitest', diff --git a/test/core/test/spy.test.ts b/test/core/test/spy.test.ts index 719160540b01..35cddb90374a 100644 --- a/test/core/test/spy.test.ts +++ b/test/core/test/spy.test.ts @@ -15,7 +15,7 @@ describe('spyOn', () => { test('infers a class correctly', () => { vi.spyOn(mock, 'HelloWorld').mockImplementationOnce(() => { - const Mock = vi.fn() + const Mock = vi.fn() Mock.prototype.hello = vi.fn(() => 'hello world') return new Mock() }) diff --git a/test/core/test/task-collector.test.ts b/test/core/test/task-collector.test.ts index e30d7f1834c2..f37009f40c07 100644 --- a/test/core/test/task-collector.test.ts +++ b/test/core/test/task-collector.test.ts @@ -2,9 +2,9 @@ import { expect, test, vi } from 'vitest' import { createTaskCollector } from 'vitest/suite' test('collector keeps the order of arguments', () => { - const fn = vi.fn() + const fn = vi.fn() const collector = createTaskCollector(fn) - const cb = vi.fn() + const cb = vi.fn() const options = {} collector('a', cb, options) diff --git a/test/core/test/vi.spec.ts b/test/core/test/vi.spec.ts index 5de26f05f495..79f0164efc3d 100644 --- a/test/core/test/vi.spec.ts +++ b/test/core/test/vi.spec.ts @@ -46,10 +46,7 @@ describe('testing vi utils', () => { expectType boolean>>(vi.fn<() => boolean>(() => true)) expectType<() => boolean>(vi.fn(() => true)) - // @ts-expect-error default unkonwn expectType<(v: number) => boolean>(vi.fn()) - - expectType<(v: number) => boolean>(vi.fn()) }) test('vi partial mocked', () => { From 72adccf42d83e0954abe8d28ed71ab345a934e89 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 21 Jun 2024 10:24:38 +0900 Subject: [PATCH 24/24] chore: more any --- test/core/__mocks__/zustand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/core/__mocks__/zustand.ts b/test/core/__mocks__/zustand.ts index 3e79bb10ae99..6bfe4ce3bf86 100644 --- a/test/core/__mocks__/zustand.ts +++ b/test/core/__mocks__/zustand.ts @@ -2,7 +2,7 @@ import actualCreate from 'zustand' import { vi } from 'vitest' // when creating a store, we get its initial state, create a reset function and add it in the set -const create = vi.fn((createState: any) => { +const create = vi.fn((createState) => { const store = actualCreate(createState) return store })