Skip to content

Commit 9a256db

Browse files
authored
fix: allow mocking on script setup components (#1861)
We need to differentiate how the mixin used for mocking updates the isntance, as Vue v3.2.45 now forbids to update directly a script setup component from a component written with the Options API. Fixes the error `set' on proxy: trap returned falsish for property` This comes from the fact that mocks are set via a mixin (Options API) and we run into the more strict behavior of Vue v3.2.45 introduced in vuejs/core@f73925d#diff-ea4d1ddabb7e22e17e80ada458eef70679af4005df2a1a6b73418fec897603ceR404
1 parent bf1fbe9 commit 9a256db

File tree

4 files changed

+42
-8
lines changed

4 files changed

+42
-8
lines changed

src/mount.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { MountingOptions, Slot } from './types'
3333
import {
3434
getComponentsFromStubs,
3535
getDirectivesFromStubs,
36+
hasSetupState,
3637
isFunctionalComponent,
3738
isObject,
3839
isObjectComponent,
@@ -475,15 +476,23 @@ export function mount(
475476

476477
// global mocks mixin
477478
if (global?.mocks) {
478-
const mixin = {
479+
const mixin = defineComponent({
479480
beforeCreate() {
480481
for (const [k, v] of Object.entries(
481482
global.mocks as { [key: string]: any }
482483
)) {
483-
;(this as any)[k] = v
484+
// we need to differentiate components that are or not not `script setup`
485+
// otherwise we run into a proxy set error
486+
// due to https://github.com/vuejs/core/commit/f73925d76a76ee259749b8b48cb68895f539a00f#diff-ea4d1ddabb7e22e17e80ada458eef70679af4005df2a1a6b73418fec897603ceR404
487+
// introduced in Vue v3.2.45
488+
if (hasSetupState(this as any)) {
489+
;(this as any).$.setupState[k] = v
490+
} else {
491+
;(this as any)[k] = v
492+
}
484493
}
485494
}
486-
}
495+
})
487496

488497
app.mixin(mixin)
489498
}

src/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { GlobalMountOptions, RefSelector, Stub, Stubs } from './types'
22
import {
33
Component,
44
ComponentOptions,
5+
ComponentPublicInstance,
56
ConcreteComponent,
67
Directive,
78
FunctionalComponent
@@ -185,3 +186,11 @@ export function getDirectivesFromStubs(
185186
.map(([key, value]) => [key.substring(1), value])
186187
) as Record<string, Directive>
187188
}
189+
export function hasSetupState(
190+
vm: ComponentPublicInstance
191+
): vm is ComponentPublicInstance & { setupState: Record<string, unknown> } {
192+
return (
193+
vm &&
194+
(vm.$ as unknown as { devtoolsRawSetupState: any }).devtoolsRawSetupState
195+
)
196+
}

src/vueWrapper.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { config } from './config'
99
import domEvents from './constants/dom-events'
1010
import { VueElement, VueNode } from './types'
11-
import { mergeDeep } from './utils'
11+
import { hasSetupState, mergeDeep } from './utils'
1212
import { getRootNodes } from './utils/getRootNodes'
1313
import { emitted, recordEvent, removeEventHistory } from './emit'
1414
import BaseWrapper from './baseWrapper'
@@ -107,10 +107,7 @@ export class VueWrapper<
107107
// This does not work for functional components though (as they have no vm)
108108
// or for components with a setup that returns a render function (as they have an empty proxy)
109109
// in both cases, we return `vm` directly instead
110-
if (
111-
vm &&
112-
(vm.$ as unknown as { devtoolsRawSetupState: any }).devtoolsRawSetupState
113-
) {
110+
if (hasSetupState(vm)) {
114111
this.componentVM = createVMProxy<T>(vm, (vm.$ as any).setupState)
115112
} else {
116113
this.componentVM = vm

tests/expose.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,23 @@ describe('expose', () => {
9191
await wrapper.find('button').trigger('click')
9292
expect(wrapper.html()).toContain('3')
9393
})
94+
95+
it('should not throw when mocking', async () => {
96+
const spiedIncrement = vi.fn()
97+
const wrapper = mount(ScriptSetup, {
98+
global: {
99+
mocks: {
100+
count: -1,
101+
inc: spiedIncrement
102+
}
103+
}
104+
})
105+
expect(wrapper.html()).toContain('-1')
106+
107+
await wrapper.find('button').trigger('click')
108+
await nextTick()
109+
110+
expect(spiedIncrement).toHaveBeenCalled()
111+
expect(wrapper.html()).toContain('-1')
112+
})
94113
})

0 commit comments

Comments
 (0)