component_spec.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. var _ = require('src/util')
  2. var Vue = require('src')
  3. describe('Component', function () {
  4. var el
  5. beforeEach(function () {
  6. el = document.createElement('div')
  7. document.body.appendChild(el)
  8. spyWarns()
  9. })
  10. afterEach(function () {
  11. document.body.removeChild(el)
  12. })
  13. it('static', function () {
  14. new Vue({
  15. el: el,
  16. template: '<test></test>',
  17. components: {
  18. test: {
  19. data: function () {
  20. return { a: 123 }
  21. },
  22. template: '{{a}}'
  23. }
  24. }
  25. })
  26. expect(el.innerHTML).toBe('<test>123</test>')
  27. })
  28. it('replace', function () {
  29. new Vue({
  30. el: el,
  31. template: '<test></test>',
  32. components: {
  33. test: {
  34. replace: true,
  35. data: function () {
  36. return { a: 123 }
  37. },
  38. template: '<p>{{a}}</p>'
  39. }
  40. }
  41. })
  42. expect(el.innerHTML).toBe('<p>123</p>')
  43. })
  44. it('"is" on table elements', function () {
  45. var vm = new Vue({
  46. el: el,
  47. template: '<table><tbody><tr is="test"></tr></tbody></table>',
  48. components: {
  49. test: {
  50. data: function () {
  51. return { a: 123 }
  52. },
  53. template: '<td>{{a}}</td>'
  54. }
  55. }
  56. })
  57. expect(el.innerHTML).toBe(vm.$options.template.replace(/<tr.*\/tr>/, '<tr><td>123</td></tr>'))
  58. expect(getWarnCount()).toBe(0)
  59. })
  60. it('inline-template', function () {
  61. new Vue({
  62. el: el,
  63. template: '<test inline-template>{{a}}</test>',
  64. data: {
  65. a: 'parent'
  66. },
  67. components: {
  68. test: {
  69. data: function () {
  70. return { a: 'child' }
  71. },
  72. template: 'child option template'
  73. }
  74. }
  75. })
  76. expect(el.innerHTML).toBe('<test>child</test>')
  77. })
  78. it('block replace', function () {
  79. new Vue({
  80. el: el,
  81. template: '<test></test>',
  82. components: {
  83. test: {
  84. replace: true,
  85. data: function () {
  86. return { a: 123, b: 234 }
  87. },
  88. template: '<p>{{a}}</p><p>{{b}}</p>'
  89. }
  90. }
  91. })
  92. expect(el.innerHTML).toBe('<p>123</p><p>234</p>')
  93. })
  94. it('dynamic', function (done) {
  95. var vm = new Vue({
  96. el: el,
  97. template: '<component :is="view" :view="view"></component>',
  98. data: {
  99. view: 'view-a'
  100. },
  101. components: {
  102. 'view-a': {
  103. template: '<div>AAA</div>',
  104. replace: true,
  105. data: function () {
  106. return { view: 'a' }
  107. }
  108. },
  109. 'view-b': {
  110. template: '<div>BBB</div>',
  111. replace: true,
  112. data: function () {
  113. return { view: 'b' }
  114. }
  115. }
  116. }
  117. })
  118. expect(el.innerHTML).toBe('<div view="view-a">AAA</div>')
  119. vm.view = 'view-b'
  120. _.nextTick(function () {
  121. expect(el.innerHTML).toBe('<div view="view-b">BBB</div>')
  122. vm.view = ''
  123. _.nextTick(function () {
  124. expect(el.innerHTML).toBe('')
  125. done()
  126. })
  127. })
  128. })
  129. it('keep-alive', function (done) {
  130. var spyA = jasmine.createSpy()
  131. var spyB = jasmine.createSpy()
  132. var vm = new Vue({
  133. el: el,
  134. template: '<component :is="view" keep-alive></component>',
  135. data: {
  136. view: 'view-a'
  137. },
  138. components: {
  139. 'view-a': {
  140. created: spyA,
  141. template: '<div>AAA</div>',
  142. replace: true
  143. },
  144. 'view-b': {
  145. created: spyB,
  146. template: '<div>BBB</div>',
  147. replace: true
  148. }
  149. }
  150. })
  151. expect(el.innerHTML).toBe('<div>AAA</div>')
  152. expect(spyA.calls.count()).toBe(1)
  153. expect(spyB.calls.count()).toBe(0)
  154. vm.view = 'view-b'
  155. _.nextTick(function () {
  156. expect(el.innerHTML).toBe('<div>BBB</div>')
  157. expect(spyA.calls.count()).toBe(1)
  158. expect(spyB.calls.count()).toBe(1)
  159. vm.view = 'view-a'
  160. _.nextTick(function () {
  161. expect(el.innerHTML).toBe('<div>AAA</div>')
  162. expect(spyA.calls.count()).toBe(1)
  163. expect(spyB.calls.count()).toBe(1)
  164. vm.view = 'view-b'
  165. _.nextTick(function () {
  166. expect(el.innerHTML).toBe('<div>BBB</div>')
  167. expect(spyA.calls.count()).toBe(1)
  168. expect(spyB.calls.count()).toBe(1)
  169. done()
  170. })
  171. })
  172. })
  173. })
  174. it('should compile parent template directives & content in parent scope', function (done) {
  175. var vm = new Vue({
  176. el: el,
  177. data: {
  178. ok: false,
  179. message: 'hello'
  180. },
  181. template: '<test v-show="ok">{{message}}</test>',
  182. components: {
  183. test: {
  184. template: '<div><slot></slot> {{message}}</div>',
  185. replace: true,
  186. data: function () {
  187. return {
  188. message: 'world'
  189. }
  190. }
  191. }
  192. }
  193. })
  194. expect(el.firstChild.style.display).toBe('none')
  195. expect(el.firstChild.textContent).toBe('hello world')
  196. vm.ok = true
  197. vm.message = 'bye'
  198. _.nextTick(function () {
  199. expect(el.firstChild.style.display).toBe('')
  200. expect(el.firstChild.textContent).toBe('bye world')
  201. done()
  202. })
  203. })
  204. it('parent content + v-if', function (done) {
  205. var vm = new Vue({
  206. el: el,
  207. data: {
  208. ok: false,
  209. message: 'hello'
  210. },
  211. template: '<test v-if="ok">{{message}}</test>',
  212. components: {
  213. test: {
  214. template: '<slot></slot> {{message}}',
  215. data: function () {
  216. return {
  217. message: 'world'
  218. }
  219. }
  220. }
  221. }
  222. })
  223. expect(el.textContent).toBe('')
  224. expect(vm.$children.length).toBe(0)
  225. expect(vm._directives.length).toBe(1) // v-if
  226. vm.ok = true
  227. _.nextTick(function () {
  228. expect(vm.$children.length).toBe(1)
  229. expect(vm._directives.length).toBe(3) // v-if, component, v-text
  230. expect(el.textContent).toBe('hello world')
  231. done()
  232. })
  233. })
  234. it('props', function () {
  235. new Vue({
  236. el: el,
  237. data: {
  238. list: [{a: 1}, {a: 2}]
  239. },
  240. template: '<test :collection="list"></test>',
  241. components: {
  242. test: {
  243. template: '<ul><li v-for="item in collection">{{item.a}}</li></ul>',
  244. replace: true,
  245. props: ['collection']
  246. }
  247. }
  248. })
  249. expect(el.innerHTML).toBe('<ul><li>1</li><li>2</li></ul>')
  250. })
  251. it('activate hook for static component', function (done) {
  252. new Vue({
  253. el: el,
  254. template: '<view-a></view-a>',
  255. components: {
  256. 'view-a': {
  257. template: 'AAA',
  258. activate: function (ready) {
  259. setTimeout(function () {
  260. expect(el.textContent).toBe('')
  261. ready()
  262. expect(el.textContent).toBe('AAA')
  263. done()
  264. }, 0)
  265. }
  266. }
  267. }
  268. })
  269. })
  270. it('multiple activate hooks', function (done) {
  271. var mixinSpy = jasmine.createSpy('mixin activate')
  272. new Vue({
  273. el: el,
  274. template: '<view-a></view-a>',
  275. components: {
  276. 'view-a': {
  277. template: 'AAA',
  278. mixins: [{
  279. activate: function (done) {
  280. expect(el.textContent).toBe('')
  281. mixinSpy()
  282. done()
  283. }
  284. }],
  285. activate: function (ready) {
  286. setTimeout(function () {
  287. expect(mixinSpy).toHaveBeenCalled()
  288. expect(el.textContent).toBe('')
  289. ready()
  290. expect(el.textContent).toBe('AAA')
  291. done()
  292. }, 0)
  293. }
  294. }
  295. }
  296. })
  297. })
  298. it('activate hook for dynamic components', function (done) {
  299. var next
  300. var vm = new Vue({
  301. el: el,
  302. data: {
  303. view: 'view-a'
  304. },
  305. template: '<component :is="view"></component>',
  306. components: {
  307. 'view-a': {
  308. template: 'AAA',
  309. activate: function (ready) {
  310. next = ready
  311. }
  312. },
  313. 'view-b': {
  314. template: 'BBB',
  315. activate: function (ready) {
  316. next = ready
  317. }
  318. }
  319. }
  320. })
  321. expect(next).toBeTruthy()
  322. expect(el.textContent).toBe('')
  323. next()
  324. expect(el.textContent).toBe('AAA')
  325. vm.view = 'view-b'
  326. _.nextTick(function () {
  327. expect(el.textContent).toBe('AAA')
  328. // old vm is already removed, this is the new vm
  329. expect(vm.$children.length).toBe(1)
  330. next()
  331. expect(el.textContent).toBe('BBB')
  332. // ensure switching before ready event correctly
  333. // cleans up the component being waited on.
  334. // see #1152
  335. vm.view = 'view-a'
  336. // store the ready callback for view-a
  337. var callback = next
  338. _.nextTick(function () {
  339. vm.view = 'view-b'
  340. _.nextTick(function () {
  341. expect(vm.$children.length).toBe(1)
  342. expect(vm.$children[0].$el.textContent).toBe('BBB')
  343. // calling view-a's ready callback here should not throw
  344. // because it should've been cancelled (#1994)
  345. expect(callback).not.toThrow()
  346. done()
  347. })
  348. })
  349. })
  350. })
  351. it('activate hook + keep-alive', function (done) {
  352. var next
  353. var vm = new Vue({
  354. el: el,
  355. data: {
  356. view: 'view-a'
  357. },
  358. template: '<component :is="view" keep-alive></component>',
  359. components: {
  360. 'view-a': {
  361. template: 'AAA',
  362. activate: function (ready) {
  363. next = ready
  364. }
  365. },
  366. 'view-b': {
  367. template: 'BBB',
  368. activate: function (ready) {
  369. next = ready
  370. }
  371. }
  372. }
  373. })
  374. next()
  375. expect(el.textContent).toBe('AAA')
  376. vm.view = 'view-b'
  377. _.nextTick(function () {
  378. expect(vm.$children.length).toBe(2)
  379. next()
  380. expect(el.textContent).toBe('BBB')
  381. vm.view = 'view-a'
  382. _.nextTick(function () {
  383. // should switch without the need to emit
  384. // because of keep-alive
  385. expect(el.textContent).toBe('AAA')
  386. done()
  387. })
  388. })
  389. })
  390. it('transition-mode: in-out', function (done) {
  391. var spy1 = jasmine.createSpy('enter')
  392. var spy2 = jasmine.createSpy('leave')
  393. var next
  394. var vm = new Vue({
  395. el: el,
  396. data: {
  397. view: 'view-a'
  398. },
  399. template: '<component :is="view" transition="test" transition-mode="in-out"></component>',
  400. components: {
  401. 'view-a': { template: 'AAA' },
  402. 'view-b': { template: 'BBB' }
  403. },
  404. transitions: {
  405. test: {
  406. enter: function (el, done) {
  407. spy1()
  408. next = done
  409. },
  410. leave: function (el, done) {
  411. spy2()
  412. _.nextTick(done)
  413. }
  414. }
  415. }
  416. })
  417. expect(el.textContent).toBe('AAA')
  418. vm.view = 'view-b'
  419. _.nextTick(function () {
  420. expect(spy1).toHaveBeenCalled()
  421. expect(spy2).not.toHaveBeenCalled()
  422. expect(el.textContent).toBe('AAABBB')
  423. next()
  424. _.nextTick(function () {
  425. expect(spy2).toHaveBeenCalled()
  426. _.nextTick(function () {
  427. expect(el.textContent).toBe('BBB')
  428. done()
  429. })
  430. })
  431. })
  432. })
  433. it('transition-mode: out-in', function (done) {
  434. var spy1 = jasmine.createSpy('enter')
  435. var spy2 = jasmine.createSpy('leave')
  436. var next
  437. var vm = new Vue({
  438. el: el,
  439. data: {
  440. view: 'view-a'
  441. },
  442. template: '<component :is="view" transition="test" transition-mode="out-in"></component>',
  443. components: {
  444. 'view-a': { template: 'AAA' },
  445. 'view-b': { template: 'BBB' }
  446. },
  447. transitions: {
  448. test: {
  449. enter: function (el, done) {
  450. spy2()
  451. _.nextTick(done)
  452. },
  453. leave: function (el, done) {
  454. spy1()
  455. next = done
  456. }
  457. }
  458. }
  459. })
  460. expect(el.textContent).toBe('AAA')
  461. vm.view = 'view-b'
  462. _.nextTick(function () {
  463. expect(spy1).toHaveBeenCalled()
  464. expect(spy2).not.toHaveBeenCalled()
  465. expect(el.textContent).toBe('AAA')
  466. next()
  467. expect(spy2).toHaveBeenCalled()
  468. expect(el.textContent).toBe('BBB')
  469. done()
  470. })
  471. })
  472. it('teardown', function (done) {
  473. var vm = new Vue({
  474. el: el,
  475. template: '<component :is="view" keep-alive></component>',
  476. data: {
  477. view: 'test'
  478. },
  479. components: {
  480. test: {},
  481. test2: {}
  482. }
  483. })
  484. vm.view = 'test2'
  485. _.nextTick(function () {
  486. expect(vm.$children.length).toBe(2)
  487. var child = vm.$children[0]
  488. var child2 = vm.$children[1]
  489. vm._directives[0].unbind()
  490. expect(vm._directives[0].cache).toBeNull()
  491. expect(vm.$children.length).toBe(0)
  492. expect(child._isDestroyed).toBe(true)
  493. expect(child2._isDestroyed).toBe(true)
  494. done()
  495. })
  496. })
  497. it('already mounted warn', function () {
  498. el.setAttribute('is', 'test')
  499. new Vue({
  500. el: el
  501. })
  502. expect(hasWarned('cannot mount component "test" on already mounted element')).toBe(true)
  503. })
  504. it('not found component should not throw', function () {
  505. expect(function () {
  506. new Vue({
  507. el: el,
  508. template: '<div is="non-existent"></div>'
  509. })
  510. }).not.toThrow()
  511. })
  512. })