Skip to content

Commit a9dd893

Browse files
38elementseddyerburgh
authored andcommitted
feat(slots): Improve passing text to slots (#274)
1 parent ff4fadb commit a9dd893

File tree

6 files changed

+93
-46
lines changed

6 files changed

+93
-46
lines changed

Diff for: docs/en/api/options.md

+3-15
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,10 @@ expect(wrapper.find('div')).toBe(true)
6464
#### Passing text
6565

6666
You can pass text to `slots`.
67-
There are two limitations to this.
67+
There is a limitation to this.
6868

69-
This works with Vue 2.2+.
70-
71-
This works for the text below.
72-
73-
```js
74-
const wrapper = mount(ComponentWithSlots, { slots: { default: 'foobar' }})
75-
```
76-
77-
This does not work for the text below.
78-
79-
```js
80-
const wrapper1 = mount(ComponentWithSlots, { slots: { default: 'foo<span>bar</span>' }})
81-
const wrapper2 = mount(FooComponent, { slots: { default: 'foo {{ bar }}' }})
82-
```
69+
This does not support PhantomJS.
70+
Please use [Puppeteer](https://github.com/karma-runner/karma-chrome-launcher#headless-chromium-with-puppeteer).
8371

8472
### `stubs`
8573

Diff for: src/lib/add-slots.js

+23-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// @flow
22

3-
import Vue from 'vue'
43
import { compileToFunctions } from 'vue-template-compiler'
54
import { throwError } from './util'
65

@@ -10,27 +9,40 @@ function isValidSlot (slot: any): boolean {
109

1110
function addSlotToVm (vm: Component, slotName: string, slotValue: Component | string | Array<Component> | Array<string>): void {
1211
let elem
13-
const vueVersion = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`)
1412
if (typeof slotValue === 'string') {
1513
if (!compileToFunctions) {
1614
throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined')
1715
}
18-
if (slotValue.trim()[0] === '<') {
16+
if (window.navigator.userAgent.match(/PhantomJS/i)) {
17+
throwError('option.slots does not support strings in PhantomJS. Please use Puppeteer, or pass a component')
18+
}
19+
const domParser = new window.DOMParser()
20+
const document = domParser.parseFromString(slotValue, 'text/html')
21+
const _slotValue = slotValue.trim()
22+
if (_slotValue[0] === '<' && _slotValue[_slotValue.length - 1] === '>' && document.body.childElementCount === 1) {
1923
elem = vm.$createElement(compileToFunctions(slotValue))
2024
} else {
21-
if (vueVersion >= 2.2) {
22-
elem = vm._v(slotValue)
23-
} else {
24-
throwError('vue-test-utils support for passing text to slots at [email protected]+')
25-
}
25+
const compiledResult = compileToFunctions(`<div>${slotValue}{{ }}</div>`)
26+
const _staticRenderFns = vm._renderProxy.$options.staticRenderFns
27+
vm._renderProxy.$options.staticRenderFns = compiledResult.staticRenderFns
28+
elem = compiledResult.render.call(vm._renderProxy, vm.$createElement).children
29+
vm._renderProxy.$options.staticRenderFns = _staticRenderFns
2630
}
2731
} else {
2832
elem = vm.$createElement(slotValue)
2933
}
30-
if (Array.isArray(vm.$slots[slotName])) {
31-
vm.$slots[slotName].push(elem)
34+
if (Array.isArray(elem)) {
35+
if (Array.isArray(vm.$slots[slotName])) {
36+
vm.$slots[slotName] = [...vm.$slots[slotName], ...elem]
37+
} else {
38+
vm.$slots[slotName] = [...elem]
39+
}
3240
} else {
33-
vm.$slots[slotName] = [elem]
41+
if (Array.isArray(vm.$slots[slotName])) {
42+
vm.$slots[slotName].push(elem)
43+
} else {
44+
vm.$slots[slotName] = [elem]
45+
}
3446
}
3547
}
3648

Diff for: src/lib/create-instance.js

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import createLocalVue from '../create-local-vue'
1313
import extractOptions from '../options/extract-options'
1414
import deleteMountingOptions from '../options/delete-mounting-options'
1515
import createFunctionalComponent from './create-functional-component'
16+
import cloneDeep from 'lodash/cloneDeep'
1617

1718
export default function createConstructor (
1819
component: Component,
@@ -58,6 +59,9 @@ export default function createConstructor (
5859
addAttrs(vm, mountingOptions.attrs)
5960
addListeners(vm, mountingOptions.listeners)
6061

62+
vm.$_mountingOptionsSlots = mountingOptions.slots
63+
vm.$_originalSlots = cloneDeep(vm.$slots)
64+
6165
if (mountingOptions.slots) {
6266
addSlots(vm, mountingOptions.slots)
6367
}

Diff for: src/wrappers/vue-wrapper.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
// @flow
22

33
import Wrapper from './wrapper'
4+
import addSlots from '../lib/add-slots'
5+
import cloneDeep from 'lodash/cloneDeep'
46

57
function update () {
6-
this._update(this._render())
8+
// the only component made by mount()
9+
if (this.$_originalSlots) {
10+
this.$slots = cloneDeep(this.$_originalSlots)
11+
}
12+
if (this.$_mountingOptionsSlots) {
13+
addSlots(this, this.$_mountingOptionsSlots)
14+
}
15+
const vnodes = this._render()
16+
this._update(vnodes)
717
this.$children.forEach(child => update.call(child))
818
}
919

Diff for: test/resources/components/component-with-slots.vue

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="container">
2+
<div class="container" @keydown="change">
33
<header>
44
<slot name="header"></slot>
55
</header>
@@ -14,6 +14,16 @@
1414

1515
<script>
1616
export default {
17-
name: 'component-with-slots'
17+
name: 'component-with-slots',
18+
data () {
19+
return {
20+
'foo': 'bar'
21+
}
22+
},
23+
methods: {
24+
change () {
25+
this.foo = 'BAR'
26+
}
27+
}
1828
}
1929
</script>

Diff for: test/unit/specs/mount/options/slots.spec.js

+40-17
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,20 @@ import { compileToFunctions } from 'vue-template-compiler'
22
import { mount } from '~vue-test-utils'
33
import Component from '~resources/components/component.vue'
44
import ComponentWithSlots from '~resources/components/component-with-slots.vue'
5-
import { vueVersion } from '~resources/test-utils'
65

76
describe('mount.slots', () => {
7+
let _window
8+
9+
beforeEach(() => {
10+
_window = window
11+
})
12+
13+
afterEach(() => {
14+
if (!window.navigator.userAgent.match(/Chrome/i)) {
15+
window = _window // eslint-disable-line no-native-reassign
16+
}
17+
})
18+
819
it('mounts component with default slot if passed component in slot object', () => {
920
const wrapper = mount(ComponentWithSlots, { slots: { default: Component }})
1021
expect(wrapper.contains(Component)).to.equal(true)
@@ -26,15 +37,33 @@ describe('mount.slots', () => {
2637
expect(wrapper.contains('span')).to.equal(true)
2738
})
2839

29-
it('mounts component with default slot if passed string in slot object', () => {
30-
if (vueVersion >= 2.2) {
31-
const wrapper = mount(ComponentWithSlots, { slots: { default: 'foo' }})
32-
expect(wrapper.find('main').text()).to.equal('foo')
33-
} else {
34-
const message = '[vue-test-utils]: vue-test-utils support for passing text to slots at [email protected]+'
35-
const fn = () => mount(ComponentWithSlots, { slots: { default: 'foo' }})
36-
expect(fn).to.throw().with.property('message', message)
40+
it('throws error if the UserAgent is PhantomJS when passed string is in slot object', () => {
41+
if (window.navigator.userAgent.match(/Chrome/i)) {
42+
return
3743
}
44+
window = { navigator: { userAgent: 'PhantomJS' }} // eslint-disable-line no-native-reassign
45+
const message = '[vue-test-utils]: option.slots does not support strings in PhantomJS. Please use Puppeteer, or pass a component'
46+
const fn = () => mount(ComponentWithSlots, { slots: { default: 'foo' }})
47+
expect(fn).to.throw().with.property('message', message)
48+
})
49+
50+
it('mounts component with default slot if passed string in slot object', () => {
51+
const wrapper1 = mount(ComponentWithSlots, { slots: { default: 'foo<span>123</span>{{ foo }}' }})
52+
expect(wrapper1.find('main').html()).to.equal('<main>foo<span>123</span>bar</main>')
53+
const wrapper2 = mount(ComponentWithSlots, { slots: { default: '<p>1</p>{{ foo }}2' }})
54+
expect(wrapper2.find('main').html()).to.equal('<main><p>1</p>bar2</main>')
55+
const wrapper3 = mount(ComponentWithSlots, { slots: { default: '<p>1</p>{{ foo }}<p>2</p>' }})
56+
expect(wrapper3.find('main').html()).to.equal('<main><p>1</p>bar<p>2</p></main>')
57+
const wrapper4 = mount(ComponentWithSlots, { slots: { default: '123' }})
58+
expect(wrapper4.find('main').html()).to.equal('<main>123</main>')
59+
const wrapper5 = mount(ComponentWithSlots, { slots: { default: '1{{ foo }}2' }})
60+
expect(wrapper5.find('main').html()).to.equal('<main>1bar2</main>')
61+
wrapper5.trigger('keydown')
62+
expect(wrapper5.find('main').html()).to.equal('<main>1BAR2</main>')
63+
const wrapper6 = mount(ComponentWithSlots, { slots: { default: '<p>1</p><p>2</p>' }})
64+
expect(wrapper6.find('main').html()).to.equal('<main><p>1</p><p>2</p></main>')
65+
const wrapper7 = mount(ComponentWithSlots, { slots: { default: '1<p>2</p>3' }})
66+
expect(wrapper7.find('main').html()).to.equal('<main>1<p>2</p>3</main>')
3867
})
3968

4069
it('throws error if passed string in default slot object and vue-template-compiler is undefined', () => {
@@ -59,14 +88,8 @@ describe('mount.slots', () => {
5988
})
6089

6190
it('mounts component with default slot if passed string in slot text array object', () => {
62-
if (vueVersion >= 2.2) {
63-
const wrapper = mount(ComponentWithSlots, { slots: { default: ['foo', 'bar'] }})
64-
expect(wrapper.find('main').text()).to.equal('foobar')
65-
} else {
66-
const message = '[vue-test-utils]: vue-test-utils support for passing text to slots at [email protected]+'
67-
const fn = () => mount(ComponentWithSlots, { slots: { default: ['foo', 'bar'] }})
68-
expect(fn).to.throw().with.property('message', message)
69-
}
91+
const wrapper = mount(ComponentWithSlots, { slots: { default: ['{{ foo }}<span>1</span>', 'bar'] }})
92+
expect(wrapper.find('main').html()).to.equal('<main>bar<span>1</span>bar</main>')
7093
})
7194

7295
it('throws error if passed string in default slot array vue-template-compiler is undefined', () => {

0 commit comments

Comments
 (0)