import { ref, nodeOps, h, render, nextTick, defineComponent, reactive, serializeInner, shallowRef } from '@vue/runtime-test' // reference: https://vue-composition-api-rfc.netlify.com/api.html#template-refs 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 = jest.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 = jest.fn() const fn2 = jest.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 = jest.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('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) }) test('string ref inside slots', async () => { const root = nodeOps.createElement('div') const spy = jest.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]) }) 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(`
`) await nextTick() // ref should be updated expect(serializeInner(root)).toBe(`
foo
`) }) // #1834 test('exchange refs', async () => { const refToggle = ref(false) const spy = jest.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 = jest.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(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) }) })