style.spec.js 11 KB

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