| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- import {
- KeepAlive,
- TrackOpTypes,
- h,
- nextTick,
- nodeOps,
- onActivated,
- onBeforeMount,
- onBeforeUnmount,
- onBeforeUpdate,
- onMounted,
- onRenderTracked,
- onRenderTriggered,
- onUnmounted,
- onUpdated,
- reactive,
- ref,
- render,
- serializeInner,
- } from '@vue/runtime-test'
- import {
- type DebuggerEvent,
- ITERATE_KEY,
- TriggerOpTypes,
- } from '@vue/reactivity'
- describe('api: lifecycle hooks', () => {
- it('onBeforeMount', () => {
- const root = nodeOps.createElement('div')
- const fn = vi.fn(() => {
- // should be called before inner div is rendered
- expect(serializeInner(root)).toBe(``)
- })
- const Comp = {
- setup() {
- onBeforeMount(fn)
- return () => h('div')
- },
- }
- render(h(Comp), root)
- expect(fn).toHaveBeenCalledTimes(1)
- // #10863
- expect(fn).toHaveBeenCalledWith()
- })
- it('onMounted', () => {
- const root = nodeOps.createElement('div')
- const fn = vi.fn(() => {
- // should be called after inner div is rendered
- expect(serializeInner(root)).toBe(`<div></div>`)
- })
- const Comp = {
- setup() {
- onMounted(fn)
- return () => h('div')
- },
- }
- render(h(Comp), root)
- expect(fn).toHaveBeenCalledTimes(1)
- })
- it('onBeforeUpdate', async () => {
- const count = ref(0)
- const root = nodeOps.createElement('div')
- const fn = vi.fn(() => {
- // should be called before inner div is updated
- expect(serializeInner(root)).toBe(`<div>0</div>`)
- })
- const Comp = {
- setup() {
- onBeforeUpdate(fn)
- return () => h('div', count.value)
- },
- }
- render(h(Comp), root)
- count.value++
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(1)
- expect(serializeInner(root)).toBe(`<div>1</div>`)
- })
- it('state mutation in onBeforeUpdate', async () => {
- const count = ref(0)
- const root = nodeOps.createElement('div')
- const fn = vi.fn(() => {
- // should be called before inner div is updated
- expect(serializeInner(root)).toBe(`<div>0</div>`)
- count.value++
- })
- const renderSpy = vi.fn()
- const Comp = {
- setup() {
- onBeforeUpdate(fn)
- return () => {
- renderSpy()
- return h('div', count.value)
- }
- },
- }
- render(h(Comp), root)
- expect(renderSpy).toHaveBeenCalledTimes(1)
- count.value++
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(1)
- expect(renderSpy).toHaveBeenCalledTimes(2)
- expect(serializeInner(root)).toBe(`<div>2</div>`)
- })
- it('onUpdated', async () => {
- const count = ref(0)
- const root = nodeOps.createElement('div')
- const fn = vi.fn(() => {
- // should be called after inner div is updated
- expect(serializeInner(root)).toBe(`<div>1</div>`)
- })
- const Comp = {
- setup() {
- onUpdated(fn)
- return () => h('div', count.value)
- },
- }
- render(h(Comp), root)
- count.value++
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(1)
- })
- it('onBeforeUnmount', async () => {
- const toggle = ref(true)
- const root = nodeOps.createElement('div')
- const fn = vi.fn(() => {
- // should be called before inner div is removed
- expect(serializeInner(root)).toBe(`<div></div>`)
- })
- const Comp = {
- setup() {
- return () => (toggle.value ? h(Child) : null)
- },
- }
- const Child = {
- setup() {
- onBeforeUnmount(fn)
- return () => h('div')
- },
- }
- render(h(Comp), root)
- toggle.value = false
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(1)
- })
- it('onUnmounted', async () => {
- const toggle = ref(true)
- const root = nodeOps.createElement('div')
- const fn = vi.fn(() => {
- // should be called after inner div is removed
- expect(serializeInner(root)).toBe(`<!---->`)
- })
- const Comp = {
- setup() {
- return () => (toggle.value ? h(Child) : null)
- },
- }
- const Child = {
- setup() {
- onUnmounted(fn)
- return () => h('div')
- },
- }
- render(h(Comp), root)
- toggle.value = false
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(1)
- })
- it('onBeforeUnmount in onMounted', async () => {
- const toggle = ref(true)
- const root = nodeOps.createElement('div')
- const fn = vi.fn(() => {
- // should be called before inner div is removed
- expect(serializeInner(root)).toBe(`<div></div>`)
- })
- const Comp = {
- setup() {
- return () => (toggle.value ? h(Child) : null)
- },
- }
- const Child = {
- setup() {
- onMounted(() => {
- onBeforeUnmount(fn)
- })
- return () => h('div')
- },
- }
- render(h(Comp), root)
- toggle.value = false
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(1)
- })
- it('lifecycle call order', async () => {
- const count = ref(0)
- const root = nodeOps.createElement('div')
- 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, { 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, { 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
- render(h(Root), root)
- 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
- render(null, root)
- 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, 'bar' in obj, Object.keys(obj).join('')])
- },
- }
- render(h(Comp), nodeOps.createElement('div'))
- expect(onTrack).toHaveBeenCalledTimes(3)
- expect(events).toMatchObject([
- {
- target: obj,
- type: TrackOpTypes.GET,
- key: 'foo',
- },
- {
- target: obj,
- type: TrackOpTypes.HAS,
- key: 'bar',
- },
- {
- target: obj,
- type: TrackOpTypes.ITERATE,
- key: ITERATE_KEY,
- },
- ])
- })
- 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, 'bar' in obj, Object.keys(obj).join('')])
- },
- }
- render(h(Comp), nodeOps.createElement('div'))
- obj.foo++
- await nextTick()
- expect(onTrigger).toHaveBeenCalledTimes(1)
- expect(events[0]).toMatchObject({
- type: TriggerOpTypes.SET,
- key: 'foo',
- oldValue: 1,
- newValue: 2,
- })
- delete obj.bar
- await nextTick()
- expect(onTrigger).toHaveBeenCalledTimes(2)
- expect(events[1]).toMatchObject({
- type: TriggerOpTypes.DELETE,
- key: 'bar',
- oldValue: 2,
- })
- ;(obj as any).baz = 3
- await nextTick()
- expect(onTrigger).toHaveBeenCalledTimes(3)
- expect(events[2]).toMatchObject({
- type: TriggerOpTypes.ADD,
- key: 'baz',
- newValue: 3,
- })
- })
- it('runs shared hook fn for each instance', async () => {
- const fn = vi.fn()
- const toggle = ref(true)
- const Comp = {
- setup() {
- return () => (toggle.value ? [h(Child), h(Child)] : null)
- },
- }
- const Child = {
- setup() {
- onMounted(fn)
- onBeforeUnmount(fn)
- return () => h('div')
- },
- }
- render(h(Comp), nodeOps.createElement('div'))
- expect(fn).toHaveBeenCalledTimes(2)
- toggle.value = false
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(4)
- })
- it('immediately trigger unmount during rendering', async () => {
- const fn = vi.fn()
- const toggle = ref(false)
- const Child = {
- setup() {
- onMounted(fn)
- // trigger unmount immediately
- toggle.value = false
- return () => h('div')
- },
- }
- const Comp = {
- setup() {
- return () => (toggle.value ? [h(Child)] : null)
- },
- }
- render(h(Comp), nodeOps.createElement('div'))
- toggle.value = true
- await nextTick()
- expect(fn).toHaveBeenCalledTimes(0)
- })
- it('immediately trigger unmount during rendering(with KeepAlive)', async () => {
- const mountedSpy = vi.fn()
- const activeSpy = vi.fn()
- const toggle = ref(false)
- const Child = {
- setup() {
- onMounted(mountedSpy)
- onActivated(activeSpy)
- // trigger unmount immediately
- toggle.value = false
- return () => h('div')
- },
- }
- const Comp = {
- setup() {
- return () => h(KeepAlive, [toggle.value ? h(Child) : null])
- },
- }
- render(h(Comp), nodeOps.createElement('div'))
- toggle.value = true
- await nextTick()
- expect(mountedSpy).toHaveBeenCalledTimes(0)
- expect(activeSpy).toHaveBeenCalledTimes(0)
- })
- })
|