无法使Jest与包含主题的样式化组件一起使用 [英] Can't get Jest to work with Styled Components which contain theming

查看:47
本文介绍了无法使Jest与包含主题的样式化组件一起使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在使用笑话

I've been using Jest and Enzyme to write tests for my React components build with the awesome Styled Components library.

但是,由于我已经实现了主题,所以我所有的测试都失败了.让我给你举个例子.

However, since I've implemented theming all my tests are breaking. Let me give you an example.

这是我的 LooksBrowser 组件的代码(我已经删除了所有导入和属性类型,以使其更具可读性):

This is the code of my LooksBrowser component (I've removed all of my imports and prop-types to make it a little more readable):

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 组件进行测试(以下是完整代码):

Then now my tests for my LooksBrowser component (following is the full code):

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();
});

在实现主题之前,所有这些测试均已通过.实施主题设置后,每次测试都会出现以下错误:

Before I implemented theming all of these tests were passing. After I implemented theming I get the following error from every test:

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 |

好的,很有道理.未定义主题.

Ok, makes sense. The theme is not defined.

因此,在进行了谷歌搜索之后,我找到了以下解决方案":

So after some googling I found the following 'solution':

https://github.com/styled-components/jest-styled-组件#主题

推荐的解决方案是将主题作为道具传递: const包装器=浅(< Button theme = {theme}/>)

因此,我将以下代码添加到我的 LooksBrowser 测试文件中:

So I add the following code to my LooksBrowser test file:

const theme = {
  LooksBrowser: {
    SlideTitle: {
      FontFamily: 'Futura-Light, sans-serif'
    },
    SlideSubtitle: {
      FontFamily: 'Futura-Demi, sans-serif'
    }
  }
};

然后编辑所有测试以手动通过主题.例如:

And edit all my tests to pass the theme manually. For example:

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();
});

完成此操作后,我将再次运行测试.仍然发生相同的错误.

After I've done this I run my tests again. Still the same error occurs.

我决定将我的组件包装在 ThemeProvider 内.这样可以修复我的比较快照渲染器中的错误,而不会崩溃测试.

I decided to wrap my components inside of the Styled Components ThemeProvider. This fixes the errors from my Compare snapshots and Renders without crashing tests.

但是,由于我还更改了 LooksBrowser 组件的props/状态并测试了结果,因此不再起作用.这是因为 setProps setState 函数只能在根/包装组件上使用.

However, since I'm also changing the props/state of my LooksBrowser component and testing the results, this doesn't work anymore. This is because the setProps and setState functions only can be used on the root/wrapper component.

因此,将我的组件包装在 ThemeProvider 组件中也不是有效的解决方案.

So wrapping my components in the ThemeProvider component isn't a valid solution either.

我决定尝试记录一下我的样式组件之一的道具.因此,我将 SlideTitle 子组件更改为此:

I decided to try the log the props of one of my Styled Components. So I changed my SlideTitle subcomponent to this:

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 (这是一个可怕的解决方案,顺便说一句,这意味着我需要将主题手动传递给整个项目中的每个样式化组件).

Ok, seems like the entire theme prop is just empty. Let's try manually passing the theme to the SlideTitle (which is a horrendous solution btw, it would mean I need to pass my theme manually to every Styled Component in my entire project).

所以我添加了以下代码:

So I added the following code:

<SlideTitle theme={this.props.theme} color{this.state.currentSlide.textColor}>{this.state.currentSlide.title}</SlideTitle>

然后我再次运行测试.我在终端中看到以下行:

And I run my tests again. I see the following line in my terminal:

console.log src/app/components/LooksBrowser/LooksBrowser.js:46
Futura-Light, sans-serif

是的,这就是我想要的!我向下滚动并再次看到相同的错误... 喜欢.

Yeah, this is what I'm looking for! I scroll down and see the same error again... yikes.

Jest样式的组件文档中,我还看到了以下解决方案:

In the Jest Styled Components documentation I also saw the following solution:

const shallowWithTheme = (tree, theme) => {
  const context = shallow(<ThemeProvider theme={theme} />)
    .instance()
    .getChildContext()
  return shallow(tree, { context })
}

const wrapper = shallowWithTheme(<Button />, theme)

好的,看起来很有希望.因此,我将此功能添加到了测试文件中,并更新了 Check componentDidUpdate是否被调用测试:

Ok, looks promising. So I added this function to my test file and updated my Check if componentDidUpdate gets called test to this:

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();
});

我运行测试并得到以下错误:

I run the test and get the following error:

Error
Cannot tween a null target. thrown

有道理,因为我正在使用.因此,我将功能更改为使用 mount 而不是浅表:

Makes sense since I'm using shallow. So I change the function to use mount instead of shallow:

const shallowWithTheme = (tree, theme) => {
  const context = mount(<ThemeProvider theme={theme} />)
    .instance()
    .getChildContext()
  return mount(tree, { context })
}

我再次运行测试,瞧!

TypeError:无法读取未定义的属性"SlideTitle"

我正式没主意了.

如果有人对此有任何想法,将不胜感激!!在此先感谢大家.

If anyone has any thoughts on this it would be greatly appreciated! Thanks in advance everyone.

我现在还在Github上打开了两个问题,一个在样式化组件中rea Jest样式的组件repo 中的一个.

I've now also opened two issues on Github, one in the Styled Components repo and one in the Jest Styled Components repo.

到目前为止,我已经尝试了那里提供的所有解决方案,但都无济于事.因此,如果这里有人对如何解决此问题有任何想法,请与他们分享!

I've tried all of the solutions provided there so far without any avail. So if anyone here has any ideas on how to fix this problem please share them!

推荐答案

我已经在同事的帮助下解决了这个问题.我还有一个扩展 SlideTitle 组件的组件,它破坏了测试:

I've managed to fix this problem with help of a colleague. I had another component extend the SlideTitle component which broke the test:

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};
`;

我的测试再次开始通过!

And my tests starting passing again!

这篇关于无法使Jest与包含主题的样式化组件一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆