From e397ef26909a9dc23dbb4aef905479d189888d46 Mon Sep 17 00:00:00 2001 From: 38elements Date: Sun, 17 Dec 2017 12:15:05 +0900 Subject: [PATCH 01/14] Improve passing text to slots --- docs/en/api/options.md | 20 --------------- src/lib/add-slots.js | 17 +++++++------ .../components/component-with-slots.vue | 7 +++++- test/unit/specs/mount/options/slots.spec.js | 25 ++++++------------- 4 files changed, 23 insertions(+), 46 deletions(-) diff --git a/docs/en/api/options.md b/docs/en/api/options.md index d93a0ee2b..f9a094156 100644 --- a/docs/en/api/options.md +++ b/docs/en/api/options.md @@ -61,26 +61,6 @@ const wrapper = shallow(Component, { expect(wrapper.find('div')).toBe(true) ``` -#### Passing text - -You can pass text to `slots`. -There are two limitations to this. - -This works with Vue 2.2+. - -This works for the text below. - -```js -const wrapper = mount(ComponentWithSlots, { slots: { default: 'foobar' }}) -``` - -This does not work for the text below. - -```js -const wrapper1 = mount(ComponentWithSlots, { slots: { default: 'foobar' }}) -const wrapper2 = mount(FooComponent, { slots: { default: 'foo {{ bar }}' }}) -``` - ### `stubs` - type: `{ [name: string]: Component | boolean } | Array` diff --git a/src/lib/add-slots.js b/src/lib/add-slots.js index 536b8de02..a777c12ef 100644 --- a/src/lib/add-slots.js +++ b/src/lib/add-slots.js @@ -1,6 +1,5 @@ // @flow -import Vue from 'vue' import { compileToFunctions } from 'vue-template-compiler' import { throwError } from './util' @@ -10,19 +9,21 @@ function isValidSlot (slot: any): boolean { function addSlotToVm (vm: Component, slotName: string, slotValue: Component | string | Array | Array): void { let elem - const vueVersion = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`) if (typeof slotValue === 'string') { if (!compileToFunctions) { throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined') } - if (slotValue.trim()[0] === '<') { + const domParser = new window.DOMParser() + const document = domParser.parseFromString(slotValue, 'text/html') + const _slotValue = slotValue.trim() + if (_slotValue[0] === '<' && _slotValue[_slotValue.length - 1] === '>' && document.body.childElementCount === 1) { elem = vm.$createElement(compileToFunctions(slotValue)) } else { - if (vueVersion >= 2.2) { - elem = vm._v(slotValue) - } else { - throwError('vue-test-utils support for passing text to slots at vue@2.2+') - } + const compiledResult = compileToFunctions(`
${slotValue}
`) + const _staticRenderFns = vm._renderProxy.$options.staticRenderFns + vm._renderProxy.$options.staticRenderFns = compiledResult.staticRenderFns + elem = compiledResult.render.call(vm._renderProxy, vm.$createElement).children + vm._renderProxy.$options.staticRenderFns = _staticRenderFns } } else { elem = vm.$createElement(slotValue) diff --git a/test/resources/components/component-with-slots.vue b/test/resources/components/component-with-slots.vue index c2134d3a3..3e4e74f90 100644 --- a/test/resources/components/component-with-slots.vue +++ b/test/resources/components/component-with-slots.vue @@ -14,6 +14,11 @@ diff --git a/test/unit/specs/mount/options/slots.spec.js b/test/unit/specs/mount/options/slots.spec.js index 73191f2a1..ce398c1e4 100644 --- a/test/unit/specs/mount/options/slots.spec.js +++ b/test/unit/specs/mount/options/slots.spec.js @@ -2,7 +2,6 @@ import { compileToFunctions } from 'vue-template-compiler' import { mount } from '~vue-test-utils' import Component from '~resources/components/component.vue' import ComponentWithSlots from '~resources/components/component-with-slots.vue' -import { vueVersion } from '~resources/test-utils' describe('mount.slots', () => { it('mounts component with default slot if passed component in slot object', () => { @@ -27,14 +26,12 @@ describe('mount.slots', () => { }) it('mounts component with default slot if passed string in slot object', () => { - if (vueVersion >= 2.2) { - const wrapper = mount(ComponentWithSlots, { slots: { default: 'foo' }}) - expect(wrapper.find('main').text()).to.equal('foo') - } else { - const message = '[vue-test-utils]: vue-test-utils support for passing text to slots at vue@2.2+' - const fn = () => mount(ComponentWithSlots, { slots: { default: 'foo' }}) - expect(fn).to.throw().with.property('message', message) - } + const wrapper1 = mount(ComponentWithSlots, { slots: { default: 'foo123{{ foo }}' }}) + expect(wrapper1.find('main').html()).to.equal('
foo123bar
') + const wrapper2 = mount(ComponentWithSlots, { slots: { default: '

1

{{ foo }}' }}) + expect(wrapper2.find('main').html()).to.equal('

1

bar
') + const wrapper3 = mount(ComponentWithSlots, { slots: { default: '

1

{{ foo }}

' }}) + expect(wrapper3.find('main').html()).to.equal('

1

bar

') }) it('throws error if passed string in default slot object and vue-template-compiler is undefined', () => { @@ -59,14 +56,8 @@ describe('mount.slots', () => { }) it('mounts component with default slot if passed string in slot text array object', () => { - if (vueVersion >= 2.2) { - const wrapper = mount(ComponentWithSlots, { slots: { default: ['foo', 'bar'] }}) - expect(wrapper.find('main').text()).to.equal('foobar') - } else { - const message = '[vue-test-utils]: vue-test-utils support for passing text to slots at vue@2.2+' - const fn = () => mount(ComponentWithSlots, { slots: { default: ['foo', 'bar'] }}) - expect(fn).to.throw().with.property('message', message) - } + const wrapper = mount(ComponentWithSlots, { slots: { default: ['{{ foo }}1', 'bar'] }}) + expect(wrapper.find('main').html()).to.equal('
bar1bar
') }) it('throws error if passed string in default slot array vue-template-compiler is undefined', () => { From 76f14f67f8556a93b0282f19a6ae3a8ca076afae Mon Sep 17 00:00:00 2001 From: 38elements Date: Sun, 17 Dec 2017 15:32:49 +0900 Subject: [PATCH 02/14] Add test --- test/unit/specs/mount/options/slots.spec.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/unit/specs/mount/options/slots.spec.js b/test/unit/specs/mount/options/slots.spec.js index ce398c1e4..64e9b1cd1 100644 --- a/test/unit/specs/mount/options/slots.spec.js +++ b/test/unit/specs/mount/options/slots.spec.js @@ -28,10 +28,14 @@ describe('mount.slots', () => { it('mounts component with default slot if passed string in slot object', () => { const wrapper1 = mount(ComponentWithSlots, { slots: { default: 'foo123{{ foo }}' }}) expect(wrapper1.find('main').html()).to.equal('
foo123bar
') - const wrapper2 = mount(ComponentWithSlots, { slots: { default: '

1

{{ foo }}' }}) - expect(wrapper2.find('main').html()).to.equal('

1

bar
') - const wrapper3 = mount(ComponentWithSlots, { slots: { default: '

1

{{ foo }}

' }}) - expect(wrapper3.find('main').html()).to.equal('

1

bar

') + const wrapper2 = mount(ComponentWithSlots, { slots: { default: '

1

{{ foo }}2' }}) + expect(wrapper2.find('main').html()).to.equal('

1

bar2
') + const wrapper3 = mount(ComponentWithSlots, { slots: { default: '

1

{{ foo }}

2

' }}) + expect(wrapper3.find('main').html()).to.equal('

1

bar

2

') + const wrapper4 = mount(ComponentWithSlots, { slots: { default: '123' }}) + expect(wrapper4.find('main').html()).to.equal('
123
') + const wrapper5 = mount(ComponentWithSlots, { slots: { default: '1{{ foo }}2' }}) + expect(wrapper5.find('main').html()).to.equal('
1bar2
') }) it('throws error if passed string in default slot object and vue-template-compiler is undefined', () => { From 99c0f7a8b64413913041575f4f8820f1f433050b Mon Sep 17 00:00:00 2001 From: 38elements Date: Sun, 17 Dec 2017 15:50:30 +0900 Subject: [PATCH 03/14] Add document --- docs/en/api/options.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/en/api/options.md b/docs/en/api/options.md index f9a094156..4b28688c1 100644 --- a/docs/en/api/options.md +++ b/docs/en/api/options.md @@ -61,6 +61,29 @@ const wrapper = shallow(Component, { expect(wrapper.find('div')).toBe(true) ``` +#### Passing text + +You can pass text to `slots`. +There is a limitation to this. + +The text works below. + +```js +const wrapper1 = mount(ComponentWithSlots, { slots: { default: '1{{ foo }}2' }}) +const wrapper2 = mount(ComponentWithSlots, { slots: { default: '

1

{{ foo }}

2

' }}) +const wrapper3 = mount(ComponentWithSlots, { slots: { default: '

1

{{ foo }}' }}) +const wrapper4 = mount(ComponentWithSlots, { slots: { default: '123' }}) +const wrapper5 = mount(ComponentWithSlots, { slots: { default: '1

2

{{ foo }}3' }}) +``` + +This does not work for the text below. +When there are some elements, `{{ }}` is required. + +```js +const wrapper1 = mount(ComponentWithSlots, { slots: { default: '

1

2

' }}) +const wrapper2 = mount(ComponentWithSlots, { slots: { default: '1

2

3' }}) +``` + ### `stubs` - type: `{ [name: string]: Component | boolean } | Array` From dec437c4275f8cf5b1bde85be9a9f8df0e954b76 Mon Sep 17 00:00:00 2001 From: 38elements Date: Sun, 17 Dec 2017 19:06:52 +0900 Subject: [PATCH 04/14] Add throw error --- docs/en/api/options.md | 4 +++- src/lib/add-slots.js | 3 +++ test/unit/specs/mount/options/slots.spec.js | 21 +++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/en/api/options.md b/docs/en/api/options.md index 4b28688c1..ea770b92d 100644 --- a/docs/en/api/options.md +++ b/docs/en/api/options.md @@ -64,7 +64,9 @@ expect(wrapper.find('div')).toBe(true) #### Passing text You can pass text to `slots`. -There is a limitation to this. +There is two limitations to this. + +This does not support PhantomJS. The text works below. diff --git a/src/lib/add-slots.js b/src/lib/add-slots.js index a777c12ef..100e52387 100644 --- a/src/lib/add-slots.js +++ b/src/lib/add-slots.js @@ -13,6 +13,9 @@ function addSlotToVm (vm: Component, slotName: string, slotValue: Component | st if (!compileToFunctions) { throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined') } + if (window.navigator.userAgent.match(/PhantomJS/i)) { + throwError('option.slots does not support PhantomJS. Please use Puppeteer') + } const domParser = new window.DOMParser() const document = domParser.parseFromString(slotValue, 'text/html') const _slotValue = slotValue.trim() diff --git a/test/unit/specs/mount/options/slots.spec.js b/test/unit/specs/mount/options/slots.spec.js index 64e9b1cd1..4aabac481 100644 --- a/test/unit/specs/mount/options/slots.spec.js +++ b/test/unit/specs/mount/options/slots.spec.js @@ -4,6 +4,18 @@ import Component from '~resources/components/component.vue' import ComponentWithSlots from '~resources/components/component-with-slots.vue' describe('mount.slots', () => { + let _window + + beforeEach(() => { + _window = window + }) + + afterEach(() => { + /* eslint-disable */ + window = _window + /* eslint-enable */ + }) + it('mounts component with default slot if passed component in slot object', () => { const wrapper = mount(ComponentWithSlots, { slots: { default: Component }}) expect(wrapper.contains(Component)).to.equal(true) @@ -25,6 +37,15 @@ describe('mount.slots', () => { expect(wrapper.contains('span')).to.equal(true) }) + it('throws error if the UserAgent is PhantomJS when passed string is in slot object', () => { + /* eslint-disable */ + window = { navigator: { userAgent:'PhantomJS' } } + /* eslint-enable */ + const message = '[vue-test-utils]: option.slots does not support PhantomJS. Please use Puppeteer' + const fn = () => mount(ComponentWithSlots, { slots: { default: 'foo' }}) + expect(fn).to.throw().with.property('message', message) + }) + it('mounts component with default slot if passed string in slot object', () => { const wrapper1 = mount(ComponentWithSlots, { slots: { default: 'foo123{{ foo }}' }}) expect(wrapper1.find('main').html()).to.equal('
foo123bar
') From 912fbe1ff91d86a70b39c8619f470b444679e06c Mon Sep 17 00:00:00 2001 From: 38elements Date: Sun, 17 Dec 2017 19:14:26 +0900 Subject: [PATCH 05/14] Add link --- docs/en/api/options.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/en/api/options.md b/docs/en/api/options.md index ea770b92d..fdd888341 100644 --- a/docs/en/api/options.md +++ b/docs/en/api/options.md @@ -66,7 +66,8 @@ expect(wrapper.find('div')).toBe(true) You can pass text to `slots`. There is two limitations to this. -This does not support PhantomJS. +This does not support PhantomJS. +Please use [Puppeteer](ihttps://github.com/karma-runner/karma-chrome-launcher#headless-chromium-with-puppeteer). The text works below. From 3bd9da6a2b23f267b7cd7c5cea0c65ba3fed1a44 Mon Sep 17 00:00:00 2001 From: 38elements Date: Sun, 17 Dec 2017 19:28:48 +0900 Subject: [PATCH 06/14] Skip --- test/unit/specs/mount/options/slots.spec.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/unit/specs/mount/options/slots.spec.js b/test/unit/specs/mount/options/slots.spec.js index 4aabac481..e2b3264a2 100644 --- a/test/unit/specs/mount/options/slots.spec.js +++ b/test/unit/specs/mount/options/slots.spec.js @@ -11,9 +11,11 @@ describe('mount.slots', () => { }) afterEach(() => { - /* eslint-disable */ - window = _window - /* eslint-enable */ + if (!window.navigator.userAgent.match(/Chrome/i)) { + /* eslint-disable */ + window = _window + /* eslint-enable */ + } }) it('mounts component with default slot if passed component in slot object', () => { @@ -38,6 +40,9 @@ describe('mount.slots', () => { }) it('throws error if the UserAgent is PhantomJS when passed string is in slot object', () => { + if (window.navigator.userAgent.match(/Chrome/i)) { + return + } /* eslint-disable */ window = { navigator: { userAgent:'PhantomJS' } } /* eslint-enable */ From 3c3abf8755f675e49054269afc58390c1290c813 Mon Sep 17 00:00:00 2001 From: 38elements Date: Sun, 17 Dec 2017 19:58:06 +0900 Subject: [PATCH 07/14] Lint --- test/unit/specs/mount/options/slots.spec.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/unit/specs/mount/options/slots.spec.js b/test/unit/specs/mount/options/slots.spec.js index e2b3264a2..c4c0682b0 100644 --- a/test/unit/specs/mount/options/slots.spec.js +++ b/test/unit/specs/mount/options/slots.spec.js @@ -12,9 +12,7 @@ describe('mount.slots', () => { afterEach(() => { if (!window.navigator.userAgent.match(/Chrome/i)) { - /* eslint-disable */ - window = _window - /* eslint-enable */ + window = _window // eslint-disable-line no-native-reassign } }) @@ -43,9 +41,7 @@ describe('mount.slots', () => { if (window.navigator.userAgent.match(/Chrome/i)) { return } - /* eslint-disable */ - window = { navigator: { userAgent:'PhantomJS' } } - /* eslint-enable */ + window = { navigator: { userAgent: 'PhantomJS' }} // eslint-disable-line no-native-reassign const message = '[vue-test-utils]: option.slots does not support PhantomJS. Please use Puppeteer' const fn = () => mount(ComponentWithSlots, { slots: { default: 'foo' }}) expect(fn).to.throw().with.property('message', message) From c3750a8a797ca05eed0de154cbae3954e887efa4 Mon Sep 17 00:00:00 2001 From: 38elements Date: Sun, 17 Dec 2017 20:03:55 +0900 Subject: [PATCH 08/14] Fix document --- docs/en/api/options.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/api/options.md b/docs/en/api/options.md index fdd888341..8014bfe5c 100644 --- a/docs/en/api/options.md +++ b/docs/en/api/options.md @@ -64,12 +64,12 @@ expect(wrapper.find('div')).toBe(true) #### Passing text You can pass text to `slots`. -There is two limitations to this. +There are two limitations to this. This does not support PhantomJS. Please use [Puppeteer](ihttps://github.com/karma-runner/karma-chrome-launcher#headless-chromium-with-puppeteer). -The text works below. +This works for the text below. ```js const wrapper1 = mount(ComponentWithSlots, { slots: { default: '1{{ foo }}2' }}) From 66ecd969f369ec7fccb8bde9c66fdc7a7611ef04 Mon Sep 17 00:00:00 2001 From: 38elements Date: Sun, 17 Dec 2017 20:10:13 +0900 Subject: [PATCH 09/14] Fix link --- docs/en/api/options.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/api/options.md b/docs/en/api/options.md index 8014bfe5c..49a5b644e 100644 --- a/docs/en/api/options.md +++ b/docs/en/api/options.md @@ -67,7 +67,7 @@ You can pass text to `slots`. There are two limitations to this. This does not support PhantomJS. -Please use [Puppeteer](ihttps://github.com/karma-runner/karma-chrome-launcher#headless-chromium-with-puppeteer). +Please use [Puppeteer](https://github.com/karma-runner/karma-chrome-launcher#headless-chromium-with-puppeteer). This works for the text below. From 3b99090a6917b7861096b2903fc9ec7811607464 Mon Sep 17 00:00:00 2001 From: 38elements Date: Mon, 18 Dec 2017 13:05:11 +0900 Subject: [PATCH 10/14] Fix update() --- src/lib/add-slots.js | 14 +++++++++++--- src/lib/create-instance.js | 4 ++++ src/wrappers/vue-wrapper.js | 12 +++++++++++- test/resources/components/component-with-slots.vue | 7 ++++++- test/unit/specs/mount/options/slots.spec.js | 2 ++ 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/lib/add-slots.js b/src/lib/add-slots.js index 100e52387..0f4067446 100644 --- a/src/lib/add-slots.js +++ b/src/lib/add-slots.js @@ -31,10 +31,18 @@ function addSlotToVm (vm: Component, slotName: string, slotValue: Component | st } else { elem = vm.$createElement(slotValue) } - if (Array.isArray(vm.$slots[slotName])) { - vm.$slots[slotName].push(elem) + if (Array.isArray(elem)) { + if (Array.isArray(vm.$slots[slotName])) { + vm.$slots[slotName] = [...vm.$slots[slotName], ...elem] + } else { + vm.$slots[slotName] = [...elem] + } } else { - vm.$slots[slotName] = [elem] + if (Array.isArray(vm.$slots[slotName])) { + vm.$slots[slotName].push(elem) + } else { + vm.$slots[slotName] = [elem] + } } } diff --git a/src/lib/create-instance.js b/src/lib/create-instance.js index 993b7b921..03a316424 100644 --- a/src/lib/create-instance.js +++ b/src/lib/create-instance.js @@ -13,6 +13,7 @@ import createLocalVue from '../create-local-vue' import extractOptions from '../options/extract-options' import deleteMountingOptions from '../options/delete-mounting-options' import createFunctionalComponent from './create-functional-component' +import cloneDeep from 'lodash/cloneDeep' export default function createConstructor ( component: Component, @@ -58,6 +59,9 @@ export default function createConstructor ( addAttrs(vm, mountingOptions.attrs) addListeners(vm, mountingOptions.listeners) + vm.$_mountingOptionsSlots = mountingOptions.slots + vm.$_originalSlots = cloneDeep(vm.$slots) + if (mountingOptions.slots) { addSlots(vm, mountingOptions.slots) } diff --git a/src/wrappers/vue-wrapper.js b/src/wrappers/vue-wrapper.js index 8e853bf4b..7c7b14af5 100644 --- a/src/wrappers/vue-wrapper.js +++ b/src/wrappers/vue-wrapper.js @@ -1,9 +1,19 @@ // @flow import Wrapper from './wrapper' +import addSlots from '../lib/add-slots' +import cloneDeep from 'lodash/cloneDeep' function update () { - this._update(this._render()) + // the only component made by mount() + if (this.$_originalSlots) { + this.$slots = cloneDeep(this.$_originalSlots) + } + if (this.$_mountingOptionsSlots) { + addSlots(this, this.$_mountingOptionsSlots) + } + const vnodes = this._render() + this._update(vnodes) this.$children.forEach(child => update.call(child)) } diff --git a/test/resources/components/component-with-slots.vue b/test/resources/components/component-with-slots.vue index 3e4e74f90..ec2585bd0 100644 --- a/test/resources/components/component-with-slots.vue +++ b/test/resources/components/component-with-slots.vue @@ -1,5 +1,5 @@