style.spec.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. import Vue from 'vue'
  2. function checkPrefixedProp (prop) {
  3. var el = document.createElement('div')
  4. var upper = prop.charAt(0).toUpperCase() + prop.slice(1)
  5. if (!(prop in el.style)) {
  6. var prefixes = ['Webkit', 'Moz', 'ms']
  7. var i = prefixes.length
  8. while (i--) {
  9. if ((prefixes[i] + upper) in el.style) {
  10. prop = prefixes[i] + upper
  11. }
  12. }
  13. }
  14. return prop
  15. }
  16. describe('Directive v-bind:style', () => {
  17. let vm
  18. beforeEach(() => {
  19. vm = new Vue({
  20. template: '<div :style="styles"></div>',
  21. data () {
  22. return {
  23. styles: {},
  24. fontSize: 16
  25. }
  26. }
  27. }).$mount()
  28. })
  29. it('string', done => {
  30. vm.styles = 'color:red;'
  31. waitForUpdate(() => {
  32. expect(vm.$el.style.cssText.replace(/\s/g, '')).toBe('color:red;')
  33. }).then(done)
  34. })
  35. it('falsy number', done => {
  36. vm.styles = { opacity: 0 }
  37. waitForUpdate(() => {
  38. expect(vm.$el.style.opacity).toBe('0')
  39. }).then(done)
  40. })
  41. it('plain object', done => {
  42. vm.styles = { color: 'red' }
  43. waitForUpdate(() => {
  44. expect(vm.$el.style.cssText.replace(/\s/g, '')).toBe('color:red;')
  45. }).then(done)
  46. })
  47. it('camelCase', done => {
  48. vm.styles = { marginRight: '10px' }
  49. waitForUpdate(() => {
  50. expect(vm.$el.style.marginRight).toBe('10px')
  51. }).then(done)
  52. })
  53. it('remove if falsy value', done => {
  54. vm.$el.style.color = 'red'
  55. waitForUpdate(() => {
  56. vm.styles = { color: null }
  57. }).then(() => {
  58. expect(vm.$el.style.color).toBe('')
  59. }).then(done)
  60. })
  61. it('ignore unsupported property', done => {
  62. vm.styles = { foo: 'bar' }
  63. waitForUpdate(() => {
  64. expect(vm.$el.style.foo).not.toBe('bar')
  65. }).then(done)
  66. })
  67. it('auto prefix', done => {
  68. const prop = checkPrefixedProp('transform')
  69. const val = 'scale(0.5)'
  70. vm.styles = { transform: val }
  71. waitForUpdate(() => {
  72. expect(vm.$el.style[prop]).toBe(val)
  73. }).then(done)
  74. })
  75. it('object with multiple entries', done => {
  76. vm.$el.style.color = 'red'
  77. vm.styles = {
  78. marginLeft: '10px',
  79. marginRight: '15px'
  80. }
  81. waitForUpdate(() => {
  82. expect(vm.$el.style.getPropertyValue('color')).toBe('red')
  83. expect(vm.$el.style.getPropertyValue('margin-left')).toBe('10px')
  84. expect(vm.$el.style.getPropertyValue('margin-right')).toBe('15px')
  85. vm.styles = {
  86. color: 'blue',
  87. padding: null
  88. }
  89. }).then(() => {
  90. expect(vm.$el.style.getPropertyValue('color')).toBe('blue')
  91. expect(vm.$el.style.getPropertyValue('padding')).toBeFalsy()
  92. expect(vm.$el.style.getPropertyValue('margin-left')).toBeFalsy()
  93. expect(vm.$el.style.getPropertyValue('margin-right')).toBeFalsy()
  94. // handle falsy value
  95. vm.styles = null
  96. }).then(() => {
  97. expect(vm.$el.style.getPropertyValue('color')).toBeFalsy()
  98. expect(vm.$el.style.getPropertyValue('padding')).toBeFalsy()
  99. expect(vm.$el.style.getPropertyValue('margin-left')).toBeFalsy()
  100. expect(vm.$el.style.getPropertyValue('margin-right')).toBeFalsy()
  101. }).then(done)
  102. })
  103. it('array of objects', done => {
  104. vm.$el.style.padding = '10px'
  105. vm.styles = [{ color: 'red' }, { marginRight: '20px' }]
  106. waitForUpdate(() => {
  107. expect(vm.$el.style.getPropertyValue('color')).toBe('red')
  108. expect(vm.$el.style.getPropertyValue('margin-right')).toBe('20px')
  109. expect(vm.$el.style.getPropertyValue('padding')).toBe('10px')
  110. vm.styles = [{ color: 'blue' }, { padding: null }]
  111. }).then(() => {
  112. expect(vm.$el.style.getPropertyValue('color')).toBe('blue')
  113. expect(vm.$el.style.getPropertyValue('margin-right')).toBeFalsy()
  114. expect(vm.$el.style.getPropertyValue('padding')).toBeFalsy()
  115. }).then(done)
  116. })
  117. it('updates objects deeply', done => {
  118. vm.styles = { display: 'none' }
  119. waitForUpdate(() => {
  120. expect(vm.$el.style.display).toBe('none')
  121. vm.styles.display = 'block'
  122. }).then(() => {
  123. expect(vm.$el.style.display).toBe('block')
  124. }).then(done)
  125. })
  126. it('background size with only one value', done => {
  127. vm.styles = { backgroundSize: '100%' }
  128. waitForUpdate(() => {
  129. expect(vm.$el.style.cssText.replace(/\s/g, '')).toMatch(/background-size:100%(auto)?;/)
  130. }).then(done)
  131. })
  132. it('should work with interpolation', done => {
  133. vm.styles = { fontSize: `${vm.fontSize}px` }
  134. waitForUpdate(() => {
  135. expect(vm.$el.style.fontSize).toBe('16px')
  136. }).then(done)
  137. })
  138. const supportCssVariable = () => {
  139. const el = document.createElement('div')
  140. el.style.setProperty('--color', 'red')
  141. return el.style.getPropertyValue('--color') === 'red'
  142. }
  143. if (supportCssVariable()) {
  144. it('CSS variables', done => {
  145. vm.styles = { '--color': 'red' }
  146. waitForUpdate(() => {
  147. expect(vm.$el.style.getPropertyValue('--color')).toBe('red')
  148. }).then(done)
  149. })
  150. }
  151. it('should merge static style with binding style', () => {
  152. const vm = new Vue({
  153. template: '<div style="background: url(https://vuejs.org/images/logo.png);color: blue" :style="test"></div>',
  154. data: {
  155. test: { color: 'red', fontSize: '12px' }
  156. }
  157. }).$mount()
  158. const style = vm.$el.style
  159. expect(style.getPropertyValue('background-image')).toMatch('https://vuejs.org/images/logo.png')
  160. expect(style.getPropertyValue('color')).toBe('red')
  161. expect(style.getPropertyValue('font-size')).toBe('12px')
  162. })
  163. it('should merge between parent and child', done => {
  164. const vm = new Vue({
  165. template: '<child style="text-align: left;margin-right:20px" :style="test"></child>',
  166. data: {
  167. test: { color: 'red', fontSize: '12px' }
  168. },
  169. components: {
  170. child: {
  171. template: '<div style="margin-right:10px;" :style="{marginLeft: marginLeft}"></div>',
  172. data: () => ({ marginLeft: '16px' })
  173. }
  174. }
  175. }).$mount()
  176. const style = vm.$el.style
  177. const child = vm.$children[0]
  178. const css = style.cssText.replace(/\s/g, '')
  179. expect(css).toContain('margin-right:20px;')
  180. expect(css).toContain('margin-left:16px;')
  181. expect(css).toContain('text-align:left;')
  182. expect(css).toContain('color:red;')
  183. expect(css).toContain('font-size:12px;')
  184. expect(style.color).toBe('red')
  185. expect(style.marginRight).toBe('20px')
  186. vm.test.color = 'blue'
  187. waitForUpdate(() => {
  188. expect(style.color).toBe('blue')
  189. child.marginLeft = '30px'
  190. }).then(() => {
  191. expect(style.marginLeft).toBe('30px')
  192. child.fontSize = '30px'
  193. }).then(() => {
  194. expect(style.fontSize).toBe('12px')
  195. }).then(done)
  196. })
  197. it('should not pass to child root element', () => {
  198. const vm = new Vue({
  199. template: '<child :style="test"></child>',
  200. data: {
  201. test: { color: 'red', fontSize: '12px' }
  202. },
  203. components: {
  204. child: {
  205. template: '<div><nested ref="nested" style="color: blue;text-align:left"></nested></div>',
  206. components: {
  207. nested: {
  208. template: '<div></div>'
  209. }
  210. }
  211. }
  212. }
  213. }).$mount()
  214. const style = vm.$el.style
  215. expect(style.color).toBe('red')
  216. expect(style.textAlign).toBe('')
  217. expect(style.fontSize).toBe('12px')
  218. expect(vm.$children[0].$refs.nested.$el.style.color).toBe('blue')
  219. })
  220. it('should merge between nested components', (done) => {
  221. const vm = new Vue({
  222. template: '<child :style="test"></child>',
  223. data: {
  224. test: { color: 'red', fontSize: '12px' }
  225. },
  226. components: {
  227. child: {
  228. template: '<nested style="color: blue;text-align:left"></nested>',
  229. components: {
  230. nested: {
  231. template: '<div style="margin-left: 12px;" :style="nestedStyle"></div>',
  232. data: () => ({ nestedStyle: { marginLeft: '30px' }})
  233. }
  234. }
  235. }
  236. }
  237. }).$mount()
  238. const style = vm.$el.style
  239. const child = vm.$children[0].$children[0]
  240. expect(style.color).toBe('red')
  241. expect(style.marginLeft).toBe('30px')
  242. expect(style.textAlign).toBe('left')
  243. expect(style.fontSize).toBe('12px')
  244. vm.test.color = 'yellow'
  245. waitForUpdate(() => {
  246. child.nestedStyle.marginLeft = '60px'
  247. }).then(() => {
  248. expect(style.marginLeft).toBe('60px')
  249. child.nestedStyle = {
  250. fontSize: '14px',
  251. marginLeft: '40px'
  252. }
  253. }).then(() => {
  254. expect(style.fontSize).toBe('12px')
  255. expect(style.marginLeft).toBe('40px')
  256. }).then(done)
  257. })
  258. it('should not merge for different adjacent elements', (done) => {
  259. const vm = new Vue({
  260. template:
  261. '<div>' +
  262. '<section style="color: blue" :style="style" v-if="!bool"></section>' +
  263. '<div></div>' +
  264. '<section style="margin-top: 12px" v-if="bool"></section>' +
  265. '</div>',
  266. data: {
  267. bool: false,
  268. style: {
  269. fontSize: '12px'
  270. }
  271. }
  272. }).$mount()
  273. const style = vm.$el.children[0].style
  274. expect(style.fontSize).toBe('12px')
  275. expect(style.color).toBe('blue')
  276. waitForUpdate(() => {
  277. vm.bool = true
  278. }).then(() => {
  279. expect(style.color).toBe('')
  280. expect(style.fontSize).toBe('')
  281. expect(style.marginTop).toBe('12px')
  282. }).then(done)
  283. })
  284. })