transition.spec.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879
  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. let el
  9. beforeEach(() => {
  10. el = document.createElement('div')
  11. document.body.appendChild(el)
  12. })
  13. it('basic transition', done => {
  14. const vm = new Vue({
  15. template: '<div><transition><div v-if="ok" class="test">foo</div></transition></div>',
  16. data: { ok: true }
  17. }).$mount(el)
  18. // should not apply transition on initial render by default
  19. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  20. vm.ok = false
  21. waitForUpdate(() => {
  22. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  23. }).thenWaitFor(nextFrame).then(() => {
  24. expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
  25. }).thenWaitFor(duration + buffer).then(() => {
  26. expect(vm.$el.children.length).toBe(0)
  27. vm.ok = true
  28. }).then(() => {
  29. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  30. }).thenWaitFor(nextFrame).then(() => {
  31. expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
  32. }).thenWaitFor(duration + buffer).then(() => {
  33. expect(vm.$el.children[0].className).toBe('test')
  34. }).then(done)
  35. })
  36. it('named transition', done => {
  37. const vm = new Vue({
  38. template: '<div><transition name="test"><div v-if="ok" class="test">foo</div></transition></div>',
  39. data: { ok: true }
  40. }).$mount(el)
  41. // should not apply transition on initial render by default
  42. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  43. vm.ok = false
  44. waitForUpdate(() => {
  45. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  46. }).thenWaitFor(nextFrame).then(() => {
  47. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  48. }).thenWaitFor(duration + buffer).then(() => {
  49. expect(vm.$el.children.length).toBe(0)
  50. vm.ok = true
  51. }).then(() => {
  52. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  53. }).thenWaitFor(nextFrame).then(() => {
  54. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  55. }).thenWaitFor(duration + buffer).then(() => {
  56. expect(vm.$el.children[0].className).toBe('test')
  57. }).then(done)
  58. })
  59. it('custom transition classes', done => {
  60. const vm = new Vue({
  61. template: `
  62. <div>
  63. <transition
  64. enter-class="hello"
  65. enter-active-class="hello-active"
  66. enter-to-class="hello-to"
  67. leave-class="bye"
  68. leave-to-class="bye-to"
  69. leave-active-class="byebye active">
  70. <div v-if="ok" class="test">foo</div>
  71. </transition>
  72. </div>
  73. `,
  74. data: { ok: true }
  75. }).$mount(el)
  76. // should not apply transition on initial render by default
  77. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  78. vm.ok = false
  79. waitForUpdate(() => {
  80. expect(vm.$el.children[0].className).toBe('test bye byebye active')
  81. }).thenWaitFor(nextFrame).then(() => {
  82. expect(vm.$el.children[0].className).toBe('test byebye active bye-to')
  83. }).thenWaitFor(duration + buffer).then(() => {
  84. expect(vm.$el.children.length).toBe(0)
  85. vm.ok = true
  86. }).then(() => {
  87. expect(vm.$el.children[0].className).toBe('test hello hello-active')
  88. }).thenWaitFor(nextFrame).then(() => {
  89. expect(vm.$el.children[0].className).toBe('test hello-active hello-to')
  90. }).thenWaitFor(duration + buffer).then(() => {
  91. expect(vm.$el.children[0].className).toBe('test')
  92. }).then(done)
  93. })
  94. it('dynamic transition', done => {
  95. const vm = new Vue({
  96. template: `
  97. <div>
  98. <transition :name="trans">
  99. <div v-if="ok" class="test">foo</div>
  100. </transition>
  101. </div>
  102. `,
  103. data: {
  104. ok: true,
  105. trans: 'test'
  106. }
  107. }).$mount(el)
  108. // should not apply transition on initial render by default
  109. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  110. vm.ok = false
  111. waitForUpdate(() => {
  112. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  113. }).thenWaitFor(nextFrame).then(() => {
  114. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  115. }).thenWaitFor(duration + buffer).then(() => {
  116. expect(vm.$el.children.length).toBe(0)
  117. vm.ok = true
  118. vm.trans = 'changed'
  119. }).then(() => {
  120. expect(vm.$el.children[0].className).toBe('test changed-enter changed-enter-active')
  121. }).thenWaitFor(nextFrame).then(() => {
  122. expect(vm.$el.children[0].className).toBe('test changed-enter-active changed-enter-to')
  123. }).thenWaitFor(duration + buffer).then(() => {
  124. expect(vm.$el.children[0].className).toBe('test')
  125. }).then(done)
  126. })
  127. it('inline transition object', done => {
  128. const enter = jasmine.createSpy('enter')
  129. const leave = jasmine.createSpy('leave')
  130. const vm = new Vue({
  131. render (h) {
  132. return h('div', null, [
  133. h('transition', {
  134. props: {
  135. name: 'inline',
  136. enterClass: 'hello',
  137. enterToClass: 'hello-to',
  138. enterActiveClass: 'hello-active',
  139. leaveClass: 'bye',
  140. leaveToClass: 'bye-to',
  141. leaveActiveClass: 'byebye active'
  142. },
  143. on: {
  144. enter,
  145. leave
  146. }
  147. }, this.ok ? [h('div', { class: 'test' }, 'foo')] : undefined)
  148. ])
  149. },
  150. data: { ok: true }
  151. }).$mount(el)
  152. // should not apply transition on initial render by default
  153. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  154. vm.ok = false
  155. waitForUpdate(() => {
  156. expect(vm.$el.children[0].className).toBe('test bye byebye active')
  157. expect(leave).toHaveBeenCalled()
  158. }).thenWaitFor(nextFrame).then(() => {
  159. expect(vm.$el.children[0].className).toBe('test byebye active bye-to')
  160. }).thenWaitFor(duration + buffer).then(() => {
  161. expect(vm.$el.children.length).toBe(0)
  162. vm.ok = true
  163. }).then(() => {
  164. expect(vm.$el.children[0].className).toBe('test hello hello-active')
  165. expect(enter).toHaveBeenCalled()
  166. }).thenWaitFor(nextFrame).then(() => {
  167. expect(vm.$el.children[0].className).toBe('test hello-active hello-to')
  168. }).thenWaitFor(duration + buffer).then(() => {
  169. expect(vm.$el.children[0].className).toBe('test')
  170. }).then(done)
  171. })
  172. it('transition events', done => {
  173. const onLeaveSpy = jasmine.createSpy('leave')
  174. const onEnterSpy = jasmine.createSpy('enter')
  175. const beforeLeaveSpy = jasmine.createSpy('beforeLeave')
  176. const beforeEnterSpy = jasmine.createSpy('beforeEnter')
  177. const afterLeaveSpy = jasmine.createSpy('afterLeave')
  178. const afterEnterSpy = jasmine.createSpy('afterEnter')
  179. const vm = new Vue({
  180. template: `
  181. <div>
  182. <transition
  183. name="test"
  184. @before-enter="beforeEnter"
  185. @enter="enter"
  186. @after-enter="afterEnter"
  187. @before-leave="beforeLeave"
  188. @leave="leave"
  189. @after-leave="afterLeave">
  190. <div v-if="ok" class="test">foo</div>
  191. </transition>
  192. </div>
  193. `,
  194. data: { ok: true },
  195. methods: {
  196. beforeLeave: (el) => {
  197. expect(el).toBe(vm.$el.children[0])
  198. expect(el.className).toBe('test')
  199. beforeLeaveSpy(el)
  200. },
  201. leave: (el) => onLeaveSpy(el),
  202. afterLeave: (el) => afterLeaveSpy(el),
  203. beforeEnter: (el) => {
  204. expect(vm.$el.contains(el)).toBe(false)
  205. expect(el.className).toBe('test')
  206. beforeEnterSpy(el)
  207. },
  208. enter: (el) => {
  209. expect(vm.$el.contains(el)).toBe(true)
  210. onEnterSpy(el)
  211. },
  212. afterEnter: (el) => afterEnterSpy(el)
  213. }
  214. }).$mount(el)
  215. // should not apply transition on initial render by default
  216. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  217. let _el = vm.$el.children[0]
  218. vm.ok = false
  219. waitForUpdate(() => {
  220. expect(beforeLeaveSpy).toHaveBeenCalledWith(_el)
  221. expect(onLeaveSpy).toHaveBeenCalledWith(_el)
  222. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  223. }).thenWaitFor(nextFrame).then(() => {
  224. expect(afterLeaveSpy).not.toHaveBeenCalled()
  225. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  226. }).thenWaitFor(duration + buffer).then(() => {
  227. expect(afterLeaveSpy).toHaveBeenCalledWith(_el)
  228. expect(vm.$el.children.length).toBe(0)
  229. vm.ok = true
  230. }).then(() => {
  231. _el = vm.$el.children[0]
  232. expect(beforeEnterSpy).toHaveBeenCalledWith(_el)
  233. expect(onEnterSpy).toHaveBeenCalledWith(_el)
  234. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  235. }).thenWaitFor(nextFrame).then(() => {
  236. expect(afterEnterSpy).not.toHaveBeenCalled()
  237. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  238. }).thenWaitFor(duration + buffer).then(() => {
  239. expect(afterEnterSpy).toHaveBeenCalledWith(_el)
  240. expect(vm.$el.children[0].className).toBe('test')
  241. }).then(done)
  242. })
  243. it('transition events (v-show)', done => {
  244. const onLeaveSpy = jasmine.createSpy('leave')
  245. const onEnterSpy = jasmine.createSpy('enter')
  246. const beforeLeaveSpy = jasmine.createSpy('beforeLeave')
  247. const beforeEnterSpy = jasmine.createSpy('beforeEnter')
  248. const afterLeaveSpy = jasmine.createSpy('afterLeave')
  249. const afterEnterSpy = jasmine.createSpy('afterEnter')
  250. const vm = new Vue({
  251. template: `
  252. <div>
  253. <transition
  254. name="test"
  255. @before-enter="beforeEnter"
  256. @enter="enter"
  257. @after-enter="afterEnter"
  258. @before-leave="beforeLeave"
  259. @leave="leave"
  260. @after-leave="afterLeave">
  261. <div v-show="ok" class="test">foo</div>
  262. </transition>
  263. </div>
  264. `,
  265. data: { ok: true },
  266. methods: {
  267. beforeLeave: (el) => {
  268. expect(el.style.display).toBe('')
  269. expect(el).toBe(vm.$el.children[0])
  270. expect(el.className).toBe('test')
  271. beforeLeaveSpy(el)
  272. },
  273. leave: (el) => {
  274. expect(el.style.display).toBe('')
  275. onLeaveSpy(el)
  276. },
  277. afterLeave: (el) => {
  278. expect(el.style.display).toBe('none')
  279. afterLeaveSpy(el)
  280. },
  281. beforeEnter: (el) => {
  282. expect(el.className).toBe('test')
  283. expect(el.style.display).toBe('none')
  284. beforeEnterSpy(el)
  285. },
  286. enter: (el) => {
  287. expect(el.style.display).toBe('')
  288. onEnterSpy(el)
  289. },
  290. afterEnter: (el) => {
  291. expect(el.style.display).toBe('')
  292. afterEnterSpy(el)
  293. }
  294. }
  295. }).$mount(el)
  296. // should not apply transition on initial render by default
  297. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  298. let _el = vm.$el.children[0]
  299. vm.ok = false
  300. waitForUpdate(() => {
  301. expect(beforeLeaveSpy).toHaveBeenCalledWith(_el)
  302. expect(onLeaveSpy).toHaveBeenCalledWith(_el)
  303. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  304. }).thenWaitFor(nextFrame).then(() => {
  305. expect(afterLeaveSpy).not.toHaveBeenCalled()
  306. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  307. }).thenWaitFor(duration + buffer).then(() => {
  308. expect(afterLeaveSpy).toHaveBeenCalledWith(_el)
  309. expect(vm.$el.children[0].style.display).toBe('none')
  310. vm.ok = true
  311. }).then(() => {
  312. _el = vm.$el.children[0]
  313. expect(beforeEnterSpy).toHaveBeenCalledWith(_el)
  314. expect(onEnterSpy).toHaveBeenCalledWith(_el)
  315. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  316. }).thenWaitFor(nextFrame).then(() => {
  317. expect(afterEnterSpy).not.toHaveBeenCalled()
  318. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  319. }).thenWaitFor(duration + buffer).then(() => {
  320. expect(afterEnterSpy).toHaveBeenCalledWith(_el)
  321. expect(vm.$el.children[0].className).toBe('test')
  322. }).then(done)
  323. })
  324. it('explicit user callback in JavaScript hooks', done => {
  325. let next
  326. const vm = new Vue({
  327. template: `<div>
  328. <transition name="test" @enter="enter" @leave="leave">
  329. <div v-if="ok" class="test">foo</div>
  330. </transition>
  331. </div>`,
  332. data: { ok: true },
  333. methods: {
  334. enter: (el, cb) => {
  335. next = cb
  336. },
  337. leave: (el, cb) => {
  338. next = cb
  339. }
  340. }
  341. }).$mount(el)
  342. vm.ok = false
  343. waitForUpdate(() => {
  344. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  345. }).thenWaitFor(nextFrame).then(() => {
  346. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  347. }).thenWaitFor(duration + buffer).then(() => {
  348. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  349. expect(next).toBeTruthy()
  350. next()
  351. expect(vm.$el.children.length).toBe(0)
  352. }).then(() => {
  353. vm.ok = true
  354. }).then(() => {
  355. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  356. }).thenWaitFor(nextFrame).then(() => {
  357. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  358. }).thenWaitFor(duration + buffer).then(() => {
  359. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  360. expect(next).toBeTruthy()
  361. next()
  362. expect(vm.$el.children[0].className).toBe('test')
  363. }).then(done)
  364. })
  365. it('css: false', done => {
  366. const enterSpy = jasmine.createSpy('enter')
  367. const leaveSpy = jasmine.createSpy('leave')
  368. const vm = new Vue({
  369. template: `
  370. <div>
  371. <transition :css="false" name="test" @enter="enter" @leave="leave">
  372. <div v-if="ok" class="test">foo</div>
  373. </transition>
  374. </div>
  375. `,
  376. data: { ok: true },
  377. methods: {
  378. enter: enterSpy,
  379. leave: leaveSpy
  380. }
  381. }).$mount(el)
  382. vm.ok = false
  383. waitForUpdate(() => {
  384. expect(leaveSpy).toHaveBeenCalled()
  385. expect(vm.$el.innerHTML).toBe('<!---->')
  386. vm.ok = true
  387. }).then(() => {
  388. expect(enterSpy).toHaveBeenCalled()
  389. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  390. }).then(done)
  391. })
  392. it('no transition detected', done => {
  393. const enterSpy = jasmine.createSpy('enter')
  394. const leaveSpy = jasmine.createSpy('leave')
  395. const vm = new Vue({
  396. template: '<div><transition name="nope" @enter="enter" @leave="leave"><div v-if="ok">foo</div></transition></div>',
  397. data: { ok: true },
  398. methods: {
  399. enter: enterSpy,
  400. leave: leaveSpy
  401. }
  402. }).$mount(el)
  403. vm.ok = false
  404. waitForUpdate(() => {
  405. expect(leaveSpy).toHaveBeenCalled()
  406. expect(vm.$el.innerHTML).toBe('<div class="nope-leave nope-leave-active">foo</div><!---->')
  407. }).thenWaitFor(nextFrame).then(() => {
  408. expect(vm.$el.innerHTML).toBe('<!---->')
  409. vm.ok = true
  410. }).then(() => {
  411. expect(enterSpy).toHaveBeenCalled()
  412. expect(vm.$el.innerHTML).toBe('<div class="nope-enter nope-enter-active">foo</div>')
  413. }).thenWaitFor(nextFrame).then(() => {
  414. expect(vm.$el.innerHTML).toMatch(/<div( class="")?>foo<\/div>/)
  415. }).then(done)
  416. })
  417. it('enterCancelled', done => {
  418. const spy = jasmine.createSpy('enterCancelled')
  419. const vm = new Vue({
  420. template: `
  421. <div>
  422. <transition name="test" @enter-cancelled="enterCancelled">
  423. <div v-if="ok" class="test">foo</div>
  424. </transition>
  425. </div>
  426. `,
  427. data: { ok: false },
  428. methods: {
  429. enterCancelled: spy
  430. }
  431. }).$mount(el)
  432. expect(vm.$el.innerHTML).toBe('<!---->')
  433. vm.ok = true
  434. waitForUpdate(() => {
  435. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  436. }).thenWaitFor(nextFrame).then(() => {
  437. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  438. }).thenWaitFor(duration / 2).then(() => {
  439. vm.ok = false
  440. }).then(() => {
  441. expect(spy).toHaveBeenCalled()
  442. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  443. }).thenWaitFor(nextFrame).then(() => {
  444. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  445. }).thenWaitFor(duration + buffer).then(() => {
  446. expect(vm.$el.children.length).toBe(0)
  447. }).then(done)
  448. })
  449. it('should remove stale leaving elements', done => {
  450. const spy = jasmine.createSpy('afterLeave')
  451. const vm = new Vue({
  452. template: `
  453. <div>
  454. <transition name="test" @after-leave="afterLeave">
  455. <div v-if="ok" class="test">foo</div>
  456. </transition>
  457. </div>
  458. `,
  459. data: { ok: true },
  460. methods: {
  461. afterLeave: spy
  462. }
  463. }).$mount(el)
  464. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  465. vm.ok = false
  466. waitForUpdate(() => {
  467. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  468. }).thenWaitFor(duration / 2).then(() => {
  469. vm.ok = true
  470. }).then(() => {
  471. expect(spy).toHaveBeenCalled()
  472. expect(vm.$el.children.length).toBe(1) // should have removed leaving element
  473. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  474. }).thenWaitFor(nextFrame).then(() => {
  475. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  476. }).thenWaitFor(duration + buffer).then(() => {
  477. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  478. }).then(done)
  479. })
  480. it('transition with v-show', done => {
  481. const vm = new Vue({
  482. template: `
  483. <div>
  484. <transition name="test">
  485. <div v-show="ok" class="test">foo</div>
  486. </transition>
  487. </div>
  488. `,
  489. data: { ok: true }
  490. }).$mount(el)
  491. // should not apply transition on initial render by default
  492. expect(vm.$el.textContent).toBe('foo')
  493. expect(vm.$el.children[0].style.display).toBe('')
  494. expect(vm.$el.children[0].className).toBe('test')
  495. vm.ok = false
  496. waitForUpdate(() => {
  497. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  498. }).thenWaitFor(nextFrame).then(() => {
  499. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  500. }).thenWaitFor(duration + buffer).then(() => {
  501. expect(vm.$el.children[0].style.display).toBe('none')
  502. vm.ok = true
  503. }).then(() => {
  504. expect(vm.$el.children[0].style.display).toBe('')
  505. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  506. }).thenWaitFor(nextFrame).then(() => {
  507. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  508. }).thenWaitFor(duration + buffer).then(() => {
  509. expect(vm.$el.children[0].className).toBe('test')
  510. }).then(done)
  511. })
  512. it('transition with v-show, inside child component', done => {
  513. const vm = new Vue({
  514. template: `
  515. <div>
  516. <test v-show="ok"></test>
  517. </div>
  518. `,
  519. data: { ok: true },
  520. components: {
  521. test: {
  522. template: `<transition name="test"><div class="test">foo</div></transition>`
  523. }
  524. }
  525. }).$mount(el)
  526. // should not apply transition on initial render by default
  527. expect(vm.$el.textContent).toBe('foo')
  528. expect(vm.$el.children[0].style.display).toBe('')
  529. vm.ok = false
  530. waitForUpdate(() => {
  531. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  532. }).thenWaitFor(nextFrame).then(() => {
  533. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  534. }).thenWaitFor(duration + buffer).then(() => {
  535. expect(vm.$el.children[0].style.display).toBe('none')
  536. vm.ok = true
  537. }).then(() => {
  538. expect(vm.$el.children[0].style.display).toBe('')
  539. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  540. }).thenWaitFor(nextFrame).then(() => {
  541. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  542. }).thenWaitFor(duration + buffer).then(() => {
  543. expect(vm.$el.children[0].className).toBe('test')
  544. }).then(done)
  545. })
  546. it('leaveCancelled (v-show only)', done => {
  547. const spy = jasmine.createSpy('leaveCancelled')
  548. const vm = new Vue({
  549. template: `
  550. <div>
  551. <transition name="test" @leave-cancelled="leaveCancelled">
  552. <div v-show="ok" class="test">foo</div>
  553. </transition>
  554. </div>
  555. `,
  556. data: { ok: true },
  557. methods: {
  558. leaveCancelled: spy
  559. }
  560. }).$mount(el)
  561. expect(vm.$el.children[0].style.display).toBe('')
  562. vm.ok = false
  563. waitForUpdate(() => {
  564. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  565. }).thenWaitFor(nextFrame).then(() => {
  566. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  567. }).thenWaitFor(10).then(() => {
  568. vm.ok = true
  569. }).then(() => {
  570. expect(spy).toHaveBeenCalled()
  571. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  572. }).thenWaitFor(nextFrame).then(() => {
  573. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  574. }).thenWaitFor(duration + buffer).then(() => {
  575. expect(vm.$el.children[0].style.display).toBe('')
  576. }).then(done)
  577. })
  578. it('animations', done => {
  579. const vm = new Vue({
  580. template: `
  581. <div>
  582. <transition name="test-anim">
  583. <div v-if="ok">foo</div>
  584. </transition>
  585. </div>
  586. `,
  587. data: { ok: true }
  588. }).$mount(el)
  589. // should not apply transition on initial render by default
  590. expect(vm.$el.innerHTML).toBe('<div>foo</div>')
  591. vm.ok = false
  592. waitForUpdate(() => {
  593. expect(vm.$el.children[0].className).toBe('test-anim-leave test-anim-leave-active')
  594. }).thenWaitFor(nextFrame).then(() => {
  595. expect(vm.$el.children[0].className).toBe('test-anim-leave-active test-anim-leave-to')
  596. }).thenWaitFor(duration + buffer).then(() => {
  597. expect(vm.$el.children.length).toBe(0)
  598. vm.ok = true
  599. }).then(() => {
  600. expect(vm.$el.children[0].className).toBe('test-anim-enter test-anim-enter-active')
  601. }).thenWaitFor(nextFrame).then(() => {
  602. expect(vm.$el.children[0].className).toBe('test-anim-enter-active test-anim-enter-to')
  603. }).thenWaitFor(duration + buffer).then(() => {
  604. expect(vm.$el.children[0].className).toBe('')
  605. }).then(done)
  606. })
  607. it('explicit transition type', done => {
  608. const vm = new Vue({
  609. template: `
  610. <div>
  611. <transition name="test-anim-long" type="animation">
  612. <div v-if="ok" class="test">foo</div>
  613. </transition>
  614. </div>
  615. `,
  616. data: { ok: true }
  617. }).$mount(el)
  618. // should not apply transition on initial render by default
  619. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  620. vm.ok = false
  621. waitForUpdate(() => {
  622. expect(vm.$el.children[0].className).toBe('test test-anim-long-leave test-anim-long-leave-active')
  623. }).thenWaitFor(nextFrame).then(() => {
  624. expect(vm.$el.children[0].className).toBe('test test-anim-long-leave-active test-anim-long-leave-to')
  625. }).thenWaitFor(duration + 5).then(() => {
  626. // should not end early due to transition presence
  627. expect(vm.$el.children[0].className).toBe('test test-anim-long-leave-active test-anim-long-leave-to')
  628. }).thenWaitFor(duration + 5).then(() => {
  629. expect(vm.$el.children.length).toBe(0)
  630. vm.ok = true
  631. }).then(() => {
  632. expect(vm.$el.children[0].className).toBe('test test-anim-long-enter test-anim-long-enter-active')
  633. }).thenWaitFor(nextFrame).then(() => {
  634. expect(vm.$el.children[0].className).toBe('test test-anim-long-enter-active test-anim-long-enter-to')
  635. }).thenWaitFor(duration + 5).then(() => {
  636. expect(vm.$el.children[0].className).toBe('test test-anim-long-enter-active test-anim-long-enter-to')
  637. }).thenWaitFor(duration + 5).then(() => {
  638. expect(vm.$el.children[0].className).toBe('test')
  639. }).then(done)
  640. })
  641. it('transition on appear', done => {
  642. const vm = new Vue({
  643. template: `
  644. <div>
  645. <transition name="test"
  646. appear
  647. appear-class="test-appear"
  648. appear-to-class="test-appear-to"
  649. appear-active-class="test-appear-active">
  650. <div v-if="ok" class="test">foo</div>
  651. </transition>
  652. </div>
  653. `,
  654. data: { ok: true }
  655. }).$mount(el)
  656. waitForUpdate(() => {
  657. expect(vm.$el.children[0].className).toBe('test test-appear test-appear-active')
  658. }).thenWaitFor(nextFrame).then(() => {
  659. expect(vm.$el.children[0].className).toBe('test test-appear-active test-appear-to')
  660. }).thenWaitFor(duration + buffer).then(() => {
  661. expect(vm.$el.children[0].className).toBe('test')
  662. }).then(done)
  663. })
  664. it('transition on appear with v-show', done => {
  665. const vm = new Vue({
  666. template: `
  667. <div>
  668. <transition name="test" appear>
  669. <div v-show="ok" class="test">foo</div>
  670. </transition>
  671. </div>
  672. `,
  673. data: { ok: true }
  674. }).$mount(el)
  675. waitForUpdate(() => {
  676. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  677. }).thenWaitFor(nextFrame).then(() => {
  678. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  679. }).thenWaitFor(duration + buffer).then(() => {
  680. expect(vm.$el.children[0].className).toBe('test')
  681. }).then(done)
  682. })
  683. it('transition on SVG elements', done => {
  684. const vm = new Vue({
  685. template: `
  686. <svg>
  687. <transition>
  688. <circle cx="0" cy="0" r="10" v-if="ok" class="test"></circle>
  689. </transition>
  690. </svg>
  691. `,
  692. data: { ok: true }
  693. }).$mount(el)
  694. // should not apply transition on initial render by default
  695. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test')
  696. vm.ok = false
  697. waitForUpdate(() => {
  698. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-leave v-leave-active')
  699. }).thenWaitFor(nextFrame).then(() => {
  700. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-leave-active v-leave-to')
  701. }).thenWaitFor(duration + buffer).then(() => {
  702. expect(vm.$el.childNodes.length).toBe(1)
  703. expect(vm.$el.childNodes[0].nodeType).toBe(8) // should be an empty comment node
  704. expect(vm.$el.childNodes[0].textContent).toBe('')
  705. vm.ok = true
  706. }).then(() => {
  707. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-enter v-enter-active')
  708. }).thenWaitFor(nextFrame).then(() => {
  709. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-enter-active v-enter-to')
  710. }).thenWaitFor(duration + buffer).then(() => {
  711. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test')
  712. }).then(done)
  713. })
  714. it('transition on child components', done => {
  715. const vm = new Vue({
  716. template: `
  717. <div>
  718. <transition>
  719. <test v-if="ok" class="test"></test>
  720. </transition>
  721. </div>
  722. `,
  723. data: { ok: true },
  724. components: {
  725. test: {
  726. template: `
  727. <transition name="test">
  728. <div>foo</div>
  729. </transition>
  730. ` // test transition override from parent
  731. }
  732. }
  733. }).$mount(el)
  734. // should not apply transition on initial render by default
  735. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  736. vm.ok = false
  737. waitForUpdate(() => {
  738. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  739. }).thenWaitFor(nextFrame).then(() => {
  740. expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
  741. }).thenWaitFor(duration + buffer).then(() => {
  742. expect(vm.$el.children.length).toBe(0)
  743. vm.ok = true
  744. }).then(() => {
  745. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  746. }).thenWaitFor(nextFrame).then(() => {
  747. expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
  748. }).thenWaitFor(duration + buffer).then(() => {
  749. expect(vm.$el.children[0].className).toBe('test')
  750. }).then(done)
  751. })
  752. it('transition inside child component', done => {
  753. const vm = new Vue({
  754. template: `
  755. <div>
  756. <test v-if="ok" class="test"></test>
  757. </div>
  758. `,
  759. data: { ok: true },
  760. components: {
  761. test: {
  762. template: `
  763. <transition>
  764. <div>foo</div>
  765. </transition>
  766. `
  767. }
  768. }
  769. }).$mount(el)
  770. // should not apply transition on initial render by default
  771. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  772. vm.ok = false
  773. waitForUpdate(() => {
  774. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  775. }).thenWaitFor(nextFrame).then(() => {
  776. expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
  777. }).thenWaitFor(duration + buffer).then(() => {
  778. expect(vm.$el.children.length).toBe(0)
  779. vm.ok = true
  780. }).then(() => {
  781. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  782. }).thenWaitFor(nextFrame).then(() => {
  783. expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
  784. }).thenWaitFor(duration + buffer).then(() => {
  785. expect(vm.$el.children[0].className).toBe('test')
  786. }).then(done)
  787. })
  788. it('custom transition higher-order component', done => {
  789. const vm = new Vue({
  790. template: '<div><my-transition><div v-if="ok" class="test">foo</div></my-transition></div>',
  791. data: { ok: true },
  792. components: {
  793. 'my-transition': {
  794. functional: true,
  795. render (h, { data, children }) {
  796. (data.props || (data.props = {})).name = 'test'
  797. return h('transition', data, children)
  798. }
  799. }
  800. }
  801. }).$mount(el)
  802. // should not apply transition on initial render by default
  803. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  804. vm.ok = false
  805. waitForUpdate(() => {
  806. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  807. }).thenWaitFor(nextFrame).then(() => {
  808. expect(vm.$el.children[0].className).toBe('test test-leave-active test-leave-to')
  809. }).thenWaitFor(duration + buffer).then(() => {
  810. expect(vm.$el.children.length).toBe(0)
  811. vm.ok = true
  812. }).then(() => {
  813. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  814. }).thenWaitFor(nextFrame).then(() => {
  815. expect(vm.$el.children[0].className).toBe('test test-enter-active test-enter-to')
  816. }).thenWaitFor(duration + buffer).then(() => {
  817. expect(vm.$el.children[0].className).toBe('test')
  818. }).then(done)
  819. })
  820. it('warn when used on multiple elements', () => {
  821. new Vue({
  822. template: `<transition><p>1</p><p>2</p></transition>`
  823. }).$mount()
  824. expect(`<transition> can only be used on a single element`).toHaveBeenWarned()
  825. })
  826. })
  827. }