rendererTemplateRef.spec.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. import {
  2. defineComponent,
  3. h,
  4. nextTick,
  5. nodeOps,
  6. reactive,
  7. ref,
  8. render,
  9. serializeInner,
  10. shallowRef,
  11. } from '@vue/runtime-test'
  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 = vi.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 = vi.fn()
  83. const fn2 = vi.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 = vi.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 = vi.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. expect('Template ref "refKey" used on a non-ref value').toHaveBeenWarned()
  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. })