transition.spec.js 32 KB

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