From aebcc1304c372c0c32c7dda26568b6153dfefbd0 Mon Sep 17 00:00:00 2001 From: Jamie Stumme Date: Mon, 9 Mar 2020 09:07:31 -0500 Subject: [PATCH 1/5] test(issue #1377): add test for props on stubbed child component --- ...nt-with-nested-childern-and-attributes.vue | 24 +++++++++++++++++++ test/specs/mounting-options/stubs.spec.js | 23 ++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 test/resources/components/component-with-nested-childern-and-attributes.vue diff --git a/test/resources/components/component-with-nested-childern-and-attributes.vue b/test/resources/components/component-with-nested-childern-and-attributes.vue new file mode 100644 index 000000000..18f73eef0 --- /dev/null +++ b/test/resources/components/component-with-nested-childern-and-attributes.vue @@ -0,0 +1,24 @@ + + + diff --git a/test/specs/mounting-options/stubs.spec.js b/test/specs/mounting-options/stubs.spec.js index 905c369a7..a5aa50ed3 100644 --- a/test/specs/mounting-options/stubs.spec.js +++ b/test/specs/mounting-options/stubs.spec.js @@ -2,6 +2,7 @@ import ComponentWithChild from '~resources/components/component-with-child.vue' import ComponentWithNestedChildren from '~resources/components/component-with-nested-children.vue' import Component from '~resources/components/component.vue' import ComponentAsAClass from '~resources/components/component-as-a-class.vue' +import ComponentWithNestedChildrenAndAttributes from '~resources/components/component-with-nested-childern-and-attributes.vue' import { createLocalVue, config } from '@vue/test-utils' import { config as serverConfig } from '@vue/server-test-utils' import Vue from 'vue' @@ -590,4 +591,26 @@ describeWithShallowAndMount('options.stub', mountingMethod => { expect(result.props().propA).to.equal('A') delete Vue.options.components['child-component'] }) + + it.only('replaces component with a component and inherits attributes', () => { + const mounted = sandbox.stub() + const Stub = { + template: '
', + mounted + } + const wrapper = mountingMethod(ComponentWithNestedChildrenAndAttributes, { + stubs: { + SlotComponent: Stub, + ChildComponent: '
' + } + }) + + const childStub = wrapper.find('#child-component') + expect(wrapper.vm.$el.innerHTML).to.include('prop1="foobar"') + expect(wrapper.vm.$el.innerHTML).to.include('prop2="fizzbuzz"') + expect(childStub.attributes()).to.eql({ + prop1: 'foobar', + prop2: 'fizzbuzz' + }) + }) }) From 68640c180f442f2de2cd639a5ff95782452414f8 Mon Sep 17 00:00:00 2001 From: Jamie Stumme Date: Tue, 17 Mar 2020 00:26:20 -0500 Subject: [PATCH 2/5] feat(create-instance): convert string stubs to template close #1377 This also fixes the component name being dropped for template stubs --- .../create-instance/create-component-stubs.js | 43 +++++++------ packages/shared/compile-template.js | 24 ++++---- ...nt-with-nested-childern-and-attributes.vue | 12 ++-- test/specs/mounting-options/stubs.spec.js | 60 +++++++++++++------ 4 files changed, 86 insertions(+), 53 deletions(-) diff --git a/packages/create-instance/create-component-stubs.js b/packages/create-instance/create-component-stubs.js index b82addd99..61dd0ae5d 100644 --- a/packages/create-instance/create-component-stubs.js +++ b/packages/create-instance/create-component-stubs.js @@ -6,7 +6,8 @@ import { camelize, capitalize, hyphenate, - keys + keys, + warn } from '../shared/util' import { componentNeedsCompiling, @@ -15,7 +16,7 @@ import { isDynamicComponent, isConstructor } from '../shared/validators' -import { compileTemplate, compileFromString } from '../shared/compile-template' +import { compileTemplate } from '../shared/compile-template' function isVueComponentStub(comp): boolean { return (comp && comp.template) || isVueComponent(comp) @@ -145,24 +146,31 @@ export function createStubFromComponent( } } -function createStubFromString( - templateString: string, - originalComponent: Component = {}, - name: string, - _Vue: Component -): Component { +// DEPRECATED: converts string stub to template stub. +function createStubFromString(templateString: string, name: string): Component { + warn('String stubs are deprecated and will be removed in future versions') + if (templateContainsComponent(templateString, name)) { throwError('options.stub cannot contain a circular reference') } - const componentOptions = resolveOptions(originalComponent, _Vue) return { - ...getCoreProperties(componentOptions), - $_doNotStubChildren: true, - ...compileFromString(templateString) + template: templateString, + $_doNotStubChildren: true } } +function setStubComponentName( + stub: Object, + originalComponent: Component = {}, + _Vue: Component +) { + if (stub.name) return + + const componentOptions = resolveOptions(originalComponent, _Vue) + stub.name = getCoreProperties(componentOptions).name +} + function validateStub(stub) { if (!isValidStub(stub)) { throwError(`options.stub values must be passed a string or ` + `component`) @@ -175,7 +183,7 @@ export function createStubsFromStubsObject( _Vue: Component ): Components { return Object.keys(stubs || {}).reduce((acc, stubName) => { - const stub = stubs[stubName] + let stub = stubs[stubName] validateStub(stub) @@ -183,18 +191,19 @@ export function createStubsFromStubsObject( return acc } + const component = resolveComponent(originalComponents, stubName) + if (stub === true) { - const component = resolveComponent(originalComponents, stubName) acc[stubName] = createStubFromComponent(component, stubName, _Vue) return acc } if (typeof stub === 'string') { - const component = resolveComponent(originalComponents, stubName) - acc[stubName] = createStubFromString(stub, component, stubName, _Vue) - return acc + stub = createStubFromString(stub, stubName) + stubs[stubName] } + setStubComponentName(stub, component, _Vue) if (componentNeedsCompiling(stub)) { compileTemplate(stub) } diff --git a/packages/shared/compile-template.js b/packages/shared/compile-template.js index 7999894bc..49bbdeb91 100644 --- a/packages/shared/compile-template.js +++ b/packages/shared/compile-template.js @@ -4,19 +4,16 @@ import { compileToFunctions } from 'vue-template-compiler' import { componentNeedsCompiling } from './validators' import { throwError } from './util' -export function compileFromString(str: string) { - if (!compileToFunctions) { - throwError( - `vueTemplateCompiler is undefined, you must pass ` + - `precompiled components if vue-template-compiler is ` + - `undefined` - ) - } - return compileToFunctions(str) -} - export function compileTemplate(component: Component): void { if (component.template) { + if (!compileToFunctions) { + throwError( + `vueTemplateCompiler is undefined, you must pass ` + + `precompiled components if vue-template-compiler is ` + + `undefined` + ) + } + if (component.template.charAt('#') === '#') { var el = document.querySelector(component.template) if (!el) { @@ -27,7 +24,10 @@ export function compileTemplate(component: Component): void { component.template = el.innerHTML } - Object.assign(component, compileToFunctions(component.template)) + Object.assign(component, { + ...compileToFunctions(component.template), + name: component.name + }) } if (component.components) { diff --git a/test/resources/components/component-with-nested-childern-and-attributes.vue b/test/resources/components/component-with-nested-childern-and-attributes.vue index 18f73eef0..6f04db2f9 100644 --- a/test/resources/components/component-with-nested-childern-and-attributes.vue +++ b/test/resources/components/component-with-nested-childern-and-attributes.vue @@ -1,24 +1,22 @@ diff --git a/test/specs/mounting-options/stubs.spec.js b/test/specs/mounting-options/stubs.spec.js index a5aa50ed3..e72ffdc06 100644 --- a/test/specs/mounting-options/stubs.spec.js +++ b/test/specs/mounting-options/stubs.spec.js @@ -19,6 +19,7 @@ describeWithShallowAndMount('options.stub', mountingMethod => { serverConfigSave = serverConfig.stubs config.stubs = {} serverConfig.stubs = {} + sandbox.stub(console, 'error').callThrough() }) afterEach(() => { @@ -33,21 +34,24 @@ describeWithShallowAndMount('options.stub', mountingMethod => { const ComponentWithoutRender = { template: '
' } const ExtendedComponent = { extends: ComponentWithRender } const SubclassedComponent = Vue.extend({ template: '
' }) + const StringComponent = '
' mountingMethod(ComponentWithChild, { stubs: { ChildComponent: ComponentWithRender, ChildComponent2: ComponentAsAClass, ChildComponent3: ComponentWithoutRender, ChildComponent4: ExtendedComponent, - ChildComponent5: SubclassedComponent + ChildComponent5: SubclassedComponent, + ChildComponent6: StringComponent } }) }) it('replaces component with template string ', () => { + const Stub = { template: '
' } const wrapper = mountingMethod(ComponentWithChild, { stubs: { - ChildComponent: '
' + ChildComponent: Stub } }) expect(wrapper.findAll('.stub').length).to.equal(1) @@ -322,7 +326,7 @@ describeWithShallowAndMount('options.stub', mountingMethod => { const wrapper = mountingMethod(TestComponent, { stubs: { - 'span-component': '

' + 'span-component': { template: '

' } }, localVue }) @@ -343,7 +347,7 @@ describeWithShallowAndMount('options.stub', mountingMethod => { const wrapper = mountingMethod(TestComponent, { stubs: { - 'time-component': '' + 'time-component': { template: '' } }, localVue }) @@ -415,7 +419,7 @@ describeWithShallowAndMount('options.stub', mountingMethod => { expect(wrapper.html()).contains('No render function') }) - it('throws an error when passed a circular reference', () => { + it('throws an error when passed a circular reference for string stubs', () => { const names = ['child-component', 'ChildComponent', 'childComponent'] const validValues = [ '', @@ -592,25 +596,47 @@ describeWithShallowAndMount('options.stub', mountingMethod => { delete Vue.options.components['child-component'] }) - it.only('replaces component with a component and inherits attributes', () => { - const mounted = sandbox.stub() - const Stub = { - template: '

', - mounted - } + it('renders props in the element as attributes', () => { + const ComponentStub = { template: '
' } + const StringStub = '
' + const BooleanStub = true + const wrapper = mountingMethod(ComponentWithNestedChildrenAndAttributes, { stubs: { - SlotComponent: Stub, - ChildComponent: '
' + SlotComponent: ComponentStub, + ChildComponent: StringStub, + OriginalComponent: BooleanStub } }) - const childStub = wrapper.find('#child-component') - expect(wrapper.vm.$el.innerHTML).to.include('prop1="foobar"') - expect(wrapper.vm.$el.innerHTML).to.include('prop2="fizzbuzz"') - expect(childStub.attributes()).to.eql({ + expect(wrapper.find('#component-stub').attributes()).to.eql({ + id: 'component-stub', + prop1: 'foobar', + prop2: 'fizzbuzz' + }) + expect(wrapper.find('#string-stub').attributes()).to.eql({ + id: 'string-stub', + prop1: 'foobar', + prop2: 'fizzbuzz' + }) + expect(wrapper.find('originalcomponent-stub').attributes()).to.eql({ prop1: 'foobar', prop2: 'fizzbuzz' }) }) + + it('warns when passing a string', () => { + const StringComponent = '
' + mountingMethod(ComponentWithChild, { + stubs: { + ChildComponent6: StringComponent + } + }) + + expect(console.error).calledWith( + sandbox.match( + '[vue-test-utils]: String stubs are deprecated and will be removed in future versions' + ) + ) + }) }) From 34aadf238426f751c102509123b933a2058ff78b Mon Sep 17 00:00:00 2001 From: Jamie Stumme Date: Tue, 17 Mar 2020 00:44:32 -0500 Subject: [PATCH 3/5] docs(api): update stubs documentation --- docs/api/mount.md | 2 +- docs/api/options.md | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/api/mount.md b/docs/api/mount.md index 6744186e5..c057160c4 100644 --- a/docs/api/mount.md +++ b/docs/api/mount.md @@ -116,7 +116,7 @@ describe('Foo', () => { it('renders a div', () => { const wrapper = mount(Foo, { stubs: { - Bar: '
', + Bar: '
', // DEPRECATED BarFoo: true, FooBar: Faz } diff --git a/docs/api/options.md b/docs/api/options.md index 1704ed31b..c9396f7e4 100644 --- a/docs/api/options.md +++ b/docs/api/options.md @@ -208,10 +208,13 @@ const wrapper = mount(WrapperComp).find(ComponentUnderTest) ## stubs -- type: `{ [name: string]: Component | boolean } | Array` +- type: `{ [name: string]: Component | string | boolean } | Array` Stubs child components. Can be an Array of component names to stub, or an object. If `stubs` is an Array, every stub is `<${component name}-stub>`. +Depercation note: +If declaring an object, string stubs are now deprecated and will be removed in a future version. + Example: ```js From 30428b43bc5ff67fe12b58b38cce2785d7dd412d Mon Sep 17 00:00:00 2001 From: Jamie Stumme Date: Wed, 18 Mar 2020 22:46:00 -0500 Subject: [PATCH 4/5] test(stubs): limit stub props to 2.2 and above --- test/specs/mounting-options/stubs.spec.js | 56 ++++++++++++----------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/test/specs/mounting-options/stubs.spec.js b/test/specs/mounting-options/stubs.spec.js index e72ffdc06..8c8715462 100644 --- a/test/specs/mounting-options/stubs.spec.js +++ b/test/specs/mounting-options/stubs.spec.js @@ -596,34 +596,38 @@ describeWithShallowAndMount('options.stub', mountingMethod => { delete Vue.options.components['child-component'] }) - it('renders props in the element as attributes', () => { - const ComponentStub = { template: '
' } - const StringStub = '
' - const BooleanStub = true + itRunIf( + vueVersion >= 2.2, + 'renders props in the element as attributes', + () => { + const ComponentStub = { template: '
' } + const StringStub = '
' + const BooleanStub = true - const wrapper = mountingMethod(ComponentWithNestedChildrenAndAttributes, { - stubs: { - SlotComponent: ComponentStub, - ChildComponent: StringStub, - OriginalComponent: BooleanStub - } - }) + const wrapper = mountingMethod(ComponentWithNestedChildrenAndAttributes, { + stubs: { + SlotComponent: ComponentStub, + ChildComponent: StringStub, + OriginalComponent: BooleanStub + } + }) - expect(wrapper.find('#component-stub').attributes()).to.eql({ - id: 'component-stub', - prop1: 'foobar', - prop2: 'fizzbuzz' - }) - expect(wrapper.find('#string-stub').attributes()).to.eql({ - id: 'string-stub', - prop1: 'foobar', - prop2: 'fizzbuzz' - }) - expect(wrapper.find('originalcomponent-stub').attributes()).to.eql({ - prop1: 'foobar', - prop2: 'fizzbuzz' - }) - }) + expect(wrapper.find('#component-stub').attributes()).to.eql({ + id: 'component-stub', + prop1: 'foobar', + prop2: 'fizzbuzz' + }) + expect(wrapper.find('#string-stub').attributes()).to.eql({ + id: 'string-stub', + prop1: 'foobar', + prop2: 'fizzbuzz' + }) + expect(wrapper.find('originalcomponent-stub').attributes()).to.eql({ + prop1: 'foobar', + prop2: 'fizzbuzz' + }) + } + ) it('warns when passing a string', () => { const StringComponent = '
' From 2bd67b6cc63b970d848cdfbb032f808dfd76402e Mon Sep 17 00:00:00 2001 From: Jamie Stumme Date: Wed, 25 Mar 2020 08:43:33 -0500 Subject: [PATCH 5/5] docs(api): updated docs wording --- docs/api/mount.md | 8 ++++++-- docs/api/options.md | 7 ++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/api/mount.md b/docs/api/mount.md index c057160c4..240c15b97 100644 --- a/docs/api/mount.md +++ b/docs/api/mount.md @@ -116,9 +116,9 @@ describe('Foo', () => { it('renders a div', () => { const wrapper = mount(Foo, { stubs: { - Bar: '
', // DEPRECATED BarFoo: true, - FooBar: Faz + FooBar: Faz, + Bar: { template: '
' } } }) expect(wrapper.contains('.stubbed')).toBe(true) @@ -127,4 +127,8 @@ describe('Foo', () => { }) ``` +**Deprecation Notice:** + +When stubbing components, supplying a string (`ComponentToStub: '
`) is no longer supported. + - **See also:** [Wrapper](wrapper/) diff --git a/docs/api/options.md b/docs/api/options.md index c9396f7e4..f3b13fce5 100644 --- a/docs/api/options.md +++ b/docs/api/options.md @@ -210,10 +210,11 @@ const wrapper = mount(WrapperComp).find(ComponentUnderTest) - type: `{ [name: string]: Component | string | boolean } | Array` -Stubs child components. Can be an Array of component names to stub, or an object. If `stubs` is an Array, every stub is `<${component name}-stub>`. +Stubs child components can be an Array of component names to stub, or an object. If `stubs` is an Array, every stub is `<${component name}-stub>`. -Depercation note: -If declaring an object, string stubs are now deprecated and will be removed in a future version. +**Deprecation Notice:** + +When stubbing components, supplying a string (`ComponentToStub: '
`) is no longer supported. Example: