fix(runtime-core): allow spying on proxy methods (#4216)

Since Jest v26.6.1, the mock method changed (see this commit 30e8020362)  to rely on `Object.defineProperty` in some cases.

This breaks spying on proxy's methods, because even if Jest is properly calling `Object.defineProperty`, the cached value in the `get` section of the proxy is never updated, and the spy is in fact never used.
This is easily reproducible as vue-next already uses a version of jest with these changes.

This is blocking projects (like vue-test-utils-next and vue-cli) to update to recent Jest versions.

This commit adds a `defineProperty` method to the proxy handler, that properly updates the defined value in the cache.
This commit is contained in:
Cédric Exbrayat 2022-02-12 09:35:05 +01:00 committed by GitHub
parent 436c500d2c
commit 8457d8b980
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 83 additions and 0 deletions

View File

@ -214,6 +214,74 @@ describe('component: proxy', () => {
]) ])
}) })
test('allow updating proxy with Object.defineProperty', () => {
let instanceProxy: any
const Comp = {
render() {},
setup() {
return {
isDisplayed: true
}
},
mounted() {
instanceProxy = this
}
}
const app = createApp(Comp)
app.mount(nodeOps.createElement('div'))
Object.defineProperty(instanceProxy, 'isDisplayed', { value: false })
expect(instanceProxy.isDisplayed).toBe(false)
Object.defineProperty(instanceProxy, 'isDisplayed', { value: true })
expect(instanceProxy.isDisplayed).toBe(true)
Object.defineProperty(instanceProxy, 'isDisplayed', {
get() {
return false
}
})
expect(instanceProxy.isDisplayed).toBe(false)
Object.defineProperty(instanceProxy, 'isDisplayed', {
get() {
return true
}
})
expect(instanceProxy.isDisplayed).toBe(true)
})
test('allow spying on proxy methods', () => {
let instanceProxy: any
const Comp = {
render() {},
setup() {
return {
toggle() {}
}
},
mounted() {
instanceProxy = this
}
}
const app = createApp(Comp)
app.mount(nodeOps.createElement('div'))
const spy = jest.spyOn(instanceProxy, 'toggle')
instanceProxy.toggle()
expect(spy).toHaveBeenCalled()
})
// #864 // #864
test('should not warn declared but absent props', () => { test('should not warn declared but absent props', () => {
const Comp = { const Comp = {

View File

@ -397,8 +397,10 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
const { data, setupState, ctx } = instance const { data, setupState, ctx } = instance
if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) { if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
setupState[key] = value setupState[key] = value
return true
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) { } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
data[key] = value data[key] = value
return true
} else if (hasOwn(instance.props, key)) { } else if (hasOwn(instance.props, key)) {
__DEV__ && __DEV__ &&
warn( warn(
@ -445,6 +447,19 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
hasOwn(publicPropertiesMap, key) || hasOwn(publicPropertiesMap, key) ||
hasOwn(appContext.config.globalProperties, key) hasOwn(appContext.config.globalProperties, key)
) )
},
defineProperty(
target: ComponentRenderContext,
key: string,
descriptor: PropertyDescriptor
) {
if (descriptor.get != null) {
this.set!(target, key, descriptor.get(), null)
} else if (descriptor.value != null) {
this.set!(target, key, descriptor.value, null)
}
return Reflect.defineProperty(target, key, descriptor)
} }
} }