2
0

apiTemplateRef.spec.ts 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import {
  2. ref,
  3. nodeOps,
  4. h,
  5. render,
  6. nextTick,
  7. defineComponent,
  8. reactive,
  9. serializeInner
  10. } from '@vue/runtime-test'
  11. // reference: https://vue-composition-api-rfc.netlify.com/api.html#template-refs
  12. describe('api: template refs', () => {
  13. it('string ref mount', () => {
  14. const root = nodeOps.createElement('div')
  15. const el = ref(null)
  16. const Comp = {
  17. setup() {
  18. return {
  19. refKey: el
  20. }
  21. },
  22. render() {
  23. return h('div', { ref: 'refKey' })
  24. }
  25. }
  26. render(h(Comp), root)
  27. expect(el.value).toBe(root.children[0])
  28. })
  29. it('string ref update', async () => {
  30. const root = nodeOps.createElement('div')
  31. const fooEl = ref(null)
  32. const barEl = ref(null)
  33. const refKey = ref('foo')
  34. const Comp = {
  35. setup() {
  36. return {
  37. foo: fooEl,
  38. bar: barEl
  39. }
  40. },
  41. render() {
  42. return h('div', { ref: refKey.value })
  43. }
  44. }
  45. render(h(Comp), root)
  46. expect(fooEl.value).toBe(root.children[0])
  47. expect(barEl.value).toBe(null)
  48. refKey.value = 'bar'
  49. await nextTick()
  50. expect(fooEl.value).toBe(null)
  51. expect(barEl.value).toBe(root.children[0])
  52. })
  53. it('string ref unmount', async () => {
  54. const root = nodeOps.createElement('div')
  55. const el = ref(null)
  56. const toggle = ref(true)
  57. const Comp = {
  58. setup() {
  59. return {
  60. refKey: el
  61. }
  62. },
  63. render() {
  64. return toggle.value ? h('div', { ref: 'refKey' }) : null
  65. }
  66. }
  67. render(h(Comp), root)
  68. expect(el.value).toBe(root.children[0])
  69. toggle.value = false
  70. await nextTick()
  71. expect(el.value).toBe(null)
  72. })
  73. it('function ref mount', () => {
  74. const root = nodeOps.createElement('div')
  75. const fn = jest.fn()
  76. const Comp = defineComponent(() => () => h('div', { ref: fn }))
  77. render(h(Comp), root)
  78. expect(fn.mock.calls[0][0]).toBe(root.children[0])
  79. })
  80. it('function ref update', async () => {
  81. const root = nodeOps.createElement('div')
  82. const fn1 = jest.fn()
  83. const fn2 = jest.fn()
  84. const fn = ref(fn1)
  85. const Comp = defineComponent(() => () => h('div', { ref: fn.value }))
  86. render(h(Comp), root)
  87. expect(fn1.mock.calls).toHaveLength(1)
  88. expect(fn1.mock.calls[0][0]).toBe(root.children[0])
  89. expect(fn2.mock.calls).toHaveLength(0)
  90. fn.value = fn2
  91. await nextTick()
  92. expect(fn1.mock.calls).toHaveLength(1)
  93. expect(fn2.mock.calls).toHaveLength(1)
  94. expect(fn2.mock.calls[0][0]).toBe(root.children[0])
  95. })
  96. it('function ref unmount', async () => {
  97. const root = nodeOps.createElement('div')
  98. const fn = jest.fn()
  99. const toggle = ref(true)
  100. const Comp = defineComponent(() => () =>
  101. toggle.value ? h('div', { ref: fn }) : null
  102. )
  103. render(h(Comp), root)
  104. expect(fn.mock.calls[0][0]).toBe(root.children[0])
  105. toggle.value = false
  106. await nextTick()
  107. expect(fn.mock.calls[1][0]).toBe(null)
  108. })
  109. it('render function ref mount', () => {
  110. const root = nodeOps.createElement('div')
  111. const el = ref(null)
  112. const Comp = {
  113. setup() {
  114. return () => h('div', { ref: el })
  115. }
  116. }
  117. render(h(Comp), root)
  118. expect(el.value).toBe(root.children[0])
  119. })
  120. it('render function ref update', async () => {
  121. const root = nodeOps.createElement('div')
  122. const refs = {
  123. foo: ref(null),
  124. bar: ref(null)
  125. }
  126. const refKey = ref<keyof typeof refs>('foo')
  127. const Comp = {
  128. setup() {
  129. return () => h('div', { ref: refs[refKey.value] })
  130. }
  131. }
  132. render(h(Comp), root)
  133. expect(refs.foo.value).toBe(root.children[0])
  134. expect(refs.bar.value).toBe(null)
  135. refKey.value = 'bar'
  136. await nextTick()
  137. expect(refs.foo.value).toBe(null)
  138. expect(refs.bar.value).toBe(root.children[0])
  139. })
  140. it('render function ref unmount', async () => {
  141. const root = nodeOps.createElement('div')
  142. const el = ref(null)
  143. const toggle = ref(true)
  144. const Comp = {
  145. setup() {
  146. return () => (toggle.value ? h('div', { ref: el }) : null)
  147. }
  148. }
  149. render(h(Comp), root)
  150. expect(el.value).toBe(root.children[0])
  151. toggle.value = false
  152. await nextTick()
  153. expect(el.value).toBe(null)
  154. })
  155. test('string ref inside slots', async () => {
  156. const root = nodeOps.createElement('div')
  157. const spy = jest.fn()
  158. const Child = {
  159. render(this: any) {
  160. return this.$slots.default()
  161. }
  162. }
  163. const Comp = {
  164. render() {
  165. return h(Child, () => {
  166. return h('div', { ref: 'foo' })
  167. })
  168. },
  169. mounted(this: any) {
  170. spy(this.$refs.foo.tag)
  171. }
  172. }
  173. render(h(Comp), root)
  174. expect(spy).toHaveBeenCalledWith('div')
  175. })
  176. it('should work with direct reactive property', () => {
  177. const root = nodeOps.createElement('div')
  178. const state = reactive({
  179. refKey: null
  180. })
  181. const Comp = {
  182. setup() {
  183. return state
  184. },
  185. render() {
  186. return h('div', { ref: 'refKey' })
  187. }
  188. }
  189. render(h(Comp), root)
  190. expect(state.refKey).toBe(root.children[0])
  191. })
  192. test('multiple root refs', () => {
  193. const root = nodeOps.createElement('div')
  194. const refKey1 = ref(null)
  195. const refKey2 = ref(null)
  196. const refKey3 = ref(null)
  197. const Comp = {
  198. setup() {
  199. return {
  200. refKey1,
  201. refKey2,
  202. refKey3
  203. }
  204. },
  205. render() {
  206. return [
  207. h('div', { ref: 'refKey1' }),
  208. h('div', { ref: 'refKey2' }),
  209. h('div', { ref: 'refKey3' })
  210. ]
  211. }
  212. }
  213. render(h(Comp), root)
  214. expect(refKey1.value).toBe(root.children[1])
  215. expect(refKey2.value).toBe(root.children[2])
  216. expect(refKey3.value).toBe(root.children[3])
  217. })
  218. // #1505
  219. test('reactive template ref in the same template', async () => {
  220. const Comp = {
  221. setup() {
  222. const el = ref()
  223. return { el }
  224. },
  225. render(this: any) {
  226. return h('div', { id: 'foo', ref: 'el' }, this.el && this.el.props.id)
  227. }
  228. }
  229. const root = nodeOps.createElement('div')
  230. render(h(Comp), root)
  231. // ref not ready on first render, but should queue an update immediately
  232. expect(serializeInner(root)).toBe(`<div id="foo"></div>`)
  233. await nextTick()
  234. // ref should be updated
  235. expect(serializeInner(root)).toBe(`<div id="foo">foo</div>`)
  236. })
  237. // #1834
  238. test('exchange refs', async () => {
  239. const refToggle = ref(false)
  240. const spy = jest.fn()
  241. const Comp = {
  242. render(this: any) {
  243. return [
  244. h('p', { ref: refToggle.value ? 'foo' : 'bar' }),
  245. h('i', { ref: refToggle.value ? 'bar' : 'foo' })
  246. ]
  247. },
  248. mounted(this: any) {
  249. spy(this.$refs.foo.tag, this.$refs.bar.tag)
  250. },
  251. updated(this: any) {
  252. spy(this.$refs.foo.tag, this.$refs.bar.tag)
  253. }
  254. }
  255. const root = nodeOps.createElement('div')
  256. render(h(Comp), root)
  257. expect(spy.mock.calls[0][0]).toBe('i')
  258. expect(spy.mock.calls[0][1]).toBe('p')
  259. refToggle.value = true
  260. await nextTick()
  261. expect(spy.mock.calls[1][0]).toBe('p')
  262. expect(spy.mock.calls[1][1]).toBe('i')
  263. })
  264. // #1789
  265. test('toggle the same ref to different elements', async () => {
  266. const refToggle = ref(false)
  267. const spy = jest.fn()
  268. const Comp = {
  269. render(this: any) {
  270. return refToggle.value ? h('p', { ref: 'foo' }) : h('i', { ref: 'foo' })
  271. },
  272. mounted(this: any) {
  273. spy(this.$refs.foo.tag)
  274. },
  275. updated(this: any) {
  276. spy(this.$refs.foo.tag)
  277. }
  278. }
  279. const root = nodeOps.createElement('div')
  280. render(h(Comp), root)
  281. expect(spy.mock.calls[0][0]).toBe('i')
  282. refToggle.value = true
  283. await nextTick()
  284. expect(spy.mock.calls[1][0]).toBe('p')
  285. })
  286. })