diff --git a/src/stubs.ts b/src/stubs.ts index a2747fc98..c1d0c555a 100644 --- a/src/stubs.ts +++ b/src/stubs.ts @@ -7,7 +7,8 @@ import { defineComponent, VNodeTypes, ConcreteComponent, - ComponentPropsOptions + ComponentPropsOptions, + ComponentObjectPropsOptions } from 'vue' import { hyphenate } from './utils/vueShared' import { matchName } from './utils/matchName' @@ -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, @@ -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({ diff --git a/tests/components/PropWithSymbol.vue b/tests/components/PropWithSymbol.vue new file mode 100644 index 000000000..aea5e64fc --- /dev/null +++ b/tests/components/PropWithSymbol.vue @@ -0,0 +1,13 @@ + + + diff --git a/tests/props.spec.ts b/tests/props.spec.ts index 968ef48fd..f4788c3e1 100644 --- a/tests/props.spec.ts +++ b/tests/props.spec.ts @@ -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' @@ -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: `
Symbol: {{ sym }}
`, + props: { + sym: { + type: Symbol, + default: () => Symbol() + } + } + }) + + const wrapper = shallowMount(Comp) + + expect(wrapper.html()).toBe('
Symbol: Symbol()
') + }) + + it('works with symbol as array syntax', () => { + const Comp = defineComponent({ + name: 'Comp', + template: `
Symbol: {{ sym }}
`, + props: ['sym'] + }) + + const wrapper = shallowMount({ + render() { + return h(Comp, { sym: Symbol() }) + } + }) + + expect(wrapper.html()).toBe('') + }) + + it('works with symbol as default from SFC', () => { + const App = defineComponent({ + template: ``, + components: { PropWithSymbol }, + data() { + return { + sym: Symbol() + } + } + }) + const wrapper = shallowMount(App) + + expect(wrapper.html()).toBe( + '' + ) + }) + }) })