| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722 |
- import {
- KeepAlive,
- defineAsyncComponent,
- defineComponent,
- h,
- nextTick,
- nodeOps,
- reactive,
- ref,
- render,
- serializeInner,
- shallowRef,
- watch,
- } from '@vue/runtime-test'
- describe('api: template refs', () => {
- it('string ref mount', () => {
- const root = nodeOps.createElement('div')
- const el = ref(null)
- const Comp = {
- setup() {
- return {
- refKey: el,
- }
- },
- render() {
- return h('div', { ref: 'refKey' })
- },
- }
- render(h(Comp), root)
- expect(el.value).toBe(root.children[0])
- })
- it('string ref update', async () => {
- const root = nodeOps.createElement('div')
- const fooEl = ref(null)
- const barEl = ref(null)
- const refKey = ref('foo')
- const Comp = {
- setup() {
- return {
- foo: fooEl,
- bar: barEl,
- }
- },
- render() {
- return h('div', { ref: refKey.value })
- },
- }
- render(h(Comp), root)
- expect(fooEl.value).toBe(root.children[0])
- expect(barEl.value).toBe(null)
- refKey.value = 'bar'
- await nextTick()
- expect(fooEl.value).toBe(null)
- expect(barEl.value).toBe(root.children[0])
- })
- it('string ref unmount', async () => {
- const root = nodeOps.createElement('div')
- const el = ref(null)
- const toggle = ref(true)
- const Comp = {
- setup() {
- return {
- refKey: el,
- }
- },
- render() {
- return toggle.value ? h('div', { ref: 'refKey' }) : null
- },
- }
- render(h(Comp), root)
- expect(el.value).toBe(root.children[0])
- toggle.value = false
- await nextTick()
- expect(el.value).toBe(null)
- })
- it('function ref mount', () => {
- const root = nodeOps.createElement('div')
- const fn = vi.fn()
- const Comp = defineComponent(() => () => h('div', { ref: fn }))
- render(h(Comp), root)
- expect(fn.mock.calls[0][0]).toBe(root.children[0])
- })
- it('function ref update', async () => {
- const root = nodeOps.createElement('div')
- const fn1 = vi.fn()
- const fn2 = vi.fn()
- const fn = ref(fn1)
- const Comp = defineComponent(() => () => h('div', { ref: fn.value }))
- render(h(Comp), root)
- expect(fn1.mock.calls).toHaveLength(1)
- expect(fn1.mock.calls[0][0]).toBe(root.children[0])
- expect(fn2.mock.calls).toHaveLength(0)
- fn.value = fn2
- await nextTick()
- expect(fn1.mock.calls).toHaveLength(1)
- expect(fn2.mock.calls).toHaveLength(1)
- expect(fn2.mock.calls[0][0]).toBe(root.children[0])
- })
- it('function ref unmount', async () => {
- const root = nodeOps.createElement('div')
- const fn = vi.fn()
- const toggle = ref(true)
- const Comp = defineComponent(
- () => () => (toggle.value ? h('div', { ref: fn }) : null),
- )
- render(h(Comp), root)
- expect(fn.mock.calls[0][0]).toBe(root.children[0])
- toggle.value = false
- await nextTick()
- expect(fn.mock.calls[1][0]).toBe(null)
- })
- it('render function ref mount', () => {
- const root = nodeOps.createElement('div')
- const el = ref(null)
- const Comp = {
- setup() {
- return () => h('div', { ref: el })
- },
- }
- render(h(Comp), root)
- expect(el.value).toBe(root.children[0])
- })
- it('render function ref update', async () => {
- const root = nodeOps.createElement('div')
- const refs = {
- foo: ref(null),
- bar: ref(null),
- }
- const refKey = ref<keyof typeof refs>('foo')
- const Comp = {
- setup() {
- return () => h('div', { ref: refs[refKey.value] })
- },
- }
- render(h(Comp), root)
- expect(refs.foo.value).toBe(root.children[0])
- expect(refs.bar.value).toBe(null)
- refKey.value = 'bar'
- await nextTick()
- expect(refs.foo.value).toBe(null)
- expect(refs.bar.value).toBe(root.children[0])
- })
- it('render function ref unmount', async () => {
- const root = nodeOps.createElement('div')
- const el = ref(null)
- const toggle = ref(true)
- const Comp = {
- setup() {
- return () => (toggle.value ? h('div', { ref: el }) : null)
- },
- }
- render(h(Comp), root)
- expect(el.value).toBe(root.children[0])
- toggle.value = false
- await nextTick()
- expect(el.value).toBe(null)
- })
- // #12639
- it('update and unmount child in the same tick', async () => {
- const root = nodeOps.createElement('div')
- const el = ref(null)
- const toggle = ref(true)
- const show = ref(true)
- const Comp = defineComponent({
- emits: ['change'],
- props: ['show'],
- setup(props, { emit }) {
- watch(
- () => props.show,
- () => {
- emit('change')
- },
- )
- return () => h('div', 'hi')
- },
- })
- const App = {
- setup() {
- return {
- refKey: el,
- }
- },
- render() {
- return toggle.value
- ? h(Comp, {
- ref: 'refKey',
- show: show.value,
- onChange: () => (toggle.value = false),
- })
- : null
- },
- }
- render(h(App), root)
- expect(el.value).not.toBe(null)
- show.value = false
- await nextTick()
- expect(el.value).toBe(null)
- })
- it('set and change ref in the same tick', async () => {
- const root = nodeOps.createElement('div')
- const show = ref(false)
- const refName = ref('a')
- const Child = defineComponent({
- setup() {
- refName.value = 'b'
- return () => {}
- },
- })
- const Comp = {
- render() {
- return h(Child, {
- ref: refName.value,
- })
- },
- updated(this: any) {
- expect(this.$refs.a).toBe(null)
- expect(this.$refs.b).not.toBe(null)
- },
- }
- const App = {
- render() {
- return show.value ? h(Comp) : null
- },
- }
- render(h(App), root)
- expect(refName.value).toBe('a')
- show.value = true
- await nextTick()
- expect(refName.value).toBe('b')
- })
- it('unset old ref when new ref is absent', async () => {
- const root1 = nodeOps.createElement('div')
- const root2 = nodeOps.createElement('div')
- const el1 = ref(null)
- const el2 = ref(null)
- const toggle = ref(true)
- const Comp1 = {
- setup() {
- return () => (toggle.value ? h('div', { ref: el1 }) : h('div'))
- },
- }
- const Comp2 = {
- setup() {
- return () => h('div', { ref: toggle.value ? el2 : undefined })
- },
- }
- render(h(Comp1), root1)
- render(h(Comp2), root2)
- expect(el1.value).toBe(root1.children[0])
- expect(el2.value).toBe(root2.children[0])
- toggle.value = false
- await nextTick()
- expect(el1.value).toBe(null)
- expect(el2.value).toBe(null)
- })
- test('string ref inside slots', async () => {
- const root = nodeOps.createElement('div')
- const spy = vi.fn()
- const Child = {
- render(this: any) {
- return this.$slots.default()
- },
- }
- const Comp = {
- render() {
- return h(Child, () => {
- return h('div', { ref: 'foo' })
- })
- },
- mounted(this: any) {
- spy(this.$refs.foo.tag)
- },
- }
- render(h(Comp), root)
- expect(spy).toHaveBeenCalledWith('div')
- })
- it('should work with direct reactive property', () => {
- const root = nodeOps.createElement('div')
- const state = reactive({
- refKey: null,
- })
- const Comp = {
- setup() {
- return state
- },
- render() {
- return h('div', { ref: 'refKey' })
- },
- }
- render(h(Comp), root)
- expect(state.refKey).toBe(root.children[0])
- expect('Template ref "refKey" used on a non-ref value').toHaveBeenWarned()
- })
- test('multiple root refs', () => {
- const root = nodeOps.createElement('div')
- const refKey1 = ref(null)
- const refKey2 = ref(null)
- const refKey3 = ref(null)
- const Comp = {
- setup() {
- return {
- refKey1,
- refKey2,
- refKey3,
- }
- },
- render() {
- return [
- h('div', { ref: 'refKey1' }),
- h('div', { ref: 'refKey2' }),
- h('div', { ref: 'refKey3' }),
- ]
- },
- }
- render(h(Comp), root)
- expect(refKey1.value).toBe(root.children[1])
- expect(refKey2.value).toBe(root.children[2])
- expect(refKey3.value).toBe(root.children[3])
- })
- // #1505
- test('reactive template ref in the same template', async () => {
- const Comp = {
- setup() {
- const el = ref()
- return { el }
- },
- render(this: any) {
- return h('div', { id: 'foo', ref: 'el' }, this.el && this.el.props.id)
- },
- }
- const root = nodeOps.createElement('div')
- render(h(Comp), root)
- // ref not ready on first render, but should queue an update immediately
- expect(serializeInner(root)).toBe(`<div id="foo"></div>`)
- await nextTick()
- // ref should be updated
- expect(serializeInner(root)).toBe(`<div id="foo">foo</div>`)
- })
- // #1834
- test('exchange refs', async () => {
- const refToggle = ref(false)
- const spy = vi.fn()
- const Comp = {
- render(this: any) {
- return [
- h('p', { ref: refToggle.value ? 'foo' : 'bar' }),
- h('i', { ref: refToggle.value ? 'bar' : 'foo' }),
- ]
- },
- mounted(this: any) {
- spy(this.$refs.foo.tag, this.$refs.bar.tag)
- },
- updated(this: any) {
- spy(this.$refs.foo.tag, this.$refs.bar.tag)
- },
- }
- const root = nodeOps.createElement('div')
- render(h(Comp), root)
- expect(spy.mock.calls[0][0]).toBe('i')
- expect(spy.mock.calls[0][1]).toBe('p')
- refToggle.value = true
- await nextTick()
- expect(spy.mock.calls[1][0]).toBe('p')
- expect(spy.mock.calls[1][1]).toBe('i')
- })
- // #1789
- test('toggle the same ref to different elements', async () => {
- const refToggle = ref(false)
- const spy = vi.fn()
- const Comp = {
- render(this: any) {
- return refToggle.value ? h('p', { ref: 'foo' }) : h('i', { ref: 'foo' })
- },
- mounted(this: any) {
- spy(this.$refs.foo.tag)
- },
- updated(this: any) {
- spy(this.$refs.foo.tag)
- },
- }
- const root = nodeOps.createElement('div')
- render(h(Comp), root)
- expect(spy.mock.calls[0][0]).toBe('i')
- refToggle.value = true
- await nextTick()
- expect(spy.mock.calls[1][0]).toBe('p')
- })
- // #2078
- test('handling multiple merged refs', async () => {
- const Foo = {
- render: () => h('div', 'foo'),
- }
- const Bar = {
- render: () => h('div', 'bar'),
- }
- const viewRef = shallowRef<any>(Foo)
- const elRef1 = ref()
- const elRef2 = ref()
- const App = {
- render() {
- if (!viewRef.value) {
- return null
- }
- const view = h(viewRef.value, { ref: elRef1 })
- return h(view, { ref: elRef2 })
- },
- }
- const root = nodeOps.createElement('div')
- render(h(App), root)
- expect(serializeInner(elRef1.value.$el)).toBe('foo')
- expect(elRef1.value).toBe(elRef2.value)
- viewRef.value = Bar
- await nextTick()
- expect(serializeInner(elRef1.value.$el)).toBe('bar')
- expect(elRef1.value).toBe(elRef2.value)
- viewRef.value = null
- await nextTick()
- expect(elRef1.value).toBeNull()
- expect(elRef1.value).toBe(elRef2.value)
- })
- // compiled output of <script setup> inline mode
- test('raw ref with ref_key', () => {
- let refs: any
- const el = ref()
- const App = {
- mounted() {
- refs = (this as any).$refs
- },
- render() {
- return h(
- 'div',
- {
- ref: el,
- ref_key: 'el',
- },
- 'hello',
- )
- },
- }
- const root = nodeOps.createElement('div')
- render(h(App), root)
- expect(serializeInner(el.value)).toBe('hello')
- expect(serializeInner(refs.el)).toBe('hello')
- })
- // compiled output of v-for + template ref
- test('ref in v-for', async () => {
- const show = ref(true)
- const list = reactive([1, 2, 3])
- const listRefs = ref([])
- const mapRefs = () => listRefs.value.map(n => serializeInner(n))
- const App = {
- render() {
- return show.value
- ? h(
- 'ul',
- list.map(i =>
- h(
- 'li',
- {
- ref: listRefs,
- ref_for: true,
- },
- i,
- ),
- ),
- )
- : null
- },
- }
- const root = nodeOps.createElement('div')
- render(h(App), root)
- expect(mapRefs()).toMatchObject(['1', '2', '3'])
- list.push(4)
- await nextTick()
- expect(mapRefs()).toMatchObject(['1', '2', '3', '4'])
- list.shift()
- await nextTick()
- expect(mapRefs()).toMatchObject(['2', '3', '4'])
- show.value = !show.value
- await nextTick()
- expect(mapRefs()).toMatchObject([])
- show.value = !show.value
- await nextTick()
- expect(mapRefs()).toMatchObject(['2', '3', '4'])
- })
- test('named ref in v-for', async () => {
- const show = ref(true)
- const list = reactive([1, 2, 3])
- const listRefs = ref([])
- const mapRefs = () => listRefs.value.map(n => serializeInner(n))
- const App = {
- setup() {
- return { listRefs }
- },
- render() {
- return show.value
- ? h(
- 'ul',
- list.map(i =>
- h(
- 'li',
- {
- ref: 'listRefs',
- ref_for: true,
- },
- i,
- ),
- ),
- )
- : null
- },
- }
- const root = nodeOps.createElement('div')
- render(h(App), root)
- expect(mapRefs()).toMatchObject(['1', '2', '3'])
- list.push(4)
- await nextTick()
- expect(mapRefs()).toMatchObject(['1', '2', '3', '4'])
- list.shift()
- await nextTick()
- expect(mapRefs()).toMatchObject(['2', '3', '4'])
- show.value = !show.value
- await nextTick()
- expect(mapRefs()).toMatchObject([])
- show.value = !show.value
- await nextTick()
- expect(mapRefs()).toMatchObject(['2', '3', '4'])
- })
- // #6697 v-for ref behaves differently under production and development
- test('named ref in v-for , should be responsive when rendering', async () => {
- const list = ref([1, 2, 3])
- const listRefs = ref([])
- const App = {
- setup() {
- return { listRefs }
- },
- render() {
- return h('div', null, [
- h('div', null, String(listRefs.value)),
- h(
- 'ul',
- list.value.map(i =>
- h(
- 'li',
- {
- ref: 'listRefs',
- ref_for: true,
- },
- i,
- ),
- ),
- ),
- ])
- },
- }
- const root = nodeOps.createElement('div')
- render(h(App), root)
- await nextTick()
- expect(String(listRefs.value)).toBe(
- '[object Object],[object Object],[object Object]',
- )
- expect(serializeInner(root)).toBe(
- '<div><div>[object Object],[object Object],[object Object]</div><ul><li>1</li><li>2</li><li>3</li></ul></div>',
- )
- list.value.splice(0, 1)
- await nextTick()
- expect(String(listRefs.value)).toBe('[object Object],[object Object]')
- expect(serializeInner(root)).toBe(
- '<div><div>[object Object],[object Object]</div><ul><li>2</li><li>3</li></ul></div>',
- )
- })
- test('with async component which nested in KeepAlive', async () => {
- const AsyncComp = defineAsyncComponent(
- () =>
- new Promise(resolve =>
- setTimeout(() =>
- resolve(
- defineComponent({
- setup(_, { expose }) {
- expose({
- name: 'AsyncComp',
- })
- return () => h('div')
- },
- }) as any,
- ),
- ),
- ),
- )
- const Comp = defineComponent({
- setup(_, { expose }) {
- expose({
- name: 'Comp',
- })
- return () => h('div')
- },
- })
- const toggle = ref(false)
- const instanceRef = ref<any>(null)
- const App = {
- render: () => {
- return h(KeepAlive, () =>
- toggle.value
- ? h(AsyncComp, { ref: instanceRef })
- : h(Comp, { ref: instanceRef }),
- )
- },
- }
- const root = nodeOps.createElement('div')
- render(h(App), root)
- expect(instanceRef.value.name).toBe('Comp')
- // switch to async component
- toggle.value = true
- await nextTick()
- expect(instanceRef.value).toBe(null)
- await new Promise(r => setTimeout(r))
- expect(instanceRef.value.name).toBe('AsyncComp')
- // switch back to normal component
- toggle.value = false
- await nextTick()
- expect(instanceRef.value.name).toBe('Comp')
- // switch to async component again
- toggle.value = true
- await nextTick()
- expect(instanceRef.value.name).toBe('AsyncComp')
- })
- })
|