component_spec.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. var _ = require('../../../../src/util')
  2. var Vue = require('../../../../src/vue')
  3. if (_.inBrowser) {
  4. describe('v-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('keep-alive', function (done) {
  131. var spyA = jasmine.createSpy()
  132. var spyB = jasmine.createSpy()
  133. var vm = new Vue({
  134. el: el,
  135. template: '<component is="{{view}}" keep-alive></component>',
  136. data: {
  137. view: 'view-a'
  138. },
  139. components: {
  140. 'view-a': {
  141. created: spyA,
  142. template: '<div>AAA</div>',
  143. replace: true
  144. },
  145. 'view-b': {
  146. created: spyB,
  147. template: '<div>BBB</div>',
  148. replace: true
  149. }
  150. }
  151. })
  152. expect(el.innerHTML).toBe('<div>AAA</div>')
  153. expect(spyA.calls.count()).toBe(1)
  154. expect(spyB.calls.count()).toBe(0)
  155. vm.view = 'view-b'
  156. _.nextTick(function () {
  157. expect(el.innerHTML).toBe('<div>BBB</div>')
  158. expect(spyA.calls.count()).toBe(1)
  159. expect(spyB.calls.count()).toBe(1)
  160. vm.view = 'view-a'
  161. _.nextTick(function () {
  162. expect(el.innerHTML).toBe('<div>AAA</div>')
  163. expect(spyA.calls.count()).toBe(1)
  164. expect(spyB.calls.count()).toBe(1)
  165. vm.view = 'view-b'
  166. _.nextTick(function () {
  167. expect(el.innerHTML).toBe('<div>BBB</div>')
  168. expect(spyA.calls.count()).toBe(1)
  169. expect(spyB.calls.count()).toBe(1)
  170. done()
  171. })
  172. })
  173. })
  174. })
  175. it('should compile parent template directives & content in parent scope', function (done) {
  176. var vm = new Vue({
  177. el: el,
  178. data: {
  179. ok: false,
  180. message: 'hello'
  181. },
  182. template: '<test v-show="ok">{{message}}</test>',
  183. components: {
  184. test: {
  185. template: '<div><content></content> {{message}}</div>',
  186. replace: true,
  187. data: function () {
  188. return {
  189. message: 'world'
  190. }
  191. }
  192. }
  193. }
  194. })
  195. expect(el.firstChild.style.display).toBe('none')
  196. expect(el.firstChild.textContent).toBe('hello world')
  197. vm.ok = true
  198. vm.message = 'bye'
  199. _.nextTick(function () {
  200. expect(el.firstChild.style.display).toBe('')
  201. expect(el.firstChild.textContent).toBe('bye world')
  202. done()
  203. })
  204. })
  205. it('parent content + v-if', function (done) {
  206. var vm = new Vue({
  207. el: el,
  208. data: {
  209. ok: false,
  210. message: 'hello'
  211. },
  212. template: '<test v-if="ok">{{message}}</test>',
  213. components: {
  214. test: {
  215. template: '<content></content> {{message}}',
  216. data: function () {
  217. return {
  218. message: 'world'
  219. }
  220. }
  221. }
  222. }
  223. })
  224. expect(el.textContent).toBe('')
  225. expect(vm.$children.length).toBe(0)
  226. expect(vm._directives.length).toBe(1) // v-if
  227. vm.ok = true
  228. _.nextTick(function () {
  229. expect(vm.$children.length).toBe(1)
  230. expect(vm._directives.length).toBe(3) // v-if, v-component, v-text
  231. expect(el.textContent).toBe('hello world')
  232. done()
  233. })
  234. })
  235. it('props', function () {
  236. new Vue({
  237. el: el,
  238. data: {
  239. list: [{a: 1}, {a: 2}]
  240. },
  241. template: '<test collection="{{list}}"></test>',
  242. components: {
  243. test: {
  244. template: '<ul><li v-repeat="collection">{{a}}</li></ul>',
  245. replace: true,
  246. props: ['collection']
  247. }
  248. }
  249. })
  250. expect(el.innerHTML).toBe('<ul><li>1</li><li>2</li></ul>')
  251. })
  252. it('wait-for for static component', function () {
  253. var vm = new Vue({
  254. el: el,
  255. template: '<view-a wait-for="ok"></view-a>',
  256. components: {
  257. 'view-a': {
  258. template: 'AAA'
  259. }
  260. }
  261. })
  262. expect(el.textContent).toBe('')
  263. vm.$children[0].$emit('ok')
  264. expect(el.textContent).toBe('AAA')
  265. })
  266. it('sync wait-for inside compiled hook', function () {
  267. new Vue({
  268. el: el,
  269. template: '<view-a wait-for="ok"></view-a>',
  270. components: {
  271. 'view-a': {
  272. template: 'AAA',
  273. compiled: function () {
  274. expect(el.textContent).toBe('')
  275. this.$emit('ok')
  276. }
  277. }
  278. }
  279. })
  280. expect(el.textContent).toBe('AAA')
  281. })
  282. it('wait-for for dynamic components', function (done) {
  283. var vm = new Vue({
  284. el: el,
  285. data: {
  286. view: 'view-a'
  287. },
  288. template: '<component is="{{view}}" wait-for="ok"></component>',
  289. components: {
  290. 'view-a': {
  291. template: 'AAA'
  292. },
  293. 'view-b': {
  294. template: 'BBB'
  295. }
  296. }
  297. })
  298. vm.$children[0].$emit('ok')
  299. expect(el.textContent).toBe('AAA')
  300. vm.view = 'view-b'
  301. _.nextTick(function () {
  302. expect(el.textContent).toBe('AAA')
  303. // old vm is already removed, this is the new vm
  304. expect(vm.$children.length).toBe(1)
  305. vm.$children[0].$emit('ok')
  306. expect(el.textContent).toBe('BBB')
  307. // ensure switching before ready event correctly
  308. // cleans up the component being waited on.
  309. // see #1152
  310. vm.view = 'view-a'
  311. _.nextTick(function () {
  312. vm.view = 'view-b'
  313. _.nextTick(function () {
  314. expect(vm.$children.length).toBe(1)
  315. expect(vm.$children[0].$el.textContent).toBe('BBB')
  316. done()
  317. })
  318. })
  319. })
  320. })
  321. // #1150
  322. it('wait-for + keep-alive', function (done) {
  323. var vm = new Vue({
  324. el: el,
  325. data: {
  326. view: 'view-a'
  327. },
  328. template: '<component is="{{view}}" wait-for="ok" keep-alive></component>',
  329. components: {
  330. 'view-a': {
  331. template: 'AAA'
  332. },
  333. 'view-b': {
  334. template: 'BBB'
  335. }
  336. }
  337. })
  338. vm.$children[0].$emit('ok')
  339. expect(el.textContent).toBe('AAA')
  340. vm.view = 'view-b'
  341. _.nextTick(function () {
  342. expect(vm.$children.length).toBe(2)
  343. vm.$children[1].$emit('ok')
  344. expect(el.textContent).toBe('BBB')
  345. vm.view = 'view-a'
  346. _.nextTick(function () {
  347. // should switch without the need to emit
  348. // because of keep-alive
  349. expect(el.textContent).toBe('AAA')
  350. done()
  351. })
  352. })
  353. })
  354. it('transition-mode: in-out', function (done) {
  355. var spy1 = jasmine.createSpy('enter')
  356. var spy2 = jasmine.createSpy('leave')
  357. var next
  358. var vm = new Vue({
  359. el: el,
  360. data: {
  361. view: 'view-a'
  362. },
  363. template: '<component is="{{view}}" v-transition="test" transition-mode="in-out"></component>',
  364. components: {
  365. 'view-a': { template: 'AAA' },
  366. 'view-b': { template: 'BBB' }
  367. },
  368. transitions: {
  369. test: {
  370. enter: function (el, done) {
  371. spy1()
  372. next = done
  373. },
  374. leave: function (el, done) {
  375. spy2()
  376. _.nextTick(done)
  377. }
  378. }
  379. }
  380. })
  381. expect(el.textContent).toBe('AAA')
  382. vm.view = 'view-b'
  383. _.nextTick(function () {
  384. expect(spy1).toHaveBeenCalled()
  385. expect(spy2).not.toHaveBeenCalled()
  386. expect(el.textContent).toBe('AAABBB')
  387. next()
  388. _.nextTick(function () {
  389. expect(spy2).toHaveBeenCalled()
  390. _.nextTick(function () {
  391. expect(el.textContent).toBe('BBB')
  392. done()
  393. })
  394. })
  395. })
  396. })
  397. it('transition-mode: out-in', function (done) {
  398. var spy1 = jasmine.createSpy('enter')
  399. var spy2 = jasmine.createSpy('leave')
  400. var next
  401. var vm = new Vue({
  402. el: el,
  403. data: {
  404. view: 'view-a'
  405. },
  406. template: '<component is="{{view}}" v-transition="test" transition-mode="out-in"></component>',
  407. components: {
  408. 'view-a': { template: 'AAA' },
  409. 'view-b': { template: 'BBB' }
  410. },
  411. transitions: {
  412. test: {
  413. enter: function (el, done) {
  414. spy2()
  415. _.nextTick(done)
  416. },
  417. leave: function (el, done) {
  418. spy1()
  419. next = done
  420. }
  421. }
  422. }
  423. })
  424. expect(el.textContent).toBe('AAA')
  425. vm.view = 'view-b'
  426. _.nextTick(function () {
  427. expect(spy1).toHaveBeenCalled()
  428. expect(spy2).not.toHaveBeenCalled()
  429. expect(el.textContent).toBe('AAA')
  430. next()
  431. expect(spy2).toHaveBeenCalled()
  432. expect(el.textContent).toBe('BBB')
  433. done()
  434. })
  435. })
  436. it('teardown', function (done) {
  437. var vm = new Vue({
  438. el: el,
  439. template: '<component is="{{view}}" keep-alive></component>',
  440. data: {
  441. view: 'test'
  442. },
  443. components: {
  444. test: {},
  445. test2: {}
  446. }
  447. })
  448. vm.view = 'test2'
  449. _.nextTick(function () {
  450. expect(vm.$children.length).toBe(2)
  451. var child = vm.$children[0]
  452. var child2 = vm.$children[1]
  453. vm._directives[0].unbind()
  454. expect(vm._directives[0].cache).toBeNull()
  455. expect(vm.$children.length).toBe(0)
  456. expect(child._isDestroyed).toBe(true)
  457. expect(child2._isDestroyed).toBe(true)
  458. done()
  459. })
  460. })
  461. it('already mounted warn', function () {
  462. el.setAttribute('v-_component', 'test')
  463. new Vue({
  464. el: el
  465. })
  466. expect(hasWarned(_, 'cannot mount component "test" on already mounted element')).toBe(true)
  467. })
  468. it('not found component should not throw', function () {
  469. expect(function () {
  470. new Vue({
  471. el: el,
  472. template: '<div v-component="non-existent"></div>'
  473. })
  474. }).not.toThrow()
  475. })
  476. })
  477. }