style.spec.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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('!important', done => {
  76. vm.styles = { display: 'block !important' }
  77. waitForUpdate(() => {
  78. expect(vm.$el.style.getPropertyPriority('display')).toBe('important')
  79. }).then(done)
  80. })
  81. it('object with multiple entries', done => {
  82. vm.$el.style.color = 'red'
  83. vm.styles = {
  84. marginLeft: '10px',
  85. marginRight: '15px'
  86. }
  87. waitForUpdate(() => {
  88. expect(vm.$el.style.getPropertyValue('color')).toBe('red')
  89. expect(vm.$el.style.getPropertyValue('margin-left')).toBe('10px')
  90. expect(vm.$el.style.getPropertyValue('margin-right')).toBe('15px')
  91. vm.styles = {
  92. color: 'blue',
  93. padding: null
  94. }
  95. }).then(() => {
  96. expect(vm.$el.style.getPropertyValue('color')).toBe('blue')
  97. expect(vm.$el.style.getPropertyValue('padding')).toBeFalsy()
  98. expect(vm.$el.style.getPropertyValue('margin-left')).toBeFalsy()
  99. expect(vm.$el.style.getPropertyValue('margin-right')).toBeFalsy()
  100. // handle falsy value
  101. vm.styles = null
  102. }).then(() => {
  103. expect(vm.$el.style.getPropertyValue('color')).toBeFalsy()
  104. expect(vm.$el.style.getPropertyValue('padding')).toBeFalsy()
  105. expect(vm.$el.style.getPropertyValue('margin-left')).toBeFalsy()
  106. expect(vm.$el.style.getPropertyValue('margin-right')).toBeFalsy()
  107. }).then(done)
  108. })
  109. it('array of objects', done => {
  110. vm.$el.style.padding = '10px'
  111. vm.styles = [{ color: 'red' }, { marginRight: '20px' }]
  112. waitForUpdate(() => {
  113. expect(vm.$el.style.getPropertyValue('color')).toBe('red')
  114. expect(vm.$el.style.getPropertyValue('margin-right')).toBe('20px')
  115. expect(vm.$el.style.getPropertyValue('padding')).toBe('10px')
  116. vm.styles = [{ color: 'blue' }, { padding: null }]
  117. }).then(() => {
  118. expect(vm.$el.style.getPropertyValue('color')).toBe('blue')
  119. expect(vm.$el.style.getPropertyValue('margin-right')).toBeFalsy()
  120. expect(vm.$el.style.getPropertyValue('padding')).toBeFalsy()
  121. }).then(done)
  122. })
  123. it('updates objects deeply', done => {
  124. vm.styles = { display: 'none' }
  125. waitForUpdate(() => {
  126. expect(vm.$el.style.display).toBe('none')
  127. vm.styles.display = 'block'
  128. }).then(() => {
  129. expect(vm.$el.style.display).toBe('block')
  130. }).then(done)
  131. })
  132. it('background size with only one value', done => {
  133. vm.styles = { backgroundSize: '100%' }
  134. waitForUpdate(() => {
  135. expect(vm.$el.style.cssText.replace(/\s/g, '')).toMatch(/background-size:100%(auto)?;/)
  136. }).then(done)
  137. })
  138. it('should work with interpolation', done => {
  139. vm.styles = { fontSize: `${vm.fontSize}px` }
  140. waitForUpdate(() => {
  141. expect(vm.$el.style.fontSize).toBe('16px')
  142. }).then(done)
  143. })
  144. const supportCssVariable = () => {
  145. const el = document.createElement('div')
  146. el.style.setProperty('--color', 'red')
  147. return el.style.getPropertyValue('--color') === 'red'
  148. }
  149. if (supportCssVariable()) {
  150. it('CSS variables', done => {
  151. vm.styles = { '--color': 'red' }
  152. waitForUpdate(() => {
  153. expect(vm.$el.style.getPropertyValue('--color')).toBe('red')
  154. }).then(done)
  155. })
  156. }
  157. it('should merge static style with binding style', () => {
  158. const vm = new Vue({
  159. template: '<div style="background: url(https://vuejs.org/images/logo.png);color: blue" :style="test"></div>',
  160. data: {
  161. test: { color: 'red', fontSize: '12px' }
  162. }
  163. }).$mount()
  164. const style = vm.$el.style
  165. expect(style.getPropertyValue('background-image')).toMatch('https://vuejs.org/images/logo.png')
  166. expect(style.getPropertyValue('color')).toBe('red')
  167. expect(style.getPropertyValue('font-size')).toBe('12px')
  168. })
  169. it('should merge between parent and child', done => {
  170. const vm = new Vue({
  171. template: '<child style="text-align: left;margin-right:20px" :style="test"></child>',
  172. data: {
  173. test: { color: 'red', fontSize: '12px' }
  174. },
  175. components: {
  176. child: {
  177. template: '<div style="margin-right:10px;" :style="{marginLeft: marginLeft}"></div>',
  178. data: () => ({ marginLeft: '16px' })
  179. }
  180. }
  181. }).$mount()
  182. const style = vm.$el.style
  183. const child = vm.$children[0]
  184. const css = style.cssText.replace(/\s/g, '')
  185. expect(css).toContain('margin-right:20px;')
  186. expect(css).toContain('margin-left:16px;')
  187. expect(css).toContain('text-align:left;')
  188. expect(css).toContain('color:red;')
  189. expect(css).toContain('font-size:12px;')
  190. expect(style.color).toBe('red')
  191. expect(style.marginRight).toBe('20px')
  192. vm.test.color = 'blue'
  193. waitForUpdate(() => {
  194. expect(style.color).toBe('blue')
  195. child.marginLeft = '30px'
  196. }).then(() => {
  197. expect(style.marginLeft).toBe('30px')
  198. child.fontSize = '30px'
  199. }).then(() => {
  200. expect(style.fontSize).toBe('12px')
  201. }).then(done)
  202. })
  203. it('should not pass to child root element', () => {
  204. const vm = new Vue({
  205. template: '<child :style="test"></child>',
  206. data: {
  207. test: { color: 'red', fontSize: '12px' }
  208. },
  209. components: {
  210. child: {
  211. template: '<div><nested ref="nested" style="color: blue;text-align:left"></nested></div>',
  212. components: {
  213. nested: {
  214. template: '<div></div>'
  215. }
  216. }
  217. }
  218. }
  219. }).$mount()
  220. const style = vm.$el.style
  221. expect(style.color).toBe('red')
  222. expect(style.textAlign).toBe('')
  223. expect(style.fontSize).toBe('12px')
  224. expect(vm.$children[0].$refs.nested.$el.style.color).toBe('blue')
  225. })
  226. it('should merge between nested components', (done) => {
  227. const vm = new Vue({
  228. template: '<child :style="test"></child>',
  229. data: {
  230. test: { color: 'red', fontSize: '12px' }
  231. },
  232. components: {
  233. child: {
  234. template: '<nested style="color: blue;text-align:left"></nested>',
  235. components: {
  236. nested: {
  237. template: '<div style="margin-left: 12px;" :style="nestedStyle"></div>',
  238. data: () => ({ nestedStyle: { marginLeft: '30px' }})
  239. }
  240. }
  241. }
  242. }
  243. }).$mount()
  244. const style = vm.$el.style
  245. const child = vm.$children[0].$children[0]
  246. expect(style.color).toBe('red')
  247. expect(style.marginLeft).toBe('30px')
  248. expect(style.textAlign).toBe('left')
  249. expect(style.fontSize).toBe('12px')
  250. vm.test.color = 'yellow'
  251. waitForUpdate(() => {
  252. child.nestedStyle.marginLeft = '60px'
  253. }).then(() => {
  254. expect(style.marginLeft).toBe('60px')
  255. child.nestedStyle = {
  256. fontSize: '14px',
  257. marginLeft: '40px'
  258. }
  259. }).then(() => {
  260. expect(style.fontSize).toBe('12px')
  261. expect(style.marginLeft).toBe('40px')
  262. }).then(done)
  263. })
  264. it('should not merge for different adjacent elements', (done) => {
  265. const vm = new Vue({
  266. template:
  267. '<div>' +
  268. '<section style="color: blue" :style="style" v-if="!bool"></section>' +
  269. '<div></div>' +
  270. '<section style="margin-top: 12px" v-if="bool"></section>' +
  271. '</div>',
  272. data: {
  273. bool: false,
  274. style: {
  275. fontSize: '12px'
  276. }
  277. }
  278. }).$mount()
  279. const style = vm.$el.children[0].style
  280. expect(style.fontSize).toBe('12px')
  281. expect(style.color).toBe('blue')
  282. waitForUpdate(() => {
  283. vm.bool = true
  284. }).then(() => {
  285. expect(style.color).toBe('')
  286. expect(style.fontSize).toBe('')
  287. expect(style.marginTop).toBe('12px')
  288. }).then(done)
  289. })
  290. it('should not merge for v-if, v-else-if and v-else elements', (done) => {
  291. const vm = new Vue({
  292. template:
  293. '<div>' +
  294. '<section style="color: blue" :style="style" v-if="foo"></section>' +
  295. '<section style="margin-top: 12px" v-else-if="bar"></section>' +
  296. '<section style="margin-bottom: 24px" v-else></section>' +
  297. '<div></div>' +
  298. '</div>',
  299. data: {
  300. foo: true,
  301. bar: false,
  302. style: {
  303. fontSize: '12px'
  304. }
  305. }
  306. }).$mount()
  307. const style = vm.$el.children[0].style
  308. expect(style.fontSize).toBe('12px')
  309. expect(style.color).toBe('blue')
  310. waitForUpdate(() => {
  311. vm.foo = false
  312. }).then(() => {
  313. expect(style.color).toBe('')
  314. expect(style.fontSize).toBe('')
  315. expect(style.marginBottom).toBe('24px')
  316. vm.bar = true
  317. }).then(() => {
  318. expect(style.color).toBe('')
  319. expect(style.fontSize).toBe('')
  320. expect(style.marginBottom).toBe('')
  321. expect(style.marginTop).toBe('12px')
  322. }).then(done)
  323. })
  324. })