Skip to content

Commit

Permalink
fix: allow mocking on script setup components (#1861)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
cexbrayat committed Nov 15, 2022
1 parent bf1fbe9 commit 9a256db
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 8 deletions.
15 changes: 12 additions & 3 deletions src/mount.ts
Expand Up @@ -33,6 +33,7 @@ import { MountingOptions, Slot } from './types'
import {
getComponentsFromStubs,
getDirectivesFromStubs,
hasSetupState,
isFunctionalComponent,
isObject,
isObjectComponent,
Expand Down Expand Up @@ -475,15 +476,23 @@ export function mount(

// global mocks mixin
if (global?.mocks) {
const mixin = {
const mixin = defineComponent({
beforeCreate() {
for (const [k, v] of Object.entries(
global.mocks as { [key: string]: any }
)) {
;(this as any)[k] = v
// we need to differentiate components that are or not not `script setup`
// otherwise we run into a proxy set error
// due to https://github.com/vuejs/core/commit/f73925d76a76ee259749b8b48cb68895f539a00f#diff-ea4d1ddabb7e22e17e80ada458eef70679af4005df2a1a6b73418fec897603ceR404
// introduced in Vue v3.2.45
if (hasSetupState(this as any)) {
;(this as any).$.setupState[k] = v
} else {
;(this as any)[k] = v
}
}
}
}
})

app.mixin(mixin)
}
Expand Down
9 changes: 9 additions & 0 deletions src/utils.ts
Expand Up @@ -2,6 +2,7 @@ import { GlobalMountOptions, RefSelector, Stub, Stubs } from './types'
import {
Component,
ComponentOptions,
ComponentPublicInstance,
ConcreteComponent,
Directive,
FunctionalComponent
Expand Down Expand Up @@ -185,3 +186,11 @@ export function getDirectivesFromStubs(
.map(([key, value]) => [key.substring(1), value])
) as Record<string, Directive>
}
export function hasSetupState(
vm: ComponentPublicInstance
): vm is ComponentPublicInstance & { setupState: Record<string, unknown> } {
return (
vm &&
(vm.$ as unknown as { devtoolsRawSetupState: any }).devtoolsRawSetupState
)
}
7 changes: 2 additions & 5 deletions src/vueWrapper.ts
Expand Up @@ -8,7 +8,7 @@ import {
import { config } from './config'
import domEvents from './constants/dom-events'
import { VueElement, VueNode } from './types'
import { mergeDeep } from './utils'
import { hasSetupState, mergeDeep } from './utils'
import { getRootNodes } from './utils/getRootNodes'
import { emitted, recordEvent, removeEventHistory } from './emit'
import BaseWrapper from './baseWrapper'
Expand Down Expand Up @@ -107,10 +107,7 @@ export class VueWrapper<
// This does not work for functional components though (as they have no vm)
// or for components with a setup that returns a render function (as they have an empty proxy)
// in both cases, we return `vm` directly instead
if (
vm &&
(vm.$ as unknown as { devtoolsRawSetupState: any }).devtoolsRawSetupState
) {
if (hasSetupState(vm)) {
this.componentVM = createVMProxy<T>(vm, (vm.$ as any).setupState)
} else {
this.componentVM = vm
Expand Down
19 changes: 19 additions & 0 deletions tests/expose.spec.ts
Expand Up @@ -91,4 +91,23 @@ describe('expose', () => {
await wrapper.find('button').trigger('click')
expect(wrapper.html()).toContain('3')
})

it('should not throw when mocking', async () => {
const spiedIncrement = vi.fn()
const wrapper = mount(ScriptSetup, {
global: {
mocks: {
count: -1,
inc: spiedIncrement
}
}
})
expect(wrapper.html()).toContain('-1')

await wrapper.find('button').trigger('click')
await nextTick()

expect(spiedIncrement).toHaveBeenCalled()
expect(wrapper.html()).toContain('-1')
})
})

0 comments on commit 9a256db

Please sign in to comment.