]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): allow spying on proxy methods (#4216)
authorCédric Exbrayat <cexbrayat@users.noreply.github.com>
Sat, 12 Feb 2022 08:35:05 +0000 (09:35 +0100)
committerGitHub <noreply@github.com>
Sat, 12 Feb 2022 08:35:05 +0000 (03:35 -0500)
Since Jest v26.6.1, the mock method changed (see this commit https://github.com/facebook/jest/commit/30e802036291f4c9c9fd4feef6faba485df54dd2)  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.

packages/runtime-core/__tests__/componentPublicInstance.spec.ts
packages/runtime-core/src/componentPublicInstance.ts

index 0274351f8639f225d62974cd6dc3b2c0350fe5bd..fe588115d31f2668bb17e84f5d7eec94171b0d79 100644 (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
   test('should not warn declared but absent props', () => {
     const Comp = {
index 1d7cfbdec69825bd3989dd47bffecbbccba5abb9..0413730032c53dda584eecfd40f50647ab073956 100644 (file)
@@ -397,8 +397,10 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
     const { data, setupState, ctx } = instance
     if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
       setupState[key] = value
+      return true
     } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
       data[key] = value
+      return true
     } else if (hasOwn(instance.props, key)) {
       __DEV__ &&
         warn(
@@ -445,6 +447,19 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
       hasOwn(publicPropertiesMap, 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)
   }
 }