如何测试样式是否动态应用于 React 组件
How to test if styles are dynamically applied on a React component
我写了一个 React 组件,Button
:
import PropTypes from 'prop-types'
import Radium from 'radium'
import React from 'react'
import { Icon } from 'components'
import { COLOURS, GLOBAL_STYLES, ICONS, MEASUREMENTS } from 'app-constants'
@Radium
export default class Button extends React.Component {
static propTypes = {
children: PropTypes.string,
dark: PropTypes.bool,
icon: PropTypes.oneOf(Object.values(ICONS)).isRequired,
style: PropTypes.object,
}
render() {
const { children, dark, icon, style } = this.props
let mergedStyles = Object.assign({}, styles.base, style)
if (!children)
mergedStyles.icon.left = 0
if (dark)
mergedStyles = Object.assign(mergedStyles, styles.dark)
return (
<button
className="btn btn-secondary"
style={mergedStyles}
tabIndex={-1}>
<Icon name={icon} style={mergedStyles.icon} />
{children &&
<span style={mergedStyles.text}>{children}</span>
}
</button>
)
}
}
export const styles = {
base: {
backgroundColor: COLOURS.WHITE,
border: `1px solid ${COLOURS.BORDER_LIGHT}`,
borderRadius: GLOBAL_STYLES.BORDER_RADIUS,
cursor: 'pointer',
padding: GLOBAL_STYLES.BUTTON_PADDING,
':focus': {
outline: 'none',
},
':hover': {
boxShadow: GLOBAL_STYLES.BOX_SHADOW,
},
icon: {
fontSize: GLOBAL_STYLES.ICON_SIZE_TINY,
left: '-3px',
verticalAlign: 'middle',
},
text: {
fontSize: GLOBAL_STYLES.FONT_SIZE_TINY,
fontWeight: GLOBAL_STYLES.FONT_2_WEIGHT_MEDIUM,
marginLeft: `${MEASUREMENTS.BUTTON_PADDING.HORIZONTAL}px`,
verticalAlign: 'middle',
},
},
dark: {
backgroundColor: COLOURS.PRIMARY_3,
border: `1px solid ${COLOURS.PRIMARY_2}`,
color: COLOURS.WHITE,
':hover': {
boxShadow: GLOBAL_STYLES.BOX_SHADOW_DARK,
},
},
}
我还用 Jest 和 Enzyme 为 Button
编写了一个测试,它验证当其 dark
属性设置为 [=18= 时是否应用其 dark
样式]:
import { ICONS } from 'app-constants'
import Button, { styles } from 'components/Button'
describe("<Button>", () => {
let props
let mountedComponent
const getComponent = () => {
if (!mountedComponent)
mountedComponent = shallow(
<Button {...props} />
)
return mountedComponent
}
beforeEach(() => {
mountedComponent = undefined
props = {
children: undefined,
dark: undefined,
icon: ICONS.VIBE,
style: undefined,
}
})
describe("when `dark` is `true`", () => {
beforeEach(() => {
props.dark = true
})
it("applies the component's `dark` styles", () => {
const componentStyles = getComponent().props().style
expect(componentStyles).toEqual(expect.objectContaining(styles.dark))
})
})
})
如您所见,我通过检查 styles.dark
的属性是否在呈现的 Button
的 style
属性内来执行此操作。如果是,则表示样式应用成功。
问题是 styles.dark
和 componentStyles
不匹配:
console.log(styles.dark)
的输出
ObjectContaining{
":hover": {
"boxShadow": "0px 0px 0px 2px rgba(0,0,0,0.2)"
},
"backgroundColor": [Object],
"border": "1px solid rgb(47, 52, 63)",
"color": [Object]
}
console.log(componentStyles)
的输出
{
"backgroundColor": "rgb(31, 34, 40)",
"border": "1px solid rgb(47, 52, 63)",
"borderRadius": "4px",
"color": "rgb(255, 255, 255)",
"cursor": "pointer",
"padding": "3px 5px 3px 5px"
}
我注意到这里有几个问题:
styles.dark
有几个 Color()
[Object]
来自 color
库。他们没有将 rgb()
值作为字符串输出,但 componentStyles
中的相同属性有,因此导致不匹配。
componentStyles
剥离了 Radium 的交互样式,例如 :focus
和 :hover
(我假设 Radium 在渲染期间由 Enzyme 的 shallow()
函数触发)。这会导致与 styles.dark
不匹配,styles.dark
没有删除这些属性。
因此,我不确定如何测试它。我想不出任何替代解决方案来验证是否已应用 styles.dark
。我认为在测试期间对 styles.dark
执行以下操作将是一个解决方案:
- 递归地导致所有
Color()
[Object]
进行处理,以便它们将 rgb()
值作为字符串输出。
- 递归删除所有交互式 Radium 样式(如
:focus
和 :hover
)
这样做会使styles.dark
等于componentStyles
的值,从而通过测试。我只是不知道该怎么做。
几天后我重新审视这个问题并想到了一个解决方案:
describe("<Button>", () => {
let props
let mountedComponent
let defaultComponent
const getComponent = () => {
if (!mountedComponent)
mountedComponent = shallow(
<Button {...props} />
)
return mountedComponent
}
beforeEach(() => {
props = {
children: undefined,
dark: undefined,
icon: ICONS.VIBE,
style: undefined,
}
defaultComponent = getComponent()
mountedComponent = undefined
})
describe("when `dark` is `true`", () => {
beforeEach(() => {
props.dark = true
})
it("applies the component's `dark` styles", () => {
const darkStyles = getComponent().props().style
expect(defaultComponent.props().style).not.toEqual(darkStyles)
})
})
})
而不是断言渲染组件的 style
prop 包含 styles.dark
(这很脆弱),它只是检查 dark
时样式是否发生了变化道具设置为 true
.
我写了一个 React 组件,Button
:
import PropTypes from 'prop-types'
import Radium from 'radium'
import React from 'react'
import { Icon } from 'components'
import { COLOURS, GLOBAL_STYLES, ICONS, MEASUREMENTS } from 'app-constants'
@Radium
export default class Button extends React.Component {
static propTypes = {
children: PropTypes.string,
dark: PropTypes.bool,
icon: PropTypes.oneOf(Object.values(ICONS)).isRequired,
style: PropTypes.object,
}
render() {
const { children, dark, icon, style } = this.props
let mergedStyles = Object.assign({}, styles.base, style)
if (!children)
mergedStyles.icon.left = 0
if (dark)
mergedStyles = Object.assign(mergedStyles, styles.dark)
return (
<button
className="btn btn-secondary"
style={mergedStyles}
tabIndex={-1}>
<Icon name={icon} style={mergedStyles.icon} />
{children &&
<span style={mergedStyles.text}>{children}</span>
}
</button>
)
}
}
export const styles = {
base: {
backgroundColor: COLOURS.WHITE,
border: `1px solid ${COLOURS.BORDER_LIGHT}`,
borderRadius: GLOBAL_STYLES.BORDER_RADIUS,
cursor: 'pointer',
padding: GLOBAL_STYLES.BUTTON_PADDING,
':focus': {
outline: 'none',
},
':hover': {
boxShadow: GLOBAL_STYLES.BOX_SHADOW,
},
icon: {
fontSize: GLOBAL_STYLES.ICON_SIZE_TINY,
left: '-3px',
verticalAlign: 'middle',
},
text: {
fontSize: GLOBAL_STYLES.FONT_SIZE_TINY,
fontWeight: GLOBAL_STYLES.FONT_2_WEIGHT_MEDIUM,
marginLeft: `${MEASUREMENTS.BUTTON_PADDING.HORIZONTAL}px`,
verticalAlign: 'middle',
},
},
dark: {
backgroundColor: COLOURS.PRIMARY_3,
border: `1px solid ${COLOURS.PRIMARY_2}`,
color: COLOURS.WHITE,
':hover': {
boxShadow: GLOBAL_STYLES.BOX_SHADOW_DARK,
},
},
}
我还用 Jest 和 Enzyme 为 Button
编写了一个测试,它验证当其 dark
属性设置为 [=18= 时是否应用其 dark
样式]:
import { ICONS } from 'app-constants'
import Button, { styles } from 'components/Button'
describe("<Button>", () => {
let props
let mountedComponent
const getComponent = () => {
if (!mountedComponent)
mountedComponent = shallow(
<Button {...props} />
)
return mountedComponent
}
beforeEach(() => {
mountedComponent = undefined
props = {
children: undefined,
dark: undefined,
icon: ICONS.VIBE,
style: undefined,
}
})
describe("when `dark` is `true`", () => {
beforeEach(() => {
props.dark = true
})
it("applies the component's `dark` styles", () => {
const componentStyles = getComponent().props().style
expect(componentStyles).toEqual(expect.objectContaining(styles.dark))
})
})
})
如您所见,我通过检查 styles.dark
的属性是否在呈现的 Button
的 style
属性内来执行此操作。如果是,则表示样式应用成功。
问题是 styles.dark
和 componentStyles
不匹配:
console.log(styles.dark)
的输出
ObjectContaining{
":hover": {
"boxShadow": "0px 0px 0px 2px rgba(0,0,0,0.2)"
},
"backgroundColor": [Object],
"border": "1px solid rgb(47, 52, 63)",
"color": [Object]
}
console.log(componentStyles)
的输出
{
"backgroundColor": "rgb(31, 34, 40)",
"border": "1px solid rgb(47, 52, 63)",
"borderRadius": "4px",
"color": "rgb(255, 255, 255)",
"cursor": "pointer",
"padding": "3px 5px 3px 5px"
}
我注意到这里有几个问题:
styles.dark
有几个Color()
[Object]
来自color
库。他们没有将rgb()
值作为字符串输出,但componentStyles
中的相同属性有,因此导致不匹配。componentStyles
剥离了 Radium 的交互样式,例如:focus
和:hover
(我假设 Radium 在渲染期间由 Enzyme 的shallow()
函数触发)。这会导致与styles.dark
不匹配,styles.dark
没有删除这些属性。
因此,我不确定如何测试它。我想不出任何替代解决方案来验证是否已应用 styles.dark
。我认为在测试期间对 styles.dark
执行以下操作将是一个解决方案:
- 递归地导致所有
Color()
[Object]
进行处理,以便它们将rgb()
值作为字符串输出。 - 递归删除所有交互式 Radium 样式(如
:focus
和:hover
)
这样做会使styles.dark
等于componentStyles
的值,从而通过测试。我只是不知道该怎么做。
几天后我重新审视这个问题并想到了一个解决方案:
describe("<Button>", () => {
let props
let mountedComponent
let defaultComponent
const getComponent = () => {
if (!mountedComponent)
mountedComponent = shallow(
<Button {...props} />
)
return mountedComponent
}
beforeEach(() => {
props = {
children: undefined,
dark: undefined,
icon: ICONS.VIBE,
style: undefined,
}
defaultComponent = getComponent()
mountedComponent = undefined
})
describe("when `dark` is `true`", () => {
beforeEach(() => {
props.dark = true
})
it("applies the component's `dark` styles", () => {
const darkStyles = getComponent().props().style
expect(defaultComponent.props().style).not.toEqual(darkStyles)
})
})
})
而不是断言渲染组件的 style
prop 包含 styles.dark
(这很脆弱),它只是检查 dark
时样式是否发生了变化道具设置为 true
.