无法让 Jest 与包含主题的样式化组件一起工作
Can't get Jest to work with Styled Components which contain theming
问题
我一直在使用 Jest and Enzyme to write tests for my React components build with the awesome Styled Components 库。
但是,自从我实施了主题化后,我的所有测试都失败了。让我举一个例子。
这是我的 LooksBrowser
组件的代码(我删除了所有导入和 prop-types 以使其更具可读性):
const LooksBrowserWrapper = styled.div`
position: relative;
padding: 0 0 56.25%;
`;
const CurrentSlideWrapper = styled.div`
position: absolute;
top: 0;
left: 0;
z-index: 2;
`;
const NextSlideWrapper = CurrentSlideWrapper.extend`
z-index: 1;
`;
const SlideImage = styled.img`
display: block;
width: 100%;
`;
const SlideText = styled.div`
display: flex;
position: absolute;
top: 25%;
left: ${PXToVW(72)};
height: 25%;
flex-direction: column;
justify-content: center;
`;
const SlideTitle = styled.p`
flex: 0 0 auto;
text-transform: uppercase;
line-height: 1;
color: ${props => props.color};
font-family: ${props => props.theme.LooksBrowser.SlideTitle.FontFamily};
font-size: ${PXToVW(52)};
`;
const SlideSubtitle = SlideTitle.extend`
font-family: ${props => props.theme.LooksBrowser.SlideSubtitle.FontFamily};
`;
export default class LooksBrowser extends React.Component {
state = {
currentSlide: {
imageURL: this.props.currentSlide.imageURL,
index: this.props.currentSlide.index,
subtitle: this.props.currentSlide.subtitle,
textColor: this.props.currentSlide.textColor,
title: this.props.currentSlide.title
},
nextSlide: {
imageURL: this.props.nextSlide.imageURL,
index: this.props.nextSlide.index,
subtitle: this.props.nextSlide.subtitle,
textColor: this.props.nextSlide.textColor,
title: this.props.nextSlide.title
},
nextSlideIsLoaded: false
};
componentDidMount() {
this.setVariables();
}
componentWillReceiveProps(nextProps) {
// Only update the state when the nextSlide data is different than the current nextSlide data
// and when the LooksBrowser component isn't animating
if (this.props.nextSlide.imageURL !== nextProps.nextSlide.imageURL && !this.isAnimating) {
this.setState(prevState => update(prevState, {
nextSlide: {
imageURL: { $set: nextProps.nextSlide.imageURL },
index: { $set: nextProps.nextSlide.index },
subtitle: { $set: nextProps.nextSlide.subtitle },
textColor: { $set: nextProps.nextSlide.textColor },
title: { $set: nextProps.nextSlide.title }
}
}));
}
}
componentDidUpdate() {
if (!this.isAnimating) {
if (this.state.nextSlide.imageURL !== '' && this.state.nextSlideIsLoaded) {
// Only do the animation when the nextSlide is done loading and it defined inside of the state
this.animateToNextSlide();
} else if (this.state.currentSlide.imageURL !== this.props.nextSlide.imageURL && this.state.nextSlide.imageURL !== this.props.nextSlide.imageURL) {
// This usecase is for when the LooksBrowser already received another look while still being in an animation
// After the animation is done it checks if the new nextSlide data is different than the current currentSlide data
// And also checks if the current nextSlide state data is different than the new nextSlide data
// If so, it updates the nextSlide part of the state so that in the next render animateToNextSlide will be called
this.setState(prevState => update(prevState, {
nextSlide: {
imageURL: { $set: this.props.nextSlide.imageURL },
index: { $set: this.props.nextSlide.index },
subtitle: { $set: this.props.nextSlide.subtitle },
textColor: { $set: this.props.nextSlide.textColor },
title: { $set: this.props.nextSlide.title }
}
}));
} else if (!this.state.nextSlideIsLoaded) {
// Reset currentSlide position to prevent 'flash'
TweenMax.set(this.currentSlide, {
x: '0%'
});
}
}
}
setVariables() {
this.TL = new TimelineMax();
this.isAnimating = false;
}
nextSlideIsLoaded = () => {
this.setState(prevState => update(prevState, {
nextSlideIsLoaded: { $set: true }
}));
};
animateToNextSlide() {
const AnimateForward = this.state.currentSlide.index < this.state.nextSlide.index;
this.isAnimating = true;
this.TL.clear();
this.TL
.set(this.currentSlide, {
x: '0%'
})
.set(this.nextSlide, {
x: AnimateForward ? '100%' : '-100%'
})
.to(this.currentSlide, 0.7, {
x: AnimateForward ? '-100%' : '100%',
ease: Quad.easeInOut
})
.to(this.nextSlide, 0.7, {
x: '0%',
ease: Quad.easeInOut,
onComplete: () => {
this.isAnimating = false;
this.setState(prevState => update(prevState, {
currentSlide: {
imageURL: { $set: prevState.nextSlide.imageURL },
index: { $set: prevState.nextSlide.index },
subtitle: { $set: prevState.nextSlide.subtitle },
textColor: { $set: prevState.nextSlide.textColor },
title: { $set: prevState.nextSlide.title }
},
nextSlide: {
imageURL: { $set: '' },
index: { $set: 0 },
subtitle: { $set: '' },
textColor: { $set: '' },
title: { $set: '' }
},
nextSlideIsLoaded: { $set: false }
}));
}
}, '-=0.7');
}
render() {
return(
<LooksBrowserWrapper>
<CurrentSlideWrapper innerRef={div => this.currentSlide = div} >
<SlideImage src={this.state.currentSlide.imageURL} alt={this.state.currentSlide.title} />
<SlideText>
<SlideTitle color={this.state.currentSlide.textColor}>{this.state.currentSlide.title}</SlideTitle>
<SlideSubtitle color={this.state.currentSlide.textColor}>{this.state.currentSlide.subtitle}</SlideSubtitle>
</SlideText>
</CurrentSlideWrapper>
{this.state.nextSlide.imageURL &&
<NextSlideWrapper innerRef={div => this.nextSlide = div}>
<SlideImage src={this.state.nextSlide.imageURL} alt={this.state.nextSlide.title} onLoad={this.nextSlideIsLoaded} />
<SlideText>
<SlideTitle color={this.state.nextSlide.textColor}>{this.state.nextSlide.title}</SlideTitle>
<SlideSubtitle color={this.state.nextSlide.textColor}>{this.state.nextSlide.subtitle}</SlideSubtitle>
</SlideText>
</NextSlideWrapper>
}
</LooksBrowserWrapper>
);
}
}
然后现在我对 LooksBrowser
组件进行测试(以下是完整代码):
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import renderer from 'react-test-renderer';
import Adapter from 'enzyme-adapter-react-16';
import 'jest-styled-components';
import LooksBrowser from './../src/app/components/LooksBrowser/LooksBrowser';
Enzyme.configure({ adapter: new Adapter() });
test('Compare snapshots', () => {
const Component = renderer.create(<LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} />);
const Tree = Component.toJSON();
expect(Tree).toMatchSnapshot();
});
test('Renders without crashing', () => {
mount(<LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} />);
});
test('Check if componentDidUpdate gets called', () => {
const spy = jest.spyOn(LooksBrowser.prototype, 'componentDidUpdate');
const Component = mount(<LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} />);
Component.setProps({ nextSlide: { imageURL: 'http://localhost:3001/img/D2_VW_SPW.jpg', index: 2, subtitle: 'Don\'t walk here at night', title: 'What A View', textColor: '#fff' } });
expect(spy).toBeCalled();
});
test('Check if animateToNextSlide gets called', () => {
const spy = jest.spyOn(LooksBrowser.prototype, 'animateToNextSlide');
const Component = mount(<LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} />);
Component.setProps({ nextSlide: { imageURL: 'http://localhost:3001/img/D2_VW_SPW.jpg', index: 2, subtitle: 'Don\'t walk here at night', title: 'What A View', textColor: '#fff' } });
Component.setState({ nextSlideIsLoaded: true });
expect(spy).toBeCalled();
});
在我实施主题之前,所有这些测试都通过了。实施主题后,每次测试都会出现以下错误:
TypeError: Cannot read property 'SlideTitle' of undefined
44 | line-height: 1;
45 | color: ${props => props.color};
> 46 | font-family: ${props => props.theme.LooksBrowser.SlideTitle.FontFamily};
47 | font-size: ${PXToVW(52)};
48 | `;
49 |
好的,有道理。主题未定义。
解决方案一
所以在谷歌搜索后我发现了以下内容'solution':
https://github.com/styled-components/jest-styled-components#theming
The recommended solution is to pass the theme as a prop:
const wrapper = shallow(<Button theme={theme} />)
所以我将以下代码添加到我的 LooksBrowser
测试文件中:
const theme = {
LooksBrowser: {
SlideTitle: {
FontFamily: 'Futura-Light, sans-serif'
},
SlideSubtitle: {
FontFamily: 'Futura-Demi, sans-serif'
}
}
};
并编辑我所有的测试以手动通过主题。例如:
test('Compare snapshots', () => {
const Component = renderer.create(<LooksBrowser theme={theme} currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} />);
const Tree = Component.toJSON();
expect(Tree).toMatchSnapshot();
});
完成此操作后,我再次 运行 我的测试。还是出现同样的错误。
方案二
我决定将我的组件包装在样式化组件中 ThemeProvider。这修复了我的 Compare snapshots
和 Renders without crashing
测试中的错误。
但是,由于我也在更改 LooksBrowser
组件的 props/state 并测试结果,因此这不再有效。这是因为 setProps
和 setState
函数只能在 root/wrapper 组件上使用。
因此,将我的组件包装在 ThemeProvider
组件中也不是有效的解决方案。
方案三
我决定尝试记录我的一个样式化组件的道具。所以我将 SlideTitle
子组件更改为:
const SlideTitle = styled.p`
flex: 0 0 auto;
text-transform: uppercase;
line-height: 1;
color: ${props => {
console.log(props.theme.LooksBrowser.SlideTitle.FontFamily);
return props.color;
}};
font-family: ${props => props.theme.LooksBrowser.SlideTitle.FontFamily};
font-size: ${PXToVW(52)};
`;
我收到以下错误:
TypeError: Cannot read property 'SlideTitle' of undefined
44 | line-height: 1;
45 | color: ${props => {
> 46 | console.log(props.theme.LooksBrowser.SlideTitle.FontFamily);
47 | return props.color;
48 | }};
49 | font-family: ${props => props.theme.LooksBrowser.SlideTitle.FontFamily};
好吧,好像整个主题道具都是空的。让我们尝试手动将主题传递给 SlideTitle
(顺便说一句,这是一个可怕的解决方案,这意味着我需要将我的主题手动传递给整个项目中的每个样式化组件)。
所以我添加了以下代码:
<SlideTitle theme={this.props.theme} color{this.state.currentSlide.textColor}>{this.state.currentSlide.title}</SlideTitle>
然后我又 运行 我的测试。我在终端中看到以下行:
console.log src/app/components/LooksBrowser/LooksBrowser.js:46
Futura-Light, sans-serif
是的,这就是我要找的!我向下滚动并再次看到相同的错误... yikes.
方案四
在Jest Styled Components documentation我还看到了下面的解决方法:
const shallowWithTheme = (tree, theme) => {
const context = shallow(<ThemeProvider theme={theme} />)
.instance()
.getChildContext()
return shallow(tree, { context })
}
const wrapper = shallowWithTheme(<Button />, theme)
好的,看起来很有希望。所以我将这个函数添加到我的测试文件并将我的 Check if componentDidUpdate gets called
测试更新为:
test('Check if componentDidUpdate gets called', () => {
const spy = jest.spyOn(LooksBrowser.prototype, 'componentDidUpdate');
const Component = shallowWithTheme(<LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} />, Theme);
Component.setProps({ nextSlide: { imageURL: 'http://localhost:3001/img/D2_VW_SPW.jpg', index: 2, subtitle: 'Don\'t walk here at night', title: 'What A View', textColor: '#fff' } });
expect(spy).toBeCalled();
});
我运行测试得到如下错误:
Error
Cannot tween a null target. thrown
有意义,因为我使用的是 shallow. So I change the function to use mount 而不是 shallow:
const shallowWithTheme = (tree, theme) => {
const context = mount(<ThemeProvider theme={theme} />)
.instance()
.getChildContext()
return mount(tree, { context })
}
我 运行 我再次测试,瞧:
TypeError: Cannot read property 'SlideTitle' of undefined
我真的没主意了。
如果有人对此有任何想法,非常将不胜感激!提前谢谢大家。
编辑
我现在还在 Github 上开了两期,一期在 Styled Components repo and one in the Jest Styled Components repo.
到目前为止,我已经尝试了那里提供的所有解决方案,但没有任何效果。因此,如果这里的任何人对如何解决此问题有任何想法,请分享!
在一位同事的帮助下,我设法解决了这个问题。我有另一个组件扩展了破坏测试的 SlideTitle
组件:
const SlideSubtitle = SlideTitle.extend`
font-family: ${props =>
props.theme.LooksBrowser.SlideSubtitle.FontFamily};
`;
我将我的代码重构为:
const SlideTitlesSharedStyling = styled.p`
flex: 0 0 auto;
text-transform: uppercase;
line-height: 1;
color: ${props => props.color};
font-size: ${PXToVW(52)};
`;
const SlideTitle = SlideTitlesSharedStyling.extend`
font-family: ${props => props.theme.LooksBrowser.SlideTitle.FontFamily};
`;
const SlideSubtitle = SlideTitlesSharedStyling.extend`
font-family: ${props => props.theme.LooksBrowser.SlideSubtitle.FontFamily};
`;
我的测试又开始通过了!
将 ThemeProvider
包裹在组件周围并将 theme
对象传递给它,对我来说效果很好。
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { render, cleanup } from '@testing-library/react';
import Home from '../Home';
import { themelight } from '../../Layout/theme';
afterEach(cleanup);
test('home renders correctly', () => {
let { getByText } = render(
<ThemeProvider theme={themelight}>
<Home name={name} />
</ThemeProvider>
);
getByText('ANURAG HAZRA');
})
问题
我一直在使用 Jest and Enzyme to write tests for my React components build with the awesome Styled Components 库。
但是,自从我实施了主题化后,我的所有测试都失败了。让我举一个例子。
这是我的 LooksBrowser
组件的代码(我删除了所有导入和 prop-types 以使其更具可读性):
const LooksBrowserWrapper = styled.div`
position: relative;
padding: 0 0 56.25%;
`;
const CurrentSlideWrapper = styled.div`
position: absolute;
top: 0;
left: 0;
z-index: 2;
`;
const NextSlideWrapper = CurrentSlideWrapper.extend`
z-index: 1;
`;
const SlideImage = styled.img`
display: block;
width: 100%;
`;
const SlideText = styled.div`
display: flex;
position: absolute;
top: 25%;
left: ${PXToVW(72)};
height: 25%;
flex-direction: column;
justify-content: center;
`;
const SlideTitle = styled.p`
flex: 0 0 auto;
text-transform: uppercase;
line-height: 1;
color: ${props => props.color};
font-family: ${props => props.theme.LooksBrowser.SlideTitle.FontFamily};
font-size: ${PXToVW(52)};
`;
const SlideSubtitle = SlideTitle.extend`
font-family: ${props => props.theme.LooksBrowser.SlideSubtitle.FontFamily};
`;
export default class LooksBrowser extends React.Component {
state = {
currentSlide: {
imageURL: this.props.currentSlide.imageURL,
index: this.props.currentSlide.index,
subtitle: this.props.currentSlide.subtitle,
textColor: this.props.currentSlide.textColor,
title: this.props.currentSlide.title
},
nextSlide: {
imageURL: this.props.nextSlide.imageURL,
index: this.props.nextSlide.index,
subtitle: this.props.nextSlide.subtitle,
textColor: this.props.nextSlide.textColor,
title: this.props.nextSlide.title
},
nextSlideIsLoaded: false
};
componentDidMount() {
this.setVariables();
}
componentWillReceiveProps(nextProps) {
// Only update the state when the nextSlide data is different than the current nextSlide data
// and when the LooksBrowser component isn't animating
if (this.props.nextSlide.imageURL !== nextProps.nextSlide.imageURL && !this.isAnimating) {
this.setState(prevState => update(prevState, {
nextSlide: {
imageURL: { $set: nextProps.nextSlide.imageURL },
index: { $set: nextProps.nextSlide.index },
subtitle: { $set: nextProps.nextSlide.subtitle },
textColor: { $set: nextProps.nextSlide.textColor },
title: { $set: nextProps.nextSlide.title }
}
}));
}
}
componentDidUpdate() {
if (!this.isAnimating) {
if (this.state.nextSlide.imageURL !== '' && this.state.nextSlideIsLoaded) {
// Only do the animation when the nextSlide is done loading and it defined inside of the state
this.animateToNextSlide();
} else if (this.state.currentSlide.imageURL !== this.props.nextSlide.imageURL && this.state.nextSlide.imageURL !== this.props.nextSlide.imageURL) {
// This usecase is for when the LooksBrowser already received another look while still being in an animation
// After the animation is done it checks if the new nextSlide data is different than the current currentSlide data
// And also checks if the current nextSlide state data is different than the new nextSlide data
// If so, it updates the nextSlide part of the state so that in the next render animateToNextSlide will be called
this.setState(prevState => update(prevState, {
nextSlide: {
imageURL: { $set: this.props.nextSlide.imageURL },
index: { $set: this.props.nextSlide.index },
subtitle: { $set: this.props.nextSlide.subtitle },
textColor: { $set: this.props.nextSlide.textColor },
title: { $set: this.props.nextSlide.title }
}
}));
} else if (!this.state.nextSlideIsLoaded) {
// Reset currentSlide position to prevent 'flash'
TweenMax.set(this.currentSlide, {
x: '0%'
});
}
}
}
setVariables() {
this.TL = new TimelineMax();
this.isAnimating = false;
}
nextSlideIsLoaded = () => {
this.setState(prevState => update(prevState, {
nextSlideIsLoaded: { $set: true }
}));
};
animateToNextSlide() {
const AnimateForward = this.state.currentSlide.index < this.state.nextSlide.index;
this.isAnimating = true;
this.TL.clear();
this.TL
.set(this.currentSlide, {
x: '0%'
})
.set(this.nextSlide, {
x: AnimateForward ? '100%' : '-100%'
})
.to(this.currentSlide, 0.7, {
x: AnimateForward ? '-100%' : '100%',
ease: Quad.easeInOut
})
.to(this.nextSlide, 0.7, {
x: '0%',
ease: Quad.easeInOut,
onComplete: () => {
this.isAnimating = false;
this.setState(prevState => update(prevState, {
currentSlide: {
imageURL: { $set: prevState.nextSlide.imageURL },
index: { $set: prevState.nextSlide.index },
subtitle: { $set: prevState.nextSlide.subtitle },
textColor: { $set: prevState.nextSlide.textColor },
title: { $set: prevState.nextSlide.title }
},
nextSlide: {
imageURL: { $set: '' },
index: { $set: 0 },
subtitle: { $set: '' },
textColor: { $set: '' },
title: { $set: '' }
},
nextSlideIsLoaded: { $set: false }
}));
}
}, '-=0.7');
}
render() {
return(
<LooksBrowserWrapper>
<CurrentSlideWrapper innerRef={div => this.currentSlide = div} >
<SlideImage src={this.state.currentSlide.imageURL} alt={this.state.currentSlide.title} />
<SlideText>
<SlideTitle color={this.state.currentSlide.textColor}>{this.state.currentSlide.title}</SlideTitle>
<SlideSubtitle color={this.state.currentSlide.textColor}>{this.state.currentSlide.subtitle}</SlideSubtitle>
</SlideText>
</CurrentSlideWrapper>
{this.state.nextSlide.imageURL &&
<NextSlideWrapper innerRef={div => this.nextSlide = div}>
<SlideImage src={this.state.nextSlide.imageURL} alt={this.state.nextSlide.title} onLoad={this.nextSlideIsLoaded} />
<SlideText>
<SlideTitle color={this.state.nextSlide.textColor}>{this.state.nextSlide.title}</SlideTitle>
<SlideSubtitle color={this.state.nextSlide.textColor}>{this.state.nextSlide.subtitle}</SlideSubtitle>
</SlideText>
</NextSlideWrapper>
}
</LooksBrowserWrapper>
);
}
}
然后现在我对 LooksBrowser
组件进行测试(以下是完整代码):
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import renderer from 'react-test-renderer';
import Adapter from 'enzyme-adapter-react-16';
import 'jest-styled-components';
import LooksBrowser from './../src/app/components/LooksBrowser/LooksBrowser';
Enzyme.configure({ adapter: new Adapter() });
test('Compare snapshots', () => {
const Component = renderer.create(<LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} />);
const Tree = Component.toJSON();
expect(Tree).toMatchSnapshot();
});
test('Renders without crashing', () => {
mount(<LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} />);
});
test('Check if componentDidUpdate gets called', () => {
const spy = jest.spyOn(LooksBrowser.prototype, 'componentDidUpdate');
const Component = mount(<LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} />);
Component.setProps({ nextSlide: { imageURL: 'http://localhost:3001/img/D2_VW_SPW.jpg', index: 2, subtitle: 'Don\'t walk here at night', title: 'What A View', textColor: '#fff' } });
expect(spy).toBeCalled();
});
test('Check if animateToNextSlide gets called', () => {
const spy = jest.spyOn(LooksBrowser.prototype, 'animateToNextSlide');
const Component = mount(<LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} />);
Component.setProps({ nextSlide: { imageURL: 'http://localhost:3001/img/D2_VW_SPW.jpg', index: 2, subtitle: 'Don\'t walk here at night', title: 'What A View', textColor: '#fff' } });
Component.setState({ nextSlideIsLoaded: true });
expect(spy).toBeCalled();
});
在我实施主题之前,所有这些测试都通过了。实施主题后,每次测试都会出现以下错误:
TypeError: Cannot read property 'SlideTitle' of undefined
44 | line-height: 1;
45 | color: ${props => props.color};
> 46 | font-family: ${props => props.theme.LooksBrowser.SlideTitle.FontFamily};
47 | font-size: ${PXToVW(52)};
48 | `;
49 |
好的,有道理。主题未定义。
解决方案一
所以在谷歌搜索后我发现了以下内容'solution':
https://github.com/styled-components/jest-styled-components#theming
The recommended solution is to pass the theme as a prop:
const wrapper = shallow(<Button theme={theme} />)
所以我将以下代码添加到我的 LooksBrowser
测试文件中:
const theme = {
LooksBrowser: {
SlideTitle: {
FontFamily: 'Futura-Light, sans-serif'
},
SlideSubtitle: {
FontFamily: 'Futura-Demi, sans-serif'
}
}
};
并编辑我所有的测试以手动通过主题。例如:
test('Compare snapshots', () => {
const Component = renderer.create(<LooksBrowser theme={theme} currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} />);
const Tree = Component.toJSON();
expect(Tree).toMatchSnapshot();
});
完成此操作后,我再次 运行 我的测试。还是出现同样的错误。
方案二
我决定将我的组件包装在样式化组件中 ThemeProvider。这修复了我的 Compare snapshots
和 Renders without crashing
测试中的错误。
但是,由于我也在更改 LooksBrowser
组件的 props/state 并测试结果,因此这不再有效。这是因为 setProps
和 setState
函数只能在 root/wrapper 组件上使用。
因此,将我的组件包装在 ThemeProvider
组件中也不是有效的解决方案。
方案三
我决定尝试记录我的一个样式化组件的道具。所以我将 SlideTitle
子组件更改为:
const SlideTitle = styled.p`
flex: 0 0 auto;
text-transform: uppercase;
line-height: 1;
color: ${props => {
console.log(props.theme.LooksBrowser.SlideTitle.FontFamily);
return props.color;
}};
font-family: ${props => props.theme.LooksBrowser.SlideTitle.FontFamily};
font-size: ${PXToVW(52)};
`;
我收到以下错误:
TypeError: Cannot read property 'SlideTitle' of undefined
44 | line-height: 1;
45 | color: ${props => {
> 46 | console.log(props.theme.LooksBrowser.SlideTitle.FontFamily);
47 | return props.color;
48 | }};
49 | font-family: ${props => props.theme.LooksBrowser.SlideTitle.FontFamily};
好吧,好像整个主题道具都是空的。让我们尝试手动将主题传递给 SlideTitle
(顺便说一句,这是一个可怕的解决方案,这意味着我需要将我的主题手动传递给整个项目中的每个样式化组件)。
所以我添加了以下代码:
<SlideTitle theme={this.props.theme} color{this.state.currentSlide.textColor}>{this.state.currentSlide.title}</SlideTitle>
然后我又 运行 我的测试。我在终端中看到以下行:
console.log src/app/components/LooksBrowser/LooksBrowser.js:46
Futura-Light, sans-serif
是的,这就是我要找的!我向下滚动并再次看到相同的错误... yikes.
方案四
在Jest Styled Components documentation我还看到了下面的解决方法:
const shallowWithTheme = (tree, theme) => {
const context = shallow(<ThemeProvider theme={theme} />)
.instance()
.getChildContext()
return shallow(tree, { context })
}
const wrapper = shallowWithTheme(<Button />, theme)
好的,看起来很有希望。所以我将这个函数添加到我的测试文件并将我的 Check if componentDidUpdate gets called
测试更新为:
test('Check if componentDidUpdate gets called', () => {
const spy = jest.spyOn(LooksBrowser.prototype, 'componentDidUpdate');
const Component = shallowWithTheme(<LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} />, Theme);
Component.setProps({ nextSlide: { imageURL: 'http://localhost:3001/img/D2_VW_SPW.jpg', index: 2, subtitle: 'Don\'t walk here at night', title: 'What A View', textColor: '#fff' } });
expect(spy).toBeCalled();
});
我运行测试得到如下错误:
Error
Cannot tween a null target. thrown
有意义,因为我使用的是 shallow. So I change the function to use mount 而不是 shallow:
const shallowWithTheme = (tree, theme) => {
const context = mount(<ThemeProvider theme={theme} />)
.instance()
.getChildContext()
return mount(tree, { context })
}
我 运行 我再次测试,瞧:
TypeError: Cannot read property 'SlideTitle' of undefined
我真的没主意了。
如果有人对此有任何想法,非常将不胜感激!提前谢谢大家。
编辑
我现在还在 Github 上开了两期,一期在 Styled Components repo and one in the Jest Styled Components repo.
到目前为止,我已经尝试了那里提供的所有解决方案,但没有任何效果。因此,如果这里的任何人对如何解决此问题有任何想法,请分享!
在一位同事的帮助下,我设法解决了这个问题。我有另一个组件扩展了破坏测试的 SlideTitle
组件:
const SlideSubtitle = SlideTitle.extend`
font-family: ${props =>
props.theme.LooksBrowser.SlideSubtitle.FontFamily};
`;
我将我的代码重构为:
const SlideTitlesSharedStyling = styled.p`
flex: 0 0 auto;
text-transform: uppercase;
line-height: 1;
color: ${props => props.color};
font-size: ${PXToVW(52)};
`;
const SlideTitle = SlideTitlesSharedStyling.extend`
font-family: ${props => props.theme.LooksBrowser.SlideTitle.FontFamily};
`;
const SlideSubtitle = SlideTitlesSharedStyling.extend`
font-family: ${props => props.theme.LooksBrowser.SlideSubtitle.FontFamily};
`;
我的测试又开始通过了!
将 ThemeProvider
包裹在组件周围并将 theme
对象传递给它,对我来说效果很好。
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { render, cleanup } from '@testing-library/react';
import Home from '../Home';
import { themelight } from '../../Layout/theme';
afterEach(cleanup);
test('home renders correctly', () => {
let { getByText } = render(
<ThemeProvider theme={themelight}>
<Home name={name} />
</ThemeProvider>
);
getByText('ANURAG HAZRA');
})