functional.spec.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import Vue from 'vue'
  2. import { createEmptyVNode } from 'core/vdom/vnode'
  3. describe('Options functional', () => {
  4. it('should work', done => {
  5. const vm = new Vue({
  6. data: { test: 'foo' },
  7. template: '<div><wrap :msg="test">bar</wrap></div>',
  8. components: {
  9. wrap: {
  10. functional: true,
  11. props: ['msg'],
  12. render (h, { props, children }) {
  13. return h('div', null, [props.msg, ' '].concat(children))
  14. }
  15. }
  16. }
  17. }).$mount()
  18. expect(vm.$el.innerHTML).toBe('<div>foo bar</div>')
  19. vm.test = 'qux'
  20. waitForUpdate(() => {
  21. expect(vm.$el.innerHTML).toBe('<div>qux bar</div>')
  22. }).then(done)
  23. })
  24. it('should expose all props when not declared', done => {
  25. const fn = {
  26. functional: true,
  27. render (h, { props }) {
  28. return h('div', `${props.msg} ${props.kebabMsg}`)
  29. }
  30. }
  31. const vm = new Vue({
  32. data: { test: 'foo' },
  33. render (h) {
  34. return h('div', [
  35. h(fn, {
  36. props: { msg: this.test },
  37. attrs: { 'kebab-msg': 'bar' }
  38. })
  39. ])
  40. }
  41. }).$mount()
  42. expect(vm.$el.innerHTML).toBe('<div>foo bar</div>')
  43. vm.test = 'qux'
  44. waitForUpdate(() => {
  45. expect(vm.$el.innerHTML).toBe('<div>qux bar</div>')
  46. }).then(done)
  47. })
  48. it('should expose data.on as listeners', () => {
  49. const foo = jasmine.createSpy('foo')
  50. const bar = jasmine.createSpy('bar')
  51. const vm = new Vue({
  52. template: '<div><wrap @click="foo" @test="bar"/></div>',
  53. methods: { foo, bar },
  54. components: {
  55. wrap: {
  56. functional: true,
  57. render (h, { listeners }) {
  58. return h('div', {
  59. on: {
  60. click: [listeners.click, () => listeners.test('bar')]
  61. }
  62. })
  63. }
  64. }
  65. }
  66. }).$mount()
  67. triggerEvent(vm.$el.children[0], 'click')
  68. expect(foo).toHaveBeenCalled()
  69. expect(foo.calls.argsFor(0)[0].type).toBe('click') // should have click event
  70. triggerEvent(vm.$el.children[0], 'mousedown')
  71. expect(bar).toHaveBeenCalledWith('bar')
  72. })
  73. it('should support returning more than one root node', () => {
  74. const vm = new Vue({
  75. template: `<div><test></test></div>`,
  76. components: {
  77. test: {
  78. functional: true,
  79. render (h) {
  80. return [h('span', 'foo'), h('span', 'bar')]
  81. }
  82. }
  83. }
  84. }).$mount()
  85. expect(vm.$el.innerHTML).toBe('<span>foo</span><span>bar</span>')
  86. })
  87. it('should support slots', () => {
  88. const vm = new Vue({
  89. data: { test: 'foo' },
  90. template: '<div><wrap><div slot="a">foo</div><div slot="b">bar</div></wrap></div>',
  91. components: {
  92. wrap: {
  93. functional: true,
  94. props: ['msg'],
  95. render (h, { slots }) {
  96. slots = slots()
  97. return h('div', null, [slots.b, slots.a])
  98. }
  99. }
  100. }
  101. }).$mount()
  102. expect(vm.$el.innerHTML).toBe('<div><div>bar</div><div>foo</div></div>')
  103. })
  104. it('should let vnode raw data pass through', done => {
  105. const onValid = jasmine.createSpy('valid')
  106. const vm = new Vue({
  107. data: { msg: 'hello' },
  108. template: `<div>
  109. <validate field="field1" @valid="onValid">
  110. <input type="text" v-model="msg">
  111. </validate>
  112. </div>`,
  113. components: {
  114. validate: {
  115. functional: true,
  116. props: ['field'],
  117. render (h, { props, children, data: { on }}) {
  118. props.child = children[0]
  119. return h('validate-control', { props, on })
  120. }
  121. },
  122. 'validate-control': {
  123. props: ['field', 'child'],
  124. render () {
  125. return this.child
  126. },
  127. mounted () {
  128. this.$el.addEventListener('input', this.onInput)
  129. },
  130. destroyed () {
  131. this.$el.removeEventListener('input', this.onInput)
  132. },
  133. methods: {
  134. onInput (e) {
  135. const value = e.target.value
  136. if (this.validate(value)) {
  137. this.$emit('valid', this)
  138. }
  139. },
  140. // something validation logic here
  141. validate (val) {
  142. return val.length > 0
  143. }
  144. }
  145. }
  146. },
  147. methods: { onValid }
  148. }).$mount()
  149. document.body.appendChild(vm.$el)
  150. const input = vm.$el.querySelector('input')
  151. expect(onValid).not.toHaveBeenCalled()
  152. waitForUpdate(() => {
  153. input.value = 'foo'
  154. triggerEvent(input, 'input')
  155. }).then(() => {
  156. expect(onValid).toHaveBeenCalled()
  157. }).then(() => {
  158. document.body.removeChild(vm.$el)
  159. vm.$destroy()
  160. }).then(done)
  161. })
  162. it('create empty vnode when render return null', () => {
  163. const child = {
  164. functional: true,
  165. render () {
  166. return null
  167. }
  168. }
  169. const vm = new Vue({
  170. components: {
  171. child
  172. }
  173. })
  174. const h = vm.$createElement
  175. const vnode = h('child')
  176. expect(vnode).toEqual(createEmptyVNode())
  177. })
  178. })