mirror of https://github.com/vuejs/vue.git
370 lines
9.2 KiB
TypeScript
370 lines
9.2 KiB
TypeScript
import Vue from 'vue'
|
|
import { createEmptyVNode } from 'core/vdom/vnode'
|
|
|
|
describe('Options functional', () => {
|
|
it('should work', done => {
|
|
const vm = new Vue({
|
|
data: { test: 'foo' },
|
|
template: '<div><wrap :msg="test">bar</wrap></div>',
|
|
components: {
|
|
wrap: {
|
|
functional: true,
|
|
props: ['msg'],
|
|
render(h, { props, children }) {
|
|
return h('div', null, [props.msg, ' '].concat(children))
|
|
}
|
|
}
|
|
}
|
|
}).$mount()
|
|
expect(vm.$el.innerHTML).toBe('<div>foo bar</div>')
|
|
vm.test = 'qux'
|
|
waitForUpdate(() => {
|
|
expect(vm.$el.innerHTML).toBe('<div>qux bar</div>')
|
|
}).then(done)
|
|
})
|
|
|
|
it('should expose all props when not declared', done => {
|
|
const fn = {
|
|
functional: true,
|
|
render(h, { props }) {
|
|
return h('div', `${props.msg} ${props.kebabMsg}`)
|
|
}
|
|
}
|
|
|
|
const vm = new Vue({
|
|
data: { test: 'foo' },
|
|
render(h) {
|
|
return h('div', [
|
|
h(fn, {
|
|
props: { msg: this.test },
|
|
attrs: { 'kebab-msg': 'bar' }
|
|
})
|
|
])
|
|
}
|
|
}).$mount()
|
|
|
|
expect(vm.$el.innerHTML).toBe('<div>foo bar</div>')
|
|
vm.test = 'qux'
|
|
waitForUpdate(() => {
|
|
expect(vm.$el.innerHTML).toBe('<div>qux bar</div>')
|
|
}).then(done)
|
|
})
|
|
|
|
it('should expose data.on as listeners', () => {
|
|
const foo = vi.fn()
|
|
const bar = vi.fn()
|
|
const vm = new Vue({
|
|
template: '<div><wrap @click="foo" @test="bar"/></div>',
|
|
methods: { foo, bar },
|
|
components: {
|
|
wrap: {
|
|
functional: true,
|
|
render(h, { listeners }) {
|
|
return h('div', {
|
|
on: {
|
|
click: [listeners.click, () => listeners.test('bar')]
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}).$mount()
|
|
|
|
document.body.appendChild(vm.$el)
|
|
triggerEvent(vm.$el.children[0], 'click')
|
|
expect(foo).toHaveBeenCalled()
|
|
expect(foo.mock.calls[0][0].type).toBe('click') // should have click event
|
|
triggerEvent(vm.$el.children[0], 'mousedown')
|
|
expect(bar).toHaveBeenCalledWith('bar')
|
|
document.body.removeChild(vm.$el)
|
|
})
|
|
|
|
it('should expose scopedSlots on render context', () => {
|
|
const vm = new Vue({
|
|
template:
|
|
'<div><wrap>foo<p slot="p" slot-scope="a">{{ a }}</p></wrap></div>',
|
|
components: {
|
|
wrap: {
|
|
functional: true,
|
|
render(h, { scopedSlots }) {
|
|
return [
|
|
// scoped
|
|
scopedSlots.p('a'),
|
|
// normal slot content should be exposed as well
|
|
scopedSlots.default()
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}).$mount()
|
|
|
|
expect(vm.$el.textContent).toBe('afoo')
|
|
})
|
|
|
|
it('should support returning more than one root node', () => {
|
|
const vm = new Vue({
|
|
template: `<div><test></test></div>`,
|
|
components: {
|
|
test: {
|
|
functional: true,
|
|
render(h) {
|
|
return [h('span', 'foo'), h('span', 'bar')]
|
|
}
|
|
}
|
|
}
|
|
}).$mount()
|
|
expect(vm.$el.innerHTML).toBe('<span>foo</span><span>bar</span>')
|
|
})
|
|
|
|
it('should support slots', () => {
|
|
const vm = new Vue({
|
|
data: { test: 'foo' },
|
|
template:
|
|
'<div><wrap><div slot="a">foo</div><div slot="b">bar</div></wrap></div>',
|
|
components: {
|
|
wrap: {
|
|
functional: true,
|
|
props: ['msg'],
|
|
render(h, { slots }) {
|
|
slots = slots()
|
|
return h('div', null, [slots.b, slots.a])
|
|
}
|
|
}
|
|
}
|
|
}).$mount()
|
|
expect(vm.$el.innerHTML).toBe('<div><div>bar</div><div>foo</div></div>')
|
|
})
|
|
|
|
it('should let vnode raw data pass through', done => {
|
|
const onValid = vi.fn()
|
|
const vm = new Vue({
|
|
data: { msg: 'hello' },
|
|
template: `<div>
|
|
<validate field="field1" @valid="onValid">
|
|
<input type="text" v-model="msg">
|
|
</validate>
|
|
</div>`,
|
|
components: {
|
|
validate: {
|
|
functional: true,
|
|
props: ['field'],
|
|
render(h, { props, children, data: { on } }) {
|
|
props.child = children[0]
|
|
return h('validate-control', { props, on })
|
|
}
|
|
},
|
|
'validate-control': {
|
|
props: ['field', 'child'],
|
|
render() {
|
|
return this.child
|
|
},
|
|
mounted() {
|
|
this.$el.addEventListener('input', this.onInput)
|
|
},
|
|
destroyed() {
|
|
this.$el.removeEventListener('input', this.onInput)
|
|
},
|
|
methods: {
|
|
onInput(e) {
|
|
const value = e.target.value
|
|
if (this.validate(value)) {
|
|
this.$emit('valid', this)
|
|
}
|
|
},
|
|
// something validation logic here
|
|
validate(val) {
|
|
return val.length > 0
|
|
}
|
|
}
|
|
}
|
|
},
|
|
methods: { onValid }
|
|
}).$mount()
|
|
document.body.appendChild(vm.$el)
|
|
const input = vm.$el.querySelector('input')
|
|
expect(onValid).not.toHaveBeenCalled()
|
|
waitForUpdate(() => {
|
|
input.value = 'foo'
|
|
triggerEvent(input, 'input')
|
|
})
|
|
.then(() => {
|
|
expect(onValid).toHaveBeenCalled()
|
|
})
|
|
.then(() => {
|
|
document.body.removeChild(vm.$el)
|
|
vm.$destroy()
|
|
})
|
|
.then(done)
|
|
})
|
|
|
|
it('create empty vnode when render return null', () => {
|
|
const child = {
|
|
functional: true,
|
|
render() {
|
|
return null
|
|
}
|
|
}
|
|
const vm = new Vue({
|
|
components: {
|
|
child
|
|
}
|
|
})
|
|
const h = vm.$createElement
|
|
const vnode = h('child')
|
|
expect(vnode).toEqual(createEmptyVNode())
|
|
})
|
|
|
|
// #7282
|
|
it('should normalize top-level arrays', () => {
|
|
const Foo = {
|
|
functional: true,
|
|
render(h) {
|
|
return [h('span', 'hi'), null]
|
|
}
|
|
}
|
|
const vm = new Vue({
|
|
template: `<div><foo/></div>`,
|
|
components: { Foo }
|
|
}).$mount()
|
|
expect(vm.$el.innerHTML).toBe('<span>hi</span>')
|
|
})
|
|
|
|
it('should work when used as named slot and returning array', () => {
|
|
const Foo = {
|
|
template: `<div><slot name="test"/></div>`
|
|
}
|
|
|
|
const Bar = {
|
|
functional: true,
|
|
render: h => [h('div', 'one'), h('div', 'two'), h(Baz)]
|
|
}
|
|
|
|
const Baz = {
|
|
functional: true,
|
|
render: h => h('div', 'three')
|
|
}
|
|
|
|
const vm = new Vue({
|
|
template: `<foo><bar slot="test"/></foo>`,
|
|
components: { Foo, Bar }
|
|
}).$mount()
|
|
|
|
expect(vm.$el.innerHTML).toBe(
|
|
'<div>one</div><div>two</div><div>three</div>'
|
|
)
|
|
})
|
|
|
|
it('should apply namespace when returning arrays', () => {
|
|
const Child = {
|
|
functional: true,
|
|
render: h => [h('foo'), h('bar')]
|
|
}
|
|
const vm = new Vue({
|
|
template: `<svg><child/></svg>`,
|
|
components: { Child }
|
|
}).$mount()
|
|
|
|
expect(vm.$el.childNodes[0].namespaceURI).toContain('svg')
|
|
expect(vm.$el.childNodes[1].namespaceURI).toContain('svg')
|
|
})
|
|
|
|
it('should work with render fns compiled from template', done => {
|
|
const render = function (_h, _vm) {
|
|
const _c = _vm._c
|
|
return _c(
|
|
'div',
|
|
[
|
|
_c('h2', { staticClass: 'red' }, [_vm._v(_vm._s(_vm.props.msg))]),
|
|
_vm._t('default'),
|
|
_vm._t('slot2'),
|
|
_vm._t('scoped', null, { msg: _vm.props.msg }),
|
|
_vm._m(0),
|
|
_c(
|
|
'div',
|
|
{ staticClass: 'clickable', on: { click: _vm.parent.fn } },
|
|
[_vm._v('click me')]
|
|
)
|
|
],
|
|
2
|
|
)
|
|
}
|
|
const staticRenderFns = [
|
|
function (_h, _vm) {
|
|
const _c = _vm._c
|
|
return _c('div', [_vm._v('Some '), _c('span', [_vm._v('text')])])
|
|
}
|
|
]
|
|
|
|
const child = {
|
|
functional: true,
|
|
_compiled: true,
|
|
render,
|
|
staticRenderFns
|
|
}
|
|
|
|
const parent = new Vue({
|
|
components: {
|
|
child
|
|
},
|
|
data: {
|
|
msg: 'hello'
|
|
},
|
|
template: `
|
|
<div>
|
|
<child :msg="msg">
|
|
<span>{{ msg }}</span>
|
|
<div slot="slot2">Second slot</div>
|
|
<template slot="scoped" slot-scope="scope">{{ scope.msg }}</template>
|
|
</child>
|
|
</div>
|
|
`,
|
|
methods: {
|
|
fn() {
|
|
this.msg = 'bye'
|
|
}
|
|
}
|
|
}).$mount()
|
|
|
|
function assertMarkup() {
|
|
expect(parent.$el.innerHTML).toBe(
|
|
`<div>` +
|
|
`<h2 class="red">${parent.msg}</h2>` +
|
|
`<span>${parent.msg}</span> ` +
|
|
`<div>Second slot</div>` +
|
|
parent.msg +
|
|
// static
|
|
`<div>Some <span>text</span></div>` +
|
|
`<div class="clickable">click me</div>` +
|
|
`</div>`
|
|
)
|
|
}
|
|
|
|
assertMarkup()
|
|
triggerEvent(parent.$el.querySelector('.clickable'), 'click')
|
|
waitForUpdate(assertMarkup).then(done)
|
|
})
|
|
|
|
// #8468
|
|
it('should normalize nested arrays when use functional components with v-for', () => {
|
|
const Foo = {
|
|
functional: true,
|
|
props: {
|
|
name: {}
|
|
},
|
|
render(h, context) {
|
|
return [h('span', 'hi'), h('span', context.props.name)]
|
|
}
|
|
}
|
|
const vm = new Vue({
|
|
template: `<div><foo v-for="name in names" :name="name" /></div>`,
|
|
data: {
|
|
names: ['foo', 'bar']
|
|
},
|
|
components: { Foo }
|
|
}).$mount()
|
|
expect(vm.$el.innerHTML).toBe(
|
|
'<span>hi</span><span>foo</span><span>hi</span><span>bar</span>'
|
|
)
|
|
})
|
|
})
|