component_spec.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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('"is" on table elements', function () {
  46. var vm = new Vue({
  47. el: el,
  48. template: '<table><tbody><tr is="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" :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><slot></slot> {{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: '<slot></slot> {{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, 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-for="item in collection">{{item.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('activate hook for static component', function (done) {
  253. new Vue({
  254. el: el,
  255. template: '<view-a></view-a>',
  256. components: {
  257. 'view-a': {
  258. template: 'AAA',
  259. activate: function (ready) {
  260. setTimeout(function () {
  261. expect(el.textContent).toBe('')
  262. ready()
  263. expect(el.textContent).toBe('AAA')
  264. done()
  265. }, 0)
  266. }
  267. }
  268. }
  269. })
  270. })
  271. it('activate hook for dynamic components', function (done) {
  272. var next
  273. var vm = new Vue({
  274. el: el,
  275. data: {
  276. view: 'view-a'
  277. },
  278. template: '<component :is="view"></component>',
  279. components: {
  280. 'view-a': {
  281. template: 'AAA',
  282. activate: function (ready) {
  283. next = ready
  284. }
  285. },
  286. 'view-b': {
  287. template: 'BBB',
  288. activate: function (ready) {
  289. next = ready
  290. }
  291. }
  292. }
  293. })
  294. expect(next).toBeTruthy()
  295. expect(el.textContent).toBe('')
  296. next()
  297. expect(el.textContent).toBe('AAA')
  298. vm.view = 'view-b'
  299. _.nextTick(function () {
  300. expect(el.textContent).toBe('AAA')
  301. // old vm is already removed, this is the new vm
  302. expect(vm.$children.length).toBe(1)
  303. next()
  304. expect(el.textContent).toBe('BBB')
  305. // ensure switching before ready event correctly
  306. // cleans up the component being waited on.
  307. // see #1152
  308. vm.view = 'view-a'
  309. _.nextTick(function () {
  310. vm.view = 'view-b'
  311. _.nextTick(function () {
  312. expect(vm.$children.length).toBe(1)
  313. expect(vm.$children[0].$el.textContent).toBe('BBB')
  314. done()
  315. })
  316. })
  317. })
  318. })
  319. it('activate hook + keep-alive', function (done) {
  320. var next
  321. var vm = new Vue({
  322. el: el,
  323. data: {
  324. view: 'view-a'
  325. },
  326. template: '<component :is="view" keep-alive></component>',
  327. components: {
  328. 'view-a': {
  329. template: 'AAA',
  330. activate: function (ready) {
  331. next = ready
  332. }
  333. },
  334. 'view-b': {
  335. template: 'BBB',
  336. activate: function (ready) {
  337. next = ready
  338. }
  339. }
  340. }
  341. })
  342. next()
  343. expect(el.textContent).toBe('AAA')
  344. vm.view = 'view-b'
  345. _.nextTick(function () {
  346. expect(vm.$children.length).toBe(2)
  347. next()
  348. expect(el.textContent).toBe('BBB')
  349. vm.view = 'view-a'
  350. _.nextTick(function () {
  351. // should switch without the need to emit
  352. // because of keep-alive
  353. expect(el.textContent).toBe('AAA')
  354. done()
  355. })
  356. })
  357. })
  358. it('transition-mode: in-out', function (done) {
  359. var spy1 = jasmine.createSpy('enter')
  360. var spy2 = jasmine.createSpy('leave')
  361. var next
  362. var vm = new Vue({
  363. el: el,
  364. data: {
  365. view: 'view-a'
  366. },
  367. template: '<component :is="view" transition="test" transition-mode="in-out"></component>',
  368. components: {
  369. 'view-a': { template: 'AAA' },
  370. 'view-b': { template: 'BBB' }
  371. },
  372. transitions: {
  373. test: {
  374. enter: function (el, done) {
  375. spy1()
  376. next = done
  377. },
  378. leave: function (el, done) {
  379. spy2()
  380. _.nextTick(done)
  381. }
  382. }
  383. }
  384. })
  385. expect(el.textContent).toBe('AAA')
  386. vm.view = 'view-b'
  387. _.nextTick(function () {
  388. expect(spy1).toHaveBeenCalled()
  389. expect(spy2).not.toHaveBeenCalled()
  390. expect(el.textContent).toBe('AAABBB')
  391. next()
  392. _.nextTick(function () {
  393. expect(spy2).toHaveBeenCalled()
  394. _.nextTick(function () {
  395. expect(el.textContent).toBe('BBB')
  396. done()
  397. })
  398. })
  399. })
  400. })
  401. it('transition-mode: out-in', function (done) {
  402. var spy1 = jasmine.createSpy('enter')
  403. var spy2 = jasmine.createSpy('leave')
  404. var next
  405. var vm = new Vue({
  406. el: el,
  407. data: {
  408. view: 'view-a'
  409. },
  410. template: '<component :is="view" transition="test" transition-mode="out-in"></component>',
  411. components: {
  412. 'view-a': { template: 'AAA' },
  413. 'view-b': { template: 'BBB' }
  414. },
  415. transitions: {
  416. test: {
  417. enter: function (el, done) {
  418. spy2()
  419. _.nextTick(done)
  420. },
  421. leave: function (el, done) {
  422. spy1()
  423. next = done
  424. }
  425. }
  426. }
  427. })
  428. expect(el.textContent).toBe('AAA')
  429. vm.view = 'view-b'
  430. _.nextTick(function () {
  431. expect(spy1).toHaveBeenCalled()
  432. expect(spy2).not.toHaveBeenCalled()
  433. expect(el.textContent).toBe('AAA')
  434. next()
  435. expect(spy2).toHaveBeenCalled()
  436. expect(el.textContent).toBe('BBB')
  437. done()
  438. })
  439. })
  440. it('teardown', function (done) {
  441. var vm = new Vue({
  442. el: el,
  443. template: '<component :is="view" keep-alive></component>',
  444. data: {
  445. view: 'test'
  446. },
  447. components: {
  448. test: {},
  449. test2: {}
  450. }
  451. })
  452. vm.view = 'test2'
  453. _.nextTick(function () {
  454. expect(vm.$children.length).toBe(2)
  455. var child = vm.$children[0]
  456. var child2 = vm.$children[1]
  457. vm._directives[0].unbind()
  458. expect(vm._directives[0].cache).toBeNull()
  459. expect(vm.$children.length).toBe(0)
  460. expect(child._isDestroyed).toBe(true)
  461. expect(child2._isDestroyed).toBe(true)
  462. done()
  463. })
  464. })
  465. it('already mounted warn', function () {
  466. el.setAttribute('is', 'test')
  467. new Vue({
  468. el: el
  469. })
  470. expect(hasWarned(_, 'cannot mount component "test" on already mounted element')).toBe(true)
  471. })
  472. it('not found component should not throw', function () {
  473. expect(function () {
  474. new Vue({
  475. el: el,
  476. template: '<div is="non-existent"></div>'
  477. })
  478. }).not.toThrow()
  479. })
  480. })
  481. }