transition.spec.js 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127
  1. import Vue from 'vue'
  2. import injectStyles from './inject-styles'
  3. import { isIE9 } from 'core/util/env'
  4. import { nextFrame } from 'web/runtime/transition-util'
  5. if (!isIE9) {
  6. describe('Transition basic', () => {
  7. const { duration, buffer } = injectStyles()
  8. const explicitDuration = duration * 2
  9. let el
  10. beforeEach(() => {
  11. el = document.createElement('div')
  12. document.body.appendChild(el)
  13. })
  14. it('basic transition', done => {
  15. const vm = new Vue({
  16. template: '<div><transition><div v-if="ok" class="test">foo</div></transition></div>',
  17. data: { ok: true }
  18. }).$mount(el)
  19. // should not apply transition on initial render by default
  20. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  21. vm.ok = false
  22. waitForUpdate(() => {
  23. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  24. }).thenWaitFor(nextFrame).then(() => {
  25. expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
  26. }).thenWaitFor(duration + buffer).then(() => {
  27. expect(vm.$el.children.length).toBe(0)
  28. vm.ok = true
  29. }).then(() => {
  30. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  31. }).thenWaitFor(nextFrame).then(() => {
  32. expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
  33. }).thenWaitFor(duration + buffer).then(() => {
  34. expect(vm.$el.children[0].className).toBe('test')
  35. }).then(done)
  36. })
  37. it('named transition', done => {
  38. const vm = new Vue({
  39. template: '<div><transition name="test"><div v-if="ok" class="test">foo</div></transition></div>',
  40. data: { ok: true }
  41. }).$mount(el)
  42. // should not apply transition on initial render by default
  43. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  44. vm.ok = false
  45. waitForUpdate(() => {
  46. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  47. }).thenWaitFor(nextFrame).then(() => {
  48. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  49. }).thenWaitFor(duration + buffer).then(() => {
  50. expect(vm.$el.children.length).toBe(0)
  51. vm.ok = true
  52. }).then(() => {
  53. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  54. }).thenWaitFor(nextFrame).then(() => {
  55. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  56. }).thenWaitFor(duration + buffer).then(() => {
  57. expect(vm.$el.children[0].className).toBe('test')
  58. }).then(done)
  59. })
  60. it('custom transition classes', done => {
  61. const vm = new Vue({
  62. template: `
  63. <div>
  64. <transition
  65. enter-class="hello"
  66. enter-active-class="hello-active"
  67. enter-to-class="hello-to"
  68. leave-class="bye"
  69. leave-to-class="bye-to"
  70. leave-active-class="byebye active more ">
  71. <div v-if="ok" class="test">foo</div>
  72. </transition>
  73. </div>
  74. `,
  75. data: { ok: true }
  76. }).$mount(el)
  77. // should not apply transition on initial render by default
  78. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  79. vm.ok = false
  80. waitForUpdate(() => {
  81. expect(vm.$el.children[0].className).toBe('test bye byebye active more')
  82. }).thenWaitFor(nextFrame).then(() => {
  83. expect(vm.$el.children[0].className).toBe('test byebye active more bye-to')
  84. }).thenWaitFor(duration + buffer).then(() => {
  85. expect(vm.$el.children.length).toBe(0)
  86. vm.ok = true
  87. }).then(() => {
  88. expect(vm.$el.children[0].className).toBe('test hello hello-active')
  89. }).thenWaitFor(nextFrame).then(() => {
  90. expect(vm.$el.children[0].className).toBe('test hello-active hello-to')
  91. }).thenWaitFor(duration + buffer).then(() => {
  92. expect(vm.$el.children[0].className).toBe('test')
  93. }).then(done)
  94. })
  95. it('dynamic transition', done => {
  96. const vm = new Vue({
  97. template: `
  98. <div>
  99. <transition :name="trans">
  100. <div v-if="ok" class="test">foo</div>
  101. </transition>
  102. </div>
  103. `,
  104. data: {
  105. ok: true,
  106. trans: 'test'
  107. }
  108. }).$mount(el)
  109. // should not apply transition on initial render by default
  110. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  111. vm.ok = false
  112. waitForUpdate(() => {
  113. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  114. }).thenWaitFor(nextFrame).then(() => {
  115. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  116. }).thenWaitFor(duration + buffer).then(() => {
  117. expect(vm.$el.children.length).toBe(0)
  118. vm.ok = true
  119. vm.trans = 'changed'
  120. }).then(() => {
  121. expect(vm.$el.children[0].className).toBe('test changed-enter changed-enter-active')
  122. }).thenWaitFor(nextFrame).then(() => {
  123. expect(vm.$el.children[0].className).toBe('test changed-enter-active changed-enter-to')
  124. }).thenWaitFor(duration + buffer).then(() => {
  125. expect(vm.$el.children[0].className).toBe('test')
  126. }).then(done)
  127. })
  128. it('inline transition object', done => {
  129. const enter = jasmine.createSpy('enter')
  130. const leave = jasmine.createSpy('leave')
  131. const vm = new Vue({
  132. render (h) {
  133. return h('div', null, [
  134. h('transition', {
  135. props: {
  136. name: 'inline',
  137. enterClass: 'hello',
  138. enterToClass: 'hello-to',
  139. enterActiveClass: 'hello-active',
  140. leaveClass: 'bye',
  141. leaveToClass: 'bye-to',
  142. leaveActiveClass: 'byebye active'
  143. },
  144. on: {
  145. enter,
  146. leave
  147. }
  148. }, this.ok ? [h('div', { class: 'test' }, 'foo')] : undefined)
  149. ])
  150. },
  151. data: { ok: true }
  152. }).$mount(el)
  153. // should not apply transition on initial render by default
  154. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  155. vm.ok = false
  156. waitForUpdate(() => {
  157. expect(vm.$el.children[0].className).toBe('test bye byebye active')
  158. expect(leave).toHaveBeenCalled()
  159. }).thenWaitFor(nextFrame).then(() => {
  160. expect(vm.$el.children[0].className).toBe('test byebye active bye-to')
  161. }).thenWaitFor(duration + buffer).then(() => {
  162. expect(vm.$el.children.length).toBe(0)
  163. vm.ok = true
  164. }).then(() => {
  165. expect(vm.$el.children[0].className).toBe('test hello hello-active')
  166. expect(enter).toHaveBeenCalled()
  167. }).thenWaitFor(nextFrame).then(() => {
  168. expect(vm.$el.children[0].className).toBe('test hello-active hello-to')
  169. }).thenWaitFor(duration + buffer).then(() => {
  170. expect(vm.$el.children[0].className).toBe('test')
  171. }).then(done)
  172. })
  173. it('transition events', done => {
  174. const onLeaveSpy = jasmine.createSpy('leave')
  175. const onEnterSpy = jasmine.createSpy('enter')
  176. const beforeLeaveSpy = jasmine.createSpy('beforeLeave')
  177. const beforeEnterSpy = jasmine.createSpy('beforeEnter')
  178. const afterLeaveSpy = jasmine.createSpy('afterLeave')
  179. const afterEnterSpy = jasmine.createSpy('afterEnter')
  180. const vm = new Vue({
  181. template: `
  182. <div>
  183. <transition
  184. name="test"
  185. @before-enter="beforeEnter"
  186. @enter="enter"
  187. @after-enter="afterEnter"
  188. @before-leave="beforeLeave"
  189. @leave="leave"
  190. @after-leave="afterLeave">
  191. <div v-if="ok" class="test">foo</div>
  192. </transition>
  193. </div>
  194. `,
  195. data: { ok: true },
  196. methods: {
  197. beforeLeave: (el) => {
  198. expect(el).toBe(vm.$el.children[0])
  199. expect(el.className).toBe('test')
  200. beforeLeaveSpy(el)
  201. },
  202. leave: (el) => onLeaveSpy(el),
  203. afterLeave: (el) => afterLeaveSpy(el),
  204. beforeEnter: (el) => {
  205. expect(vm.$el.contains(el)).toBe(false)
  206. expect(el.className).toBe('test')
  207. beforeEnterSpy(el)
  208. },
  209. enter: (el) => {
  210. expect(vm.$el.contains(el)).toBe(true)
  211. onEnterSpy(el)
  212. },
  213. afterEnter: (el) => afterEnterSpy(el)
  214. }
  215. }).$mount(el)
  216. // should not apply transition on initial render by default
  217. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  218. let _el = vm.$el.children[0]
  219. vm.ok = false
  220. waitForUpdate(() => {
  221. expect(beforeLeaveSpy).toHaveBeenCalledWith(_el)
  222. expect(onLeaveSpy).toHaveBeenCalledWith(_el)
  223. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  224. }).thenWaitFor(nextFrame).then(() => {
  225. expect(afterLeaveSpy).not.toHaveBeenCalled()
  226. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  227. }).thenWaitFor(duration + buffer).then(() => {
  228. expect(afterLeaveSpy).toHaveBeenCalledWith(_el)
  229. expect(vm.$el.children.length).toBe(0)
  230. vm.ok = true
  231. }).then(() => {
  232. _el = vm.$el.children[0]
  233. expect(beforeEnterSpy).toHaveBeenCalledWith(_el)
  234. expect(onEnterSpy).toHaveBeenCalledWith(_el)
  235. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  236. }).thenWaitFor(nextFrame).then(() => {
  237. expect(afterEnterSpy).not.toHaveBeenCalled()
  238. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  239. }).thenWaitFor(duration + buffer).then(() => {
  240. expect(afterEnterSpy).toHaveBeenCalledWith(_el)
  241. expect(vm.$el.children[0].className).toBe('test')
  242. }).then(done)
  243. })
  244. it('transition events (v-show)', done => {
  245. const onLeaveSpy = jasmine.createSpy('leave')
  246. const onEnterSpy = jasmine.createSpy('enter')
  247. const beforeLeaveSpy = jasmine.createSpy('beforeLeave')
  248. const beforeEnterSpy = jasmine.createSpy('beforeEnter')
  249. const afterLeaveSpy = jasmine.createSpy('afterLeave')
  250. const afterEnterSpy = jasmine.createSpy('afterEnter')
  251. const vm = new Vue({
  252. template: `
  253. <div>
  254. <transition
  255. name="test"
  256. @before-enter="beforeEnter"
  257. @enter="enter"
  258. @after-enter="afterEnter"
  259. @before-leave="beforeLeave"
  260. @leave="leave"
  261. @after-leave="afterLeave">
  262. <div v-show="ok" class="test">foo</div>
  263. </transition>
  264. </div>
  265. `,
  266. data: { ok: true },
  267. methods: {
  268. beforeLeave: (el) => {
  269. expect(el.style.display).toBe('')
  270. expect(el).toBe(vm.$el.children[0])
  271. expect(el.className).toBe('test')
  272. beforeLeaveSpy(el)
  273. },
  274. leave: (el) => {
  275. expect(el.style.display).toBe('')
  276. onLeaveSpy(el)
  277. },
  278. afterLeave: (el) => {
  279. expect(el.style.display).toBe('none')
  280. afterLeaveSpy(el)
  281. },
  282. beforeEnter: (el) => {
  283. expect(el.className).toBe('test')
  284. expect(el.style.display).toBe('none')
  285. beforeEnterSpy(el)
  286. },
  287. enter: (el) => {
  288. expect(el.style.display).toBe('')
  289. onEnterSpy(el)
  290. },
  291. afterEnter: (el) => {
  292. expect(el.style.display).toBe('')
  293. afterEnterSpy(el)
  294. }
  295. }
  296. }).$mount(el)
  297. // should not apply transition on initial render by default
  298. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  299. let _el = vm.$el.children[0]
  300. vm.ok = false
  301. waitForUpdate(() => {
  302. expect(beforeLeaveSpy).toHaveBeenCalledWith(_el)
  303. expect(onLeaveSpy).toHaveBeenCalledWith(_el)
  304. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  305. }).thenWaitFor(nextFrame).then(() => {
  306. expect(afterLeaveSpy).not.toHaveBeenCalled()
  307. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  308. }).thenWaitFor(duration + buffer).then(() => {
  309. expect(afterLeaveSpy).toHaveBeenCalledWith(_el)
  310. expect(vm.$el.children[0].style.display).toBe('none')
  311. vm.ok = true
  312. }).then(() => {
  313. _el = vm.$el.children[0]
  314. expect(beforeEnterSpy).toHaveBeenCalledWith(_el)
  315. expect(onEnterSpy).toHaveBeenCalledWith(_el)
  316. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  317. }).thenWaitFor(nextFrame).then(() => {
  318. expect(afterEnterSpy).not.toHaveBeenCalled()
  319. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  320. }).thenWaitFor(duration + buffer).then(() => {
  321. expect(afterEnterSpy).toHaveBeenCalledWith(_el)
  322. expect(vm.$el.children[0].className).toBe('test')
  323. }).then(done)
  324. })
  325. it('explicit user callback in JavaScript hooks', done => {
  326. let next
  327. const vm = new Vue({
  328. template: `<div>
  329. <transition name="test" @enter="enter" @leave="leave">
  330. <div v-if="ok" class="test">foo</div>
  331. </transition>
  332. </div>`,
  333. data: { ok: true },
  334. methods: {
  335. enter: (el, cb) => {
  336. next = cb
  337. },
  338. leave: (el, cb) => {
  339. next = cb
  340. }
  341. }
  342. }).$mount(el)
  343. vm.ok = false
  344. waitForUpdate(() => {
  345. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  346. }).thenWaitFor(nextFrame).then(() => {
  347. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  348. }).thenWaitFor(duration + buffer).then(() => {
  349. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  350. expect(next).toBeTruthy()
  351. next()
  352. expect(vm.$el.children.length).toBe(0)
  353. }).then(() => {
  354. vm.ok = true
  355. }).then(() => {
  356. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  357. }).thenWaitFor(nextFrame).then(() => {
  358. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  359. }).thenWaitFor(duration + buffer).then(() => {
  360. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  361. expect(next).toBeTruthy()
  362. next()
  363. expect(vm.$el.children[0].className).toBe('test')
  364. }).then(done)
  365. })
  366. it('css: false', done => {
  367. const enterSpy = jasmine.createSpy('enter')
  368. const leaveSpy = jasmine.createSpy('leave')
  369. const vm = new Vue({
  370. template: `
  371. <div>
  372. <transition :css="false" name="test" @enter="enter" @leave="leave">
  373. <div v-if="ok" class="test">foo</div>
  374. </transition>
  375. </div>
  376. `,
  377. data: { ok: true },
  378. methods: {
  379. enter: enterSpy,
  380. leave: leaveSpy
  381. }
  382. }).$mount(el)
  383. vm.ok = false
  384. waitForUpdate(() => {
  385. expect(leaveSpy).toHaveBeenCalled()
  386. expect(vm.$el.innerHTML).toBe('<!---->')
  387. vm.ok = true
  388. }).then(() => {
  389. expect(enterSpy).toHaveBeenCalled()
  390. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  391. }).then(done)
  392. })
  393. it('no transition detected', done => {
  394. const enterSpy = jasmine.createSpy('enter')
  395. const leaveSpy = jasmine.createSpy('leave')
  396. const vm = new Vue({
  397. template: '<div><transition name="nope" @enter="enter" @leave="leave"><div v-if="ok">foo</div></transition></div>',
  398. data: { ok: true },
  399. methods: {
  400. enter: enterSpy,
  401. leave: leaveSpy
  402. }
  403. }).$mount(el)
  404. vm.ok = false
  405. waitForUpdate(() => {
  406. expect(leaveSpy).toHaveBeenCalled()
  407. expect(vm.$el.innerHTML).toBe('<div class="nope-leave nope-leave-active">foo</div><!---->')
  408. }).thenWaitFor(nextFrame).then(() => {
  409. expect(vm.$el.innerHTML).toBe('<!---->')
  410. vm.ok = true
  411. }).then(() => {
  412. expect(enterSpy).toHaveBeenCalled()
  413. expect(vm.$el.innerHTML).toBe('<div class="nope-enter nope-enter-active">foo</div>')
  414. }).thenWaitFor(nextFrame).then(() => {
  415. expect(vm.$el.innerHTML).toBe('<div>foo</div>')
  416. }).then(done)
  417. })
  418. it('enterCancelled', done => {
  419. const spy = jasmine.createSpy('enterCancelled')
  420. const vm = new Vue({
  421. template: `
  422. <div>
  423. <transition name="test" @enter-cancelled="enterCancelled">
  424. <div v-if="ok" class="test">foo</div>
  425. </transition>
  426. </div>
  427. `,
  428. data: { ok: false },
  429. methods: {
  430. enterCancelled: spy
  431. }
  432. }).$mount(el)
  433. expect(vm.$el.innerHTML).toBe('<!---->')
  434. vm.ok = true
  435. waitForUpdate(() => {
  436. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  437. }).thenWaitFor(nextFrame).then(() => {
  438. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  439. }).thenWaitFor(duration / 2).then(() => {
  440. vm.ok = false
  441. }).then(() => {
  442. expect(spy).toHaveBeenCalled()
  443. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  444. }).thenWaitFor(nextFrame).then(() => {
  445. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  446. }).thenWaitFor(duration + buffer).then(() => {
  447. expect(vm.$el.children.length).toBe(0)
  448. }).then(done)
  449. })
  450. it('should remove stale leaving elements', done => {
  451. const spy = jasmine.createSpy('afterLeave')
  452. const vm = new Vue({
  453. template: `
  454. <div>
  455. <transition name="test" @after-leave="afterLeave">
  456. <div v-if="ok" class="test">foo</div>
  457. </transition>
  458. </div>
  459. `,
  460. data: { ok: true },
  461. methods: {
  462. afterLeave: spy
  463. }
  464. }).$mount(el)
  465. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  466. vm.ok = false
  467. waitForUpdate(() => {
  468. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  469. }).thenWaitFor(duration / 2).then(() => {
  470. vm.ok = true
  471. }).then(() => {
  472. expect(spy).toHaveBeenCalled()
  473. expect(vm.$el.children.length).toBe(1) // should have removed leaving element
  474. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  475. }).thenWaitFor(nextFrame).then(() => {
  476. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  477. }).thenWaitFor(duration + buffer).then(() => {
  478. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  479. }).then(done)
  480. })
  481. it('transition with v-show', done => {
  482. const vm = new Vue({
  483. template: `
  484. <div>
  485. <transition name="test">
  486. <div v-show="ok" class="test">foo</div>
  487. </transition>
  488. </div>
  489. `,
  490. data: { ok: true }
  491. }).$mount(el)
  492. // should not apply transition on initial render by default
  493. expect(vm.$el.textContent).toBe('foo')
  494. expect(vm.$el.children[0].style.display).toBe('')
  495. expect(vm.$el.children[0].className).toBe('test')
  496. vm.ok = false
  497. waitForUpdate(() => {
  498. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  499. }).thenWaitFor(nextFrame).then(() => {
  500. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  501. }).thenWaitFor(duration + buffer).then(() => {
  502. expect(vm.$el.children[0].style.display).toBe('none')
  503. vm.ok = true
  504. }).then(() => {
  505. expect(vm.$el.children[0].style.display).toBe('')
  506. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  507. }).thenWaitFor(nextFrame).then(() => {
  508. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  509. }).thenWaitFor(duration + buffer).then(() => {
  510. expect(vm.$el.children[0].className).toBe('test')
  511. }).then(done)
  512. })
  513. it('transition with v-show, inside child component', done => {
  514. const vm = new Vue({
  515. template: `
  516. <div>
  517. <test v-show="ok"></test>
  518. </div>
  519. `,
  520. data: { ok: true },
  521. components: {
  522. test: {
  523. template: `<transition name="test"><div class="test">foo</div></transition>`
  524. }
  525. }
  526. }).$mount(el)
  527. // should not apply transition on initial render by default
  528. expect(vm.$el.textContent).toBe('foo')
  529. expect(vm.$el.children[0].style.display).toBe('')
  530. vm.ok = false
  531. waitForUpdate(() => {
  532. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  533. }).thenWaitFor(nextFrame).then(() => {
  534. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  535. }).thenWaitFor(duration + buffer).then(() => {
  536. expect(vm.$el.children[0].style.display).toBe('none')
  537. vm.ok = true
  538. }).then(() => {
  539. expect(vm.$el.children[0].style.display).toBe('')
  540. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  541. }).thenWaitFor(nextFrame).then(() => {
  542. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  543. }).thenWaitFor(duration + buffer).then(() => {
  544. expect(vm.$el.children[0].className).toBe('test')
  545. }).then(done)
  546. })
  547. it('leaveCancelled (v-show only)', done => {
  548. const spy = jasmine.createSpy('leaveCancelled')
  549. const vm = new Vue({
  550. template: `
  551. <div>
  552. <transition name="test" @leave-cancelled="leaveCancelled">
  553. <div v-show="ok" class="test">foo</div>
  554. </transition>
  555. </div>
  556. `,
  557. data: { ok: true },
  558. methods: {
  559. leaveCancelled: spy
  560. }
  561. }).$mount(el)
  562. expect(vm.$el.children[0].style.display).toBe('')
  563. vm.ok = false
  564. waitForUpdate(() => {
  565. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  566. }).thenWaitFor(nextFrame).then(() => {
  567. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  568. }).thenWaitFor(10).then(() => {
  569. vm.ok = true
  570. }).then(() => {
  571. expect(spy).toHaveBeenCalled()
  572. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  573. }).thenWaitFor(nextFrame).then(() => {
  574. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  575. }).thenWaitFor(duration + buffer).then(() => {
  576. expect(vm.$el.children[0].style.display).toBe('')
  577. }).then(done)
  578. })
  579. it('animations', done => {
  580. const vm = new Vue({
  581. template: `
  582. <div>
  583. <transition name="test-anim">
  584. <div v-if="ok">foo</div>
  585. </transition>
  586. </div>
  587. `,
  588. data: { ok: true }
  589. }).$mount(el)
  590. // should not apply transition on initial render by default
  591. expect(vm.$el.innerHTML).toBe('<div>foo</div>')
  592. vm.ok = false
  593. waitForUpdate(() => {
  594. expect(vm.$el.children[0].className).toBe('test-anim-leave test-anim-leave-active')
  595. }).thenWaitFor(nextFrame).then(() => {
  596. expect(vm.$el.children[0].className).toBe('test-anim-leave-active test-anim-leave-to')
  597. }).thenWaitFor(duration + buffer).then(() => {
  598. expect(vm.$el.children.length).toBe(0)
  599. vm.ok = true
  600. }).then(() => {
  601. expect(vm.$el.children[0].className).toBe('test-anim-enter test-anim-enter-active')
  602. }).thenWaitFor(nextFrame).then(() => {
  603. expect(vm.$el.children[0].className).toBe('test-anim-enter-active test-anim-enter-to')
  604. }).thenWaitFor(duration + buffer).then(() => {
  605. expect(vm.$el.children[0].className).toBe('')
  606. }).then(done)
  607. })
  608. it('explicit transition type', done => {
  609. const vm = new Vue({
  610. template: `
  611. <div>
  612. <transition name="test-anim-long" type="animation">
  613. <div v-if="ok" class="test">foo</div>
  614. </transition>
  615. </div>
  616. `,
  617. data: { ok: true }
  618. }).$mount(el)
  619. // should not apply transition on initial render by default
  620. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  621. vm.ok = false
  622. waitForUpdate(() => {
  623. expect(vm.$el.children[0].className).toBe('test test-anim-long-leave test-anim-long-leave-active')
  624. }).thenWaitFor(nextFrame).then(() => {
  625. expect(vm.$el.children[0].className).toBe('test test-anim-long-leave-active test-anim-long-leave-to')
  626. }).thenWaitFor(duration + 5).then(() => {
  627. // should not end early due to transition presence
  628. expect(vm.$el.children[0].className).toBe('test test-anim-long-leave-active test-anim-long-leave-to')
  629. }).thenWaitFor(duration + 5).then(() => {
  630. expect(vm.$el.children.length).toBe(0)
  631. vm.ok = true
  632. }).then(() => {
  633. expect(vm.$el.children[0].className).toBe('test test-anim-long-enter test-anim-long-enter-active')
  634. }).thenWaitFor(nextFrame).then(() => {
  635. expect(vm.$el.children[0].className).toBe('test test-anim-long-enter-active test-anim-long-enter-to')
  636. }).thenWaitFor(duration + 5).then(() => {
  637. expect(vm.$el.children[0].className).toBe('test test-anim-long-enter-active test-anim-long-enter-to')
  638. }).thenWaitFor(duration + 5).then(() => {
  639. expect(vm.$el.children[0].className).toBe('test')
  640. }).then(done)
  641. })
  642. it('transition on appear', done => {
  643. const vm = new Vue({
  644. template: `
  645. <div>
  646. <transition name="test"
  647. appear
  648. appear-class="test-appear"
  649. appear-to-class="test-appear-to"
  650. appear-active-class="test-appear-active">
  651. <div v-if="ok" class="test">foo</div>
  652. </transition>
  653. </div>
  654. `,
  655. data: { ok: true }
  656. }).$mount(el)
  657. waitForUpdate(() => {
  658. expect(vm.$el.children[0].className).toBe('test test-appear test-appear-active')
  659. }).thenWaitFor(nextFrame).then(() => {
  660. expect(vm.$el.children[0].className).toBe('test test-appear-active test-appear-to')
  661. }).thenWaitFor(duration + buffer).then(() => {
  662. expect(vm.$el.children[0].className).toBe('test')
  663. }).then(done)
  664. })
  665. it('transition on appear with v-show', done => {
  666. const vm = new Vue({
  667. template: `
  668. <div>
  669. <transition name="test" appear>
  670. <div v-show="ok" class="test">foo</div>
  671. </transition>
  672. </div>
  673. `,
  674. data: { ok: true }
  675. }).$mount(el)
  676. waitForUpdate(() => {
  677. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  678. }).thenWaitFor(nextFrame).then(() => {
  679. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  680. }).thenWaitFor(duration + buffer).then(() => {
  681. expect(vm.$el.children[0].className).toBe('test')
  682. }).then(done)
  683. })
  684. it('transition on SVG elements', done => {
  685. const vm = new Vue({
  686. template: `
  687. <svg>
  688. <transition>
  689. <circle cx="0" cy="0" r="10" v-if="ok" class="test"></circle>
  690. </transition>
  691. </svg>
  692. `,
  693. data: { ok: true }
  694. }).$mount(el)
  695. // should not apply transition on initial render by default
  696. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test')
  697. vm.ok = false
  698. waitForUpdate(() => {
  699. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-leave v-leave-active')
  700. }).thenWaitFor(nextFrame).then(() => {
  701. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-leave-active v-leave-to')
  702. }).thenWaitFor(duration + buffer).then(() => {
  703. expect(vm.$el.childNodes.length).toBe(1)
  704. expect(vm.$el.childNodes[0].nodeType).toBe(8) // should be an empty comment node
  705. expect(vm.$el.childNodes[0].textContent).toBe('')
  706. vm.ok = true
  707. }).then(() => {
  708. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-enter v-enter-active')
  709. }).thenWaitFor(nextFrame).then(() => {
  710. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-enter-active v-enter-to')
  711. }).thenWaitFor(duration + buffer).then(() => {
  712. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test')
  713. }).then(done)
  714. })
  715. it('transition on child components', done => {
  716. const vm = new Vue({
  717. template: `
  718. <div>
  719. <transition>
  720. <test v-if="ok" class="test"></test>
  721. </transition>
  722. </div>
  723. `,
  724. data: { ok: true },
  725. components: {
  726. test: {
  727. template: `
  728. <transition name="test">
  729. <div>foo</div>
  730. </transition>
  731. ` // test transition override from parent
  732. }
  733. }
  734. }).$mount(el)
  735. // should not apply transition on initial render by default
  736. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  737. vm.ok = false
  738. waitForUpdate(() => {
  739. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  740. }).thenWaitFor(nextFrame).then(() => {
  741. expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
  742. }).thenWaitFor(duration + buffer).then(() => {
  743. expect(vm.$el.children.length).toBe(0)
  744. vm.ok = true
  745. }).then(() => {
  746. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  747. }).thenWaitFor(nextFrame).then(() => {
  748. expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
  749. }).thenWaitFor(duration + buffer).then(() => {
  750. expect(vm.$el.children[0].className).toBe('test')
  751. }).then(done)
  752. })
  753. it('transition inside child component', done => {
  754. const vm = new Vue({
  755. template: `
  756. <div>
  757. <test v-if="ok" class="test"></test>
  758. </div>
  759. `,
  760. data: { ok: true },
  761. components: {
  762. test: {
  763. template: `
  764. <transition>
  765. <div>foo</div>
  766. </transition>
  767. `
  768. }
  769. }
  770. }).$mount(el)
  771. // should not apply transition on initial render by default
  772. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  773. vm.ok = false
  774. waitForUpdate(() => {
  775. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  776. }).thenWaitFor(nextFrame).then(() => {
  777. expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
  778. }).thenWaitFor(duration + buffer).then(() => {
  779. expect(vm.$el.children.length).toBe(0)
  780. vm.ok = true
  781. }).then(() => {
  782. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  783. }).thenWaitFor(nextFrame).then(() => {
  784. expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
  785. }).thenWaitFor(duration + buffer).then(() => {
  786. expect(vm.$el.children[0].className).toBe('test')
  787. }).then(done)
  788. })
  789. it('custom transition higher-order component', done => {
  790. const vm = new Vue({
  791. template: '<div><my-transition><div v-if="ok" class="test">foo</div></my-transition></div>',
  792. data: { ok: true },
  793. components: {
  794. 'my-transition': {
  795. functional: true,
  796. render (h, { data, children }) {
  797. (data.props || (data.props = {})).name = 'test'
  798. return h('transition', data, children)
  799. }
  800. }
  801. }
  802. }).$mount(el)
  803. // should not apply transition on initial render by default
  804. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  805. vm.ok = false
  806. waitForUpdate(() => {
  807. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  808. }).thenWaitFor(nextFrame).then(() => {
  809. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  810. }).thenWaitFor(duration + buffer).then(() => {
  811. expect(vm.$el.children.length).toBe(0)
  812. vm.ok = true
  813. }).then(() => {
  814. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  815. }).thenWaitFor(nextFrame).then(() => {
  816. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  817. }).thenWaitFor(duration + buffer).then(() => {
  818. expect(vm.$el.children[0].className).toBe('test')
  819. }).then(done)
  820. })
  821. it('warn when used on multiple elements', () => {
  822. new Vue({
  823. template: `<transition><p>1</p><p>2</p></transition>`
  824. }).$mount()
  825. expect(`<transition> can only be used on a single element`).toHaveBeenWarned()
  826. })
  827. describe('explicit durations -', () => {
  828. it('single value', done => {
  829. const vm = new Vue({
  830. template: `
  831. <div>
  832. <transition duration="${explicitDuration}">
  833. <div v-if="ok" class="test">foo</div>
  834. </transition>
  835. </div>
  836. `,
  837. data: { ok: true }
  838. }).$mount(el)
  839. vm.ok = false
  840. waitForUpdate(() => {
  841. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  842. }).thenWaitFor(nextFrame).then(() => {
  843. expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
  844. }).thenWaitFor(explicitDuration + buffer).then(() => {
  845. expect(vm.$el.children.length).toBe(0)
  846. vm.ok = true
  847. }).then(() => {
  848. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  849. }).thenWaitFor(nextFrame).then(() => {
  850. expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
  851. }).thenWaitFor(explicitDuration + buffer).then(() => {
  852. expect(vm.$el.children[0].className).toBe('test')
  853. }).then(done)
  854. })
  855. it('enter and auto leave', done => {
  856. const vm = new Vue({
  857. template: `
  858. <div>
  859. <transition :duration="{ enter: ${explicitDuration} }">
  860. <div v-if="ok" class="test">foo</div>
  861. </transition>
  862. </div>
  863. `,
  864. data: { ok: true }
  865. }).$mount(el)
  866. vm.ok = false
  867. waitForUpdate(() => {
  868. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  869. }).thenWaitFor(nextFrame).then(() => {
  870. expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
  871. }).thenWaitFor(duration + buffer).then(() => {
  872. expect(vm.$el.children.length).toBe(0)
  873. vm.ok = true
  874. }).then(() => {
  875. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  876. }).thenWaitFor(nextFrame).then(() => {
  877. expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
  878. }).thenWaitFor(explicitDuration + buffer).then(() => {
  879. expect(vm.$el.children[0].className).toBe('test')
  880. }).then(done)
  881. })
  882. it('leave and auto enter', done => {
  883. const vm = new Vue({
  884. template: `
  885. <div>
  886. <transition :duration="{ leave: ${explicitDuration} }">
  887. <div v-if="ok" class="test">foo</div>
  888. </transition>
  889. </div>
  890. `,
  891. data: { ok: true }
  892. }).$mount(el)
  893. vm.ok = false
  894. waitForUpdate(() => {
  895. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  896. }).thenWaitFor(nextFrame).then(() => {
  897. expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
  898. }).thenWaitFor(explicitDuration + buffer).then(() => {
  899. expect(vm.$el.children.length).toBe(0)
  900. vm.ok = true
  901. }).then(() => {
  902. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  903. }).thenWaitFor(nextFrame).then(() => {
  904. expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
  905. }).thenWaitFor(duration + buffer).then(() => {
  906. expect(vm.$el.children[0].className).toBe('test')
  907. }).then(done)
  908. })
  909. it('separate enter and leave', done => {
  910. const enter = explicitDuration
  911. const leave = explicitDuration * 2
  912. const vm = new Vue({
  913. template: `
  914. <div>
  915. <transition :duration="{ enter: ${enter}, leave: ${leave} }">
  916. <div v-if="ok" class="test">foo</div>
  917. </transition>
  918. </div>
  919. `,
  920. data: { ok: true }
  921. }).$mount(el)
  922. vm.ok = false
  923. waitForUpdate(() => {
  924. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  925. }).thenWaitFor(nextFrame).then(() => {
  926. expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
  927. }).thenWaitFor(leave + buffer).then(() => {
  928. expect(vm.$el.children.length).toBe(0)
  929. vm.ok = true
  930. }).then(() => {
  931. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  932. }).thenWaitFor(nextFrame).then(() => {
  933. expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
  934. }).thenWaitFor(enter + buffer).then(() => {
  935. expect(vm.$el.children[0].className).toBe('test')
  936. }).then(done)
  937. })
  938. it('enter and leave + duration change', done => {
  939. const enter1 = explicitDuration * 2
  940. const enter2 = explicitDuration
  941. const leave1 = explicitDuration * 0.5
  942. const leave2 = explicitDuration * 3
  943. const vm = new Vue({
  944. template: `
  945. <div>
  946. <transition :duration="{ enter: enter, leave: leave }">
  947. <div v-if="ok" class="test">foo</div>
  948. </transition>
  949. </div>
  950. `,
  951. data: {
  952. ok: true,
  953. enter: enter1,
  954. leave: leave1
  955. }
  956. }).$mount(el)
  957. vm.ok = false
  958. waitForUpdate(() => {
  959. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  960. }).thenWaitFor(nextFrame).then(() => {
  961. expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
  962. }).thenWaitFor(leave1 + buffer).then(() => {
  963. expect(vm.$el.children.length).toBe(0)
  964. vm.ok = true
  965. }).then(() => {
  966. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  967. }).thenWaitFor(nextFrame).then(() => {
  968. expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
  969. }).thenWaitFor(enter1 + buffer).then(() => {
  970. expect(vm.$el.children[0].className).toBe('test')
  971. vm.enter = enter2
  972. vm.leave = leave2
  973. }).then(() => {
  974. vm.ok = false
  975. }).then(() => {
  976. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  977. }).thenWaitFor(nextFrame).then(() => {
  978. expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
  979. }).thenWaitFor(leave2 + buffer).then(() => {
  980. expect(vm.$el.children.length).toBe(0)
  981. vm.ok = true
  982. }).then(() => {
  983. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  984. }).thenWaitFor(nextFrame).then(() => {
  985. expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
  986. }).thenWaitFor(enter2 + buffer).then(() => {
  987. expect(vm.$el.children[0].className).toBe('test')
  988. }).then(done)
  989. }, 10000)
  990. it('warn invalid durations', done => {
  991. const vm = new Vue({
  992. template: `
  993. <div>
  994. <transition :duration="{ enter: NaN, leave: 'foo' }">
  995. <div v-if="ok" class="test">foo</div>
  996. </transition>
  997. </div>
  998. `,
  999. data: {
  1000. ok: true
  1001. }
  1002. }).$mount(el)
  1003. vm.ok = false
  1004. waitForUpdate(() => {
  1005. expect(`<transition> explicit leave duration is not a valid number - got "foo"`).toHaveBeenWarned()
  1006. }).thenWaitFor(duration + buffer).then(() => {
  1007. vm.ok = true
  1008. }).then(() => {
  1009. expect(`<transition> explicit enter duration is NaN`).toHaveBeenWarned()
  1010. }).then(done)
  1011. })
  1012. })
  1013. // #6687
  1014. it('transition on child components with empty root node', done => {
  1015. const vm = new Vue({
  1016. template: `
  1017. <div>
  1018. <transition mode="out-in">
  1019. <component class="test" :is="view"></component>
  1020. </transition>
  1021. </div>
  1022. `,
  1023. data: { view: 'one' },
  1024. components: {
  1025. 'one': {
  1026. template: '<div v-if="false">one</div>'
  1027. },
  1028. 'two': {
  1029. template: '<div>two</div>'
  1030. }
  1031. }
  1032. }).$mount(el)
  1033. // should not apply transition on initial render by default
  1034. expect(vm.$el.innerHTML).toBe('<!---->')
  1035. vm.view = 'two'
  1036. waitForUpdate(() => {
  1037. expect(vm.$el.innerHTML).toBe('<div class="test v-enter v-enter-active">two</div>')
  1038. }).thenWaitFor(nextFrame).then(() => {
  1039. expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
  1040. }).thenWaitFor(duration + buffer).then(() => {
  1041. expect(vm.$el.children[0].className).toBe('test')
  1042. vm.view = 'one'
  1043. }).then(() => {
  1044. // incoming comment node is appended instantly because it doesn't have
  1045. // data and therefore doesn't go through the transition module.
  1046. expect(vm.$el.innerHTML).toBe('<div class="test v-leave v-leave-active">two</div><!---->')
  1047. }).thenWaitFor(nextFrame).then(() => {
  1048. expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
  1049. }).thenWaitFor(duration + buffer).then(() => {
  1050. expect(vm.$el.innerHTML).toBe('<!---->')
  1051. }).then(done)
  1052. })
  1053. })
  1054. }