| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- import Vue from 'vue'
- import {
- h,
- onBeforeMount,
- onMounted,
- ref,
- reactive,
- onBeforeUpdate,
- onUpdated,
- onBeforeUnmount,
- onUnmounted,
- onRenderTracked,
- onRenderTriggered,
- DebuggerEvent,
- TrackOpTypes,
- TriggerOpTypes
- } from 'v3'
- import { nextTick } from 'core/util'
- describe('api: lifecycle hooks', () => {
- it('onBeforeMount', () => {
- const fn = vi.fn(() => {
- // should be called before root is replaced
- expect(vm.$el).toBeUndefined()
- })
- const Comp = {
- setup() {
- onBeforeMount(fn)
- return () => h('div', 'hello')
- }
- }
- const vm = new Vue(Comp)
- vm.$mount()
- expect(fn).toHaveBeenCalledTimes(1)
- expect(vm.$el.innerHTML).toBe(`hello`)
- })
- it('onMounted', () => {
- const fn = vi.fn(() => {
- // should be called after inner div is rendered
- expect(vm.$el.outerHTML).toBe(`<div></div>`)
- })
- const Comp = {
- setup() {
- onMounted(fn)
- return () => h('div')
- }
- }
- const vm = new Vue(Comp)
- vm.$mount()
- expect(fn).toHaveBeenCalledTimes(1)
- })
- it('onBeforeUpdate', async () => {
- const count = ref(0)
- const fn = vi.fn(() => {
- // should be called before inner div is updated
- expect(vm.$el.outerHTML).toBe(`<div>0</div>`)
- })
- const Comp = {
- setup() {
- onBeforeUpdate(fn)
- return () => h('div', count.value)
- }
- }
- const vm = new Vue(Comp).$mount()
- count.value++
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(1)
- expect(vm.$el.outerHTML).toBe(`<div>1</div>`)
- })
- it('state mutation in onBeforeUpdate', async () => {
- const count = ref(0)
- const fn = vi.fn(() => {
- // should be called before inner div is updated
- expect(vm.$el.outerHTML).toBe(`<div>0</div>`)
- count.value++
- })
- const renderSpy = vi.fn()
- const Comp = {
- setup() {
- onBeforeUpdate(fn)
- return () => {
- renderSpy()
- return h('div', count.value)
- }
- }
- }
- const vm = new Vue(Comp).$mount()
- expect(renderSpy).toHaveBeenCalledTimes(1)
- count.value++
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(1)
- expect(renderSpy).toHaveBeenCalledTimes(2)
- expect(vm.$el.outerHTML).toBe(`<div>2</div>`)
- })
- it('onUpdated', async () => {
- const count = ref(0)
- const fn = vi.fn(() => {
- // should be called after inner div is updated
- expect(vm.$el.outerHTML).toBe(`<div>1</div>`)
- })
- const Comp = {
- setup() {
- onUpdated(fn)
- return () => h('div', count.value)
- }
- }
- const vm = new Vue(Comp).$mount()
- count.value++
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(1)
- })
- it('onBeforeUnmount', async () => {
- const toggle = ref(true)
- const root = document.createElement('div')
- const fn = vi.fn(() => {
- // should be called before inner div is removed
- expect(root.outerHTML).toBe(`<div></div>`)
- })
- const Comp = {
- setup() {
- return () => (toggle.value ? h(Child) : null)
- }
- }
- const Child = {
- setup() {
- onBeforeUnmount(fn)
- return () => h('div')
- }
- }
- new Vue(Comp).$mount(root)
- toggle.value = false
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(1)
- })
- it('onUnmounted', async () => {
- const toggle = ref(true)
- const fn = vi.fn(() => {
- // @discrepancy should be called after inner div is removed
- // expect(vm.$el.outerHTML).toBe(`<span></span>`)
- })
- const Comp = {
- setup() {
- return () => (toggle.value ? h(Child) : h('span'))
- }
- }
- const Child = {
- setup() {
- onUnmounted(fn)
- return () => h('div')
- }
- }
- new Vue(Comp).$mount()
- toggle.value = false
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(1)
- })
- it('onBeforeUnmount in onMounted', async () => {
- const toggle = ref(true)
- const fn = vi.fn(() => {
- // should be called before inner div is removed
- expect(vm.$el.outerHTML).toBe(`<div></div>`)
- })
- const Comp = {
- setup() {
- return () => (toggle.value ? h(Child) : null)
- }
- }
- const Child = {
- setup() {
- onMounted(() => {
- onBeforeUnmount(fn)
- })
- return () => h('div')
- }
- }
- const vm = new Vue(Comp).$mount()
- toggle.value = false
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(1)
- })
- it('lifecycle call order', async () => {
- const count = ref(0)
- const calls: string[] = []
- const Root = {
- setup() {
- onBeforeMount(() => calls.push('root onBeforeMount'))
- onMounted(() => calls.push('root onMounted'))
- onBeforeUpdate(() => calls.push('root onBeforeUpdate'))
- onUpdated(() => calls.push('root onUpdated'))
- onBeforeUnmount(() => calls.push('root onBeforeUnmount'))
- onUnmounted(() => calls.push('root onUnmounted'))
- return () => h(Mid, { props: { count: count.value } })
- }
- }
- const Mid = {
- props: ['count'],
- setup(props: any) {
- onBeforeMount(() => calls.push('mid onBeforeMount'))
- onMounted(() => calls.push('mid onMounted'))
- onBeforeUpdate(() => calls.push('mid onBeforeUpdate'))
- onUpdated(() => calls.push('mid onUpdated'))
- onBeforeUnmount(() => calls.push('mid onBeforeUnmount'))
- onUnmounted(() => calls.push('mid onUnmounted'))
- return () => h(Child, { props: { count: props.count } })
- }
- }
- const Child = {
- props: ['count'],
- setup(props: any) {
- onBeforeMount(() => calls.push('child onBeforeMount'))
- onMounted(() => calls.push('child onMounted'))
- onBeforeUpdate(() => calls.push('child onBeforeUpdate'))
- onUpdated(() => calls.push('child onUpdated'))
- onBeforeUnmount(() => calls.push('child onBeforeUnmount'))
- onUnmounted(() => calls.push('child onUnmounted'))
- return () => h('div', props.count)
- }
- }
- // mount
- const vm = new Vue(Root)
- vm.$mount()
- expect(calls).toEqual([
- 'root onBeforeMount',
- 'mid onBeforeMount',
- 'child onBeforeMount',
- 'child onMounted',
- 'mid onMounted',
- 'root onMounted'
- ])
- calls.length = 0
- // update
- count.value++
- await nextTick()
- expect(calls).toEqual([
- 'root onBeforeUpdate',
- 'mid onBeforeUpdate',
- 'child onBeforeUpdate',
- 'child onUpdated',
- 'mid onUpdated',
- 'root onUpdated'
- ])
- calls.length = 0
- // unmount
- vm.$destroy()
- expect(calls).toEqual([
- 'root onBeforeUnmount',
- 'mid onBeforeUnmount',
- 'child onBeforeUnmount',
- 'child onUnmounted',
- 'mid onUnmounted',
- 'root onUnmounted'
- ])
- })
- it('onRenderTracked', () => {
- const events: DebuggerEvent[] = []
- const onTrack = vi.fn((e: DebuggerEvent) => {
- events.push(e)
- })
- const obj = reactive({ foo: 1, bar: 2 })
- const Comp = {
- setup() {
- onRenderTracked(onTrack)
- return () => h('div', [obj.foo + obj.bar])
- }
- }
- new Vue(Comp).$mount()
- expect(onTrack).toHaveBeenCalledTimes(2)
- expect(events).toMatchObject([
- {
- target: obj,
- type: TrackOpTypes.GET,
- key: 'foo'
- },
- {
- target: obj,
- type: TrackOpTypes.GET,
- key: 'bar'
- }
- ])
- })
- it('onRenderTriggered', async () => {
- const events: DebuggerEvent[] = []
- const onTrigger = vi.fn((e: DebuggerEvent) => {
- events.push(e)
- })
- const obj = reactive<{
- foo: number
- bar: number
- }>({ foo: 1, bar: 2 })
- const Comp = {
- setup() {
- onRenderTriggered(onTrigger)
- return () => h('div', [obj.foo + obj.bar])
- }
- }
- new Vue(Comp).$mount()
- obj.foo++
- await nextTick()
- expect(onTrigger).toHaveBeenCalledTimes(1)
- expect(events[0]).toMatchObject({
- type: TriggerOpTypes.SET,
- key: 'foo',
- oldValue: 1,
- newValue: 2
- })
- obj.bar++
- await nextTick()
- expect(onTrigger).toHaveBeenCalledTimes(2)
- expect(events[1]).toMatchObject({
- type: TriggerOpTypes.SET,
- key: 'bar',
- oldValue: 2,
- newValue: 3
- })
- })
- })
|