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: '
bar
', components: { wrap: { functional: true, props: ['msg'], render (h, { props, children }) { return h('div', null, [props.msg, ' '].concat(children)) } } } }).$mount() expect(vm.$el.innerHTML).toBe('
foo bar
') vm.test = 'qux' waitForUpdate(() => { expect(vm.$el.innerHTML).toBe('
qux bar
') }).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('
foo bar
') vm.test = 'qux' waitForUpdate(() => { expect(vm.$el.innerHTML).toBe('
qux bar
') }).then(done) }) it('should expose data.on as listeners', () => { const foo = jasmine.createSpy('foo') const bar = jasmine.createSpy('bar') const vm = new Vue({ template: '
', 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.calls.argsFor(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: '
foo

{{ a }}

', 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: `
`, components: { test: { functional: true, render (h) { return [h('span', 'foo'), h('span', 'bar')] } } } }).$mount() expect(vm.$el.innerHTML).toBe('foobar') }) it('should support slots', () => { const vm = new Vue({ data: { test: 'foo' }, template: '
foo
bar
', 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('
bar
foo
') }) it('should let vnode raw data pass through', done => { const onValid = jasmine.createSpy('valid') const vm = new Vue({ data: { msg: 'hello' }, template: `
`, 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: `
`, components: { Foo } }).$mount() expect(vm.$el.innerHTML).toBe('hi') }) it('should work when used as named slot and returning array', () => { const Foo = { template: `
` } 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: ``, components: { Foo, Bar } }).$mount() expect(vm.$el.innerHTML).toBe('
one
two
three
') }) it('should apply namespace when returning arrays', () => { const Child = { functional: true, render: h => ([h('foo'), h('bar')]) } const vm = new Vue({ template: ``, 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 => { // code generated via vue-template-es2015-compiler 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: `
{{ msg }}
Second slot
`, methods: { fn () { this.msg = 'bye' } } }).$mount() function assertMarkup () { expect(parent.$el.innerHTML).toBe( `
` + `

${parent.msg}

` + `${parent.msg} ` + `
Second slot
` + parent.msg + // static `
Some text
` + `
click me
` + `
` ) } 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: `
`, data: { names: ['foo', 'bar'] }, components: { Foo } }).$mount() expect(vm.$el.innerHTML).toBe('hifoohibar') }) })