Skip to content

Commit 9ef6ad8

Browse files
authored
fix: stringify symbol props in stubs (#1086)
Fixes #1076
1 parent bfd070d commit 9ef6ad8

File tree

3 files changed

+92
-3
lines changed

3 files changed

+92
-3
lines changed

src/stubs.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88
defineComponent,
99
VNodeTypes,
1010
ConcreteComponent,
11-
ComponentPropsOptions
11+
ComponentPropsOptions,
12+
ComponentObjectPropsOptions
1213
} from 'vue'
1314
import { hyphenate } from './utils/vueShared'
1415
import { matchName } from './utils/matchName'
@@ -55,6 +56,17 @@ const shouldNotStub = (type: ConcreteComponent) => doNotStubComponents.has(type)
5556
export const addToDoNotStubComponents = (type: ConcreteComponent) =>
5657
doNotStubComponents.add(type)
5758

59+
const stringifySymbols = (props: ComponentPropsOptions) => {
60+
// props are always normalized to object syntax
61+
const $props = props as unknown as ComponentObjectPropsOptions
62+
return Object.keys($props).reduce((acc, key) => {
63+
if (typeof $props[key] === 'symbol') {
64+
return { ...acc, [key]: $props[key]?.toString() }
65+
}
66+
return { ...acc, [key]: $props[key] }
67+
}, {})
68+
}
69+
5870
export const createStub = ({
5971
name,
6072
propsDeclaration,
@@ -64,7 +76,19 @@ export const createStub = ({
6476
const tag = name ? `${hyphenate(name)}-stub` : anonName
6577

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

7094
return defineComponent({
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<template>
2+
<div>Symbol: {{ $props.sym }}</div>
3+
</template>
4+
5+
<script lang="ts" setup>
6+
interface Props {
7+
sym?: Symbol
8+
}
9+
10+
withDefaults(defineProps<Props>(), {
11+
sym: () => Symbol()
12+
})
13+
</script>

tests/props.spec.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { mount } from '../src'
1+
import { mount, shallowMount } from '../src'
22
import WithProps from './components/WithProps.vue'
3+
import PropWithSymbol from './components/PropWithSymbol.vue'
34
import Hello from './components/Hello.vue'
45
import { defineComponent, h } from 'vue'
56

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

225226
expect(wrapper.text()).toEqual('hello')
226227
})
228+
229+
describe('edge case with symbol props and stubs', () => {
230+
it('works with Symbol as default', () => {
231+
const Comp = defineComponent({
232+
template: `<div>Symbol: {{ sym }}</div>`,
233+
props: {
234+
sym: {
235+
type: Symbol,
236+
default: () => Symbol()
237+
}
238+
}
239+
})
240+
241+
const wrapper = shallowMount(Comp)
242+
243+
expect(wrapper.html()).toBe('<div>Symbol: Symbol()</div>')
244+
})
245+
246+
it('works with symbol as array syntax', () => {
247+
const Comp = defineComponent({
248+
name: 'Comp',
249+
template: `<div>Symbol: {{ sym }}</div>`,
250+
props: ['sym']
251+
})
252+
253+
const wrapper = shallowMount({
254+
render() {
255+
return h(Comp, { sym: Symbol() })
256+
}
257+
})
258+
259+
expect(wrapper.html()).toBe('<comp-stub sym="Symbol()"></comp-stub>')
260+
})
261+
262+
it('works with symbol as default from SFC', () => {
263+
const App = defineComponent({
264+
template: `<PropWithSymbol :sym="sym" />`,
265+
components: { PropWithSymbol },
266+
data() {
267+
return {
268+
sym: Symbol()
269+
}
270+
}
271+
})
272+
const wrapper = shallowMount(App)
273+
274+
expect(wrapper.html()).toBe(
275+
'<prop-with-symbol-stub sym="Symbol()"></prop-with-symbol-stub>'
276+
)
277+
})
278+
})
227279
})

0 commit comments

Comments
 (0)