Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add scoped slots option #507

Merged
merged 22 commits into from
Apr 14, 2018
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/en/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Vue Test Utils is the official unit testing utility library for Vue.js.
* [Mounting Options](api/options.md)
- [context](api/options.md#context)
- [slots](api/options.md#slots)
- [scopedSlots](api/options.md#scopedslots)
- [stubs](api/options.md#stubs)
- [mocks](api/options.md#mocks)
- [localVue](api/options.md#localvue)
Expand Down
1 change: 1 addition & 0 deletions docs/en/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* [Mounting Options](api/options.md)
- [context](api/options.md#context)
- [slots](api/options.md#slots)
- [scopedSlots](api/options.md#scopedslots)
- [stubs](api/options.md#stubs)
- [mocks](api/options.md#mocks)
- [localVue](api/options.md#localvue)
Expand Down
1 change: 1 addition & 0 deletions docs/en/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* [Mounting Options](./options.md)
- [context](./options.md#context)
- [slots](./options.md#slots)
- [scopedSlots](./options.md#scopedslots)
- [stubs](./options.md#stubs)
- [mocks](./options.md#mocks)
- [localVue](./options.md#localvue)
Expand Down
24 changes: 24 additions & 0 deletions docs/en/api/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Options for `mount` and `shallow`. The options object can contain both Vue Test

- [`context`](#context)
- [`slots`](#slots)
- [`scopedSlots`](#scopedslots)
- [`stubs`](#stubs)
- [`mocks`](#mocks)
- [`localVue`](#localvue)
Expand Down Expand Up @@ -68,6 +69,29 @@ There is a limitation to this.
This does not support PhantomJS.
Please use [Puppeteer](https://github.com/karma-runner/karma-chrome-launcher#headless-chromium-with-puppeteer).

### `scopedSlots`

- type: `{ [name: string]: string }`

Provide an object of scoped slots contents to the component. The key corresponds to the slot name. The value can be a template string.

There is two limitations.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is two limitations > There are two limitations.


* This supports [email protected]+.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option is only supported in [email protected]+


* You can not set a `template` tag to top of `scopedSlots` option.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You cannot use <template> tag as the root element in thescopedSlots option.


Example:

```js
const wrapper = shallow(Component, {
scopedSlots: {
foo: '<p slot-scope="props">{{props.index}},{{props.text}}</p>'
}
})
expect(wrapper.find('#fooWrapper').html()).toBe('<div id="fooWrapper"><p>0,text1</p><p>1,text2</p><p>2,text3</p></div>')
```

### `stubs`

- type: `{ [name: string]: Component | boolean } | Array<string>`
Expand Down
1 change: 1 addition & 0 deletions flow/options.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ declare type Options = { // eslint-disable-line no-undef
attachToDocument?: boolean,
mocks?: Object,
slots?: Object,
scopedSlots?: Object,
localVue?: Component,
provide?: Object,
stubs?: Object,
Expand Down
14 changes: 14 additions & 0 deletions packages/create-instance/add-scoped-slots.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// @flow

import { compileToFunctions } from 'vue-template-compiler'
import { throwError } from 'shared/util'

export function addScopedSlots (vm: Component, scopedSlots: Object): void {
Object.keys(scopedSlots).forEach((key) => {
const template = scopedSlots[key].trim()
if (template.substr(0, 9) === '<template') {
throwError('scopedSlots option does not support template tag.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the error message read:

the scopedSlots option does not support a tag as the root element.

}
vm.$_vueTestUtils_scopedSlots[key] = compileToFunctions(template).render
})
}
24 changes: 24 additions & 0 deletions packages/create-instance/create-instance.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// @flow

import Vue from 'vue'
import { addSlots } from './add-slots'
import { addScopedSlots } from './add-scoped-slots'
import addMocks from './add-mocks'
import addAttrs from './add-attrs'
import addListeners from './add-listeners'
Expand Down Expand Up @@ -57,6 +59,28 @@ export default function createInstance (
addAttrs(vm, options.attrs)
addListeners(vm, options.listeners)

if (options.scopedSlots) {
const vueVersion = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`)
if (vueVersion >= 2.5) {
vm.$_vueTestUtils_scopedSlots = {}
const renderSlot = vm._renderProxy._t
vm._renderProxy._t = function (name, feedback, props, bindObject) {
const scopedSlotFn = vm.$_vueTestUtils_scopedSlots[name]
if (scopedSlotFn) {
props = { ...bindObject, ...props }
Copy link
Member

@eddyerburgh eddyerburgh Apr 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to set vm._renderProxy.props?

Copy link
Contributor Author

@38elements 38elements Apr 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scopedSlotFn needs vm._renderProxy.props.
If vm._renderProxy.prop is not setted, the render function can not use props.

with(this){return _c('p',{},[_v(_s(props.index)+","+_s(props.text))])}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a bug.
When slot-scope is other than props, this is not work.

vm._renderProxy.props = props
return scopedSlotFn.call(vm._renderProxy)
} else {
return renderSlot.call(vm._renderProxy, name, feedback, props, bindObject)
}
}
// $FlowIgnore
addScopedSlots(vm, options.scopedSlots)
} else {
throwError('scopedSlots option supports [email protected]+.')
}
}

if (options.slots) {
addSlots(vm, options.slots)
}
Expand Down
1 change: 1 addition & 0 deletions packages/test-utils/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ interface MountOptions<V extends Vue> extends ComponentOptions<V> {
localVue?: typeof Vue
mocks?: object
slots?: Slots
scopedSlots?: Record<string, string>
stubs?: Stubs,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be an object. Can you add a test for this in types/test?

attrs?: object
listeners?: object
Expand Down
3 changes: 3 additions & 0 deletions packages/test-utils/types/test/mount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ mount(ClassComponent, {
foo: [normalOptions, functionalOptions],
bar: ClassComponent
},
scopedSlots: {
baz: `<div>Baz</div>`
},
stubs: {
foo: normalOptions,
bar: functionalOptions,
Expand Down
25 changes: 25 additions & 0 deletions test/resources/components/component-with-scoped-slots.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<template>
<div>
<div id="scopedSlots">
<slot name="item"
v-for="(item, index) in items"
:text="item.text"
:index="index">
</slot>
</div>
<div id="slots">
<slot></slot>
</div>
</div>
</template>

<script>
export default {
name: 'component-with-scoped-slots',
data () {
return {
items: [{ text: 'a1' }, { text: 'a2' }, { text: 'a3' }]
}
}
}
</script>
47 changes: 47 additions & 0 deletions test/specs/mounting-options/scopedSlots.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { describeWithShallowAndMount, vueVersion, itDoNotRunIf } from '~resources/utils'
import ComponentWithScopedSlots from '~resources/components/component-with-scoped-slots.vue'

describeWithShallowAndMount('scopedSlots', (mountingMethod) => {
itDoNotRunIf(vueVersion < 2.5,
'mounts component scoped slots', () => {
const wrapper = mountingMethod(ComponentWithScopedSlots, {
slots: { default: '<span>123</span>' },
scopedSlots: {
'item': '<p slot-scope="props">{{props.index}},{{props.text}}</p>'
}
})
expect(wrapper.find('#slots').html()).to.equal('<div id="slots"><span>123</span></div>')
expect(wrapper.find('#scopedSlots').html()).to.equal('<div id="scopedSlots"><p>0,a1</p><p>1,a2</p><p>2,a3</p></div>')
wrapper.vm.items = [{ text: 'b1' }, { text: 'b2' }, { text: 'b3' }]
expect(wrapper.find('#scopedSlots').html()).to.equal('<div id="scopedSlots"><p>0,b1</p><p>1,b2</p><p>2,b3</p></div>')
}
)

itDoNotRunIf(vueVersion < 2.5,
'throws exception when it is seted to template tag at top', () => {
const fn = () => {
mountingMethod(ComponentWithScopedSlots, {
scopedSlots: {
'item': '<template></template>'
}
})
}
const message = '[vue-test-utils]: scopedSlots option does not support template tag.'
expect(fn).to.throw().with.property('message', message)
}
)

itDoNotRunIf(vueVersion >= 2.5,
'throws exception when vue version < 2.5', () => {
const fn = () => {
mountingMethod(ComponentWithScopedSlots, {
scopedSlots: {
'item': '<p slot="item" slot-scope="props">{{props.index}},{{props.text}}</p>'
}
})
}
const message = '[vue-test-utils]: scopedSlots option supports [email protected]+.'
expect(fn).to.throw().with.property('message', message)
}
)
})