component-keep-alive.spec.js 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157
  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, 2, 1])
  78. assertHookCalls(two, [1, 1, 2, 2, 1])
  79. }).then(done)
  80. })
  81. it('should invoke hooks on the entire sub tree', done => {
  82. one.template = '<two/>'
  83. one.components = { two }
  84. const vm = new Vue({
  85. template: `
  86. <div>
  87. <keep-alive>
  88. <one v-if="ok"/>
  89. </keep-alive>
  90. </div>
  91. `,
  92. data: {
  93. ok: true
  94. },
  95. components
  96. }).$mount()
  97. expect(vm.$el.textContent).toBe('two')
  98. assertHookCalls(one, [1, 1, 1, 0, 0])
  99. assertHookCalls(two, [1, 1, 1, 0, 0])
  100. vm.ok = false
  101. waitForUpdate(() => {
  102. expect(vm.$el.textContent).toBe('')
  103. assertHookCalls(one, [1, 1, 1, 1, 0])
  104. assertHookCalls(two, [1, 1, 1, 1, 0])
  105. vm.ok = true
  106. }).then(() => {
  107. expect(vm.$el.textContent).toBe('two')
  108. assertHookCalls(one, [1, 1, 2, 1, 0])
  109. assertHookCalls(two, [1, 1, 2, 1, 0])
  110. vm.ok = false
  111. }).then(() => {
  112. expect(vm.$el.textContent).toBe('')
  113. assertHookCalls(one, [1, 1, 2, 2, 0])
  114. assertHookCalls(two, [1, 1, 2, 2, 0])
  115. }).then(done)
  116. })
  117. it('should handle nested keep-alive hooks properly', done => {
  118. one.template = '<keep-alive><two v-if="ok" /></keep-alive>'
  119. one.data = () => ({ ok: true })
  120. one.components = { two }
  121. const vm = new Vue({
  122. template: `
  123. <div>
  124. <keep-alive>
  125. <one v-if="ok" ref="one" />
  126. </keep-alive>
  127. </div>
  128. `,
  129. data: {
  130. ok: true
  131. },
  132. components
  133. }).$mount()
  134. var oneInstance = vm.$refs.one
  135. expect(vm.$el.textContent).toBe('two')
  136. assertHookCalls(one, [1, 1, 1, 0, 0])
  137. assertHookCalls(two, [1, 1, 1, 0, 0])
  138. vm.ok = false
  139. waitForUpdate(() => {
  140. expect(vm.$el.textContent).toBe('')
  141. assertHookCalls(one, [1, 1, 1, 1, 0])
  142. assertHookCalls(two, [1, 1, 1, 1, 0])
  143. }).then(() => {
  144. vm.ok = true
  145. }).then(() => {
  146. expect(vm.$el.textContent).toBe('two')
  147. assertHookCalls(one, [1, 1, 2, 1, 0])
  148. assertHookCalls(two, [1, 1, 2, 1, 0])
  149. }).then(() => {
  150. // toggle sub component when activated
  151. oneInstance.ok = false
  152. }).then(() => {
  153. expect(vm.$el.textContent).toBe('')
  154. assertHookCalls(one, [1, 1, 2, 1, 0])
  155. assertHookCalls(two, [1, 1, 2, 2, 0])
  156. }).then(() => {
  157. oneInstance.ok = true
  158. }).then(() => {
  159. expect(vm.$el.textContent).toBe('two')
  160. assertHookCalls(one, [1, 1, 2, 1, 0])
  161. assertHookCalls(two, [1, 1, 3, 2, 0])
  162. }).then(() => {
  163. vm.ok = false
  164. }).then(() => {
  165. expect(vm.$el.textContent).toBe('')
  166. assertHookCalls(one, [1, 1, 2, 2, 0])
  167. assertHookCalls(two, [1, 1, 3, 3, 0])
  168. }).then(() => {
  169. // toggle sub component when parent is deactivated
  170. oneInstance.ok = false
  171. }).then(() => {
  172. expect(vm.$el.textContent).toBe('')
  173. assertHookCalls(one, [1, 1, 2, 2, 0])
  174. assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected
  175. }).then(() => {
  176. oneInstance.ok = true
  177. }).then(() => {
  178. expect(vm.$el.textContent).toBe('')
  179. assertHookCalls(one, [1, 1, 2, 2, 0])
  180. assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected
  181. }).then(() => {
  182. vm.ok = true
  183. }).then(() => {
  184. expect(vm.$el.textContent).toBe('two')
  185. assertHookCalls(one, [1, 1, 3, 2, 0])
  186. assertHookCalls(two, [1, 1, 4, 3, 0])
  187. }).then(() => {
  188. oneInstance.ok = false
  189. vm.ok = false
  190. }).then(() => {
  191. expect(vm.$el.textContent).toBe('')
  192. assertHookCalls(one, [1, 1, 3, 3, 0])
  193. assertHookCalls(two, [1, 1, 4, 4, 0])
  194. }).then(() => {
  195. vm.ok = true
  196. }).then(() => {
  197. expect(vm.$el.textContent).toBe('')
  198. assertHookCalls(one, [1, 1, 4, 3, 0])
  199. assertHookCalls(two, [1, 1, 4, 4, 0]) // should remain inactive
  200. }).then(done)
  201. })
  202. function sharedAssertions (vm, done) {
  203. expect(vm.$el.textContent).toBe('one')
  204. assertHookCalls(one, [1, 1, 1, 0, 0])
  205. assertHookCalls(two, [0, 0, 0, 0, 0])
  206. vm.view = 'two'
  207. waitForUpdate(() => {
  208. expect(vm.$el.textContent).toBe('two')
  209. assertHookCalls(one, [1, 1, 1, 1, 0])
  210. assertHookCalls(two, [1, 1, 0, 0, 0])
  211. vm.view = 'one'
  212. }).then(() => {
  213. expect(vm.$el.textContent).toBe('one')
  214. assertHookCalls(one, [1, 1, 2, 1, 0])
  215. assertHookCalls(two, [1, 1, 0, 0, 1])
  216. vm.view = 'two'
  217. }).then(() => {
  218. expect(vm.$el.textContent).toBe('two')
  219. assertHookCalls(one, [1, 1, 2, 2, 0])
  220. assertHookCalls(two, [2, 2, 0, 0, 1])
  221. vm.ok = false // teardown
  222. }).then(() => {
  223. expect(vm.$el.textContent).toBe('')
  224. assertHookCalls(one, [1, 1, 2, 2, 1])
  225. assertHookCalls(two, [2, 2, 0, 0, 2])
  226. }).then(done)
  227. }
  228. it('include (string)', done => {
  229. const vm = new Vue({
  230. template: `
  231. <div v-if="ok">
  232. <keep-alive include="one">
  233. <component :is="view"></component>
  234. </keep-alive>
  235. </div>
  236. `,
  237. data: {
  238. view: 'one',
  239. ok: true
  240. },
  241. components
  242. }).$mount()
  243. sharedAssertions(vm, done)
  244. })
  245. it('include (regex)', done => {
  246. const vm = new Vue({
  247. template: `
  248. <div v-if="ok">
  249. <keep-alive :include="/^one$/">
  250. <component :is="view"></component>
  251. </keep-alive>
  252. </div>
  253. `,
  254. data: {
  255. view: 'one',
  256. ok: true
  257. },
  258. components
  259. }).$mount()
  260. sharedAssertions(vm, done)
  261. })
  262. it('include (array)', done => {
  263. const vm = new Vue({
  264. template: `
  265. <div v-if="ok">
  266. <keep-alive :include="['one']">
  267. <component :is="view"></component>
  268. </keep-alive>
  269. </div>
  270. `,
  271. data: {
  272. view: 'one',
  273. ok: true
  274. },
  275. components
  276. }).$mount()
  277. sharedAssertions(vm, done)
  278. })
  279. it('exclude (string)', done => {
  280. const vm = new Vue({
  281. template: `
  282. <div v-if="ok">
  283. <keep-alive exclude="two">
  284. <component :is="view"></component>
  285. </keep-alive>
  286. </div>
  287. `,
  288. data: {
  289. view: 'one',
  290. ok: true
  291. },
  292. components
  293. }).$mount()
  294. sharedAssertions(vm, done)
  295. })
  296. it('exclude (regex)', done => {
  297. const vm = new Vue({
  298. template: `
  299. <div v-if="ok">
  300. <keep-alive :exclude="/^two$/">
  301. <component :is="view"></component>
  302. </keep-alive>
  303. </div>
  304. `,
  305. data: {
  306. view: 'one',
  307. ok: true
  308. },
  309. components
  310. }).$mount()
  311. sharedAssertions(vm, done)
  312. })
  313. it('exclude (array)', done => {
  314. const vm = new Vue({
  315. template: `
  316. <div v-if="ok">
  317. <keep-alive :exclude="['two']">
  318. <component :is="view"></component>
  319. </keep-alive>
  320. </div>
  321. `,
  322. data: {
  323. view: 'one',
  324. ok: true
  325. },
  326. components
  327. }).$mount()
  328. sharedAssertions(vm, done)
  329. })
  330. it('include + exclude', done => {
  331. const vm = new Vue({
  332. template: `
  333. <div v-if="ok">
  334. <keep-alive include="one,two" exclude="two">
  335. <component :is="view"></component>
  336. </keep-alive>
  337. </div>
  338. `,
  339. data: {
  340. view: 'one',
  341. ok: true
  342. },
  343. components
  344. }).$mount()
  345. sharedAssertions(vm, done)
  346. })
  347. it('prune cache on include/exclude change', done => {
  348. const vm = new Vue({
  349. template: `
  350. <div>
  351. <keep-alive :include="include">
  352. <component :is="view"></component>
  353. </keep-alive>
  354. </div>
  355. `,
  356. data: {
  357. view: 'one',
  358. include: 'one,two'
  359. },
  360. components
  361. }).$mount()
  362. vm.view = 'two'
  363. waitForUpdate(() => {
  364. assertHookCalls(one, [1, 1, 1, 1, 0])
  365. assertHookCalls(two, [1, 1, 1, 0, 0])
  366. vm.include = 'two'
  367. }).then(() => {
  368. assertHookCalls(one, [1, 1, 1, 1, 1])
  369. assertHookCalls(two, [1, 1, 1, 0, 0])
  370. vm.view = 'one'
  371. }).then(() => {
  372. assertHookCalls(one, [2, 2, 1, 1, 1])
  373. assertHookCalls(two, [1, 1, 1, 1, 0])
  374. }).then(done)
  375. })
  376. it('should not prune currently active instance', done => {
  377. const vm = new Vue({
  378. template: `
  379. <div>
  380. <keep-alive :include="include">
  381. <component :is="view"></component>
  382. </keep-alive>
  383. </div>
  384. `,
  385. data: {
  386. view: 'one',
  387. include: 'one,two'
  388. },
  389. components
  390. }).$mount()
  391. vm.include = 'two'
  392. waitForUpdate(() => {
  393. assertHookCalls(one, [1, 1, 1, 0, 0])
  394. assertHookCalls(two, [0, 0, 0, 0, 0])
  395. vm.view = 'two'
  396. }).then(() => {
  397. assertHookCalls(one, [1, 1, 1, 0, 1])
  398. assertHookCalls(two, [1, 1, 1, 0, 0])
  399. }).then(done)
  400. })
  401. // #3882
  402. it('deeply nested keep-alive should be destroyed properly', done => {
  403. one.template = `<div><keep-alive><two></two></keep-alive></div>`
  404. one.components = { two }
  405. const vm = new Vue({
  406. template: `<div><parent v-if="ok"></parent></div>`,
  407. data: { ok: true },
  408. components: {
  409. parent: {
  410. template: `<div><keep-alive><one></one></keep-alive></div>`,
  411. components: { one }
  412. }
  413. }
  414. }).$mount()
  415. assertHookCalls(one, [1, 1, 1, 0, 0])
  416. assertHookCalls(two, [1, 1, 1, 0, 0])
  417. vm.ok = false
  418. waitForUpdate(() => {
  419. assertHookCalls(one, [1, 1, 1, 1, 1])
  420. assertHookCalls(two, [1, 1, 1, 1, 1])
  421. }).then(done)
  422. })
  423. // #4237
  424. it('should update latest props/listeners for a re-activated component', done => {
  425. const one = {
  426. props: ['prop'],
  427. template: `<div>one {{ prop }}</div>`
  428. }
  429. const two = {
  430. props: ['prop'],
  431. template: `<div>two {{ prop }}</div>`
  432. }
  433. const vm = new Vue({
  434. data: { view: 'one', n: 1 },
  435. template: `
  436. <div>
  437. <keep-alive>
  438. <component :is="view" :prop="n"></component>
  439. </keep-alive>
  440. </div>
  441. `,
  442. components: { one, two }
  443. }).$mount()
  444. expect(vm.$el.textContent).toBe('one 1')
  445. vm.n++
  446. waitForUpdate(() => {
  447. expect(vm.$el.textContent).toBe('one 2')
  448. vm.view = 'two'
  449. }).then(() => {
  450. expect(vm.$el.textContent).toBe('two 2')
  451. }).then(done)
  452. })
  453. it('max', done => {
  454. const spyA = jasmine.createSpy()
  455. const spyB = jasmine.createSpy()
  456. const spyC = jasmine.createSpy()
  457. const spyAD = jasmine.createSpy()
  458. const spyBD = jasmine.createSpy()
  459. const spyCD = jasmine.createSpy()
  460. function assertCount (calls) {
  461. expect([
  462. spyA.calls.count(),
  463. spyAD.calls.count(),
  464. spyB.calls.count(),
  465. spyBD.calls.count(),
  466. spyC.calls.count(),
  467. spyCD.calls.count()
  468. ]).toEqual(calls)
  469. }
  470. const vm = new Vue({
  471. template: `
  472. <keep-alive max="2">
  473. <component :is="n"></component>
  474. </keep-alive>
  475. `,
  476. data: {
  477. n: 'aa'
  478. },
  479. components: {
  480. aa: {
  481. template: '<div>a</div>',
  482. created: spyA,
  483. destroyed: spyAD
  484. },
  485. bb: {
  486. template: '<div>bbb</div>',
  487. created: spyB,
  488. destroyed: spyBD
  489. },
  490. cc: {
  491. template: '<div>ccc</div>',
  492. created: spyC,
  493. destroyed: spyCD
  494. }
  495. }
  496. }).$mount()
  497. assertCount([1, 0, 0, 0, 0, 0])
  498. vm.n = 'bb'
  499. waitForUpdate(() => {
  500. assertCount([1, 0, 1, 0, 0, 0])
  501. vm.n = 'cc'
  502. }).then(() => {
  503. // should prune A because max cache reached
  504. assertCount([1, 1, 1, 0, 1, 0])
  505. vm.n = 'bb'
  506. }).then(() => {
  507. // B should be reused, and made latest
  508. assertCount([1, 1, 1, 0, 1, 0])
  509. vm.n = 'aa'
  510. }).then(() => {
  511. // C should be pruned because B was used last so C is the oldest cached
  512. assertCount([2, 1, 1, 0, 1, 1])
  513. }).then(done)
  514. })
  515. it('should warn unknown component inside', () => {
  516. new Vue({
  517. template: `<keep-alive><foo/></keep-alive>`
  518. }).$mount()
  519. expect(`Unknown custom element: <foo>`).toHaveBeenWarned()
  520. })
  521. // #6938
  522. it('should not cache anonymous component when include is specified', done => {
  523. const Foo = {
  524. name: 'foo',
  525. template: `<div>foo</div>`,
  526. created: jasmine.createSpy('foo')
  527. }
  528. const Bar = {
  529. template: `<div>bar</div>`,
  530. created: jasmine.createSpy('bar')
  531. }
  532. const Child = {
  533. functional: true,
  534. render (h, ctx) {
  535. return h(ctx.props.view ? Foo : Bar)
  536. }
  537. }
  538. const vm = new Vue({
  539. template: `
  540. <keep-alive include="foo">
  541. <child :view="view"></child>
  542. </keep-alive>
  543. `,
  544. data: {
  545. view: true
  546. },
  547. components: { Child }
  548. }).$mount()
  549. function assert (foo, bar) {
  550. expect(Foo.created.calls.count()).toBe(foo)
  551. expect(Bar.created.calls.count()).toBe(bar)
  552. }
  553. expect(vm.$el.textContent).toBe('foo')
  554. assert(1, 0)
  555. vm.view = false
  556. waitForUpdate(() => {
  557. expect(vm.$el.textContent).toBe('bar')
  558. assert(1, 1)
  559. vm.view = true
  560. }).then(() => {
  561. expect(vm.$el.textContent).toBe('foo')
  562. assert(1, 1)
  563. vm.view = false
  564. }).then(() => {
  565. expect(vm.$el.textContent).toBe('bar')
  566. assert(1, 2)
  567. }).then(done)
  568. })
  569. it('should cache anonymous components if include is not specified', done => {
  570. const Foo = {
  571. template: `<div>foo</div>`,
  572. created: jasmine.createSpy('foo')
  573. }
  574. const Bar = {
  575. template: `<div>bar</div>`,
  576. created: jasmine.createSpy('bar')
  577. }
  578. const Child = {
  579. functional: true,
  580. render (h, ctx) {
  581. return h(ctx.props.view ? Foo : Bar)
  582. }
  583. }
  584. const vm = new Vue({
  585. template: `
  586. <keep-alive>
  587. <child :view="view"></child>
  588. </keep-alive>
  589. `,
  590. data: {
  591. view: true
  592. },
  593. components: { Child }
  594. }).$mount()
  595. function assert (foo, bar) {
  596. expect(Foo.created.calls.count()).toBe(foo)
  597. expect(Bar.created.calls.count()).toBe(bar)
  598. }
  599. expect(vm.$el.textContent).toBe('foo')
  600. assert(1, 0)
  601. vm.view = false
  602. waitForUpdate(() => {
  603. expect(vm.$el.textContent).toBe('bar')
  604. assert(1, 1)
  605. vm.view = true
  606. }).then(() => {
  607. expect(vm.$el.textContent).toBe('foo')
  608. assert(1, 1)
  609. vm.view = false
  610. }).then(() => {
  611. expect(vm.$el.textContent).toBe('bar')
  612. assert(1, 1)
  613. }).then(done)
  614. })
  615. // #7105
  616. it('should not destroy active instance when pruning cache', done => {
  617. const Foo = {
  618. template: `<div>foo</div>`,
  619. destroyed: jasmine.createSpy('destroyed')
  620. }
  621. const vm = new Vue({
  622. template: `
  623. <div>
  624. <keep-alive :include="include">
  625. <foo/>
  626. </keep-alive>
  627. </div>
  628. `,
  629. data: {
  630. include: ['foo']
  631. },
  632. components: { Foo }
  633. }).$mount()
  634. // condition: a render where a previous component is reused
  635. vm.include = ['foo']
  636. waitForUpdate(() => {
  637. vm.include = ['']
  638. }).then(() => {
  639. expect(Foo.destroyed).not.toHaveBeenCalled()
  640. }).then(done)
  641. })
  642. if (!isIE9) {
  643. it('with transition-mode out-in', done => {
  644. let next
  645. const vm = new Vue({
  646. template: `<div>
  647. <transition name="test" mode="out-in" @after-leave="afterLeave">
  648. <keep-alive>
  649. <component :is="view" class="test"></component>
  650. </keep-alive>
  651. </transition>
  652. </div>`,
  653. data: {
  654. view: 'one'
  655. },
  656. components,
  657. methods: {
  658. afterLeave () {
  659. next()
  660. }
  661. }
  662. }).$mount(el)
  663. expect(vm.$el.textContent).toBe('one')
  664. assertHookCalls(one, [1, 1, 1, 0, 0])
  665. assertHookCalls(two, [0, 0, 0, 0, 0])
  666. vm.view = 'two'
  667. waitForUpdate(() => {
  668. expect(vm.$el.innerHTML).toBe(
  669. '<div class="test test-leave test-leave-active">one</div><!---->'
  670. )
  671. assertHookCalls(one, [1, 1, 1, 1, 0])
  672. assertHookCalls(two, [0, 0, 0, 0, 0])
  673. }).thenWaitFor(nextFrame).then(() => {
  674. expect(vm.$el.innerHTML).toBe(
  675. '<div class="test test-leave-active test-leave-to">one</div><!---->'
  676. )
  677. }).thenWaitFor(_next => { next = _next }).then(() => {
  678. expect(vm.$el.innerHTML).toBe('<!---->')
  679. }).thenWaitFor(nextFrame).then(() => {
  680. expect(vm.$el.innerHTML).toBe(
  681. '<div class="test test-enter test-enter-active">two</div>'
  682. )
  683. assertHookCalls(one, [1, 1, 1, 1, 0])
  684. assertHookCalls(two, [1, 1, 1, 0, 0])
  685. }).thenWaitFor(nextFrame).then(() => {
  686. expect(vm.$el.innerHTML).toBe(
  687. '<div class="test test-enter-active test-enter-to">two</div>'
  688. )
  689. }).thenWaitFor(duration + buffer).then(() => {
  690. expect(vm.$el.innerHTML).toBe(
  691. '<div class="test">two</div>'
  692. )
  693. assertHookCalls(one, [1, 1, 1, 1, 0])
  694. assertHookCalls(two, [1, 1, 1, 0, 0])
  695. }).then(() => {
  696. vm.view = 'one'
  697. }).then(() => {
  698. expect(vm.$el.innerHTML).toBe(
  699. '<div class="test test-leave test-leave-active">two</div><!---->'
  700. )
  701. assertHookCalls(one, [1, 1, 1, 1, 0])
  702. assertHookCalls(two, [1, 1, 1, 1, 0])
  703. }).thenWaitFor(nextFrame).then(() => {
  704. expect(vm.$el.innerHTML).toBe(
  705. '<div class="test test-leave-active test-leave-to">two</div><!---->'
  706. )
  707. }).thenWaitFor(_next => { next = _next }).then(() => {
  708. expect(vm.$el.innerHTML).toBe('<!---->')
  709. }).thenWaitFor(nextFrame).then(() => {
  710. expect(vm.$el.innerHTML).toBe(
  711. '<div class="test test-enter test-enter-active">one</div>'
  712. )
  713. assertHookCalls(one, [1, 1, 2, 1, 0])
  714. assertHookCalls(two, [1, 1, 1, 1, 0])
  715. }).thenWaitFor(nextFrame).then(() => {
  716. expect(vm.$el.innerHTML).toBe(
  717. '<div class="test test-enter-active test-enter-to">one</div>'
  718. )
  719. }).thenWaitFor(duration + buffer).then(() => {
  720. expect(vm.$el.innerHTML).toBe(
  721. '<div class="test">one</div>'
  722. )
  723. assertHookCalls(one, [1, 1, 2, 1, 0])
  724. assertHookCalls(two, [1, 1, 1, 1, 0])
  725. }).then(done)
  726. })
  727. it('with transition-mode out-in + include', done => {
  728. let next
  729. const vm = new Vue({
  730. template: `<div>
  731. <transition name="test" mode="out-in" @after-leave="afterLeave">
  732. <keep-alive include="one">
  733. <component :is="view" class="test"></component>
  734. </keep-alive>
  735. </transition>
  736. </div>`,
  737. data: {
  738. view: 'one'
  739. },
  740. components,
  741. methods: {
  742. afterLeave () {
  743. next()
  744. }
  745. }
  746. }).$mount(el)
  747. expect(vm.$el.textContent).toBe('one')
  748. assertHookCalls(one, [1, 1, 1, 0, 0])
  749. assertHookCalls(two, [0, 0, 0, 0, 0])
  750. vm.view = 'two'
  751. waitForUpdate(() => {
  752. expect(vm.$el.innerHTML).toBe(
  753. '<div class="test test-leave test-leave-active">one</div><!---->'
  754. )
  755. assertHookCalls(one, [1, 1, 1, 1, 0])
  756. assertHookCalls(two, [0, 0, 0, 0, 0])
  757. }).thenWaitFor(nextFrame).then(() => {
  758. expect(vm.$el.innerHTML).toBe(
  759. '<div class="test test-leave-active test-leave-to">one</div><!---->'
  760. )
  761. }).thenWaitFor(_next => { next = _next }).then(() => {
  762. expect(vm.$el.innerHTML).toBe('<!---->')
  763. }).thenWaitFor(nextFrame).then(() => {
  764. expect(vm.$el.innerHTML).toBe(
  765. '<div class="test test-enter test-enter-active">two</div>'
  766. )
  767. assertHookCalls(one, [1, 1, 1, 1, 0])
  768. assertHookCalls(two, [1, 1, 0, 0, 0])
  769. }).thenWaitFor(nextFrame).then(() => {
  770. expect(vm.$el.innerHTML).toBe(
  771. '<div class="test test-enter-active test-enter-to">two</div>'
  772. )
  773. }).thenWaitFor(duration + buffer).then(() => {
  774. expect(vm.$el.innerHTML).toBe(
  775. '<div class="test">two</div>'
  776. )
  777. assertHookCalls(one, [1, 1, 1, 1, 0])
  778. assertHookCalls(two, [1, 1, 0, 0, 0])
  779. }).then(() => {
  780. vm.view = 'one'
  781. }).then(() => {
  782. expect(vm.$el.innerHTML).toBe(
  783. '<div class="test test-leave test-leave-active">two</div><!---->'
  784. )
  785. assertHookCalls(one, [1, 1, 1, 1, 0])
  786. assertHookCalls(two, [1, 1, 0, 0, 1])
  787. }).thenWaitFor(nextFrame).then(() => {
  788. expect(vm.$el.innerHTML).toBe(
  789. '<div class="test test-leave-active test-leave-to">two</div><!---->'
  790. )
  791. }).thenWaitFor(_next => { next = _next }).then(() => {
  792. expect(vm.$el.innerHTML).toBe('<!---->')
  793. }).thenWaitFor(nextFrame).then(() => {
  794. expect(vm.$el.innerHTML).toBe(
  795. '<div class="test test-enter test-enter-active">one</div>'
  796. )
  797. assertHookCalls(one, [1, 1, 2, 1, 0])
  798. assertHookCalls(two, [1, 1, 0, 0, 1])
  799. }).thenWaitFor(nextFrame).then(() => {
  800. expect(vm.$el.innerHTML).toBe(
  801. '<div class="test test-enter-active test-enter-to">one</div>'
  802. )
  803. }).thenWaitFor(duration + buffer).then(() => {
  804. expect(vm.$el.innerHTML).toBe(
  805. '<div class="test">one</div>'
  806. )
  807. assertHookCalls(one, [1, 1, 2, 1, 0])
  808. assertHookCalls(two, [1, 1, 0, 0, 1])
  809. }).then(done)
  810. })
  811. it('with transition-mode in-out', done => {
  812. let next
  813. const vm = new Vue({
  814. template: `<div>
  815. <transition name="test" mode="in-out" @after-enter="afterEnter">
  816. <keep-alive>
  817. <component :is="view" class="test"></component>
  818. </keep-alive>
  819. </transition>
  820. </div>`,
  821. data: {
  822. view: 'one'
  823. },
  824. components,
  825. methods: {
  826. afterEnter () {
  827. next()
  828. }
  829. }
  830. }).$mount(el)
  831. expect(vm.$el.textContent).toBe('one')
  832. assertHookCalls(one, [1, 1, 1, 0, 0])
  833. assertHookCalls(two, [0, 0, 0, 0, 0])
  834. vm.view = 'two'
  835. waitForUpdate(() => {
  836. expect(vm.$el.innerHTML).toBe(
  837. '<div class="test">one</div>' +
  838. '<div class="test test-enter test-enter-active">two</div>'
  839. )
  840. assertHookCalls(one, [1, 1, 1, 1, 0])
  841. assertHookCalls(two, [1, 1, 1, 0, 0])
  842. }).thenWaitFor(nextFrame).then(() => {
  843. expect(vm.$el.innerHTML).toBe(
  844. '<div class="test">one</div>' +
  845. '<div class="test test-enter-active test-enter-to">two</div>'
  846. )
  847. }).thenWaitFor(_next => { next = _next }).then(() => {
  848. expect(vm.$el.innerHTML).toBe(
  849. '<div class="test">one</div>' +
  850. '<div class="test">two</div>'
  851. )
  852. }).then(() => {
  853. expect(vm.$el.innerHTML).toBe(
  854. '<div class="test test-leave test-leave-active">one</div>' +
  855. '<div class="test">two</div>'
  856. )
  857. }).thenWaitFor(nextFrame).then(() => {
  858. expect(vm.$el.innerHTML).toBe(
  859. '<div class="test test-leave-active test-leave-to">one</div>' +
  860. '<div class="test">two</div>'
  861. )
  862. }).thenWaitFor(duration + buffer).then(() => {
  863. expect(vm.$el.innerHTML).toBe(
  864. '<div class="test">two</div>'
  865. )
  866. assertHookCalls(one, [1, 1, 1, 1, 0])
  867. assertHookCalls(two, [1, 1, 1, 0, 0])
  868. }).then(() => {
  869. vm.view = 'one'
  870. }).then(() => {
  871. expect(vm.$el.innerHTML).toBe(
  872. '<div class="test">two</div>' +
  873. '<div class="test test-enter test-enter-active">one</div>'
  874. )
  875. assertHookCalls(one, [1, 1, 2, 1, 0])
  876. assertHookCalls(two, [1, 1, 1, 1, 0])
  877. }).thenWaitFor(nextFrame).then(() => {
  878. expect(vm.$el.innerHTML).toBe(
  879. '<div class="test">two</div>' +
  880. '<div class="test test-enter-active test-enter-to">one</div>'
  881. )
  882. }).thenWaitFor(_next => { next = _next }).then(() => {
  883. expect(vm.$el.innerHTML).toBe(
  884. '<div class="test">two</div>' +
  885. '<div class="test">one</div>'
  886. )
  887. }).then(() => {
  888. expect(vm.$el.innerHTML).toBe(
  889. '<div class="test test-leave test-leave-active">two</div>' +
  890. '<div class="test">one</div>'
  891. )
  892. }).thenWaitFor(nextFrame).then(() => {
  893. expect(vm.$el.innerHTML).toBe(
  894. '<div class="test test-leave-active test-leave-to">two</div>' +
  895. '<div class="test">one</div>'
  896. )
  897. }).thenWaitFor(duration + buffer).then(() => {
  898. expect(vm.$el.innerHTML).toBe(
  899. '<div class="test">one</div>'
  900. )
  901. assertHookCalls(one, [1, 1, 2, 1, 0])
  902. assertHookCalls(two, [1, 1, 1, 1, 0])
  903. }).then(done)
  904. })
  905. it('dynamic components, in-out with early cancel', done => {
  906. let next
  907. const vm = new Vue({
  908. template: `<div>
  909. <transition name="test" mode="in-out" @after-enter="afterEnter">
  910. <keep-alive>
  911. <component :is="view" class="test"></component>
  912. </keep-alive>
  913. </transition>
  914. </div>`,
  915. data: { view: 'one' },
  916. components,
  917. methods: {
  918. afterEnter () {
  919. next()
  920. }
  921. }
  922. }).$mount(el)
  923. expect(vm.$el.textContent).toBe('one')
  924. vm.view = 'two'
  925. waitForUpdate(() => {
  926. expect(vm.$el.innerHTML).toBe(
  927. '<div class="test">one</div>' +
  928. '<div class="test test-enter test-enter-active">two</div>'
  929. )
  930. }).thenWaitFor(nextFrame).then(() => {
  931. expect(vm.$el.innerHTML).toBe(
  932. '<div class="test">one</div>' +
  933. '<div class="test test-enter-active test-enter-to">two</div>'
  934. )
  935. // switch again before enter finishes,
  936. // this cancels both enter and leave.
  937. vm.view = 'one'
  938. }).then(() => {
  939. // 1. the pending leaving "one" should be removed instantly.
  940. // 2. the entering "two" should be placed into its final state instantly.
  941. // 3. a new "one" is created and entering
  942. expect(vm.$el.innerHTML).toBe(
  943. '<div class="test">two</div>' +
  944. '<div class="test test-enter test-enter-active">one</div>'
  945. )
  946. }).thenWaitFor(nextFrame).then(() => {
  947. expect(vm.$el.innerHTML).toBe(
  948. '<div class="test">two</div>' +
  949. '<div class="test test-enter-active test-enter-to">one</div>'
  950. )
  951. }).thenWaitFor(_next => { next = _next }).then(() => {
  952. expect(vm.$el.innerHTML).toBe(
  953. '<div class="test">two</div>' +
  954. '<div class="test">one</div>'
  955. )
  956. }).then(() => {
  957. expect(vm.$el.innerHTML).toBe(
  958. '<div class="test test-leave test-leave-active">two</div>' +
  959. '<div class="test">one</div>'
  960. )
  961. }).thenWaitFor(nextFrame).then(() => {
  962. expect(vm.$el.innerHTML).toBe(
  963. '<div class="test test-leave-active test-leave-to">two</div>' +
  964. '<div class="test">one</div>'
  965. )
  966. }).thenWaitFor(duration + buffer).then(() => {
  967. expect(vm.$el.innerHTML).toBe(
  968. '<div class="test">one</div>'
  969. )
  970. }).then(done).then(done)
  971. })
  972. // #4339
  973. it('component with inner transition', done => {
  974. const vm = new Vue({
  975. template: `
  976. <div>
  977. <keep-alive>
  978. <component ref="test" :is="view"></component>
  979. </keep-alive>
  980. </div>
  981. `,
  982. data: { view: 'foo' },
  983. components: {
  984. foo: { template: '<transition><div class="test">foo</div></transition>' },
  985. bar: { template: '<transition name="test"><div class="test">bar</div></transition>' }
  986. }
  987. }).$mount(el)
  988. // should not apply transition on initial render by default
  989. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  990. vm.view = 'bar'
  991. waitForUpdate(() => {
  992. expect(vm.$el.innerHTML).toBe(
  993. '<div class="test v-leave v-leave-active">foo</div>' +
  994. '<div class="test test-enter test-enter-active">bar</div>'
  995. )
  996. }).thenWaitFor(nextFrame).then(() => {
  997. expect(vm.$el.innerHTML).toBe(
  998. '<div class="test v-leave-active v-leave-to">foo</div>' +
  999. '<div class="test test-enter-active test-enter-to">bar</div>'
  1000. )
  1001. }).thenWaitFor(duration + buffer).then(() => {
  1002. expect(vm.$el.innerHTML).toBe(
  1003. '<div class="test">bar</div>'
  1004. )
  1005. vm.view = 'foo'
  1006. }).then(() => {
  1007. expect(vm.$el.innerHTML).toBe(
  1008. '<div class="test test-leave test-leave-active">bar</div>' +
  1009. '<div class="test v-enter v-enter-active">foo</div>'
  1010. )
  1011. }).thenWaitFor(nextFrame).then(() => {
  1012. expect(vm.$el.innerHTML).toBe(
  1013. '<div class="test test-leave-active test-leave-to">bar</div>' +
  1014. '<div class="test v-enter-active v-enter-to">foo</div>'
  1015. )
  1016. }).thenWaitFor(duration + buffer).then(() => {
  1017. expect(vm.$el.innerHTML).toBe(
  1018. '<div class="test">foo</div>'
  1019. )
  1020. }).then(done)
  1021. })
  1022. it('async components with transition-mode out-in', done => {
  1023. const barResolve = jasmine.createSpy('bar resolved')
  1024. let next
  1025. const foo = (resolve) => {
  1026. setTimeout(() => {
  1027. resolve(one)
  1028. Vue.nextTick(next)
  1029. }, duration / 2)
  1030. }
  1031. const bar = (resolve) => {
  1032. setTimeout(() => {
  1033. resolve(two)
  1034. barResolve()
  1035. }, duration / 2)
  1036. }
  1037. components = {
  1038. foo,
  1039. bar
  1040. }
  1041. const vm = new Vue({
  1042. template: `<div>
  1043. <transition name="test" mode="out-in" @after-enter="afterEnter" @after-leave="afterLeave">
  1044. <keep-alive>
  1045. <component :is="view" class="test"></component>
  1046. </keep-alive>
  1047. </transition>
  1048. </div>`,
  1049. data: {
  1050. view: 'foo'
  1051. },
  1052. components,
  1053. methods: {
  1054. afterEnter () {
  1055. next()
  1056. },
  1057. afterLeave () {
  1058. next()
  1059. }
  1060. }
  1061. }).$mount(el)
  1062. expect(vm.$el.textContent).toBe('')
  1063. next = () => {
  1064. assertHookCalls(one, [1, 1, 1, 0, 0])
  1065. assertHookCalls(two, [0, 0, 0, 0, 0])
  1066. waitForUpdate(() => {
  1067. expect(vm.$el.innerHTML).toBe(
  1068. '<div class="test test-enter test-enter-active">one</div>'
  1069. )
  1070. }).thenWaitFor(nextFrame).then(() => {
  1071. expect(vm.$el.innerHTML).toBe(
  1072. '<div class="test test-enter-active test-enter-to">one</div>'
  1073. )
  1074. }).thenWaitFor(_next => { next = _next }).then(() => {
  1075. // foo afterEnter get called
  1076. expect(vm.$el.innerHTML).toBe('<div class="test">one</div>')
  1077. vm.view = 'bar'
  1078. }).thenWaitFor(nextFrame).then(() => {
  1079. assertHookCalls(one, [1, 1, 1, 1, 0])
  1080. assertHookCalls(two, [0, 0, 0, 0, 0])
  1081. expect(vm.$el.innerHTML).toBe(
  1082. '<div class="test test-leave-active test-leave-to">one</div><!---->'
  1083. )
  1084. }).thenWaitFor(_next => { next = _next }).then(() => {
  1085. // foo afterLeave get called
  1086. // and bar has already been resolved before afterLeave get called
  1087. expect(barResolve.calls.count()).toBe(1)
  1088. expect(vm.$el.innerHTML).toBe('<!---->')
  1089. }).thenWaitFor(nextFrame).then(() => {
  1090. expect(vm.$el.innerHTML).toBe(
  1091. '<div class="test test-enter test-enter-active">two</div>'
  1092. )
  1093. assertHookCalls(one, [1, 1, 1, 1, 0])
  1094. assertHookCalls(two, [1, 1, 1, 0, 0])
  1095. }).thenWaitFor(nextFrame).then(() => {
  1096. expect(vm.$el.innerHTML).toBe(
  1097. '<div class="test test-enter-active test-enter-to">two</div>'
  1098. )
  1099. }).thenWaitFor(_next => { next = _next }).then(() => {
  1100. // bar afterEnter get called
  1101. expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
  1102. }).then(done)
  1103. }
  1104. })
  1105. }
  1106. })