misc_spec.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. // test cases for edge cases & bug fixes
  2. var Vue = require('src')
  3. var _ = Vue.util
  4. describe('Misc', function () {
  5. it('should handle directive.bind() altering its childNode structure', function () {
  6. var vm = new Vue({
  7. el: document.createElement('div'),
  8. template: '<div v-test>{{test}}</div>',
  9. data: {
  10. test: 'foo'
  11. },
  12. directives: {
  13. test: {
  14. bind: function () {
  15. this.el.insertBefore(document.createTextNode('bar '),
  16. this.el.firstChild)
  17. }
  18. }
  19. }
  20. })
  21. expect(vm.$el.textContent).toBe('bar foo')
  22. })
  23. it('attached/detached hooks for transcluded components', function () {
  24. var spy1 = jasmine.createSpy('attached')
  25. var spy2 = jasmine.createSpy('detached')
  26. var el = document.createElement('div')
  27. el.innerHTML = '<outer v-ref:outter><inner></inner></outer>'
  28. document.body.appendChild(el)
  29. var vm = new Vue({
  30. el: el,
  31. components: {
  32. outer: {
  33. template: '<slot></slot>'
  34. },
  35. inner: {
  36. template: 'foo',
  37. attached: spy1,
  38. detached: spy2
  39. }
  40. }
  41. })
  42. expect(spy1).toHaveBeenCalled()
  43. vm.$refs.outter.$remove()
  44. expect(spy2).toHaveBeenCalled()
  45. })
  46. it('v-for on component root node with replace:true', function () {
  47. var el = document.createElement('div')
  48. var vm = new Vue({
  49. el: el,
  50. template: '<test></test>',
  51. components: {
  52. test: {
  53. data: function () {
  54. return { list: [1, 2, 3] }
  55. },
  56. template: '<div v-for="n in list">{{n}}</div>',
  57. replace: true
  58. }
  59. }
  60. })
  61. expect(vm.$el.innerHTML).toBe('<div>1</div><div>2</div><div>3</div>')
  62. })
  63. // #922
  64. it('template v-for inside svg', function () {
  65. var el = document.createElement('div')
  66. new Vue({
  67. el: el,
  68. template: '<svg><template v-for="n in list"><text>{{n}}</text></template></svg>',
  69. data: {
  70. list: [1, 2, 3]
  71. }
  72. })
  73. // IE inlines svg namespace
  74. var xmlns = /\s?xmlns=".*svg"/
  75. expect(el.innerHTML.replace(xmlns, '')).toBe('<svg><text>1</text><text>2</text><text>3</text></svg>')
  76. })
  77. // #1005
  78. it('call lifecycle hooks for child components', function () {
  79. Vue.options.replace = true
  80. var el = document.createElement('div')
  81. var logs = []
  82. function log (n) {
  83. return function () {
  84. logs.push(n)
  85. }
  86. }
  87. document.body.appendChild(el)
  88. var vm = new Vue({
  89. el: el,
  90. attached: log(0),
  91. ready: log(1),
  92. detached: log(2),
  93. beforeDestroy: log(3),
  94. destroyed: log(4),
  95. template: '<div><test></test><test></test></div>',
  96. components: {
  97. test: {
  98. template: '<span>hi</span>',
  99. attached: log(5),
  100. ready: log(6),
  101. detached: log(7),
  102. beforeDestroy: log(8),
  103. destroyed: log(9)
  104. }
  105. }
  106. })
  107. expect(vm.$el.innerHTML).toBe('<span>hi</span><span>hi</span>')
  108. expect(logs.join()).toBe('0,5,6,5,6,1')
  109. logs = []
  110. vm.$destroy(true)
  111. expect(logs.join()).toBe('2,7,7,3,8,9,8,9,4')
  112. Vue.options.replace = false
  113. })
  114. // #1966
  115. it('call lifecycle hooks for child and grandchild components', function () {
  116. Vue.options.replace = true
  117. var el = document.createElement('div')
  118. var logs = []
  119. function log (n) {
  120. return function () {
  121. logs.push(n)
  122. }
  123. }
  124. document.body.appendChild(el)
  125. var vm = new Vue({
  126. el: el,
  127. attached: log(0),
  128. ready: log(1),
  129. detached: log(2),
  130. beforeDestroy: log(3),
  131. destroyed: log(4),
  132. template: '<div><test></test></div>',
  133. components: {
  134. test: {
  135. attached: log(5),
  136. ready: log(6),
  137. detached: log(7),
  138. beforeDestroy: log(8),
  139. destroyed: log(9),
  140. template: '<div><test-inner></test-inner></div>',
  141. components: {
  142. 'test-inner': {
  143. attached: log(10),
  144. ready: log(11),
  145. detached: log(12),
  146. beforeDestroy: log(13),
  147. destroyed: log(14),
  148. template: '<span>hi</span>'
  149. }
  150. }
  151. }
  152. }
  153. })
  154. expect(vm.$el.innerHTML).toBe('<div><span>hi</span></div>')
  155. expect(logs.join()).toBe('0,5,10,11,6,1')
  156. logs = []
  157. vm.$destroy(true)
  158. expect(logs.join()).toBe('2,7,12,3,8,13,14,9,4')
  159. Vue.options.replace = false
  160. })
  161. // #1006
  162. it('destroyed hook for components inside v-if', function (done) {
  163. var spy = jasmine.createSpy('v-if destroyed hook')
  164. var vm = new Vue({
  165. el: document.createElement('div'),
  166. template: '<template v-if="ok"><test></test></template>',
  167. data: {
  168. ok: true
  169. },
  170. components: {
  171. test: {
  172. destroyed: spy
  173. }
  174. }
  175. })
  176. vm.ok = false
  177. Vue.nextTick(function () {
  178. expect(spy).toHaveBeenCalled()
  179. done()
  180. })
  181. })
  182. it('frozen model, root', function (done) {
  183. var vm = new Vue({
  184. el: document.createElement('div'),
  185. template: '{{msg}}',
  186. data: Object.freeze({
  187. msg: 'foo'
  188. })
  189. })
  190. expect(vm.$el.textContent).toBe('foo')
  191. try { vm.msg = 'bar' } catch (e) {}
  192. Vue.nextTick(function () {
  193. expect(vm.$el.textContent).toBe('foo')
  194. done()
  195. })
  196. })
  197. it('frozen model, non-root', function (done) {
  198. var vm = new Vue({
  199. el: document.createElement('div'),
  200. template: '{{msg}} {{frozen.msg}}',
  201. data: {
  202. msg: 'foo',
  203. frozen: Object.freeze({
  204. msg: 'frozen'
  205. })
  206. }
  207. })
  208. expect(vm.$el.textContent).toBe('foo frozen')
  209. vm.msg = 'bar'
  210. try {
  211. vm.frozen.msg = 'changed'
  212. } catch (error) {
  213. if (!(error instanceof TypeError)) {
  214. throw error
  215. }
  216. }
  217. Vue.nextTick(function () {
  218. expect(vm.$el.textContent).toBe('bar frozen')
  219. done()
  220. })
  221. })
  222. it('should not trigger deep/Array watchers when digesting', function (done) {
  223. var spy1 = jasmine.createSpy('deep')
  224. var spy2 = jasmine.createSpy('Array')
  225. var spy3 = jasmine.createSpy('test')
  226. var spy4 = jasmine.createSpy('deep-mutated')
  227. var vm = new Vue({
  228. el: document.createElement('div'),
  229. data: {
  230. obj: {},
  231. arr: [],
  232. obj2: {}
  233. },
  234. watch: {
  235. obj: {
  236. handler: spy1,
  237. deep: true
  238. },
  239. arr: spy2,
  240. // if the watcher is watching the added value,
  241. // it should still trigger properly
  242. test: {
  243. handler: spy3,
  244. deep: true
  245. },
  246. // if the object is in fact mutated, it should
  247. // still trigger.
  248. obj2: {
  249. handler: spy4,
  250. deep: true
  251. }
  252. }
  253. })
  254. var test = []
  255. var obj2 = vm.obj2
  256. vm.$set('test', test)
  257. _.set(obj2, 'test', 123)
  258. Vue.nextTick(function () {
  259. expect(spy1).not.toHaveBeenCalled()
  260. expect(spy2).not.toHaveBeenCalled()
  261. expect(spy3).toHaveBeenCalledWith(test, undefined)
  262. expect(spy4).toHaveBeenCalledWith(obj2, obj2)
  263. done()
  264. })
  265. })
  266. it('handle interpolated textarea', function (done) {
  267. var el = document.createElement('div')
  268. el.innerHTML = '<textarea>hello {{msg}}</textarea>'
  269. var vm = new Vue({
  270. el: el,
  271. data: {
  272. msg: 'test'
  273. }
  274. })
  275. expect(el.innerHTML).toBe('<textarea>hello test</textarea>')
  276. vm.msg = 'world'
  277. Vue.nextTick(function () {
  278. expect(el.innerHTML).toBe('<textarea>hello world</textarea>')
  279. done()
  280. })
  281. })
  282. it('nested object $set should trigger parent array notify', function (done) {
  283. var vm = new Vue({
  284. el: document.createElement('div'),
  285. template: '{{items | json}}{{items[0].a}}',
  286. data: {
  287. items: [{}]
  288. }
  289. })
  290. expect(vm.$el.textContent).toBe(JSON.stringify(vm.items, null, 2))
  291. _.set(vm.items[0], 'a', 123)
  292. Vue.nextTick(function () {
  293. expect(vm.$el.textContent).toBe(JSON.stringify(vm.items, null, 2) + '123')
  294. done()
  295. })
  296. })
  297. it('warn unkown custom element', function () {
  298. new Vue({
  299. el: document.createElement('div'),
  300. template: '<custom-stuff></custom-stuff>'
  301. })
  302. expect('Unknown custom element').toHaveBeenWarned()
  303. })
  304. it('prefer bound attributes over static attributes', function (done) {
  305. var el = document.createElement('div')
  306. var count = 0
  307. var expected = [
  308. 'bound',
  309. 'bound',
  310. 'static',
  311. 'bound',
  312. 'bound'
  313. ]
  314. function check (title) {
  315. expect(title).toBe(expected[count])
  316. count++
  317. if (count === 4) {
  318. done()
  319. }
  320. }
  321. new Vue({
  322. el: el,
  323. template:
  324. '<div>' +
  325. '<comp v-bind:title="title"></comp>' +
  326. '<comp title="static" v-bind:title="title"></comp>' +
  327. '<comp title="static"></comp>' +
  328. '<comp :title="title"></comp>' +
  329. '<comp title="static" :title="title"></comp>' +
  330. '</div>',
  331. data: {
  332. title: 'bound'
  333. },
  334. components: {
  335. comp: {
  336. props: ['title'],
  337. created: function () {
  338. check(this.title)
  339. }
  340. }
  341. }
  342. })
  343. })
  344. it('deep watch for class, style and bind', function (done) {
  345. var el = document.createElement('div')
  346. var vm = new Vue({
  347. el: el,
  348. template: '<div :class="classes" :style="styles" v-bind="attrs"></div>',
  349. data: {
  350. classes: { a: true, b: false },
  351. styles: { color: 'red', fontSize: '14px' },
  352. attrs: { a: 1, b: 2 }
  353. }
  354. })
  355. var div = el.firstChild
  356. expect(div.className).toBe('a')
  357. expect(div.style.color).toBe('red')
  358. expect(div.style.fontSize).toBe('14px')
  359. expect(div.getAttribute('a')).toBe('1')
  360. expect(div.getAttribute('b')).toBe('2')
  361. vm.classes.b = true
  362. vm.styles.color = 'green'
  363. vm.attrs.a = 3
  364. Vue.nextTick(function () {
  365. expect(div.className).toBe('a b')
  366. expect(div.style.color).toBe('green')
  367. expect(div.style.fontSize).toBe('14px')
  368. expect(div.getAttribute('a')).toBe('3')
  369. expect(div.getAttribute('b')).toBe('2')
  370. done()
  371. })
  372. })
  373. it('IE9 class & :class merge during transclusion', function () {
  374. var vm = new Vue({
  375. el: document.createElement('div'),
  376. template: '<test class="outer"></test>',
  377. components: {
  378. test: {
  379. replace: true,
  380. template: '<div class="static-inner" :class="{\'inner\': true}"></div>'
  381. }
  382. }
  383. })
  384. expect(vm.$el.firstChild.className).toBe('static-inner outer inner')
  385. })
  386. it('SVG class interpolation', function () {
  387. var vm = new Vue({
  388. el: document.createElement('div'),
  389. template: '<icon class="abc" icon="def"></icon>',
  390. components: {
  391. icon: {
  392. props: ['class', 'icon'],
  393. replace: true,
  394. template: '<svg class="si-icon {{icon}} {{class}}"><use xlink:href=""></use></svg>'
  395. }
  396. }
  397. })
  398. expect(vm.$el.firstChild.getAttribute('class')).toBe('si-icon def abc')
  399. })
  400. // #1960
  401. it('class interpolation should preserve transition class', function () {
  402. var vm = new Vue({
  403. el: document.createElement('div'),
  404. template: '<div class="{{test}}" transition="test"></div>',
  405. data: {
  406. test: 'foo'
  407. }
  408. })
  409. expect(vm.$el.firstChild.className).toBe('foo test-transition')
  410. })
  411. it('transclude class merging should skip interpolated class', function () {
  412. var vm = new Vue({
  413. el: document.createElement('div'),
  414. template: '<test class="outer-{{test}}"></test>',
  415. data: {
  416. test: 'foo'
  417. },
  418. components: {
  419. test: {
  420. template: '<div class="inner"></div>',
  421. replace: true
  422. }
  423. }
  424. })
  425. expect(vm.$el.firstChild.className).toBe('outer-foo')
  426. })
  427. // #2163
  428. it('slot compilation order with v-if', function () {
  429. var vm = new Vue({
  430. el: document.createElement('div'),
  431. template:
  432. '<test>' +
  433. '<div slot="one">slot1</div>' +
  434. 'default content' +
  435. '</test>',
  436. components: {
  437. test: {
  438. template:
  439. '<div>' +
  440. '<slot v-if="true"></slot> ' +
  441. '<slot name="one"></slot>' +
  442. '</div>',
  443. replace: true
  444. }
  445. }
  446. })
  447. expect(vm.$el.textContent).toBe('default content slot1')
  448. })
  449. // #2426
  450. it('class merge untrimmed', function () {
  451. expect(function () {
  452. new Vue({
  453. el: document.createElement('div'),
  454. template: '<test class="p1 p2 "></test>',
  455. components: {
  456. test: {
  457. template: '<div class="hi"></div>',
  458. replace: true
  459. }
  460. }
  461. })
  462. }).not.toThrow()
  463. })
  464. // #2445
  465. it('fragment attach hook should check if child is inDoc', function (done) {
  466. var el = document.createElement('div')
  467. document.body.appendChild(el)
  468. var spyParent = jasmine.createSpy('attached parent')
  469. var spyChild = jasmine.createSpy('attached child')
  470. new Vue({
  471. el: el,
  472. template: '<comp v-for="n in 1"></comp>',
  473. components: {
  474. comp: {
  475. template: '<div><child></child></div>',
  476. attached: function () {
  477. expect(_.inDoc(this.$el)).toBe(true)
  478. spyParent()
  479. },
  480. activate: function (next) {
  481. setTimeout(function () {
  482. next()
  483. check()
  484. }, 100)
  485. },
  486. components: {
  487. child: {
  488. template: 'foo',
  489. attached: spyChild
  490. }
  491. }
  492. }
  493. }
  494. })
  495. function check () {
  496. expect(spyParent).toHaveBeenCalled()
  497. expect(spyChild).toHaveBeenCalled()
  498. done()
  499. }
  500. })
  501. // #2500
  502. it('template parser tag match should include hyphen', function () {
  503. var vm = new Vue({
  504. el: document.createElement('div'),
  505. template: '<div>{{{ test }}}</div>',
  506. data: {
  507. test: '<image-field></image-field>'
  508. }
  509. })
  510. expect(vm.$el.querySelector('image-field').namespaceURI).not.toMatch(/svg/)
  511. })
  512. // #2657
  513. it('template v-for with v-if', function () {
  514. var vm = new Vue({
  515. el: document.createElement('div'),
  516. template: '<div><template v-for="n in 6" v-if="n % 2">{{ n }}</template></div>'
  517. })
  518. expect(vm.$el.textContent).toBe('135')
  519. })
  520. })