patchStyle.spec.ts 5.1 KB

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