setupTemplateRef.spec.ts 11 KB

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