From d11f04b1e958c0c6d19709da0ae1e815ad76462f Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Wed, 17 Nov 2021 22:20:39 +1000 Subject: [PATCH 1/9] update --- tests/components/PropWithSymbol.vue | 13 +++++++++++++ tests/props.spec.ts | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/components/PropWithSymbol.vue diff --git a/tests/components/PropWithSymbol.vue b/tests/components/PropWithSymbol.vue new file mode 100644 index 000000000..e24f8cf2e --- /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..b28e5a930 100644 --- a/tests/props.spec.ts +++ b/tests/props.spec.ts @@ -1,5 +1,6 @@ import { mount } 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,26 @@ describe('props', () => { expect(wrapper.text()).toEqual('hello') }) + + it('works with Symbol as default', () => { + const Comp = defineComponent({ + template: `
Symbol: {{ sym }}
`, + props: { + sym: { + type: Symbol, + default: () => Symbol() + } + } + }) + + const wrapper = mount(Comp, { shallow: true }) + + expect(wrapper.html()).toBe('
Symbol: Symbol()
') + }) + + it('works with Symbol as default from SFC', () => { + const wrapper = mount(PropWithSymbol, { shallow: true }) + + expect(wrapper.html()).toBe('
Symbol: Symbol()
') + }) }) From 84670a656f2c1f335b73bdb5c212dae542fb77af Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Wed, 17 Nov 2021 22:52:34 +1000 Subject: [PATCH 2/9] repro --- tests/props.spec.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/props.spec.ts b/tests/props.spec.ts index b28e5a930..5fa068757 100644 --- a/tests/props.spec.ts +++ b/tests/props.spec.ts @@ -1,4 +1,4 @@ -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' @@ -237,13 +237,22 @@ describe('props', () => { } }) - const wrapper = mount(Comp, { shallow: true }) + const wrapper = shallowMount(Comp) expect(wrapper.html()).toBe('
Symbol: Symbol()
') }) - it('works with Symbol as default from SFC', () => { - const wrapper = mount(PropWithSymbol, { shallow: true }) + it.only('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('
Symbol: Symbol()
') }) From 4ddcd224f2ccac733a17aa398fcf0d82dd5b4fee Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Thu, 18 Nov 2021 09:43:46 +1000 Subject: [PATCH 3/9] fix --- src/stubs.ts | 29 +++++++++++++++++++++++------ tests/props.spec.ts | 2 +- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/stubs.ts b/src/stubs.ts index a2747fc98..107b7e0cb 100644 --- a/src/stubs.ts +++ b/src/stubs.ts @@ -21,7 +21,7 @@ import { Stub, Stubs } from './types' interface StubOptions { name: string - propsDeclaration?: ComponentPropsOptions + propsDeclaration: ComponentPropsOptions renderStubDefaultSlot?: boolean } @@ -50,8 +50,23 @@ export const createStub = ({ const anonName = 'anonymous-stub' const tag = name ? `${hyphenate(name)}-stub` : anonName - const render = (ctx: ComponentPublicInstance) => { - return h(tag, ctx.$props, renderStubDefaultSlot ? ctx.$slots : undefined) + const render = (ctx: ComponentPublicInstance) => { + // 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 remove any that are symbols. + let propsWithoutSymbols = Object.keys(ctx.$props).reduce((acc, key) => { + const $props = ctx.$props as Record + if (typeof $props[key] === 'symbol') { + return acc + } + return {...acc, [key]: $props[key]} + }, {}) as typeof ctx.$props + + // @ts-ignore + return h(tag, propsWithoutSymbols, renderStubDefaultSlot ? ctx.$slots : undefined) } return defineComponent({ @@ -62,7 +77,7 @@ export const createStub = ({ }) } -const createTransitionStub = ({ name }: StubOptions) => { +const createTransitionStub = ({ name, propsDeclaration }: StubOptions) => { const render = (ctx: ComponentPublicInstance) => { return h(name, {}, ctx.$slots) } @@ -164,7 +179,8 @@ export function stubComponents( if (type === Transition && 'transition' in stubs && stubs['transition']) { return [ createTransitionStub({ - name: 'transition-stub' + name: 'transition-stub', + propsDeclaration: {} }), undefined, children @@ -179,7 +195,8 @@ export function stubComponents( ) { return [ createTransitionStub({ - name: 'transition-group-stub' + name: 'transition-group-stub', + propsDeclaration: {} }), undefined, children diff --git a/tests/props.spec.ts b/tests/props.spec.ts index 5fa068757..370597b84 100644 --- a/tests/props.spec.ts +++ b/tests/props.spec.ts @@ -254,6 +254,6 @@ describe('props', () => { }) const wrapper = shallowMount(App) - expect(wrapper.html()).toBe('
Symbol: Symbol()
') + expect(wrapper.html()).toBe('') }) }) From e0f372fb809269cb60a48de1474493929f7faf65 Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Thu, 18 Nov 2021 10:01:51 +1000 Subject: [PATCH 4/9] fix bug with symbols --- src/stubs.ts | 25 ++++++++++-------- tests/props.spec.ts | 62 +++++++++++++++++++++++++++++---------------- 2 files changed, 55 insertions(+), 32 deletions(-) diff --git a/src/stubs.ts b/src/stubs.ts index 107b7e0cb..52ee8803f 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 removeSymbols = (props: T): T => { + // 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 + } + return {...acc, [key]: $props[key]} + }, {}) as T +} + export const createStub = ({ name, propsDeclaration, @@ -50,22 +62,15 @@ export const createStub = ({ const anonName = 'anonymous-stub' const tag = name ? `${hyphenate(name)}-stub` : anonName - const render = (ctx: ComponentPublicInstance) => { + const render = (ctx: ComponentPublicInstance) => { // 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 remove any that are symbols. - let propsWithoutSymbols = Object.keys(ctx.$props).reduce((acc, key) => { - const $props = ctx.$props as Record - if (typeof $props[key] === 'symbol') { - return acc - } - return {...acc, [key]: $props[key]} - }, {}) as typeof ctx.$props + const propsWithoutSymbols = removeSymbols(ctx.$props) - // @ts-ignore return h(tag, propsWithoutSymbols, renderStubDefaultSlot ? ctx.$slots : undefined) } diff --git a/tests/props.spec.ts b/tests/props.spec.ts index 370597b84..511b5c131 100644 --- a/tests/props.spec.ts +++ b/tests/props.spec.ts @@ -226,34 +226,52 @@ describe('props', () => { expect(wrapper.text()).toEqual('hello') }) - it('works with Symbol as default', () => { - const Comp = defineComponent({ - template: `
Symbol: {{ sym }}
`, - props: { - sym: { - type: Symbol, - default: () => Symbol() + 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) + const wrapper = shallowMount(Comp) - expect(wrapper.html()).toBe('
Symbol: Symbol()
') - }) + expect(wrapper.html()).toBe('
Symbol: Symbol()
') + }) - it.only('works with symbol as default from SFC', () => { - const App = defineComponent({ - template: ``, - components: { PropWithSymbol }, - data() { - return { - sym: Symbol() + it('works with Symbol an 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('') }) - const wrapper = shallowMount(App) - 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('') + }) }) }) From f051b60821ec49c9aca3241945e6ce99ee961544 Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Thu, 18 Nov 2021 10:25:06 +1000 Subject: [PATCH 5/9] lint --- src/stubs.ts | 10 +++++++--- tests/components/PropWithSymbol.vue | 2 +- tests/props.spec.ts | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/stubs.ts b/src/stubs.ts index 52ee8803f..6f1822e00 100644 --- a/src/stubs.ts +++ b/src/stubs.ts @@ -43,14 +43,14 @@ const shouldNotStub = (type: ConcreteComponent) => doNotStubComponents.has(type) export const addToDoNotStubComponents = (type: ConcreteComponent) => doNotStubComponents.add(type) -const removeSymbols = (props: T): T => { +const removeSymbols = (props: T): T => { // 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 } - return {...acc, [key]: $props[key]} + return { ...acc, [key]: $props[key] } }, {}) as T } @@ -71,7 +71,11 @@ export const createStub = ({ // props that will be passed and remove any that are symbols. const propsWithoutSymbols = removeSymbols(ctx.$props) - return h(tag, propsWithoutSymbols, renderStubDefaultSlot ? ctx.$slots : undefined) + return h( + tag, + propsWithoutSymbols, + renderStubDefaultSlot ? ctx.$slots : undefined + ) } return defineComponent({ diff --git a/tests/components/PropWithSymbol.vue b/tests/components/PropWithSymbol.vue index e24f8cf2e..aea5e64fc 100644 --- a/tests/components/PropWithSymbol.vue +++ b/tests/components/PropWithSymbol.vue @@ -1,5 +1,5 @@