transition.spec.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. import Vue from 'vue'
  2. import injectStyles from './inject-styles'
  3. import { isIE9 } from 'web/util/index'
  4. import { nextFrame } from 'web/runtime/transition-util'
  5. if (!isIE9) {
  6. describe('Transition basic', () => {
  7. const duration = 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 + 10).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 + 10).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 + 10).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 + 10).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 + 10).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 + 10).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 + 10).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 + 10).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 + 10).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 + 10).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 + 10).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 + 10).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).toBe(vm.$el.children[0])
  265. expect(el.className).toBe('test')
  266. beforeLeaveSpy(el)
  267. },
  268. leave: (el) => onLeaveSpy(el),
  269. afterLeave: (el) => afterLeaveSpy(el),
  270. beforeEnter: (el) => {
  271. expect(el.className).toBe('test')
  272. beforeEnterSpy(el)
  273. },
  274. enter: (el) => {
  275. onEnterSpy(el)
  276. },
  277. afterEnter: (el) => afterEnterSpy(el)
  278. }
  279. }).$mount(el)
  280. // should not apply transition on initial render by default
  281. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  282. let _el = vm.$el.children[0]
  283. vm.ok = false
  284. waitForUpdate(() => {
  285. expect(beforeLeaveSpy).toHaveBeenCalledWith(_el)
  286. expect(onLeaveSpy).toHaveBeenCalledWith(_el)
  287. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  288. }).thenWaitFor(nextFrame).then(() => {
  289. expect(afterLeaveSpy).not.toHaveBeenCalled()
  290. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  291. }).thenWaitFor(duration + 10).then(() => {
  292. expect(afterLeaveSpy).toHaveBeenCalledWith(_el)
  293. expect(vm.$el.children[0].style.display).toBe('none')
  294. vm.ok = true
  295. }).then(() => {
  296. _el = vm.$el.children[0]
  297. expect(beforeEnterSpy).toHaveBeenCalledWith(_el)
  298. expect(onEnterSpy).toHaveBeenCalledWith(_el)
  299. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  300. }).thenWaitFor(nextFrame).then(() => {
  301. expect(afterEnterSpy).not.toHaveBeenCalled()
  302. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  303. }).thenWaitFor(duration + 10).then(() => {
  304. expect(afterEnterSpy).toHaveBeenCalledWith(_el)
  305. expect(vm.$el.children[0].className).toBe('test')
  306. }).then(done)
  307. })
  308. it('explicit user callback in JavaScript hooks', done => {
  309. let next
  310. const vm = new Vue({
  311. template: `<div>
  312. <transition name="test" @enter="enter" @leave="leave">
  313. <div v-if="ok" class="test">foo</div>
  314. </transition>
  315. </div>`,
  316. data: { ok: true },
  317. methods: {
  318. enter: (el, cb) => {
  319. next = cb
  320. },
  321. leave: (el, cb) => {
  322. next = cb
  323. }
  324. }
  325. }).$mount(el)
  326. vm.ok = false
  327. waitForUpdate(() => {
  328. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  329. }).thenWaitFor(nextFrame).then(() => {
  330. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  331. }).thenWaitFor(duration + 10).then(() => {
  332. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  333. expect(next).toBeTruthy()
  334. next()
  335. expect(vm.$el.children.length).toBe(0)
  336. }).then(() => {
  337. vm.ok = true
  338. }).then(() => {
  339. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  340. }).thenWaitFor(nextFrame).then(() => {
  341. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  342. }).thenWaitFor(duration + 10).then(() => {
  343. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  344. expect(next).toBeTruthy()
  345. next()
  346. expect(vm.$el.children[0].className).toBe('test')
  347. }).then(done)
  348. })
  349. it('css: false', done => {
  350. const enterSpy = jasmine.createSpy('enter')
  351. const leaveSpy = jasmine.createSpy('leave')
  352. const vm = new Vue({
  353. template: `
  354. <div>
  355. <transition :css="false" name="test" @enter="enter" @leave="leave">
  356. <div v-if="ok" class="test">foo</div>
  357. </transition>
  358. </div>
  359. `,
  360. data: { ok: true },
  361. methods: {
  362. enter: enterSpy,
  363. leave: leaveSpy
  364. }
  365. }).$mount(el)
  366. vm.ok = false
  367. waitForUpdate(() => {
  368. expect(leaveSpy).toHaveBeenCalled()
  369. expect(vm.$el.innerHTML).toBe('<!---->')
  370. vm.ok = true
  371. }).then(() => {
  372. expect(enterSpy).toHaveBeenCalled()
  373. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  374. }).then(done)
  375. })
  376. it('no transition detected', done => {
  377. const enterSpy = jasmine.createSpy('enter')
  378. const leaveSpy = jasmine.createSpy('leave')
  379. const vm = new Vue({
  380. template: '<div><transition name="nope" @enter="enter" @leave="leave"><div v-if="ok">foo</div></transition></div>',
  381. data: { ok: true },
  382. methods: {
  383. enter: enterSpy,
  384. leave: leaveSpy
  385. }
  386. }).$mount(el)
  387. vm.ok = false
  388. waitForUpdate(() => {
  389. expect(leaveSpy).toHaveBeenCalled()
  390. expect(vm.$el.innerHTML).toBe('<div class="nope-leave nope-leave-active">foo</div><!---->')
  391. }).thenWaitFor(nextFrame).then(() => {
  392. expect(vm.$el.innerHTML).toBe('<!---->')
  393. vm.ok = true
  394. }).then(() => {
  395. expect(enterSpy).toHaveBeenCalled()
  396. expect(vm.$el.innerHTML).toBe('<div class="nope-enter nope-enter-active">foo</div>')
  397. }).thenWaitFor(nextFrame).then(() => {
  398. expect(vm.$el.innerHTML).toMatch(/<div( class="")?>foo<\/div>/)
  399. }).then(done)
  400. })
  401. it('enterCancelled', done => {
  402. const spy = jasmine.createSpy('enterCancelled')
  403. const vm = new Vue({
  404. template: `
  405. <div>
  406. <transition name="test" @enter-cancelled="enterCancelled">
  407. <div v-if="ok" class="test">foo</div>
  408. </transition>
  409. </div>
  410. `,
  411. data: { ok: false },
  412. methods: {
  413. enterCancelled: spy
  414. }
  415. }).$mount(el)
  416. expect(vm.$el.innerHTML).toBe('<!---->')
  417. vm.ok = true
  418. waitForUpdate(() => {
  419. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  420. }).thenWaitFor(duration / 2).then(() => {
  421. vm.ok = false
  422. }).then(() => {
  423. expect(spy).toHaveBeenCalled()
  424. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  425. }).thenWaitFor(nextFrame).then(() => {
  426. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  427. }).thenWaitFor(duration + 10).then(() => {
  428. expect(vm.$el.children.length).toBe(0)
  429. }).then(done)
  430. })
  431. it('should remove stale leaving elements', done => {
  432. const spy = jasmine.createSpy('afterLeave')
  433. const vm = new Vue({
  434. template: `
  435. <div>
  436. <transition name="test" @after-leave="afterLeave">
  437. <div v-if="ok" class="test">foo</div>
  438. </transition>
  439. </div>
  440. `,
  441. data: { ok: true },
  442. methods: {
  443. afterLeave: spy
  444. }
  445. }).$mount(el)
  446. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  447. vm.ok = false
  448. waitForUpdate(() => {
  449. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  450. }).thenWaitFor(duration / 2).then(() => {
  451. vm.ok = true
  452. }).then(() => {
  453. expect(spy).toHaveBeenCalled()
  454. expect(vm.$el.children.length).toBe(1) // should have removed leaving element
  455. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  456. }).thenWaitFor(nextFrame).then(() => {
  457. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  458. }).thenWaitFor(duration + 10).then(() => {
  459. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  460. }).then(done)
  461. })
  462. it('transition with v-show', done => {
  463. const vm = new Vue({
  464. template: `
  465. <div>
  466. <transition name="test">
  467. <div v-show="ok" class="test">foo</div>
  468. </transition>
  469. </div>
  470. `,
  471. data: { ok: true }
  472. }).$mount(el)
  473. // should not apply transition on initial render by default
  474. expect(vm.$el.textContent).toBe('foo')
  475. expect(vm.$el.children[0].style.display).toBe('')
  476. vm.ok = false
  477. waitForUpdate(() => {
  478. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  479. }).thenWaitFor(nextFrame).then(() => {
  480. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  481. }).thenWaitFor(duration + 10).then(() => {
  482. expect(vm.$el.children[0].style.display).toBe('none')
  483. vm.ok = true
  484. }).then(() => {
  485. expect(vm.$el.children[0].style.display).toBe('')
  486. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  487. }).thenWaitFor(nextFrame).then(() => {
  488. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  489. }).thenWaitFor(duration + 10).then(() => {
  490. expect(vm.$el.children[0].className).toBe('test')
  491. }).then(done)
  492. })
  493. it('transition with v-show, inside child component', done => {
  494. const vm = new Vue({
  495. template: `
  496. <div>
  497. <test v-show="ok"></test>
  498. </div>
  499. `,
  500. data: { ok: true },
  501. components: {
  502. test: {
  503. template: `<transition name="test"><div class="test">foo</div></transition>`
  504. }
  505. }
  506. }).$mount(el)
  507. // should not apply transition on initial render by default
  508. expect(vm.$el.textContent).toBe('foo')
  509. expect(vm.$el.children[0].style.display).toBe('')
  510. vm.ok = false
  511. waitForUpdate(() => {
  512. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  513. }).thenWaitFor(nextFrame).then(() => {
  514. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  515. }).thenWaitFor(duration + 10).then(() => {
  516. expect(vm.$el.children[0].style.display).toBe('none')
  517. vm.ok = true
  518. }).then(() => {
  519. expect(vm.$el.children[0].style.display).toBe('')
  520. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  521. }).thenWaitFor(nextFrame).then(() => {
  522. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  523. }).thenWaitFor(duration + 10).then(() => {
  524. expect(vm.$el.children[0].className).toBe('test')
  525. }).then(done)
  526. })
  527. it('transition with v-show, with transition outside and v-show inside', done => {
  528. const vm = new Vue({
  529. template: `
  530. <div>
  531. <transition name="test">
  532. <test :ok="ok"></test>
  533. </transition>
  534. </div>
  535. `,
  536. data: { ok: true },
  537. components: {
  538. test: {
  539. props: ['ok'],
  540. template: `<div v-show="ok" class="test">foo</div>`
  541. }
  542. }
  543. }).$mount(el)
  544. // should not apply transition on initial render by default
  545. expect(vm.$el.textContent).toBe('foo')
  546. expect(vm.$el.children[0].style.display).toBe('')
  547. vm.ok = false
  548. waitForUpdate(() => {
  549. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  550. }).thenWaitFor(nextFrame).then(() => {
  551. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  552. }).thenWaitFor(duration + 10).then(() => {
  553. expect(vm.$el.children[0].style.display).toBe('none')
  554. vm.ok = true
  555. }).then(() => {
  556. expect(vm.$el.children[0].style.display).toBe('')
  557. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  558. }).thenWaitFor(nextFrame).then(() => {
  559. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  560. }).thenWaitFor(duration + 10).then(() => {
  561. expect(vm.$el.children[0].className).toBe('test')
  562. }).then(done)
  563. })
  564. it('leaveCancelled (v-show only)', done => {
  565. const spy = jasmine.createSpy('leaveCancelled')
  566. const vm = new Vue({
  567. template: `
  568. <div>
  569. <transition name="test" @leave-cancelled="leaveCancelled">
  570. <div v-show="ok" class="test">foo</div>
  571. </transition>
  572. </div>
  573. `,
  574. data: { ok: true },
  575. methods: {
  576. leaveCancelled: spy
  577. }
  578. }).$mount(el)
  579. expect(vm.$el.children[0].style.display).toBe('')
  580. vm.ok = false
  581. waitForUpdate(() => {
  582. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  583. }).thenWaitFor(nextFrame).then(() => {
  584. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  585. }).thenWaitFor(10).then(() => {
  586. vm.ok = true
  587. }).then(() => {
  588. expect(spy).toHaveBeenCalled()
  589. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  590. }).thenWaitFor(nextFrame).then(() => {
  591. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  592. }).thenWaitFor(duration + 10).then(() => {
  593. expect(vm.$el.children[0].style.display).toBe('')
  594. }).then(done)
  595. })
  596. it('animations', done => {
  597. const vm = new Vue({
  598. template: `
  599. <div>
  600. <transition name="test-anim">
  601. <div v-if="ok">foo</div>
  602. </transition>
  603. </div>
  604. `,
  605. data: { ok: true }
  606. }).$mount(el)
  607. // should not apply transition on initial render by default
  608. expect(vm.$el.innerHTML).toBe('<div>foo</div>')
  609. vm.ok = false
  610. waitForUpdate(() => {
  611. expect(vm.$el.children[0].className).toBe('test-anim-leave test-anim-leave-active')
  612. }).thenWaitFor(nextFrame).then(() => {
  613. expect(vm.$el.children[0].className).toBe('test-anim-leave-active')
  614. }).thenWaitFor(duration + 10).then(() => {
  615. expect(vm.$el.children.length).toBe(0)
  616. vm.ok = true
  617. }).then(() => {
  618. expect(vm.$el.children[0].className).toBe('test-anim-enter test-anim-enter-active')
  619. }).thenWaitFor(nextFrame).then(() => {
  620. expect(vm.$el.children[0].className).toBe('test-anim-enter-active')
  621. }).thenWaitFor(duration + 10).then(() => {
  622. expect(vm.$el.children[0].className).toBe('')
  623. }).then(done)
  624. })
  625. it('explicit transition type', done => {
  626. const vm = new Vue({
  627. template: `
  628. <div>
  629. <transition name="test-anim-long" type="animation">
  630. <div v-if="ok" class="test">foo</div>
  631. </transition>
  632. </div>
  633. `,
  634. data: { ok: true }
  635. }).$mount(el)
  636. // should not apply transition on initial render by default
  637. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  638. vm.ok = false
  639. waitForUpdate(() => {
  640. expect(vm.$el.children[0].className).toBe('test test-anim-long-leave test-anim-long-leave-active')
  641. }).thenWaitFor(nextFrame).then(() => {
  642. expect(vm.$el.children[0].className).toBe('test test-anim-long-leave-active')
  643. }).thenWaitFor(duration + 5).then(() => {
  644. // should not end early due to transition presence
  645. expect(vm.$el.children[0].className).toBe('test test-anim-long-leave-active')
  646. }).thenWaitFor(duration + 5).then(() => {
  647. expect(vm.$el.children.length).toBe(0)
  648. vm.ok = true
  649. }).then(() => {
  650. expect(vm.$el.children[0].className).toBe('test test-anim-long-enter test-anim-long-enter-active')
  651. }).thenWaitFor(nextFrame).then(() => {
  652. expect(vm.$el.children[0].className).toBe('test test-anim-long-enter-active')
  653. }).thenWaitFor(duration + 5).then(() => {
  654. expect(vm.$el.children[0].className).toBe('test test-anim-long-enter-active')
  655. }).thenWaitFor(duration + 5).then(() => {
  656. expect(vm.$el.children[0].className).toBe('test')
  657. }).then(done)
  658. })
  659. it('transition on appear', done => {
  660. const vm = new Vue({
  661. template: `
  662. <div>
  663. <transition name="test"
  664. appear
  665. appear-class="test-appear"
  666. appear-active-class="test-appear-active">
  667. <div v-if="ok" class="test">foo</div>
  668. </transition>
  669. </div>
  670. `,
  671. data: { ok: true }
  672. }).$mount(el)
  673. waitForUpdate(() => {
  674. expect(vm.$el.children[0].className).toBe('test test-appear test-appear-active')
  675. }).thenWaitFor(nextFrame).then(() => {
  676. expect(vm.$el.children[0].className).toBe('test test-appear-active')
  677. }).thenWaitFor(duration + 10).then(() => {
  678. expect(vm.$el.children[0].className).toBe('test')
  679. }).then(done)
  680. })
  681. it('transition on appear with v-show', done => {
  682. const vm = new Vue({
  683. template: `
  684. <div>
  685. <transition name="test" appear>
  686. <div v-show="ok" class="test">foo</div>
  687. </transition>
  688. </div>
  689. `,
  690. data: { ok: true }
  691. }).$mount(el)
  692. waitForUpdate(() => {
  693. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  694. }).thenWaitFor(nextFrame).then(() => {
  695. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  696. }).thenWaitFor(duration + 10).then(() => {
  697. expect(vm.$el.children[0].className).toBe('test')
  698. }).then(done)
  699. })
  700. it('transition on SVG elements', done => {
  701. const vm = new Vue({
  702. template: `
  703. <svg>
  704. <transition>
  705. <circle cx="0" cy="0" r="10" v-if="ok" class="test"></circle>
  706. </transition>
  707. </svg>
  708. `,
  709. data: { ok: true }
  710. }).$mount(el)
  711. // should not apply transition on initial render by default
  712. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test')
  713. vm.ok = false
  714. waitForUpdate(() => {
  715. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-leave v-leave-active')
  716. }).thenWaitFor(nextFrame).then(() => {
  717. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-leave-active')
  718. }).thenWaitFor(duration + 10).then(() => {
  719. expect(vm.$el.childNodes.length).toBe(1)
  720. expect(vm.$el.childNodes[0].nodeType).toBe(8) // should be an empty comment node
  721. expect(vm.$el.childNodes[0].textContent).toBe('')
  722. vm.ok = true
  723. }).then(() => {
  724. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-enter v-enter-active')
  725. }).thenWaitFor(nextFrame).then(() => {
  726. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-enter-active')
  727. }).thenWaitFor(duration + 10).then(() => {
  728. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test')
  729. }).then(done)
  730. })
  731. it('transition on child components', done => {
  732. const vm = new Vue({
  733. template: `
  734. <div>
  735. <transition>
  736. <test v-if="ok" class="test"></test>
  737. </transition>
  738. </div>
  739. `,
  740. data: { ok: true },
  741. components: {
  742. test: {
  743. template: `
  744. <transition name="test">
  745. <div>foo</div>
  746. </transition>
  747. ` // test transition override from parent
  748. }
  749. }
  750. }).$mount(el)
  751. // should not apply transition on initial render by default
  752. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  753. vm.ok = false
  754. waitForUpdate(() => {
  755. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  756. }).thenWaitFor(nextFrame).then(() => {
  757. expect(vm.$el.children[0].className).toBe('test v-leave-active')
  758. }).thenWaitFor(duration + 10).then(() => {
  759. expect(vm.$el.children.length).toBe(0)
  760. vm.ok = true
  761. }).then(() => {
  762. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  763. }).thenWaitFor(nextFrame).then(() => {
  764. expect(vm.$el.children[0].className).toBe('test v-enter-active')
  765. }).thenWaitFor(duration + 10).then(() => {
  766. expect(vm.$el.children[0].className).toBe('test')
  767. }).then(done)
  768. })
  769. it('transition inside child component', done => {
  770. const vm = new Vue({
  771. template: `
  772. <div>
  773. <test v-if="ok" class="test"></test>
  774. </div>
  775. `,
  776. data: { ok: true },
  777. components: {
  778. test: {
  779. template: `
  780. <transition>
  781. <div>foo</div>
  782. </transition>
  783. `
  784. }
  785. }
  786. }).$mount(el)
  787. // should not apply transition on initial render by default
  788. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  789. vm.ok = false
  790. waitForUpdate(() => {
  791. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  792. }).thenWaitFor(nextFrame).then(() => {
  793. expect(vm.$el.children[0].className).toBe('test v-leave-active')
  794. }).thenWaitFor(duration + 10).then(() => {
  795. expect(vm.$el.children.length).toBe(0)
  796. vm.ok = true
  797. }).then(() => {
  798. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  799. }).thenWaitFor(nextFrame).then(() => {
  800. expect(vm.$el.children[0].className).toBe('test v-enter-active')
  801. }).thenWaitFor(duration + 10).then(() => {
  802. expect(vm.$el.children[0].className).toBe('test')
  803. }).then(done)
  804. })
  805. it('custom transition higher-order component', done => {
  806. const vm = new Vue({
  807. template: '<div><my-transition><div v-if="ok" class="test">foo</div></my-transition></div>',
  808. data: { ok: true },
  809. components: {
  810. 'my-transition': {
  811. functional: true,
  812. render (h, { data, children }) {
  813. (data.props || (data.props = {})).name = 'test'
  814. return h('transition', data, children)
  815. }
  816. }
  817. }
  818. }).$mount(el)
  819. // should not apply transition on initial render by default
  820. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  821. vm.ok = false
  822. waitForUpdate(() => {
  823. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  824. }).thenWaitFor(nextFrame).then(() => {
  825. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  826. }).thenWaitFor(duration + 10).then(() => {
  827. expect(vm.$el.children.length).toBe(0)
  828. vm.ok = true
  829. }).then(() => {
  830. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  831. }).thenWaitFor(nextFrame).then(() => {
  832. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  833. }).thenWaitFor(duration + 10).then(() => {
  834. expect(vm.$el.children[0].className).toBe('test')
  835. }).then(done)
  836. })
  837. })
  838. }