rendererTemplateRef.spec.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  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. })
  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 = vi.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 = vi.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. // #2078
  287. test('handling multiple merged refs', async () => {
  288. const Foo = {
  289. render: () => h('div', 'foo'),
  290. }
  291. const Bar = {
  292. render: () => h('div', 'bar'),
  293. }
  294. const viewRef = shallowRef<any>(Foo)
  295. const elRef1 = ref()
  296. const elRef2 = ref()
  297. const App = {
  298. render() {
  299. if (!viewRef.value) {
  300. return null
  301. }
  302. const view = h(viewRef.value, { ref: elRef1 })
  303. return h(view, { ref: elRef2 })
  304. },
  305. }
  306. const root = nodeOps.createElement('div')
  307. render(h(App), root)
  308. expect(serializeInner(elRef1.value.$el)).toBe('foo')
  309. expect(elRef1.value).toBe(elRef2.value)
  310. viewRef.value = Bar
  311. await nextTick()
  312. expect(serializeInner(elRef1.value.$el)).toBe('bar')
  313. expect(elRef1.value).toBe(elRef2.value)
  314. viewRef.value = null
  315. await nextTick()
  316. expect(elRef1.value).toBeNull()
  317. expect(elRef1.value).toBe(elRef2.value)
  318. })
  319. // compiled output of <script setup> inline mode
  320. test('raw ref with ref_key', () => {
  321. let refs: any
  322. const el = ref()
  323. const App = {
  324. mounted() {
  325. refs = (this as any).$refs
  326. },
  327. render() {
  328. return h(
  329. 'div',
  330. {
  331. ref: el,
  332. ref_key: 'el',
  333. },
  334. 'hello',
  335. )
  336. },
  337. }
  338. const root = nodeOps.createElement('div')
  339. render(h(App), root)
  340. expect(serializeInner(el.value)).toBe('hello')
  341. expect(serializeInner(refs.el)).toBe('hello')
  342. })
  343. // compiled output of v-for + template ref
  344. test('ref in v-for', async () => {
  345. const show = ref(true)
  346. const list = reactive([1, 2, 3])
  347. const listRefs = ref([])
  348. const mapRefs = () => listRefs.value.map(n => serializeInner(n))
  349. const App = {
  350. render() {
  351. return show.value
  352. ? h(
  353. 'ul',
  354. list.map(i =>
  355. h(
  356. 'li',
  357. {
  358. ref: listRefs,
  359. ref_for: true,
  360. },
  361. i,
  362. ),
  363. ),
  364. )
  365. : null
  366. },
  367. }
  368. const root = nodeOps.createElement('div')
  369. render(h(App), root)
  370. expect(mapRefs()).toMatchObject(['1', '2', '3'])
  371. list.push(4)
  372. await nextTick()
  373. expect(mapRefs()).toMatchObject(['1', '2', '3', '4'])
  374. list.shift()
  375. await nextTick()
  376. expect(mapRefs()).toMatchObject(['2', '3', '4'])
  377. show.value = !show.value
  378. await nextTick()
  379. expect(mapRefs()).toMatchObject([])
  380. show.value = !show.value
  381. await nextTick()
  382. expect(mapRefs()).toMatchObject(['2', '3', '4'])
  383. })
  384. test('named ref in v-for', async () => {
  385. const show = ref(true)
  386. const list = reactive([1, 2, 3])
  387. const listRefs = ref([])
  388. const mapRefs = () => listRefs.value.map(n => serializeInner(n))
  389. const App = {
  390. setup() {
  391. return { listRefs }
  392. },
  393. render() {
  394. return show.value
  395. ? h(
  396. 'ul',
  397. list.map(i =>
  398. h(
  399. 'li',
  400. {
  401. ref: 'listRefs',
  402. ref_for: true,
  403. },
  404. i,
  405. ),
  406. ),
  407. )
  408. : null
  409. },
  410. }
  411. const root = nodeOps.createElement('div')
  412. render(h(App), root)
  413. expect(mapRefs()).toMatchObject(['1', '2', '3'])
  414. list.push(4)
  415. await nextTick()
  416. expect(mapRefs()).toMatchObject(['1', '2', '3', '4'])
  417. list.shift()
  418. await nextTick()
  419. expect(mapRefs()).toMatchObject(['2', '3', '4'])
  420. show.value = !show.value
  421. await nextTick()
  422. expect(mapRefs()).toMatchObject([])
  423. show.value = !show.value
  424. await nextTick()
  425. expect(mapRefs()).toMatchObject(['2', '3', '4'])
  426. })
  427. // #6697 v-for ref behaves differently under production and development
  428. test('named ref in v-for , should be responsive when rendering', async () => {
  429. const list = ref([1, 2, 3])
  430. const listRefs = ref([])
  431. const App = {
  432. setup() {
  433. return { listRefs }
  434. },
  435. render() {
  436. return h('div', null, [
  437. h('div', null, String(listRefs.value)),
  438. h(
  439. 'ul',
  440. list.value.map(i =>
  441. h(
  442. 'li',
  443. {
  444. ref: 'listRefs',
  445. ref_for: true,
  446. },
  447. i,
  448. ),
  449. ),
  450. ),
  451. ])
  452. },
  453. }
  454. const root = nodeOps.createElement('div')
  455. render(h(App), root)
  456. await nextTick()
  457. expect(String(listRefs.value)).toBe(
  458. '[object Object],[object Object],[object Object]',
  459. )
  460. expect(serializeInner(root)).toBe(
  461. '<div><div>[object Object],[object Object],[object Object]</div><ul><li>1</li><li>2</li><li>3</li></ul></div>',
  462. )
  463. list.value.splice(0, 1)
  464. await nextTick()
  465. expect(String(listRefs.value)).toBe('[object Object],[object Object]')
  466. expect(serializeInner(root)).toBe(
  467. '<div><div>[object Object],[object Object]</div><ul><li>2</li><li>3</li></ul></div>',
  468. )
  469. })
  470. })