rendererTemplateRef.spec.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. import {
  2. ref,
  3. nodeOps,
  4. h,
  5. render,
  6. nextTick,
  7. defineComponent,
  8. reactive,
  9. serializeInner,
  10. shallowRef
  11. } from '@vue/runtime-test'
  12. // reference: https://vue-composition-api-rfc.netlify.com/api.html#template-refs
  13. describe('api: template refs', () => {
  14. it('string ref mount', () => {
  15. const root = nodeOps.createElement('div')
  16. const el = ref(null)
  17. const Comp = {
  18. setup() {
  19. return {
  20. refKey: el
  21. }
  22. },
  23. render() {
  24. return h('div', { ref: 'refKey' })
  25. }
  26. }
  27. render(h(Comp), root)
  28. expect(el.value).toBe(root.children[0])
  29. })
  30. it('string ref update', async () => {
  31. const root = nodeOps.createElement('div')
  32. const fooEl = ref(null)
  33. const barEl = ref(null)
  34. const refKey = ref('foo')
  35. const Comp = {
  36. setup() {
  37. return {
  38. foo: fooEl,
  39. bar: barEl
  40. }
  41. },
  42. render() {
  43. return h('div', { ref: refKey.value })
  44. }
  45. }
  46. render(h(Comp), root)
  47. expect(fooEl.value).toBe(root.children[0])
  48. expect(barEl.value).toBe(null)
  49. refKey.value = 'bar'
  50. await nextTick()
  51. expect(fooEl.value).toBe(null)
  52. expect(barEl.value).toBe(root.children[0])
  53. })
  54. it('string ref unmount', async () => {
  55. const root = nodeOps.createElement('div')
  56. const el = ref(null)
  57. const toggle = ref(true)
  58. const Comp = {
  59. setup() {
  60. return {
  61. refKey: el
  62. }
  63. },
  64. render() {
  65. return toggle.value ? h('div', { ref: 'refKey' }) : null
  66. }
  67. }
  68. render(h(Comp), root)
  69. expect(el.value).toBe(root.children[0])
  70. toggle.value = false
  71. await nextTick()
  72. expect(el.value).toBe(null)
  73. })
  74. it('function ref mount', () => {
  75. const root = nodeOps.createElement('div')
  76. const fn = vi.fn()
  77. const Comp = defineComponent(() => () => h('div', { ref: fn }))
  78. render(h(Comp), root)
  79. expect(fn.mock.calls[0][0]).toBe(root.children[0])
  80. })
  81. it('function ref update', async () => {
  82. const root = nodeOps.createElement('div')
  83. const fn1 = vi.fn()
  84. const fn2 = vi.fn()
  85. const fn = ref(fn1)
  86. const Comp = defineComponent(() => () => h('div', { ref: fn.value }))
  87. render(h(Comp), root)
  88. expect(fn1.mock.calls).toHaveLength(1)
  89. expect(fn1.mock.calls[0][0]).toBe(root.children[0])
  90. expect(fn2.mock.calls).toHaveLength(0)
  91. fn.value = fn2
  92. await nextTick()
  93. expect(fn1.mock.calls).toHaveLength(1)
  94. expect(fn2.mock.calls).toHaveLength(1)
  95. expect(fn2.mock.calls[0][0]).toBe(root.children[0])
  96. })
  97. it('function ref unmount', async () => {
  98. const root = nodeOps.createElement('div')
  99. const fn = vi.fn()
  100. const toggle = ref(true)
  101. const Comp = defineComponent(
  102. () => () => (toggle.value ? h('div', { ref: fn }) : null)
  103. )
  104. render(h(Comp), root)
  105. expect(fn.mock.calls[0][0]).toBe(root.children[0])
  106. toggle.value = false
  107. await nextTick()
  108. expect(fn.mock.calls[1][0]).toBe(null)
  109. })
  110. it('render function ref mount', () => {
  111. const root = nodeOps.createElement('div')
  112. const el = ref(null)
  113. const Comp = {
  114. setup() {
  115. return () => h('div', { ref: el })
  116. }
  117. }
  118. render(h(Comp), root)
  119. expect(el.value).toBe(root.children[0])
  120. })
  121. it('render function ref update', async () => {
  122. const root = nodeOps.createElement('div')
  123. const refs = {
  124. foo: ref(null),
  125. bar: ref(null)
  126. }
  127. const refKey = ref<keyof typeof refs>('foo')
  128. const Comp = {
  129. setup() {
  130. return () => h('div', { ref: refs[refKey.value] })
  131. }
  132. }
  133. render(h(Comp), root)
  134. expect(refs.foo.value).toBe(root.children[0])
  135. expect(refs.bar.value).toBe(null)
  136. refKey.value = 'bar'
  137. await nextTick()
  138. expect(refs.foo.value).toBe(null)
  139. expect(refs.bar.value).toBe(root.children[0])
  140. })
  141. it('render function ref unmount', async () => {
  142. const root = nodeOps.createElement('div')
  143. const el = ref(null)
  144. const toggle = ref(true)
  145. const Comp = {
  146. setup() {
  147. return () => (toggle.value ? h('div', { ref: el }) : null)
  148. }
  149. }
  150. render(h(Comp), root)
  151. expect(el.value).toBe(root.children[0])
  152. toggle.value = false
  153. await nextTick()
  154. expect(el.value).toBe(null)
  155. })
  156. test('string ref inside slots', async () => {
  157. const root = nodeOps.createElement('div')
  158. const spy = vi.fn()
  159. const Child = {
  160. render(this: any) {
  161. return this.$slots.default()
  162. }
  163. }
  164. const Comp = {
  165. render() {
  166. return h(Child, () => {
  167. return h('div', { ref: 'foo' })
  168. })
  169. },
  170. mounted(this: any) {
  171. spy(this.$refs.foo.tag)
  172. }
  173. }
  174. render(h(Comp), root)
  175. expect(spy).toHaveBeenCalledWith('div')
  176. })
  177. it('should work with direct reactive property', () => {
  178. const root = nodeOps.createElement('div')
  179. const state = reactive({
  180. refKey: null
  181. })
  182. const Comp = {
  183. setup() {
  184. return state
  185. },
  186. render() {
  187. return h('div', { ref: 'refKey' })
  188. }
  189. }
  190. render(h(Comp), root)
  191. expect(state.refKey).toBe(root.children[0])
  192. })
  193. test('multiple root refs', () => {
  194. const root = nodeOps.createElement('div')
  195. const refKey1 = ref(null)
  196. const refKey2 = ref(null)
  197. const refKey3 = ref(null)
  198. const Comp = {
  199. setup() {
  200. return {
  201. refKey1,
  202. refKey2,
  203. refKey3
  204. }
  205. },
  206. render() {
  207. return [
  208. h('div', { ref: 'refKey1' }),
  209. h('div', { ref: 'refKey2' }),
  210. h('div', { ref: 'refKey3' })
  211. ]
  212. }
  213. }
  214. render(h(Comp), root)
  215. expect(refKey1.value).toBe(root.children[1])
  216. expect(refKey2.value).toBe(root.children[2])
  217. expect(refKey3.value).toBe(root.children[3])
  218. })
  219. // #1505
  220. test('reactive template ref in the same template', async () => {
  221. const Comp = {
  222. setup() {
  223. const el = ref()
  224. return { el }
  225. },
  226. render(this: any) {
  227. return h('div', { id: 'foo', ref: 'el' }, this.el && this.el.props.id)
  228. }
  229. }
  230. const root = nodeOps.createElement('div')
  231. render(h(Comp), root)
  232. // ref not ready on first render, but should queue an update immediately
  233. expect(serializeInner(root)).toBe(`<div id="foo"></div>`)
  234. await nextTick()
  235. // ref should be updated
  236. expect(serializeInner(root)).toBe(`<div id="foo">foo</div>`)
  237. })
  238. // #1834
  239. test('exchange refs', async () => {
  240. const refToggle = ref(false)
  241. const spy = vi.fn()
  242. const Comp = {
  243. render(this: any) {
  244. return [
  245. h('p', { ref: refToggle.value ? 'foo' : 'bar' }),
  246. h('i', { ref: refToggle.value ? 'bar' : 'foo' })
  247. ]
  248. },
  249. mounted(this: any) {
  250. spy(this.$refs.foo.tag, this.$refs.bar.tag)
  251. },
  252. updated(this: any) {
  253. spy(this.$refs.foo.tag, this.$refs.bar.tag)
  254. }
  255. }
  256. const root = nodeOps.createElement('div')
  257. render(h(Comp), root)
  258. expect(spy.mock.calls[0][0]).toBe('i')
  259. expect(spy.mock.calls[0][1]).toBe('p')
  260. refToggle.value = true
  261. await nextTick()
  262. expect(spy.mock.calls[1][0]).toBe('p')
  263. expect(spy.mock.calls[1][1]).toBe('i')
  264. })
  265. // #1789
  266. test('toggle the same ref to different elements', async () => {
  267. const refToggle = ref(false)
  268. const spy = vi.fn()
  269. const Comp = {
  270. render(this: any) {
  271. return refToggle.value ? h('p', { ref: 'foo' }) : h('i', { ref: 'foo' })
  272. },
  273. mounted(this: any) {
  274. spy(this.$refs.foo.tag)
  275. },
  276. updated(this: any) {
  277. spy(this.$refs.foo.tag)
  278. }
  279. }
  280. const root = nodeOps.createElement('div')
  281. render(h(Comp), root)
  282. expect(spy.mock.calls[0][0]).toBe('i')
  283. refToggle.value = true
  284. await nextTick()
  285. expect(spy.mock.calls[1][0]).toBe('p')
  286. })
  287. // #2078
  288. test('handling multiple merged refs', async () => {
  289. const Foo = {
  290. render: () => h('div', 'foo')
  291. }
  292. const Bar = {
  293. render: () => h('div', 'bar')
  294. }
  295. const viewRef = shallowRef<any>(Foo)
  296. const elRef1 = ref()
  297. const elRef2 = ref()
  298. const App = {
  299. render() {
  300. if (!viewRef.value) {
  301. return null
  302. }
  303. const view = h(viewRef.value, { ref: elRef1 })
  304. return h(view, { ref: elRef2 })
  305. }
  306. }
  307. const root = nodeOps.createElement('div')
  308. render(h(App), root)
  309. expect(serializeInner(elRef1.value.$el)).toBe('foo')
  310. expect(elRef1.value).toBe(elRef2.value)
  311. viewRef.value = Bar
  312. await nextTick()
  313. expect(serializeInner(elRef1.value.$el)).toBe('bar')
  314. expect(elRef1.value).toBe(elRef2.value)
  315. viewRef.value = null
  316. await nextTick()
  317. expect(elRef1.value).toBeNull()
  318. expect(elRef1.value).toBe(elRef2.value)
  319. })
  320. // compiled output of <script setup> inline mode
  321. test('raw ref with ref_key', () => {
  322. let refs: any
  323. const el = ref()
  324. const App = {
  325. mounted() {
  326. refs = (this as any).$refs
  327. },
  328. render() {
  329. return h(
  330. 'div',
  331. {
  332. ref: el,
  333. ref_key: 'el'
  334. },
  335. 'hello'
  336. )
  337. }
  338. }
  339. const root = nodeOps.createElement('div')
  340. render(h(App), root)
  341. expect(serializeInner(el.value)).toBe('hello')
  342. expect(serializeInner(refs.el)).toBe('hello')
  343. })
  344. // compiled output of v-for + template ref
  345. test('ref in v-for', async () => {
  346. const show = ref(true)
  347. const list = reactive([1, 2, 3])
  348. const listRefs = ref([])
  349. const mapRefs = () => listRefs.value.map(n => serializeInner(n))
  350. const App = {
  351. render() {
  352. return show.value
  353. ? h(
  354. 'ul',
  355. list.map(i =>
  356. h(
  357. 'li',
  358. {
  359. ref: listRefs,
  360. ref_for: true
  361. },
  362. i
  363. )
  364. )
  365. )
  366. : null
  367. }
  368. }
  369. const root = nodeOps.createElement('div')
  370. render(h(App), root)
  371. expect(mapRefs()).toMatchObject(['1', '2', '3'])
  372. list.push(4)
  373. await nextTick()
  374. expect(mapRefs()).toMatchObject(['1', '2', '3', '4'])
  375. list.shift()
  376. await nextTick()
  377. expect(mapRefs()).toMatchObject(['2', '3', '4'])
  378. show.value = !show.value
  379. await nextTick()
  380. expect(mapRefs()).toMatchObject([])
  381. show.value = !show.value
  382. await nextTick()
  383. expect(mapRefs()).toMatchObject(['2', '3', '4'])
  384. })
  385. test('named ref in v-for', async () => {
  386. const show = ref(true)
  387. const list = reactive([1, 2, 3])
  388. const listRefs = ref([])
  389. const mapRefs = () => listRefs.value.map(n => serializeInner(n))
  390. const App = {
  391. setup() {
  392. return { listRefs }
  393. },
  394. render() {
  395. return show.value
  396. ? h(
  397. 'ul',
  398. list.map(i =>
  399. h(
  400. 'li',
  401. {
  402. ref: 'listRefs',
  403. ref_for: true
  404. },
  405. i
  406. )
  407. )
  408. )
  409. : null
  410. }
  411. }
  412. const root = nodeOps.createElement('div')
  413. render(h(App), root)
  414. expect(mapRefs()).toMatchObject(['1', '2', '3'])
  415. list.push(4)
  416. await nextTick()
  417. expect(mapRefs()).toMatchObject(['1', '2', '3', '4'])
  418. list.shift()
  419. await nextTick()
  420. expect(mapRefs()).toMatchObject(['2', '3', '4'])
  421. show.value = !show.value
  422. await nextTick()
  423. expect(mapRefs()).toMatchObject([])
  424. show.value = !show.value
  425. await nextTick()
  426. expect(mapRefs()).toMatchObject(['2', '3', '4'])
  427. })
  428. // #6697 v-for ref behaves differently under production and development
  429. test('named ref in v-for , should be responsive when rendering', async () => {
  430. const list = ref([1, 2, 3])
  431. const listRefs = ref([])
  432. const App = {
  433. setup() {
  434. return { listRefs }
  435. },
  436. render() {
  437. return h('div', null, [
  438. h('div', null, String(listRefs.value)),
  439. h(
  440. 'ul',
  441. list.value.map(i =>
  442. h(
  443. 'li',
  444. {
  445. ref: 'listRefs',
  446. ref_for: true
  447. },
  448. i
  449. )
  450. )
  451. )
  452. ])
  453. }
  454. }
  455. const root = nodeOps.createElement('div')
  456. render(h(App), root)
  457. await nextTick()
  458. expect(String(listRefs.value)).toBe(
  459. '[object Object],[object Object],[object Object]'
  460. )
  461. expect(serializeInner(root)).toBe(
  462. '<div><div>[object Object],[object Object],[object Object]</div><ul><li>1</li><li>2</li><li>3</li></ul></div>'
  463. )
  464. list.value.splice(0, 1)
  465. await nextTick()
  466. expect(String(listRefs.value)).toBe('[object Object],[object Object]')
  467. expect(serializeInner(root)).toBe(
  468. '<div><div>[object Object],[object Object]</div><ul><li>2</li><li>3</li></ul></div>'
  469. )
  470. })
  471. })