rendererTemplateRef.spec.ts 15 KB

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