if.spec.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import {
  2. child,
  3. createIf,
  4. insert,
  5. renderEffect,
  6. template,
  7. // @ts-expect-error
  8. withDirectives,
  9. } from '../src'
  10. import { nextTick, ref } from '@vue/runtime-dom'
  11. import type { Mock } from 'vitest'
  12. import { makeRender } from './_utils'
  13. import { unmountComponent } from '../src/component'
  14. import { setElementText } from '../src/dom/prop'
  15. const define = makeRender()
  16. describe('createIf', () => {
  17. test('basic', async () => {
  18. // mock this template:
  19. // <div>
  20. // <p v-if="counter">{{counter}}</p>
  21. // <p v-else>zero</p>
  22. // </div>
  23. let spyIfFn: Mock<any>
  24. let spyElseFn: Mock<any>
  25. const count = ref(0)
  26. const spyConditionFn = vi.fn(() => count.value)
  27. // templates can be reused through caching.
  28. const t0 = template('<div></div>')
  29. const t1 = template('<p></p>')
  30. const t2 = template('<p>zero</p>')
  31. const { host } = define(() => {
  32. const n0 = t0()
  33. insert(
  34. createIf(
  35. spyConditionFn,
  36. // v-if
  37. (spyIfFn ||= vi.fn(() => {
  38. const n2 = t1()
  39. renderEffect(() => {
  40. setElementText(n2, count.value)
  41. })
  42. return n2
  43. })),
  44. // v-else
  45. (spyElseFn ||= vi.fn(() => {
  46. const n4 = t2()
  47. return n4
  48. })),
  49. ),
  50. n0 as any as ParentNode,
  51. )
  52. return n0
  53. }).render()
  54. expect(host.innerHTML).toBe('<div><p>zero</p><!--if--></div>')
  55. expect(spyConditionFn).toHaveBeenCalledTimes(1)
  56. expect(spyIfFn!).toHaveBeenCalledTimes(0)
  57. expect(spyElseFn!).toHaveBeenCalledTimes(1)
  58. count.value++
  59. await nextTick()
  60. expect(host.innerHTML).toBe('<div><p>1</p><!--if--></div>')
  61. expect(spyConditionFn).toHaveBeenCalledTimes(2)
  62. expect(spyIfFn!).toHaveBeenCalledTimes(1)
  63. expect(spyElseFn!).toHaveBeenCalledTimes(1)
  64. count.value++
  65. await nextTick()
  66. expect(host.innerHTML).toBe('<div><p>2</p><!--if--></div>')
  67. expect(spyConditionFn).toHaveBeenCalledTimes(3)
  68. expect(spyIfFn!).toHaveBeenCalledTimes(1)
  69. expect(spyElseFn!).toHaveBeenCalledTimes(1)
  70. count.value = 0
  71. await nextTick()
  72. expect(host.innerHTML).toBe('<div><p>zero</p><!--if--></div>')
  73. expect(spyConditionFn).toHaveBeenCalledTimes(4)
  74. expect(spyIfFn!).toHaveBeenCalledTimes(1)
  75. expect(spyElseFn!).toHaveBeenCalledTimes(2)
  76. })
  77. test('should handle nested template', async () => {
  78. // mock this template:
  79. // <template v-if="ok1">
  80. // Hello <template v-if="ok2">Vapor</template>
  81. // </template>
  82. const ok1 = ref(true)
  83. const ok2 = ref(true)
  84. const t0 = template('Vapor')
  85. const t1 = template('Hello ')
  86. const { host } = define(() => {
  87. const n1 = createIf(
  88. () => ok1.value,
  89. () => {
  90. const n2 = t1()
  91. const n3 = createIf(
  92. () => ok2.value,
  93. () => {
  94. const n4 = t0()
  95. return n4
  96. },
  97. )
  98. return [n2, n3]
  99. },
  100. )
  101. return n1
  102. }).render()
  103. expect(host.innerHTML).toBe('Hello Vapor<!--if--><!--if-->')
  104. ok1.value = false
  105. await nextTick()
  106. expect(host.innerHTML).toBe('<!--if-->')
  107. ok1.value = true
  108. await nextTick()
  109. expect(host.innerHTML).toBe('Hello Vapor<!--if--><!--if-->')
  110. ok2.value = false
  111. await nextTick()
  112. expect(host.innerHTML).toBe('Hello <!--if--><!--if-->')
  113. ok1.value = false
  114. await nextTick()
  115. expect(host.innerHTML).toBe('<!--if-->')
  116. })
  117. test('with v-once', async () => {
  118. const toggle = ref(false)
  119. const { html } = define({
  120. setup() {
  121. return createIf(
  122. () => toggle.value,
  123. () => template('<p>foo</p>')(),
  124. () => template('<p>bar</p>')(),
  125. true,
  126. )
  127. },
  128. }).render()
  129. expect(html()).toBe('<p>bar</p>')
  130. toggle.value = true
  131. await nextTick()
  132. // should not change
  133. expect(html()).toBe('<p>bar</p>')
  134. })
  135. // vapor custom directives have no lifecycle hooks.
  136. test.todo('should work with directive hooks', async () => {
  137. const calls: string[] = []
  138. const show1 = ref(true)
  139. const show2 = ref(true)
  140. const update = ref(0)
  141. const spyConditionFn1 = vi.fn(() => show1.value)
  142. const spyConditionFn2 = vi.fn(() => show2.value)
  143. const vDirective: any = {
  144. created: (el: any, { value }: any) => calls.push(`${value} created`),
  145. beforeMount: (el: any, { value }: any) =>
  146. calls.push(`${value} beforeMount`),
  147. mounted: (el: any, { value }: any) => calls.push(`${value} mounted`),
  148. beforeUpdate: (el: any, { value }: any) =>
  149. calls.push(`${value} beforeUpdate`),
  150. updated: (el: any, { value }: any) => calls.push(`${value} updated`),
  151. beforeUnmount: (el: any, { value }: any) =>
  152. calls.push(`${value} beforeUnmount`),
  153. unmounted: (el: any, { value }: any) => calls.push(`${value} unmounted`),
  154. }
  155. const t0 = template('<p></p>')
  156. const { instance } = define(() => {
  157. const n1 = createIf(
  158. spyConditionFn1,
  159. () => {
  160. const n2 = t0() as ParentNode
  161. withDirectives(child(n2), [[vDirective, () => (update.value, '1')]])
  162. return n2
  163. },
  164. () =>
  165. createIf(
  166. spyConditionFn2,
  167. () => {
  168. const n2 = t0() as ParentNode
  169. withDirectives(child(n2), [[vDirective, () => '2']])
  170. return n2
  171. },
  172. () => {
  173. const n2 = t0() as ParentNode
  174. withDirectives(child(n2), [[vDirective, () => '3']])
  175. return n2
  176. },
  177. ),
  178. )
  179. return [n1]
  180. }).render()
  181. await nextTick()
  182. expect(calls).toEqual(['1 created', '1 beforeMount', '1 mounted'])
  183. calls.length = 0
  184. expect(spyConditionFn1).toHaveBeenCalledTimes(1)
  185. expect(spyConditionFn2).toHaveBeenCalledTimes(0)
  186. show1.value = false
  187. await nextTick()
  188. expect(calls).toEqual([
  189. '1 beforeUnmount',
  190. '2 created',
  191. '2 beforeMount',
  192. '1 unmounted',
  193. '2 mounted',
  194. ])
  195. calls.length = 0
  196. expect(spyConditionFn1).toHaveBeenCalledTimes(2)
  197. expect(spyConditionFn2).toHaveBeenCalledTimes(1)
  198. show2.value = false
  199. await nextTick()
  200. expect(calls).toEqual([
  201. '2 beforeUnmount',
  202. '3 created',
  203. '3 beforeMount',
  204. '2 unmounted',
  205. '3 mounted',
  206. ])
  207. calls.length = 0
  208. expect(spyConditionFn1).toHaveBeenCalledTimes(2)
  209. expect(spyConditionFn2).toHaveBeenCalledTimes(2)
  210. show1.value = true
  211. await nextTick()
  212. expect(calls).toEqual([
  213. '3 beforeUnmount',
  214. '1 created',
  215. '1 beforeMount',
  216. '3 unmounted',
  217. '1 mounted',
  218. ])
  219. calls.length = 0
  220. expect(spyConditionFn1).toHaveBeenCalledTimes(3)
  221. expect(spyConditionFn2).toHaveBeenCalledTimes(2)
  222. update.value++
  223. await nextTick()
  224. expect(calls).toEqual(['1 beforeUpdate', '1 updated'])
  225. calls.length = 0
  226. expect(spyConditionFn1).toHaveBeenCalledTimes(3)
  227. expect(spyConditionFn2).toHaveBeenCalledTimes(2)
  228. unmountComponent(instance!)
  229. expect(calls).toEqual(['1 beforeUnmount', '1 unmounted'])
  230. expect(spyConditionFn1).toHaveBeenCalledTimes(3)
  231. expect(spyConditionFn2).toHaveBeenCalledTimes(2)
  232. })
  233. })