component-keep-alive.spec.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  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, buffer } = 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. function sharedAssertions (vm, done) {
  82. expect(vm.$el.textContent).toBe('one')
  83. assertHookCalls(one, [1, 1, 1, 0, 0])
  84. assertHookCalls(two, [0, 0, 0, 0, 0])
  85. vm.view = 'two'
  86. waitForUpdate(() => {
  87. expect(vm.$el.textContent).toBe('two')
  88. assertHookCalls(one, [1, 1, 1, 1, 0])
  89. assertHookCalls(two, [1, 1, 0, 0, 0])
  90. vm.view = 'one'
  91. }).then(() => {
  92. expect(vm.$el.textContent).toBe('one')
  93. assertHookCalls(one, [1, 1, 2, 1, 0])
  94. assertHookCalls(two, [1, 1, 0, 0, 1])
  95. vm.view = 'two'
  96. }).then(() => {
  97. expect(vm.$el.textContent).toBe('two')
  98. assertHookCalls(one, [1, 1, 2, 2, 0])
  99. assertHookCalls(two, [2, 2, 0, 0, 1])
  100. vm.ok = false // teardown
  101. }).then(() => {
  102. expect(vm.$el.textContent).toBe('')
  103. assertHookCalls(one, [1, 1, 2, 3, 1])
  104. assertHookCalls(two, [2, 2, 0, 0, 2])
  105. }).then(done)
  106. }
  107. it('include (string)', done => {
  108. const vm = new Vue({
  109. template: `
  110. <div v-if="ok">
  111. <keep-alive include="one">
  112. <component :is="view"></component>
  113. </keep-alive>
  114. </div>
  115. `,
  116. data: {
  117. view: 'one',
  118. ok: true
  119. },
  120. components
  121. }).$mount()
  122. sharedAssertions(vm, done)
  123. })
  124. it('include (regex)', done => {
  125. const vm = new Vue({
  126. template: `
  127. <div v-if="ok">
  128. <keep-alive :include="/^one$/">
  129. <component :is="view"></component>
  130. </keep-alive>
  131. </div>
  132. `,
  133. data: {
  134. view: 'one',
  135. ok: true
  136. },
  137. components
  138. }).$mount()
  139. sharedAssertions(vm, done)
  140. })
  141. it('exclude (string)', done => {
  142. const vm = new Vue({
  143. template: `
  144. <div v-if="ok">
  145. <keep-alive exclude="two">
  146. <component :is="view"></component>
  147. </keep-alive>
  148. </div>
  149. `,
  150. data: {
  151. view: 'one',
  152. ok: true
  153. },
  154. components
  155. }).$mount()
  156. sharedAssertions(vm, done)
  157. })
  158. it('exclude (regex)', done => {
  159. const vm = new Vue({
  160. template: `
  161. <div v-if="ok">
  162. <keep-alive :exclude="/^two$/">
  163. <component :is="view"></component>
  164. </keep-alive>
  165. </div>
  166. `,
  167. data: {
  168. view: 'one',
  169. ok: true
  170. },
  171. components
  172. }).$mount()
  173. sharedAssertions(vm, done)
  174. })
  175. it('include + exclude', done => {
  176. const vm = new Vue({
  177. template: `
  178. <div v-if="ok">
  179. <keep-alive include="one,two" exclude="two">
  180. <component :is="view"></component>
  181. </keep-alive>
  182. </div>
  183. `,
  184. data: {
  185. view: 'one',
  186. ok: true
  187. },
  188. components
  189. }).$mount()
  190. sharedAssertions(vm, done)
  191. })
  192. // #3882
  193. it('deeply nested keep-alive should be destroyed properly', done => {
  194. one.template = `<div><keep-alive><two></two></keep-alive></div>`
  195. one.components = { two }
  196. const vm = new Vue({
  197. template: `<div><parent v-if="ok"></parent></div>`,
  198. data: { ok: true },
  199. components: {
  200. parent: {
  201. template: `<div><keep-alive><one></one></keep-alive></div>`,
  202. components: { one }
  203. }
  204. }
  205. }).$mount()
  206. assertHookCalls(one, [1, 1, 1, 0, 0])
  207. assertHookCalls(two, [1, 1, 1, 0, 0])
  208. vm.ok = false
  209. waitForUpdate(() => {
  210. assertHookCalls(one, [1, 1, 1, 1, 1])
  211. assertHookCalls(two, [1, 1, 1, 1, 1])
  212. }).then(done)
  213. })
  214. // #4237
  215. it('should update latest props/listners for a re-activated component', done => {
  216. const one = {
  217. props: ['prop'],
  218. template: `<div>one {{ prop }}</div>`
  219. }
  220. const two = {
  221. props: ['prop'],
  222. template: `<div>two {{ prop }}</div>`
  223. }
  224. const vm = new Vue({
  225. data: { view: 'one', n: 1 },
  226. template: `
  227. <div>
  228. <keep-alive>
  229. <component :is="view" :prop="n"></component>
  230. </keep-alive>
  231. </div>
  232. `,
  233. components: { one, two }
  234. }).$mount()
  235. expect(vm.$el.textContent).toBe('one 1')
  236. vm.n++
  237. waitForUpdate(() => {
  238. expect(vm.$el.textContent).toBe('one 2')
  239. vm.view = 'two'
  240. }).then(() => {
  241. expect(vm.$el.textContent).toBe('two 2')
  242. }).then(done)
  243. })
  244. if (!isIE9) {
  245. it('with transition-mode out-in', done => {
  246. let next
  247. const vm = new Vue({
  248. template: `<div>
  249. <transition name="test" mode="out-in" @after-leave="afterLeave">
  250. <keep-alive>
  251. <component :is="view" class="test"></component>
  252. </keep-alive>
  253. </transition>
  254. </div>`,
  255. data: {
  256. view: 'one'
  257. },
  258. components,
  259. methods: {
  260. afterLeave () {
  261. next()
  262. }
  263. }
  264. }).$mount(el)
  265. expect(vm.$el.textContent).toBe('one')
  266. assertHookCalls(one, [1, 1, 1, 0, 0])
  267. assertHookCalls(two, [0, 0, 0, 0, 0])
  268. vm.view = 'two'
  269. waitForUpdate(() => {
  270. expect(vm.$el.innerHTML).toBe(
  271. '<div class="test test-leave test-leave-active">one</div><!---->'
  272. )
  273. assertHookCalls(one, [1, 1, 1, 1, 0])
  274. assertHookCalls(two, [0, 0, 0, 0, 0])
  275. }).thenWaitFor(nextFrame).then(() => {
  276. expect(vm.$el.innerHTML).toBe(
  277. '<div class="test test-leave-active">one</div><!---->'
  278. )
  279. }).thenWaitFor(_next => { next = _next }).then(() => {
  280. expect(vm.$el.innerHTML).toBe('<!---->')
  281. }).thenWaitFor(nextFrame).then(() => {
  282. expect(vm.$el.innerHTML).toBe(
  283. '<div class="test test-enter test-enter-active">two</div>'
  284. )
  285. assertHookCalls(one, [1, 1, 1, 1, 0])
  286. assertHookCalls(two, [1, 1, 1, 0, 0])
  287. }).thenWaitFor(nextFrame).then(() => {
  288. expect(vm.$el.innerHTML).toBe(
  289. '<div class="test test-enter-active">two</div>'
  290. )
  291. }).thenWaitFor(duration + buffer).then(() => {
  292. expect(vm.$el.innerHTML).toBe(
  293. '<div class="test">two</div>'
  294. )
  295. assertHookCalls(one, [1, 1, 1, 1, 0])
  296. assertHookCalls(two, [1, 1, 1, 0, 0])
  297. }).then(() => {
  298. vm.view = 'one'
  299. }).then(() => {
  300. expect(vm.$el.innerHTML).toBe(
  301. '<div class="test test-leave test-leave-active">two</div><!---->'
  302. )
  303. assertHookCalls(one, [1, 1, 1, 1, 0])
  304. assertHookCalls(two, [1, 1, 1, 1, 0])
  305. }).thenWaitFor(nextFrame).then(() => {
  306. expect(vm.$el.innerHTML).toBe(
  307. '<div class="test test-leave-active">two</div><!---->'
  308. )
  309. }).thenWaitFor(_next => { next = _next }).then(() => {
  310. expect(vm.$el.innerHTML).toBe('<!---->')
  311. }).thenWaitFor(nextFrame).then(() => {
  312. expect(vm.$el.innerHTML).toBe(
  313. '<div class="test test-enter test-enter-active">one</div>'
  314. )
  315. assertHookCalls(one, [1, 1, 2, 1, 0])
  316. assertHookCalls(two, [1, 1, 1, 1, 0])
  317. }).thenWaitFor(nextFrame).then(() => {
  318. expect(vm.$el.innerHTML).toBe(
  319. '<div class="test test-enter-active">one</div>'
  320. )
  321. }).thenWaitFor(duration + buffer).then(() => {
  322. expect(vm.$el.innerHTML).toBe(
  323. '<div class="test">one</div>'
  324. )
  325. assertHookCalls(one, [1, 1, 2, 1, 0])
  326. assertHookCalls(two, [1, 1, 1, 1, 0])
  327. }).then(done)
  328. })
  329. it('with transition-mode in-out', done => {
  330. let next
  331. const vm = new Vue({
  332. template: `<div>
  333. <transition name="test" mode="in-out" @after-enter="afterEnter">
  334. <keep-alive>
  335. <component :is="view" class="test"></component>
  336. </keep-alive>
  337. </transition>
  338. </div>`,
  339. data: {
  340. view: 'one'
  341. },
  342. components,
  343. methods: {
  344. afterEnter () {
  345. next()
  346. }
  347. }
  348. }).$mount(el)
  349. expect(vm.$el.textContent).toBe('one')
  350. assertHookCalls(one, [1, 1, 1, 0, 0])
  351. assertHookCalls(two, [0, 0, 0, 0, 0])
  352. vm.view = 'two'
  353. waitForUpdate(() => {
  354. expect(vm.$el.innerHTML).toBe(
  355. '<div class="test">one</div>' +
  356. '<div class="test test-enter test-enter-active">two</div>'
  357. )
  358. assertHookCalls(one, [1, 1, 1, 1, 0])
  359. assertHookCalls(two, [1, 1, 1, 0, 0])
  360. }).thenWaitFor(nextFrame).then(() => {
  361. expect(vm.$el.innerHTML).toBe(
  362. '<div class="test">one</div>' +
  363. '<div class="test test-enter-active">two</div>'
  364. )
  365. }).thenWaitFor(_next => { next = _next }).then(() => {
  366. expect(vm.$el.innerHTML).toBe(
  367. '<div class="test">one</div>' +
  368. '<div class="test">two</div>'
  369. )
  370. }).then(() => {
  371. expect(vm.$el.innerHTML).toBe(
  372. '<div class="test test-leave test-leave-active">one</div>' +
  373. '<div class="test">two</div>'
  374. )
  375. }).thenWaitFor(nextFrame).then(() => {
  376. expect(vm.$el.innerHTML).toBe(
  377. '<div class="test test-leave-active">one</div>' +
  378. '<div class="test">two</div>'
  379. )
  380. }).thenWaitFor(duration + buffer).then(() => {
  381. expect(vm.$el.innerHTML).toBe(
  382. '<div class="test">two</div>'
  383. )
  384. assertHookCalls(one, [1, 1, 1, 1, 0])
  385. assertHookCalls(two, [1, 1, 1, 0, 0])
  386. }).then(() => {
  387. vm.view = 'one'
  388. }).then(() => {
  389. expect(vm.$el.innerHTML).toBe(
  390. '<div class="test">two</div>' +
  391. '<div class="test test-enter test-enter-active">one</div>'
  392. )
  393. assertHookCalls(one, [1, 1, 2, 1, 0])
  394. assertHookCalls(two, [1, 1, 1, 1, 0])
  395. }).thenWaitFor(nextFrame).then(() => {
  396. expect(vm.$el.innerHTML).toBe(
  397. '<div class="test">two</div>' +
  398. '<div class="test test-enter-active">one</div>'
  399. )
  400. }).thenWaitFor(_next => { next = _next }).then(() => {
  401. expect(vm.$el.innerHTML).toBe(
  402. '<div class="test">two</div>' +
  403. '<div class="test">one</div>'
  404. )
  405. }).then(() => {
  406. expect(vm.$el.innerHTML).toBe(
  407. '<div class="test test-leave test-leave-active">two</div>' +
  408. '<div class="test">one</div>'
  409. )
  410. }).thenWaitFor(nextFrame).then(() => {
  411. expect(vm.$el.innerHTML).toBe(
  412. '<div class="test test-leave-active">two</div>' +
  413. '<div class="test">one</div>'
  414. )
  415. }).thenWaitFor(duration + buffer).then(() => {
  416. expect(vm.$el.innerHTML).toBe(
  417. '<div class="test">one</div>'
  418. )
  419. assertHookCalls(one, [1, 1, 2, 1, 0])
  420. assertHookCalls(two, [1, 1, 1, 1, 0])
  421. }).then(done)
  422. })
  423. it('dynamic components, in-out with early cancel', done => {
  424. let next
  425. const vm = new Vue({
  426. template: `<div>
  427. <transition name="test" mode="in-out" @after-enter="afterEnter">
  428. <keep-alive>
  429. <component :is="view" class="test"></component>
  430. </keep-alive>
  431. </transition>
  432. </div>`,
  433. data: { view: 'one' },
  434. components,
  435. methods: {
  436. afterEnter () {
  437. next()
  438. }
  439. }
  440. }).$mount(el)
  441. expect(vm.$el.textContent).toBe('one')
  442. vm.view = 'two'
  443. waitForUpdate(() => {
  444. expect(vm.$el.innerHTML).toBe(
  445. '<div class="test">one</div>' +
  446. '<div class="test test-enter test-enter-active">two</div>'
  447. )
  448. }).thenWaitFor(nextFrame).then(() => {
  449. expect(vm.$el.innerHTML).toBe(
  450. '<div class="test">one</div>' +
  451. '<div class="test test-enter-active">two</div>'
  452. )
  453. // switch again before enter finishes,
  454. // this cancels both enter and leave.
  455. vm.view = 'one'
  456. }).then(() => {
  457. // 1. the pending leaving "one" should be removed instantly.
  458. // 2. the entering "two" should be placed into its final state instantly.
  459. // 3. a new "one" is created and entering
  460. expect(vm.$el.innerHTML).toBe(
  461. '<div class="test">two</div>' +
  462. '<div class="test test-enter test-enter-active">one</div>'
  463. )
  464. }).thenWaitFor(nextFrame).then(() => {
  465. expect(vm.$el.innerHTML).toBe(
  466. '<div class="test">two</div>' +
  467. '<div class="test test-enter-active">one</div>'
  468. )
  469. }).thenWaitFor(_next => { next = _next }).then(() => {
  470. expect(vm.$el.innerHTML).toBe(
  471. '<div class="test">two</div>' +
  472. '<div class="test">one</div>'
  473. )
  474. }).then(() => {
  475. expect(vm.$el.innerHTML).toBe(
  476. '<div class="test test-leave test-leave-active">two</div>' +
  477. '<div class="test">one</div>'
  478. )
  479. }).thenWaitFor(nextFrame).then(() => {
  480. expect(vm.$el.innerHTML).toBe(
  481. '<div class="test test-leave-active">two</div>' +
  482. '<div class="test">one</div>'
  483. )
  484. }).thenWaitFor(duration + buffer).then(() => {
  485. expect(vm.$el.innerHTML).toBe(
  486. '<div class="test">one</div>'
  487. )
  488. }).then(done).then(done)
  489. })
  490. }
  491. })