component_spec.js 14 KB

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