component-keep-alive.spec.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. import Vue from 'vue'
  2. import injectStyles from '../transition/inject-styles'
  3. import { isIE9 } from 'web/util/index'
  4. import { nextFrame } from 'web/runtime/transition-util'
  5. describe('Component keep-alive', () => {
  6. const duration = injectStyles()
  7. let components, one, two, el
  8. beforeEach(() => {
  9. one = {
  10. template: '<div>one</div>',
  11. created: jasmine.createSpy('one created'),
  12. mounted: jasmine.createSpy('one mounted'),
  13. activated: jasmine.createSpy('one activated'),
  14. deactivated: jasmine.createSpy('one deactivated'),
  15. destroyed: jasmine.createSpy('one destroyed')
  16. }
  17. two = {
  18. template: '<div>two</div>',
  19. created: jasmine.createSpy('two created'),
  20. mounted: jasmine.createSpy('two mounted'),
  21. activated: jasmine.createSpy('two activated'),
  22. deactivated: jasmine.createSpy('two deactivated'),
  23. destroyed: jasmine.createSpy('two destroyed')
  24. }
  25. components = {
  26. one,
  27. two
  28. }
  29. el = document.createElement('div')
  30. document.body.appendChild(el)
  31. })
  32. function assertHookCalls (component, callCounts) {
  33. expect([
  34. component.created.calls.count(),
  35. component.mounted.calls.count(),
  36. component.activated.calls.count(),
  37. component.deactivated.calls.count(),
  38. component.destroyed.calls.count()
  39. ]).toEqual(callCounts)
  40. }
  41. it('should work', done => {
  42. const vm = new Vue({
  43. template: `
  44. <div v-if="ok">
  45. <keep-alive>
  46. <component :is="view"></component>
  47. </keep-alive>
  48. </div>
  49. `,
  50. data: {
  51. view: 'one',
  52. ok: true
  53. },
  54. components
  55. }).$mount()
  56. expect(vm.$el.textContent).toBe('one')
  57. assertHookCalls(one, [1, 1, 1, 0, 0])
  58. assertHookCalls(two, [0, 0, 0, 0, 0])
  59. vm.view = 'two'
  60. waitForUpdate(() => {
  61. expect(vm.$el.textContent).toBe('two')
  62. assertHookCalls(one, [1, 1, 1, 1, 0])
  63. assertHookCalls(two, [1, 1, 1, 0, 0])
  64. vm.view = 'one'
  65. }).then(() => {
  66. expect(vm.$el.textContent).toBe('one')
  67. assertHookCalls(one, [1, 1, 2, 1, 0])
  68. assertHookCalls(two, [1, 1, 1, 1, 0])
  69. vm.view = 'two'
  70. }).then(() => {
  71. expect(vm.$el.textContent).toBe('two')
  72. assertHookCalls(one, [1, 1, 2, 2, 0])
  73. assertHookCalls(two, [1, 1, 2, 1, 0])
  74. vm.ok = false // teardown
  75. }).then(() => {
  76. expect(vm.$el.textContent).toBe('')
  77. assertHookCalls(one, [1, 1, 2, 3, 1])
  78. assertHookCalls(two, [1, 1, 2, 2, 1])
  79. }).then(done)
  80. })
  81. if (!isIE9) {
  82. it('with transition-mode out-in', done => {
  83. let next
  84. const vm = new Vue({
  85. template: `<div>
  86. <transition name="test" mode="out-in" @after-leave="afterLeave">
  87. <keep-alive>
  88. <component :is="view" class="test"></component>
  89. </keep-alive>
  90. <transition>
  91. </div>`,
  92. data: {
  93. view: 'one'
  94. },
  95. components,
  96. methods: {
  97. afterLeave () {
  98. next()
  99. }
  100. }
  101. }).$mount(el)
  102. expect(vm.$el.textContent).toBe('one')
  103. assertHookCalls(one, [1, 1, 1, 0, 0])
  104. assertHookCalls(two, [0, 0, 0, 0, 0])
  105. vm.view = 'two'
  106. waitForUpdate(() => {
  107. expect(vm.$el.innerHTML).toBe(
  108. '<div class="test test-leave test-leave-active">one</div><!---->'
  109. )
  110. assertHookCalls(one, [1, 1, 1, 1, 0])
  111. assertHookCalls(two, [0, 0, 0, 0, 0])
  112. }).thenWaitFor(nextFrame).then(() => {
  113. expect(vm.$el.innerHTML).toBe(
  114. '<div class="test test-leave-active">one</div><!---->'
  115. )
  116. }).thenWaitFor(_next => { next = _next }).then(() => {
  117. expect(vm.$el.innerHTML).toBe('<!---->')
  118. }).thenWaitFor(nextFrame).then(() => {
  119. expect(vm.$el.innerHTML).toBe(
  120. '<div class="test test-enter test-enter-active">two</div>'
  121. )
  122. assertHookCalls(one, [1, 1, 1, 1, 0])
  123. assertHookCalls(two, [1, 1, 1, 0, 0])
  124. }).thenWaitFor(nextFrame).then(() => {
  125. expect(vm.$el.innerHTML).toBe(
  126. '<div class="test test-enter-active">two</div>'
  127. )
  128. }).thenWaitFor(duration + 10).then(() => {
  129. expect(vm.$el.innerHTML).toBe(
  130. '<div class="test">two</div>'
  131. )
  132. assertHookCalls(one, [1, 1, 1, 1, 0])
  133. assertHookCalls(two, [1, 1, 1, 0, 0])
  134. }).then(() => {
  135. vm.view = 'one'
  136. }).then(() => {
  137. expect(vm.$el.innerHTML).toBe(
  138. '<div class="test test-leave test-leave-active">two</div><!---->'
  139. )
  140. assertHookCalls(one, [1, 1, 1, 1, 0])
  141. assertHookCalls(two, [1, 1, 1, 1, 0])
  142. }).thenWaitFor(nextFrame).then(() => {
  143. expect(vm.$el.innerHTML).toBe(
  144. '<div class="test test-leave-active">two</div><!---->'
  145. )
  146. }).thenWaitFor(_next => { next = _next }).then(() => {
  147. expect(vm.$el.innerHTML).toBe('<!---->')
  148. }).thenWaitFor(nextFrame).then(() => {
  149. expect(vm.$el.innerHTML).toBe(
  150. '<div class="test test-enter test-enter-active">one</div>'
  151. )
  152. assertHookCalls(one, [1, 1, 2, 1, 0])
  153. assertHookCalls(two, [1, 1, 1, 1, 0])
  154. }).thenWaitFor(nextFrame).then(() => {
  155. expect(vm.$el.innerHTML).toBe(
  156. '<div class="test test-enter-active">one</div>'
  157. )
  158. }).thenWaitFor(duration + 10).then(() => {
  159. expect(vm.$el.innerHTML).toBe(
  160. '<div class="test">one</div>'
  161. )
  162. assertHookCalls(one, [1, 1, 2, 1, 0])
  163. assertHookCalls(two, [1, 1, 1, 1, 0])
  164. }).then(done)
  165. })
  166. it('with transition-mode in-out', done => {
  167. let next
  168. const vm = new Vue({
  169. template: `<div>
  170. <transition name="test" mode="in-out" @after-enter="afterEnter">
  171. <keep-alive>
  172. <component :is="view" class="test"></component>
  173. </keep-alive>
  174. </transition>
  175. </div>`,
  176. data: {
  177. view: 'one'
  178. },
  179. components,
  180. methods: {
  181. afterEnter () {
  182. next()
  183. }
  184. }
  185. }).$mount(el)
  186. expect(vm.$el.textContent).toBe('one')
  187. assertHookCalls(one, [1, 1, 1, 0, 0])
  188. assertHookCalls(two, [0, 0, 0, 0, 0])
  189. vm.view = 'two'
  190. waitForUpdate(() => {
  191. expect(vm.$el.innerHTML).toBe(
  192. '<div class="test">one</div>' +
  193. '<div class="test test-enter test-enter-active">two</div>'
  194. )
  195. assertHookCalls(one, [1, 1, 1, 1, 0])
  196. assertHookCalls(two, [1, 1, 1, 0, 0])
  197. }).thenWaitFor(nextFrame).then(() => {
  198. expect(vm.$el.innerHTML).toBe(
  199. '<div class="test">one</div>' +
  200. '<div class="test test-enter-active">two</div>'
  201. )
  202. }).thenWaitFor(_next => { next = _next }).then(() => {
  203. expect(vm.$el.innerHTML).toBe(
  204. '<div class="test">one</div>' +
  205. '<div class="test">two</div>'
  206. )
  207. }).then(() => {
  208. expect(vm.$el.innerHTML).toBe(
  209. '<div class="test test-leave test-leave-active">one</div>' +
  210. '<div class="test">two</div>'
  211. )
  212. }).thenWaitFor(nextFrame).then(() => {
  213. expect(vm.$el.innerHTML).toBe(
  214. '<div class="test test-leave-active">one</div>' +
  215. '<div class="test">two</div>'
  216. )
  217. }).thenWaitFor(duration + 10).then(() => {
  218. expect(vm.$el.innerHTML).toBe(
  219. '<div class="test">two</div>'
  220. )
  221. assertHookCalls(one, [1, 1, 1, 1, 0])
  222. assertHookCalls(two, [1, 1, 1, 0, 0])
  223. }).then(() => {
  224. vm.view = 'one'
  225. }).then(() => {
  226. expect(vm.$el.innerHTML).toBe(
  227. '<div class="test">two</div>' +
  228. '<div class="test test-enter test-enter-active">one</div>'
  229. )
  230. assertHookCalls(one, [1, 1, 2, 1, 0])
  231. assertHookCalls(two, [1, 1, 1, 1, 0])
  232. }).thenWaitFor(nextFrame).then(() => {
  233. expect(vm.$el.innerHTML).toBe(
  234. '<div class="test">two</div>' +
  235. '<div class="test test-enter-active">one</div>'
  236. )
  237. }).thenWaitFor(_next => { next = _next }).then(() => {
  238. expect(vm.$el.innerHTML).toBe(
  239. '<div class="test">two</div>' +
  240. '<div class="test">one</div>'
  241. )
  242. }).then(() => {
  243. expect(vm.$el.innerHTML).toBe(
  244. '<div class="test test-leave test-leave-active">two</div>' +
  245. '<div class="test">one</div>'
  246. )
  247. }).thenWaitFor(nextFrame).then(() => {
  248. expect(vm.$el.innerHTML).toBe(
  249. '<div class="test test-leave-active">two</div>' +
  250. '<div class="test">one</div>'
  251. )
  252. }).thenWaitFor(duration + 10).then(() => {
  253. expect(vm.$el.innerHTML).toBe(
  254. '<div class="test">one</div>'
  255. )
  256. assertHookCalls(one, [1, 1, 2, 1, 0])
  257. assertHookCalls(two, [1, 1, 1, 1, 0])
  258. }).then(done)
  259. })
  260. it('dynamic components, in-out with early cancel', done => {
  261. let next
  262. const vm = new Vue({
  263. template: `<div>
  264. <transition name="test" mode="in-out" @after-enter="afterEnter">
  265. <keep-alive>
  266. <component :is="view" class="test"></component>
  267. </keep-alive>
  268. </transition>
  269. </div>`,
  270. data: { view: 'one' },
  271. components,
  272. methods: {
  273. afterEnter () {
  274. next()
  275. }
  276. }
  277. }).$mount(el)
  278. expect(vm.$el.textContent).toBe('one')
  279. vm.view = 'two'
  280. waitForUpdate(() => {
  281. expect(vm.$el.innerHTML).toBe(
  282. '<div class="test">one</div>' +
  283. '<div class="test test-enter test-enter-active">two</div>'
  284. )
  285. }).thenWaitFor(nextFrame).then(() => {
  286. expect(vm.$el.innerHTML).toBe(
  287. '<div class="test">one</div>' +
  288. '<div class="test test-enter-active">two</div>'
  289. )
  290. // switch again before enter finishes,
  291. // this cancels both enter and leave.
  292. vm.view = 'one'
  293. }).then(() => {
  294. // 1. the pending leaving "one" should be removed instantly.
  295. // 2. the entering "two" should be placed into its final state instantly.
  296. // 3. a new "one" is created and entering
  297. expect(vm.$el.innerHTML).toBe(
  298. '<div class="test">two</div>' +
  299. '<div class="test test-enter test-enter-active">one</div>'
  300. )
  301. }).thenWaitFor(nextFrame).then(() => {
  302. expect(vm.$el.innerHTML).toBe(
  303. '<div class="test">two</div>' +
  304. '<div class="test test-enter-active">one</div>'
  305. )
  306. }).thenWaitFor(_next => { next = _next }).then(() => {
  307. expect(vm.$el.innerHTML).toBe(
  308. '<div class="test">two</div>' +
  309. '<div class="test">one</div>'
  310. )
  311. }).then(() => {
  312. expect(vm.$el.innerHTML).toBe(
  313. '<div class="test test-leave test-leave-active">two</div>' +
  314. '<div class="test">one</div>'
  315. )
  316. }).thenWaitFor(nextFrame).then(() => {
  317. expect(vm.$el.innerHTML).toBe(
  318. '<div class="test test-leave-active">two</div>' +
  319. '<div class="test">one</div>'
  320. )
  321. }).thenWaitFor(duration + 10).then(() => {
  322. expect(vm.$el.innerHTML).toBe(
  323. '<div class="test">one</div>'
  324. )
  325. }).then(done).then(done)
  326. })
  327. }
  328. })