misc_spec.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. // test cases for edge cases & bug fixes
  2. var Vue = require('../../../src/index')
  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. vm.frozen.msg = 'changed'
  214. Vue.nextTick(function () {
  215. expect(vm.$el.textContent).toBe('ho frozen')
  216. done()
  217. })
  218. })
  219. it('should not trigger deep/Array watchers when digesting', function (done) {
  220. var spy1 = jasmine.createSpy('deep')
  221. var spy2 = jasmine.createSpy('Array')
  222. var spy3 = jasmine.createSpy('test')
  223. var spy4 = jasmine.createSpy('deep-mutated')
  224. var vm = new Vue({
  225. el: document.createElement('div'),
  226. data: {
  227. obj: {},
  228. arr: [],
  229. obj2: {}
  230. },
  231. watch: {
  232. obj: {
  233. handler: spy1,
  234. deep: true
  235. },
  236. arr: spy2,
  237. // if the watcher is watching the added value,
  238. // it should still trigger properly
  239. test: {
  240. handler: spy3,
  241. deep: true
  242. },
  243. // if the object is in fact mutated, it should
  244. // still trigger.
  245. obj2: {
  246. handler: spy4,
  247. deep: true
  248. }
  249. }
  250. })
  251. var test = []
  252. var obj2 = vm.obj2
  253. vm.$set('test', test)
  254. _.set(obj2, 'test', 123)
  255. Vue.nextTick(function () {
  256. expect(spy1).not.toHaveBeenCalled()
  257. expect(spy2).not.toHaveBeenCalled()
  258. expect(spy3).toHaveBeenCalledWith(test, undefined)
  259. expect(spy4).toHaveBeenCalledWith(obj2, obj2)
  260. done()
  261. })
  262. })
  263. it('handle interpolated textarea', function (done) {
  264. var el = document.createElement('div')
  265. el.innerHTML = '<textarea>hello {{msg}}</textarea>'
  266. var vm = new Vue({
  267. el: el,
  268. data: {
  269. msg: 'test'
  270. }
  271. })
  272. expect(el.innerHTML).toBe('<textarea>hello test</textarea>')
  273. vm.msg = 'world'
  274. Vue.nextTick(function () {
  275. expect(el.innerHTML).toBe('<textarea>hello world</textarea>')
  276. done()
  277. })
  278. })
  279. it('nested object $set should trigger parent array notify', function (done) {
  280. var vm = new Vue({
  281. el: document.createElement('div'),
  282. template: '{{items | json}}{{items[0].a}}',
  283. data: {
  284. items: [{}]
  285. }
  286. })
  287. expect(vm.$el.textContent).toBe(JSON.stringify(vm.items, null, 2))
  288. _.set(vm.items[0], 'a', 123)
  289. Vue.nextTick(function () {
  290. expect(vm.$el.textContent).toBe(JSON.stringify(vm.items, null, 2) + '123')
  291. done()
  292. })
  293. })
  294. it('warn unkown custom element', function () {
  295. new Vue({
  296. el: document.createElement('div'),
  297. template: '<custom-stuff></custom-stuff>'
  298. })
  299. expect(hasWarned('Unknown custom element')).toBe(true)
  300. })
  301. it('prefer bound attributes over static attributes', function (done) {
  302. var el = document.createElement('div')
  303. var count = 0
  304. var expected = [
  305. 'bound',
  306. 'bound',
  307. 'static',
  308. 'bound',
  309. 'bound'
  310. ]
  311. function check (title) {
  312. expect(title).toBe(expected[count])
  313. count++
  314. if (count === 4) {
  315. done()
  316. }
  317. }
  318. new Vue({
  319. el: el,
  320. template:
  321. '<div>\
  322. <comp v-bind:title="title"></comp>\
  323. <comp title="static" v-bind:title="title"></comp>\
  324. <comp title="static"></comp>\
  325. <comp :title="title"></comp>\
  326. <comp title="static" :title="title"></comp>\
  327. </div>',
  328. data: {
  329. title: 'bound'
  330. },
  331. components: {
  332. comp: {
  333. props: ['title'],
  334. created: function () {
  335. check(this.title)
  336. }
  337. }
  338. }
  339. })
  340. })
  341. it('deep watch for class, style and bind', function (done) {
  342. var el = document.createElement('div')
  343. var vm = new Vue({
  344. el: el,
  345. template: '<div :class="classes" :style="styles" v-bind="attrs"></div>',
  346. data: {
  347. classes: { a: true, b: false },
  348. styles: { color: 'red', fontSize: '14px' },
  349. attrs: { a: 1, b: 2 }
  350. }
  351. })
  352. var div = el.firstChild
  353. expect(div.className).toBe('a')
  354. expect(div.style.color).toBe('red')
  355. expect(div.style.fontSize).toBe('14px')
  356. expect(div.getAttribute('a')).toBe('1')
  357. expect(div.getAttribute('b')).toBe('2')
  358. vm.classes.b = true
  359. vm.styles.color = 'green'
  360. vm.attrs.a = 3
  361. Vue.nextTick(function () {
  362. expect(div.className).toBe('a b')
  363. expect(div.style.color).toBe('green')
  364. expect(div.style.fontSize).toBe('14px')
  365. expect(div.getAttribute('a')).toBe('3')
  366. expect(div.getAttribute('b')).toBe('2')
  367. done()
  368. })
  369. })
  370. it('IE9 class & :class merge during transclusion', function () {
  371. var vm = new Vue({
  372. el: document.createElement('div'),
  373. template: '<test class="outer"></test>',
  374. components: {
  375. test: {
  376. replace: true,
  377. template: '<div :class="{\'inner\': true}"></div>'
  378. }
  379. }
  380. })
  381. expect(vm.$el.firstChild.className).toBe('outer inner')
  382. })
  383. it('SVG class interpolation', function () {
  384. var vm = new Vue({
  385. el: document.createElement('div'),
  386. template: '<icon class="abc" icon="def"></icon>',
  387. components: {
  388. icon: {
  389. props: ['class', 'icon'],
  390. replace: true,
  391. template: '<svg class="si-icon {{icon}} {{class}}"><use xlink:href=""></use></svg>'
  392. }
  393. }
  394. })
  395. expect(vm.$el.firstChild.getAttribute('class')).toBe('si-icon def abc')
  396. })
  397. // #1960
  398. it('class interpolation should preserve transition class', function () {
  399. var vm = new Vue({
  400. el: document.createElement('div'),
  401. template: '<div class="{{test}}" transition="test"></div>',
  402. data: {
  403. test: 'hi'
  404. }
  405. })
  406. expect(vm.$el.firstChild.className).toBe('hi test-transition')
  407. })
  408. })