component-keep-alive.spec.js 12 KB

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