Skip to content

fix: remove symbol props from stubs #1086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Dec 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions src/stubs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
defineComponent,
VNodeTypes,
ConcreteComponent,
ComponentPropsOptions
ComponentPropsOptions,
ComponentObjectPropsOptions
} from 'vue'
import { hyphenate } from './utils/vueShared'
import { matchName } from './utils/matchName'
Expand Down Expand Up @@ -42,6 +43,17 @@ const shouldNotStub = (type: ConcreteComponent) => doNotStubComponents.has(type)
export const addToDoNotStubComponents = (type: ConcreteComponent) =>
doNotStubComponents.add(type)

const stringifySymbols = (props: ComponentPropsOptions) => {
// props are always normalized to object syntax
const $props = props as unknown as ComponentObjectPropsOptions
return Object.keys($props).reduce((acc, key) => {
if (typeof $props[key] === 'symbol') {
return { ...acc, [key]: $props[key]?.toString() }
}
return { ...acc, [key]: $props[key] }
}, {})
}

export const createStub = ({
name,
propsDeclaration,
Expand All @@ -51,7 +63,19 @@ export const createStub = ({
const tag = name ? `${hyphenate(name)}-stub` : anonName

const render = (ctx: ComponentPublicInstance) => {
return h(tag, ctx.$props, renderStubDefaultSlot ? ctx.$slots : undefined)
// https://github.com/vuejs/vue-test-utils-next/issues/1076
// Passing a symbol as a static prop is not legal, since Vue will try to do
// something like `el.setAttribute('val', Symbol())` which is not valid and
// causes an error.
// Only a problem when shallow mounting. For this reason we iterate of the
// props that will be passed and stringify any that are symbols.
const propsWithoutSymbols = stringifySymbols(ctx.$props)

return h(
tag,
propsWithoutSymbols,
renderStubDefaultSlot ? ctx.$slots : undefined
)
}

return defineComponent({
Expand Down
13 changes: 13 additions & 0 deletions tests/components/PropWithSymbol.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<template>
<div>Symbol: {{ $props.sym }}</div>
</template>

<script lang="ts" setup>
interface Props {
sym?: Symbol
}

withDefaults(defineProps<Props>(), {
sym: () => Symbol()
})
</script>
54 changes: 53 additions & 1 deletion tests/props.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { mount } from '../src'
import { mount, shallowMount } from '../src'
import WithProps from './components/WithProps.vue'
import PropWithSymbol from './components/PropWithSymbol.vue'
import Hello from './components/Hello.vue'
import { defineComponent, h } from 'vue'

Expand Down Expand Up @@ -224,4 +225,55 @@ describe('props', () => {

expect(wrapper.text()).toEqual('hello')
})

describe('edge case with symbol props and stubs', () => {
it('works with Symbol as default', () => {
const Comp = defineComponent({
template: `<div>Symbol: {{ sym }}</div>`,
props: {
sym: {
type: Symbol,
default: () => Symbol()
}
}
})

const wrapper = shallowMount(Comp)

expect(wrapper.html()).toBe('<div>Symbol: Symbol()</div>')
})

it('works with symbol as array syntax', () => {
const Comp = defineComponent({
name: 'Comp',
template: `<div>Symbol: {{ sym }}</div>`,
props: ['sym']
})

const wrapper = shallowMount({
render() {
return h(Comp, { sym: Symbol() })
}
})

expect(wrapper.html()).toBe('<comp-stub sym="Symbol()"></comp-stub>')
})

it('works with symbol as default from SFC', () => {
const App = defineComponent({
template: `<PropWithSymbol :sym="sym" />`,
components: { PropWithSymbol },
data() {
return {
sym: Symbol()
}
}
})
const wrapper = shallowMount(App)

expect(wrapper.html()).toBe(
'<prop-with-symbol-stub sym="Symbol()"></prop-with-symbol-stub>'
)
})
})
})