patchStyle.spec.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import { vi } from 'vitest'
  2. import { patchProp } from '../src/patchProp'
  3. describe(`runtime-dom: style patching`, () => {
  4. it('string', () => {
  5. const el = document.createElement('div')
  6. patchProp(el, 'style', {}, 'color:red')
  7. expect(el.style.cssText.replace(/\s/g, '')).toBe('color:red;')
  8. })
  9. // #1309
  10. it('should not patch same string style', () => {
  11. const el = document.createElement('div')
  12. const fn = vi.fn()
  13. const value = (el.style.cssText = 'color:red;')
  14. Object.defineProperty(el.style, 'cssText', {
  15. get(): any {
  16. return value
  17. },
  18. set: fn
  19. })
  20. patchProp(el, 'style', value, value)
  21. expect(el.style.cssText.replace(/\s/g, '')).toBe('color:red;')
  22. expect(fn).not.toBeCalled()
  23. })
  24. it('plain object', () => {
  25. const el = document.createElement('div')
  26. patchProp(el, 'style', {}, { color: 'red' })
  27. expect(el.style.cssText.replace(/\s/g, '')).toBe('color:red;')
  28. })
  29. it('camelCase', () => {
  30. const el = document.createElement('div')
  31. patchProp(el, 'style', {}, { marginRight: '10px' })
  32. expect(el.style.cssText.replace(/\s/g, '')).toBe('margin-right:10px;')
  33. })
  34. it('remove if falsy value', () => {
  35. const el = document.createElement('div')
  36. patchProp(el, 'style', null, {
  37. color: undefined,
  38. borderRadius: null
  39. })
  40. expect(el.style.cssText.replace(/\s/g, '')).toBe('')
  41. patchProp(
  42. el,
  43. 'style',
  44. { color: 'red' },
  45. { color: null, borderRadius: undefined }
  46. )
  47. expect(el.style.cssText.replace(/\s/g, '')).toBe('')
  48. })
  49. it('!important', () => {
  50. const el = document.createElement('div')
  51. patchProp(el, 'style', {}, { color: 'red !important' })
  52. expect(el.style.cssText.replace(/\s/g, '')).toBe('color:red!important;')
  53. })
  54. it('camelCase with !important', () => {
  55. const el = document.createElement('div')
  56. patchProp(el, 'style', {}, { marginRight: '10px !important' })
  57. expect(el.style.cssText.replace(/\s/g, '')).toBe(
  58. 'margin-right:10px!important;'
  59. )
  60. })
  61. it('object with multiple entries', () => {
  62. const el = document.createElement('div')
  63. patchProp(el, 'style', {}, { color: 'red', marginRight: '10px' })
  64. expect(el.style.getPropertyValue('color')).toBe('red')
  65. expect(el.style.getPropertyValue('margin-right')).toBe('10px')
  66. })
  67. it('patch with falsy style value', () => {
  68. const el = document.createElement('div')
  69. patchProp(el as any, 'style', { width: '100px' }, { width: 0 })
  70. expect(el.style.width).toBe('0px')
  71. })
  72. it('should remove style attribute on falsy value', () => {
  73. const el = document.createElement('div')
  74. el.style.cssText = 'color: red;'
  75. patchProp(el as any, 'style', {}, null)
  76. expect(el.hasAttribute('style')).toBe(false)
  77. expect(el.style.cssText).toBe('')
  78. })
  79. it('should warn for trailing semicolons', () => {
  80. const el = document.createElement('div')
  81. patchProp(el, 'style', null, { color: 'red;' })
  82. expect(
  83. `Unexpected semicolon at the end of 'color' style value: 'red;'`
  84. ).toHaveBeenWarned()
  85. patchProp(el, 'style', null, { '--custom': '100; ' })
  86. expect(
  87. `Unexpected semicolon at the end of '--custom' style value: '100; '`
  88. ).toHaveBeenWarned()
  89. })
  90. it('should not warn for escaped trailing semicolons', () => {
  91. const el = document.createElement('div')
  92. patchProp(el, 'style', null, { '--custom': '100\\;' })
  93. expect(el.style.getPropertyValue('--custom')).toBe('100\\;')
  94. })
  95. it('shorthand properties', () => {
  96. const el = document.createElement('div')
  97. patchProp(
  98. el as any,
  99. 'style',
  100. { borderBottom: '1px solid red' },
  101. { border: '1px solid green' }
  102. )
  103. expect(el.style.border).toBe('1px solid green')
  104. expect(el.style.borderBottom).toBe('1px solid green')
  105. })
  106. // JSDOM doesn't support custom properties on style object so we have to
  107. // mock it here.
  108. function mockElementWithStyle() {
  109. const store: any = {}
  110. return {
  111. style: {
  112. display: '',
  113. WebkitTransition: '',
  114. setProperty(key: string, val: string) {
  115. store[key] = val
  116. },
  117. getPropertyValue(key: string) {
  118. return store[key]
  119. }
  120. }
  121. }
  122. }
  123. it('CSS custom properties', () => {
  124. const el = mockElementWithStyle()
  125. patchProp(el as any, 'style', {}, { '--theme': 'red' } as any)
  126. expect(el.style.getPropertyValue('--theme')).toBe('red')
  127. })
  128. it('auto vendor prefixing', () => {
  129. const el = mockElementWithStyle()
  130. patchProp(el as any, 'style', {}, { transition: 'all 1s' })
  131. expect(el.style.WebkitTransition).toBe('all 1s')
  132. })
  133. it('multiple values', () => {
  134. const el = mockElementWithStyle()
  135. patchProp(
  136. el as any,
  137. 'style',
  138. {},
  139. { display: ['-webkit-box', '-ms-flexbox', 'flex'] }
  140. )
  141. expect(el.style.display).toBe('flex')
  142. })
  143. })