Skip to content

Commit

Permalink
fix: reuse spy when calling spyOn
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Sep 9, 2024
1 parent 4ba8197 commit 5da37ab
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 14 deletions.
5 changes: 5 additions & 0 deletions src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ export function createInternalSpy<A extends any[], R>(
export function populateSpy<A extends any[], R>(spy: SpyInternal<A, R>) {
const I = getInternalState(spy)

// already populated
if ('returns' in spy) {
return
}

define(spy, 'returns', {
get: () => I.results.map(([, r]) => r),
})
Expand Down
36 changes: 22 additions & 14 deletions src/spyOn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
populateSpy,
spies,
SpyImpl,
SpyInternal,
SpyInternalImpl,
} from './internal'
import { assert, define, defineValue, isType } from './utils'
Expand Down Expand Up @@ -47,7 +48,7 @@ export function internalSpyOn<T, K extends string & keyof T>(

let [accessName, accessType] = ((): [
string | symbol | number,
'value' | 'get' | 'set'
'value' | 'get' | 'set',
] => {
if (!isType('object', methodName)) {
return [methodName, 'value']
Expand Down Expand Up @@ -97,12 +98,6 @@ export function internalSpyOn<T, K extends string & keyof T>(
origin = obj[accessName as keyof T] as unknown as Procedure
}

if (!mock) mock = origin

let fn = createInternalSpy(mock)
if (accessType === 'value') {
prototype(fn, origin)
}
let reassign = (cb: any) => {
let { value, ...desc } = originalDescriptor || {
configurable: true,
Expand All @@ -118,13 +113,26 @@ export function internalSpyOn<T, K extends string & keyof T>(
originalDescriptor
? define(obj, accessName, originalDescriptor)
: reassign(origin)
const state = fn[S]
defineValue(state, 'restore', restore)
defineValue(state, 'getOriginal', () => (ssr ? origin() : origin))
defineValue(state, 'willCall', (newCb: Procedure) => {
state.impl = newCb
return fn
})

if (!mock) mock = origin

let fn: SpyInternal
if (origin && S in origin) {
fn = origin as SpyInternal
} else {
fn = createInternalSpy(mock)
if (accessType === 'value') {
prototype(fn, origin)
}

const state = fn[S]
defineValue(state, 'restore', restore)
defineValue(state, 'getOriginal', () => (ssr ? origin() : origin))
defineValue(state, 'willCall', (newCb: Procedure) => {
state.impl = newCb
return fn
})
}

reassign(
ssr
Expand Down
20 changes: 20 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -755,3 +755,23 @@ test('next in a row', () => {
expect(cb()).toBe(3)
expect(cb()).toBe(undefined)
})

test.only('spying twice and unspying restores original method', () => {
const obj = {
method: () => 1,
}
const spy1 = spyOn(obj, 'method').willCall(() => 2)
expect(obj.method()).toBe(2)

const spy2 = spyOn(obj, 'method')

expect(spy1).toBe(spy2)
expect(obj.method()).toBe(2)

spy2.willCall(() => 3)

expect(obj.method()).toBe(3)

spy2.restore()
expect(obj.method).not.toBe(spy1)
})

0 comments on commit 5da37ab

Please sign in to comment.