component_spec.js 13 KB

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