component-keep-alive.spec.js 11 KB

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