transition-with-keep-alive.spec.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. import Vue from 'vue'
  2. import { injectStyles, waitForUpdate, nextFrame } from './helpers'
  3. describe('Transition w/ KeepAlive', () => {
  4. const { duration, buffer } = injectStyles()
  5. let components, one, two, el
  6. beforeEach(() => {
  7. one = {
  8. template: '<div>one</div>',
  9. created: jasmine.createSpy(),
  10. mounted: jasmine.createSpy(),
  11. activated: jasmine.createSpy(),
  12. deactivated: jasmine.createSpy(),
  13. destroyed: jasmine.createSpy()
  14. }
  15. two = {
  16. template: '<div>two</div>',
  17. created: jasmine.createSpy(),
  18. mounted: jasmine.createSpy(),
  19. activated: jasmine.createSpy(),
  20. deactivated: jasmine.createSpy(),
  21. destroyed: jasmine.createSpy()
  22. }
  23. components = {
  24. one,
  25. two
  26. }
  27. el = document.createElement('div')
  28. document.body.appendChild(el)
  29. })
  30. function assertHookCalls(component, callCounts) {
  31. expect([
  32. component.created.calls.count(),
  33. component.mounted.calls.count(),
  34. component.activated.calls.count(),
  35. component.deactivated.calls.count(),
  36. component.destroyed.calls.count()
  37. ]).toEqual(callCounts)
  38. }
  39. it('with transition-mode out-in', done => {
  40. let next
  41. const vm = new Vue({
  42. template: `<div>
  43. <transition name="test" mode="out-in" @after-leave="afterLeave">
  44. <keep-alive>
  45. <component :is="view" class="test"></component>
  46. </keep-alive>
  47. </transition>
  48. </div>`,
  49. data: {
  50. view: 'one'
  51. },
  52. components,
  53. methods: {
  54. afterLeave() {
  55. next()
  56. }
  57. }
  58. }).$mount(el)
  59. expect(vm.$el.textContent).toBe('one')
  60. assertHookCalls(one, [1, 1, 1, 0, 0])
  61. assertHookCalls(two, [0, 0, 0, 0, 0])
  62. vm.view = 'two'
  63. waitForUpdate(() => {
  64. expect(vm.$el.innerHTML).toBe(
  65. '<div class="test test-leave test-leave-active">one</div><!---->'
  66. )
  67. assertHookCalls(one, [1, 1, 1, 1, 0])
  68. assertHookCalls(two, [0, 0, 0, 0, 0])
  69. })
  70. .thenWaitFor(nextFrame)
  71. .then(() => {
  72. expect(vm.$el.innerHTML).toBe(
  73. '<div class="test test-leave-active test-leave-to">one</div><!---->'
  74. )
  75. })
  76. .thenWaitFor(_next => {
  77. next = _next
  78. })
  79. .then(() => {
  80. expect(vm.$el.innerHTML).toBe('<!---->')
  81. })
  82. .thenWaitFor(nextFrame)
  83. .then(() => {
  84. expect(vm.$el.innerHTML).toBe(
  85. '<div class="test test-enter test-enter-active">two</div>'
  86. )
  87. assertHookCalls(one, [1, 1, 1, 1, 0])
  88. assertHookCalls(two, [1, 1, 1, 0, 0])
  89. })
  90. .thenWaitFor(nextFrame)
  91. .then(() => {
  92. expect(vm.$el.innerHTML).toBe(
  93. '<div class="test test-enter-active test-enter-to">two</div>'
  94. )
  95. })
  96. .thenWaitFor(duration + buffer)
  97. .then(() => {
  98. expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
  99. assertHookCalls(one, [1, 1, 1, 1, 0])
  100. assertHookCalls(two, [1, 1, 1, 0, 0])
  101. })
  102. .then(() => {
  103. vm.view = 'one'
  104. })
  105. .then(() => {
  106. expect(vm.$el.innerHTML).toBe(
  107. '<div class="test test-leave test-leave-active">two</div><!---->'
  108. )
  109. assertHookCalls(one, [1, 1, 1, 1, 0])
  110. assertHookCalls(two, [1, 1, 1, 1, 0])
  111. })
  112. .thenWaitFor(nextFrame)
  113. .then(() => {
  114. expect(vm.$el.innerHTML).toBe(
  115. '<div class="test test-leave-active test-leave-to">two</div><!---->'
  116. )
  117. })
  118. .thenWaitFor(_next => {
  119. next = _next
  120. })
  121. .then(() => {
  122. expect(vm.$el.innerHTML).toBe('<!---->')
  123. })
  124. .thenWaitFor(nextFrame)
  125. .then(() => {
  126. expect(vm.$el.innerHTML).toBe(
  127. '<div class="test test-enter test-enter-active">one</div>'
  128. )
  129. assertHookCalls(one, [1, 1, 2, 1, 0])
  130. assertHookCalls(two, [1, 1, 1, 1, 0])
  131. })
  132. .thenWaitFor(nextFrame)
  133. .then(() => {
  134. expect(vm.$el.innerHTML).toBe(
  135. '<div class="test test-enter-active test-enter-to">one</div>'
  136. )
  137. })
  138. .thenWaitFor(duration + buffer)
  139. .then(() => {
  140. expect(vm.$el.innerHTML).toBe('<div class="test">one</div>')
  141. assertHookCalls(one, [1, 1, 2, 1, 0])
  142. assertHookCalls(two, [1, 1, 1, 1, 0])
  143. })
  144. .then(done)
  145. })
  146. it('with transition-mode out-in + include', done => {
  147. let next
  148. const vm = new Vue({
  149. template: `<div>
  150. <transition name="test" mode="out-in" @after-leave="afterLeave">
  151. <keep-alive include="one">
  152. <component :is="view" class="test"></component>
  153. </keep-alive>
  154. </transition>
  155. </div>`,
  156. data: {
  157. view: 'one'
  158. },
  159. components,
  160. methods: {
  161. afterLeave() {
  162. next()
  163. }
  164. }
  165. }).$mount(el)
  166. expect(vm.$el.textContent).toBe('one')
  167. assertHookCalls(one, [1, 1, 1, 0, 0])
  168. assertHookCalls(two, [0, 0, 0, 0, 0])
  169. vm.view = 'two'
  170. waitForUpdate(() => {
  171. expect(vm.$el.innerHTML).toBe(
  172. '<div class="test test-leave test-leave-active">one</div><!---->'
  173. )
  174. assertHookCalls(one, [1, 1, 1, 1, 0])
  175. assertHookCalls(two, [0, 0, 0, 0, 0])
  176. })
  177. .thenWaitFor(nextFrame)
  178. .then(() => {
  179. expect(vm.$el.innerHTML).toBe(
  180. '<div class="test test-leave-active test-leave-to">one</div><!---->'
  181. )
  182. })
  183. .thenWaitFor(_next => {
  184. next = _next
  185. })
  186. .then(() => {
  187. expect(vm.$el.innerHTML).toBe('<!---->')
  188. })
  189. .thenWaitFor(nextFrame)
  190. .then(() => {
  191. expect(vm.$el.innerHTML).toBe(
  192. '<div class="test test-enter test-enter-active">two</div>'
  193. )
  194. assertHookCalls(one, [1, 1, 1, 1, 0])
  195. assertHookCalls(two, [1, 1, 0, 0, 0])
  196. })
  197. .thenWaitFor(nextFrame)
  198. .then(() => {
  199. expect(vm.$el.innerHTML).toBe(
  200. '<div class="test test-enter-active test-enter-to">two</div>'
  201. )
  202. })
  203. .thenWaitFor(duration + buffer)
  204. .then(() => {
  205. expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
  206. assertHookCalls(one, [1, 1, 1, 1, 0])
  207. assertHookCalls(two, [1, 1, 0, 0, 0])
  208. })
  209. .then(() => {
  210. vm.view = 'one'
  211. })
  212. .then(() => {
  213. expect(vm.$el.innerHTML).toBe(
  214. '<div class="test test-leave test-leave-active">two</div><!---->'
  215. )
  216. assertHookCalls(one, [1, 1, 1, 1, 0])
  217. assertHookCalls(two, [1, 1, 0, 0, 1])
  218. })
  219. .thenWaitFor(nextFrame)
  220. .then(() => {
  221. expect(vm.$el.innerHTML).toBe(
  222. '<div class="test test-leave-active test-leave-to">two</div><!---->'
  223. )
  224. })
  225. .thenWaitFor(_next => {
  226. next = _next
  227. })
  228. .then(() => {
  229. expect(vm.$el.innerHTML).toBe('<!---->')
  230. })
  231. .thenWaitFor(nextFrame)
  232. .then(() => {
  233. expect(vm.$el.innerHTML).toBe(
  234. '<div class="test test-enter test-enter-active">one</div>'
  235. )
  236. assertHookCalls(one, [1, 1, 2, 1, 0])
  237. assertHookCalls(two, [1, 1, 0, 0, 1])
  238. })
  239. .thenWaitFor(nextFrame)
  240. .then(() => {
  241. expect(vm.$el.innerHTML).toBe(
  242. '<div class="test test-enter-active test-enter-to">one</div>'
  243. )
  244. })
  245. .thenWaitFor(duration + buffer)
  246. .then(() => {
  247. expect(vm.$el.innerHTML).toBe('<div class="test">one</div>')
  248. assertHookCalls(one, [1, 1, 2, 1, 0])
  249. assertHookCalls(two, [1, 1, 0, 0, 1])
  250. })
  251. .then(done)
  252. })
  253. it('with transition-mode in-out', done => {
  254. let next
  255. const vm = new Vue({
  256. template: `<div>
  257. <transition name="test" mode="in-out" @after-enter="afterEnter">
  258. <keep-alive>
  259. <component :is="view" class="test"></component>
  260. </keep-alive>
  261. </transition>
  262. </div>`,
  263. data: {
  264. view: 'one'
  265. },
  266. components,
  267. methods: {
  268. afterEnter() {
  269. next()
  270. }
  271. }
  272. }).$mount(el)
  273. expect(vm.$el.textContent).toBe('one')
  274. assertHookCalls(one, [1, 1, 1, 0, 0])
  275. assertHookCalls(two, [0, 0, 0, 0, 0])
  276. vm.view = 'two'
  277. waitForUpdate(() => {
  278. expect(vm.$el.innerHTML).toBe(
  279. '<div class="test">one</div>' +
  280. '<div class="test test-enter test-enter-active">two</div>'
  281. )
  282. assertHookCalls(one, [1, 1, 1, 1, 0])
  283. assertHookCalls(two, [1, 1, 1, 0, 0])
  284. })
  285. .thenWaitFor(nextFrame)
  286. .then(() => {
  287. expect(vm.$el.innerHTML).toBe(
  288. '<div class="test">one</div>' +
  289. '<div class="test test-enter-active test-enter-to">two</div>'
  290. )
  291. })
  292. .thenWaitFor(_next => {
  293. next = _next
  294. })
  295. .then(() => {
  296. expect(vm.$el.innerHTML).toBe(
  297. '<div class="test">one</div>' + '<div class="test">two</div>'
  298. )
  299. })
  300. .then(() => {
  301. expect(vm.$el.innerHTML).toBe(
  302. '<div class="test test-leave test-leave-active">one</div>' +
  303. '<div class="test">two</div>'
  304. )
  305. })
  306. .thenWaitFor(nextFrame)
  307. .then(() => {
  308. expect(vm.$el.innerHTML).toBe(
  309. '<div class="test test-leave-active test-leave-to">one</div>' +
  310. '<div class="test">two</div>'
  311. )
  312. })
  313. .thenWaitFor(duration + buffer)
  314. .then(() => {
  315. expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
  316. assertHookCalls(one, [1, 1, 1, 1, 0])
  317. assertHookCalls(two, [1, 1, 1, 0, 0])
  318. })
  319. .then(() => {
  320. vm.view = 'one'
  321. })
  322. .then(() => {
  323. expect(vm.$el.innerHTML).toBe(
  324. '<div class="test">two</div>' +
  325. '<div class="test test-enter test-enter-active">one</div>'
  326. )
  327. assertHookCalls(one, [1, 1, 2, 1, 0])
  328. assertHookCalls(two, [1, 1, 1, 1, 0])
  329. })
  330. .thenWaitFor(nextFrame)
  331. .then(() => {
  332. expect(vm.$el.innerHTML).toBe(
  333. '<div class="test">two</div>' +
  334. '<div class="test test-enter-active test-enter-to">one</div>'
  335. )
  336. })
  337. .thenWaitFor(_next => {
  338. next = _next
  339. })
  340. .then(() => {
  341. expect(vm.$el.innerHTML).toBe(
  342. '<div class="test">two</div>' + '<div class="test">one</div>'
  343. )
  344. })
  345. .then(() => {
  346. expect(vm.$el.innerHTML).toBe(
  347. '<div class="test test-leave test-leave-active">two</div>' +
  348. '<div class="test">one</div>'
  349. )
  350. })
  351. .thenWaitFor(nextFrame)
  352. .then(() => {
  353. expect(vm.$el.innerHTML).toBe(
  354. '<div class="test test-leave-active test-leave-to">two</div>' +
  355. '<div class="test">one</div>'
  356. )
  357. })
  358. .thenWaitFor(duration + buffer)
  359. .then(() => {
  360. expect(vm.$el.innerHTML).toBe('<div class="test">one</div>')
  361. assertHookCalls(one, [1, 1, 2, 1, 0])
  362. assertHookCalls(two, [1, 1, 1, 1, 0])
  363. })
  364. .then(done)
  365. })
  366. it('dynamic components, in-out with early cancel', done => {
  367. let next
  368. const vm = new Vue({
  369. template: `<div>
  370. <transition name="test" mode="in-out" @after-enter="afterEnter">
  371. <keep-alive>
  372. <component :is="view" class="test"></component>
  373. </keep-alive>
  374. </transition>
  375. </div>`,
  376. data: { view: 'one' },
  377. components,
  378. methods: {
  379. afterEnter() {
  380. next()
  381. }
  382. }
  383. }).$mount(el)
  384. expect(vm.$el.textContent).toBe('one')
  385. vm.view = 'two'
  386. waitForUpdate(() => {
  387. expect(vm.$el.innerHTML).toBe(
  388. '<div class="test">one</div>' +
  389. '<div class="test test-enter test-enter-active">two</div>'
  390. )
  391. })
  392. .thenWaitFor(nextFrame)
  393. .then(() => {
  394. expect(vm.$el.innerHTML).toBe(
  395. '<div class="test">one</div>' +
  396. '<div class="test test-enter-active test-enter-to">two</div>'
  397. )
  398. // switch again before enter finishes,
  399. // this cancels both enter and leave.
  400. vm.view = 'one'
  401. })
  402. .then(() => {
  403. // 1. the pending leaving "one" should be removed instantly.
  404. // 2. the entering "two" should be placed into its final state instantly.
  405. // 3. a new "one" is created and entering
  406. expect(vm.$el.innerHTML).toBe(
  407. '<div class="test">two</div>' +
  408. '<div class="test test-enter test-enter-active">one</div>'
  409. )
  410. })
  411. .thenWaitFor(nextFrame)
  412. .then(() => {
  413. expect(vm.$el.innerHTML).toBe(
  414. '<div class="test">two</div>' +
  415. '<div class="test test-enter-active test-enter-to">one</div>'
  416. )
  417. })
  418. .thenWaitFor(_next => {
  419. next = _next
  420. })
  421. .then(() => {
  422. expect(vm.$el.innerHTML).toBe(
  423. '<div class="test">two</div>' + '<div class="test">one</div>'
  424. )
  425. })
  426. .then(() => {
  427. expect(vm.$el.innerHTML).toBe(
  428. '<div class="test test-leave test-leave-active">two</div>' +
  429. '<div class="test">one</div>'
  430. )
  431. })
  432. .thenWaitFor(nextFrame)
  433. .then(() => {
  434. expect(vm.$el.innerHTML).toBe(
  435. '<div class="test test-leave-active test-leave-to">two</div>' +
  436. '<div class="test">one</div>'
  437. )
  438. })
  439. .thenWaitFor(duration + buffer)
  440. .then(() => {
  441. expect(vm.$el.innerHTML).toBe('<div class="test">one</div>')
  442. })
  443. .then(done)
  444. })
  445. // #4339
  446. it('component with inner transition', done => {
  447. const vm = new Vue({
  448. template: `
  449. <div>
  450. <keep-alive>
  451. <component ref="test" :is="view"></component>
  452. </keep-alive>
  453. </div>
  454. `,
  455. data: { view: 'foo' },
  456. components: {
  457. foo: {
  458. template: '<transition><div class="test">foo</div></transition>'
  459. },
  460. bar: {
  461. template:
  462. '<transition name="test"><div class="test">bar</div></transition>'
  463. }
  464. }
  465. }).$mount(el)
  466. // should not apply transition on initial render by default
  467. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  468. vm.view = 'bar'
  469. waitForUpdate(() => {
  470. expect(vm.$el.innerHTML).toBe(
  471. '<div class="test v-leave v-leave-active">foo</div>' +
  472. '<div class="test test-enter test-enter-active">bar</div>'
  473. )
  474. })
  475. .thenWaitFor(nextFrame)
  476. .then(() => {
  477. expect(vm.$el.innerHTML).toBe(
  478. '<div class="test v-leave-active v-leave-to">foo</div>' +
  479. '<div class="test test-enter-active test-enter-to">bar</div>'
  480. )
  481. })
  482. .thenWaitFor(duration + buffer)
  483. .then(() => {
  484. expect(vm.$el.innerHTML).toBe('<div class="test">bar</div>')
  485. vm.view = 'foo'
  486. })
  487. .then(() => {
  488. expect(vm.$el.innerHTML).toBe(
  489. '<div class="test test-leave test-leave-active">bar</div>' +
  490. '<div class="test v-enter v-enter-active">foo</div>'
  491. )
  492. })
  493. .thenWaitFor(nextFrame)
  494. .then(() => {
  495. expect(vm.$el.innerHTML).toBe(
  496. '<div class="test test-leave-active test-leave-to">bar</div>' +
  497. '<div class="test v-enter-active v-enter-to">foo</div>'
  498. )
  499. })
  500. .thenWaitFor(duration + buffer)
  501. .then(() => {
  502. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  503. })
  504. .then(done)
  505. })
  506. it('async components with transition-mode out-in', done => {
  507. const barResolve = jasmine.createSpy()
  508. let next
  509. const foo = resolve => {
  510. setTimeout(() => {
  511. resolve(one)
  512. Vue.nextTick(next)
  513. }, duration / 2)
  514. }
  515. const bar = resolve => {
  516. setTimeout(() => {
  517. resolve(two)
  518. barResolve()
  519. }, duration / 2)
  520. }
  521. components = {
  522. foo,
  523. bar
  524. }
  525. const vm = new Vue({
  526. template: `<div>
  527. <transition name="test" mode="out-in" @after-enter="afterEnter" @after-leave="afterLeave">
  528. <keep-alive>
  529. <component :is="view" class="test"></component>
  530. </keep-alive>
  531. </transition>
  532. </div>`,
  533. data: {
  534. view: 'foo'
  535. },
  536. components,
  537. methods: {
  538. afterEnter() {
  539. next()
  540. },
  541. afterLeave() {
  542. next()
  543. }
  544. }
  545. }).$mount(el)
  546. expect(vm.$el.textContent).toBe('')
  547. next = () => {
  548. assertHookCalls(one, [1, 1, 1, 0, 0])
  549. assertHookCalls(two, [0, 0, 0, 0, 0])
  550. waitForUpdate(() => {
  551. expect(vm.$el.innerHTML).toBe(
  552. '<div class="test test-enter test-enter-active">one</div>'
  553. )
  554. })
  555. .thenWaitFor(nextFrame)
  556. .then(() => {
  557. expect(vm.$el.innerHTML).toBe(
  558. '<div class="test test-enter-active test-enter-to">one</div>'
  559. )
  560. })
  561. .thenWaitFor(_next => {
  562. next = _next
  563. })
  564. .then(() => {
  565. // foo afterEnter get called
  566. expect(vm.$el.innerHTML).toBe('<div class="test">one</div>')
  567. vm.view = 'bar'
  568. })
  569. .thenWaitFor(nextFrame)
  570. .then(() => {
  571. assertHookCalls(one, [1, 1, 1, 1, 0])
  572. assertHookCalls(two, [0, 0, 0, 0, 0])
  573. expect(vm.$el.innerHTML).toBe(
  574. '<div class="test test-leave-active test-leave-to">one</div><!---->'
  575. )
  576. })
  577. .thenWaitFor(_next => {
  578. next = _next
  579. })
  580. .then(() => {
  581. // foo afterLeave get called
  582. // and bar has already been resolved before afterLeave get called
  583. expect(barResolve.calls.count()).toBe(1)
  584. expect(vm.$el.innerHTML).toBe('<!---->')
  585. })
  586. .thenWaitFor(nextFrame)
  587. .then(() => {
  588. expect(vm.$el.innerHTML).toBe(
  589. '<div class="test test-enter test-enter-active">two</div>'
  590. )
  591. assertHookCalls(one, [1, 1, 1, 1, 0])
  592. assertHookCalls(two, [1, 1, 1, 0, 0])
  593. })
  594. .thenWaitFor(nextFrame)
  595. .then(() => {
  596. expect(vm.$el.innerHTML).toBe(
  597. '<div class="test test-enter-active test-enter-to">two</div>'
  598. )
  599. })
  600. .thenWaitFor(_next => {
  601. next = _next
  602. })
  603. .then(() => {
  604. // bar afterEnter get called
  605. expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
  606. })
  607. .then(done)
  608. }
  609. })
  610. // #10083
  611. it('should not attach event handler repeatedly', done => {
  612. const vm = new Vue({
  613. template: `
  614. <keep-alive>
  615. <btn v-if="showBtn" @click.native="add" />
  616. </keep-alive>
  617. `,
  618. data: { showBtn: true, n: 0 },
  619. methods: {
  620. add() {
  621. this.n++
  622. }
  623. },
  624. components: {
  625. btn: { template: '<button>add 1</button>' }
  626. }
  627. }).$mount()
  628. const btn = vm.$el
  629. expect(vm.n).toBe(0)
  630. btn.click()
  631. expect(vm.n).toBe(1)
  632. vm.showBtn = false
  633. waitForUpdate(() => {
  634. vm.showBtn = true
  635. })
  636. .then(() => {
  637. btn.click()
  638. expect(vm.n).toBe(2)
  639. })
  640. .then(done)
  641. })
  642. })