2
0

apiLifecycle.spec.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. import {
  2. onBeforeMount,
  3. h,
  4. nodeOps,
  5. render,
  6. serializeInner,
  7. onMounted,
  8. ref,
  9. onBeforeUpdate,
  10. nextTick,
  11. onUpdated,
  12. onBeforeUnmount,
  13. onUnmounted,
  14. onRenderTracked,
  15. reactive,
  16. OperationTypes,
  17. onRenderTriggered
  18. } from '@vue/runtime-test'
  19. import { ITERATE_KEY, DebuggerEvent } from '@vue/reactivity'
  20. // reference: https://vue-composition-api-rfc.netlify.com/api.html#lifecycle-hooks
  21. describe('api: lifecycle hooks', () => {
  22. it('onBeforeMount', () => {
  23. const root = nodeOps.createElement('div')
  24. const fn = jest.fn(() => {
  25. // should be called before inner div is rendered
  26. expect(serializeInner(root)).toBe(``)
  27. })
  28. const Comp = {
  29. setup() {
  30. onBeforeMount(fn)
  31. return () => h('div')
  32. }
  33. }
  34. render(h(Comp), root)
  35. expect(fn).toHaveBeenCalledTimes(1)
  36. })
  37. it('onMounted', () => {
  38. const root = nodeOps.createElement('div')
  39. const fn = jest.fn(() => {
  40. // should be called after inner div is rendered
  41. expect(serializeInner(root)).toBe(`<div></div>`)
  42. })
  43. const Comp = {
  44. setup() {
  45. onMounted(fn)
  46. return () => h('div')
  47. }
  48. }
  49. render(h(Comp), root)
  50. expect(fn).toHaveBeenCalledTimes(1)
  51. })
  52. it('onBeforeUpdate', async () => {
  53. const count = ref(0)
  54. const root = nodeOps.createElement('div')
  55. const fn = jest.fn(() => {
  56. // should be called before inner div is updated
  57. expect(serializeInner(root)).toBe(`<div>0</div>`)
  58. })
  59. const Comp = {
  60. setup() {
  61. onBeforeUpdate(fn)
  62. return () => h('div', count.value)
  63. }
  64. }
  65. render(h(Comp), root)
  66. count.value++
  67. await nextTick()
  68. expect(fn).toHaveBeenCalledTimes(1)
  69. })
  70. it('onUpdated', async () => {
  71. const count = ref(0)
  72. const root = nodeOps.createElement('div')
  73. const fn = jest.fn(() => {
  74. // should be called after inner div is updated
  75. expect(serializeInner(root)).toBe(`<div>1</div>`)
  76. })
  77. const Comp = {
  78. setup() {
  79. onUpdated(fn)
  80. return () => h('div', count.value)
  81. }
  82. }
  83. render(h(Comp), root)
  84. count.value++
  85. await nextTick()
  86. expect(fn).toHaveBeenCalledTimes(1)
  87. })
  88. it('onBeforeUnmount', async () => {
  89. const toggle = ref(true)
  90. const root = nodeOps.createElement('div')
  91. const fn = jest.fn(() => {
  92. // should be called before inner div is removed
  93. expect(serializeInner(root)).toBe(`<div></div>`)
  94. })
  95. const Comp = {
  96. setup() {
  97. return () => (toggle.value ? h(Child) : null)
  98. }
  99. }
  100. const Child = {
  101. setup() {
  102. onBeforeUnmount(fn)
  103. return () => h('div')
  104. }
  105. }
  106. render(h(Comp), root)
  107. toggle.value = false
  108. await nextTick()
  109. expect(fn).toHaveBeenCalledTimes(1)
  110. })
  111. it('onUnmounted', async () => {
  112. const toggle = ref(true)
  113. const root = nodeOps.createElement('div')
  114. const fn = jest.fn(() => {
  115. // should be called after inner div is removed
  116. expect(serializeInner(root)).toBe(`<!---->`)
  117. })
  118. const Comp = {
  119. setup() {
  120. return () => (toggle.value ? h(Child) : null)
  121. }
  122. }
  123. const Child = {
  124. setup() {
  125. onUnmounted(fn)
  126. return () => h('div')
  127. }
  128. }
  129. render(h(Comp), root)
  130. toggle.value = false
  131. await nextTick()
  132. expect(fn).toHaveBeenCalledTimes(1)
  133. })
  134. it('lifecycle call order', async () => {
  135. const count = ref(0)
  136. const root = nodeOps.createElement('div')
  137. const calls: string[] = []
  138. const Root = {
  139. setup() {
  140. onBeforeMount(() => calls.push('root onBeforeMount'))
  141. onMounted(() => calls.push('root onMounted'))
  142. onBeforeUpdate(() => calls.push('root onBeforeUpdate'))
  143. onUpdated(() => calls.push('root onUpdated'))
  144. onBeforeUnmount(() => calls.push('root onBeforeUnmount'))
  145. onUnmounted(() => calls.push('root onUnmounted'))
  146. return () => h(Mid, { count: count.value })
  147. }
  148. }
  149. const Mid = {
  150. setup(props: any) {
  151. onBeforeMount(() => calls.push('mid onBeforeMount'))
  152. onMounted(() => calls.push('mid onMounted'))
  153. onBeforeUpdate(() => calls.push('mid onBeforeUpdate'))
  154. onUpdated(() => calls.push('mid onUpdated'))
  155. onBeforeUnmount(() => calls.push('mid onBeforeUnmount'))
  156. onUnmounted(() => calls.push('mid onUnmounted'))
  157. return () => h(Child, { count: props.count })
  158. }
  159. }
  160. const Child = {
  161. setup(props: any) {
  162. onBeforeMount(() => calls.push('child onBeforeMount'))
  163. onMounted(() => calls.push('child onMounted'))
  164. onBeforeUpdate(() => calls.push('child onBeforeUpdate'))
  165. onUpdated(() => calls.push('child onUpdated'))
  166. onBeforeUnmount(() => calls.push('child onBeforeUnmount'))
  167. onUnmounted(() => calls.push('child onUnmounted'))
  168. return () => h('div', props.count)
  169. }
  170. }
  171. // mount
  172. render(h(Root), root)
  173. expect(calls).toEqual([
  174. 'root onBeforeMount',
  175. 'mid onBeforeMount',
  176. 'child onBeforeMount',
  177. 'child onMounted',
  178. 'mid onMounted',
  179. 'root onMounted'
  180. ])
  181. calls.length = 0
  182. // update
  183. count.value++
  184. await nextTick()
  185. expect(calls).toEqual([
  186. 'root onBeforeUpdate',
  187. 'mid onBeforeUpdate',
  188. 'child onBeforeUpdate',
  189. 'child onUpdated',
  190. 'mid onUpdated',
  191. 'root onUpdated'
  192. ])
  193. calls.length = 0
  194. // unmount
  195. render(null, root)
  196. expect(calls).toEqual([
  197. 'root onBeforeUnmount',
  198. 'mid onBeforeUnmount',
  199. 'child onBeforeUnmount',
  200. 'child onUnmounted',
  201. 'mid onUnmounted',
  202. 'root onUnmounted'
  203. ])
  204. })
  205. it('onRenderTracked', () => {
  206. const events: DebuggerEvent[] = []
  207. const onTrack = jest.fn((e: DebuggerEvent) => {
  208. events.push(e)
  209. })
  210. const obj = reactive({ foo: 1, bar: 2 })
  211. const Comp = {
  212. setup() {
  213. onRenderTracked(onTrack)
  214. return () =>
  215. h('div', [obj.foo, 'bar' in obj, Object.keys(obj).join('')])
  216. }
  217. }
  218. render(h(Comp), nodeOps.createElement('div'))
  219. expect(onTrack).toHaveBeenCalledTimes(3)
  220. expect(events).toMatchObject([
  221. {
  222. target: obj,
  223. type: OperationTypes.GET,
  224. key: 'foo'
  225. },
  226. {
  227. target: obj,
  228. type: OperationTypes.HAS,
  229. key: 'bar'
  230. },
  231. {
  232. target: obj,
  233. type: OperationTypes.ITERATE,
  234. key: ITERATE_KEY
  235. }
  236. ])
  237. })
  238. it('onRenderTriggered', async () => {
  239. const events: DebuggerEvent[] = []
  240. const onTrigger = jest.fn((e: DebuggerEvent) => {
  241. events.push(e)
  242. })
  243. const obj = reactive({ foo: 1, bar: 2 })
  244. const Comp = {
  245. setup() {
  246. onRenderTriggered(onTrigger)
  247. return () =>
  248. h('div', [obj.foo, 'bar' in obj, Object.keys(obj).join('')])
  249. }
  250. }
  251. render(h(Comp), nodeOps.createElement('div'))
  252. obj.foo++
  253. await nextTick()
  254. expect(onTrigger).toHaveBeenCalledTimes(1)
  255. expect(events[0]).toMatchObject({
  256. type: OperationTypes.SET,
  257. key: 'foo',
  258. oldValue: 1,
  259. newValue: 2
  260. })
  261. delete obj.bar
  262. await nextTick()
  263. expect(onTrigger).toHaveBeenCalledTimes(2)
  264. expect(events[1]).toMatchObject({
  265. type: OperationTypes.DELETE,
  266. key: 'bar',
  267. oldValue: 2
  268. })
  269. ;(obj as any).baz = 3
  270. await nextTick()
  271. expect(onTrigger).toHaveBeenCalledTimes(3)
  272. expect(events[2]).toMatchObject({
  273. type: OperationTypes.ADD,
  274. key: 'baz',
  275. newValue: 3
  276. })
  277. })
  278. test.todo('onErrorCaptured')
  279. })