rendererTemplateRef.spec.ts 17 KB

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