misc_spec.js 12 KB

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