简体   繁体   English

React 测试库/Jest - 呈现父组件时不存在子组件

[英]React Testing Library / Jest - Child component not present when rendering parent component

I'm trying to test that a click handler in my child component that is changing the parent component's state and then displaying conditional jsx in my parent component, but I can't figure out the best way to do so and I'm also having trouble debugging.我正在尝试测试我的子组件中的点击处理程序,该处理程序正在更改父组件的状态,然后在我的父组件中显示条件 jsx,但我无法找出最好的方法,而且我也有麻烦调试。 My other tests that test the parent component and child component separately are working (as in I'm able to find dom elements that I expect to be present), but when I try to test the clicking of a button in the child component by rendering the parent component, my test fails.我的其他分别测试父组件和子组件的测试正在运行(因为我能够找到我希望存在的 dom 元素),但是当我尝试通过渲染测试子组件中按钮的点击时父组件,我的测试失败了。

Expected behavior:预期行为:

  1. User clicks the div with className 'open-comparison-btn'用户单击类名为“open-comparison-btn”的 div
  2. Child component calls the props.setModalShowing function with 'true'子组件使用 'true' 调用 props.setModalShowing 函数
  3. Parent component modalShowing state is updated to true父组件modalShowing状态更新为true
  4. Parent component re-renders and displays the conditional jsx className 'comparison-modal'父组件重新渲染并显示条件 jsx className 'comparison-modal'

The functionality is working in the localhost browser, but not in my test, and I can't even find the child component's html at all in my test.该功能在本地主机浏览器中有效,但在我的测试中无效,我什至在我的测试中根本找不到子组件的 html。

Parent component:父组件:

import React, { useState, useEffect } from 'react';
import ProductCard from './ProductCard.jsx';

const RelatedProducts = (props) => {
  const [position, setPosition] = useState(0);
  const componentName = 'RelatedProducts';
  const [modalShowing, setModalShowing] = useState(false);
  const [currentProduct, setCurrentProduct] = useState({});
  const [comparisonProduct, setComparisonProduct] = useState({});

  useEffect(() => {
    setCurrentProduct(props.currentProduct);
  }, [props.currentProduct]);

  const getFeatures = () => {
    return [...currentProduct.features, ...comparisonProduct.features]
      .filter((v, i, a)=>a.findIndex(v2=>(v.feature === v2.feature && v.value === v2.value)) === i);
  };

  return (
    <>
      <div className='related-products-container' role="listbox" aria-label="related products" style={{marginLeft: `-${position}px`}}>
        {props.relatedProducts ?
          props.relatedProducts.map((product) => {
            return <ProductCard
              modalShowing={modalShowing}
              setModalShowing={setModalShowing}
              setComparisonProduct={setComparisonProduct}
              key={product.id}
              product={product}
              generateStars={props.generateStars}
              isFetching={props.isFetching}
              setIsFetching={props.setIsFetching}
              parentComponent={componentName}
              yourOutfit={props.yourOutfit}
              addToOutfit={props.addToOutfit}
            />;
          })
          : null
        }

      </div>
      <div className='fade-top'>
        { position > 0 ?
          <div className="arrow-container-left" role="button" aria-label="scroll left" onClick={() => { setPosition(position - 250); }}>
            <div className="arrow-left"></div>
          </div>
          : null
        }
        { props && props.relatedProducts && position <= (props.relatedProducts.length - 4) * 250 ?
          <div className="arrow-container-right" role="button" aria-label="scroll right" onClick={() => { setPosition(position + 250); }}>
            <div className="arrow-right"></div>
          </div>
          : null
        }
      </div>
      {modalShowing ?
        <div className='comparison-modal' role='dialog' aria-label='comparison window'>
          <div className='modal-top'>COMPARING</div>
          <div className='modal-product-names'>
            <div className='product-1'>{currentProduct.name}</div>
            <div className='product-2'>{comparisonProduct.name}</div>
          </div>
          <table className='modal-table'>
            <tbody>
              {getFeatures().map((feature, index) => {
                return (
                  <tr key={`${feature}-${index}`}>
                    <td className='left-check'>{currentProduct.features.filter(item => item.feature === feature.feature && item.value === feature.value).length > 0 ? '✓' : null}</td>
                    <td>{feature.value} {feature.feature}</td>
                    <td className='right-check'>{comparisonProduct.features.filter(item => item.feature === feature.feature && item.value === feature.value).length > 0 ? '✓' : null}</td>
                  </tr>
                );
              })}
            </tbody>
          </table>
          <div className="close-btn" onClick={() => { setModalShowing(false); }}></div>
        </div>
        : null}
    </>
  );
};

export default RelatedProducts;

Child component:子组件:

import React, { useState, useEffect } from 'react';
import ratingsAPI from '../../API/Ratings.js';
import { useNavigate } from 'react-router-dom';

const ProductCard = (props) => {
  const navigate = useNavigate();
  const [averageRating, setAverageRating] = useState();
  const stars = props.generateStars(averageRating, 'related');

  useEffect(() => {
    ratingsAPI.getReviewMetadata(props.product.id)
      .then((metadata) => {
        setAverageRating(getAverageRating(metadata.ratings));
        props.setIsFetching(false);
      });
  }, []);

  const routeChange = () => {
    const path = `/${props.product.id.toString()}`;
    navigate(path);
  };

  const displayComparison = (e) => {
    props.setComparisonProduct(props.product);
    props.setModalShowing(true);
    e.stopPropagation();
  };

  const getAverageRating = (ratings) => {
    var sum = 0;
    var count = 0;
    Object.keys(ratings).forEach(function(rating) {
      sum += rating * parseInt(ratings[rating]);
      count += parseInt(ratings[rating]);
    });
    return sum / count;
  };

  return (
    !props.isFetching ?
      <>
        <div className='product-card-container' onClick={() => routeChange(props.product.id)}>
          <img className='product-card-image' src={props.product.styles.results[0].photos[0].thumbnail_url}>
          </img>
          {props.parentComponent === 'RelatedProducts'
            ?
            <svg className="open-comparison-btn" role='button' aria-label='open comparison' width="20px" height="20px" viewBox="0 0 32 32" onClick={(e) => { displayComparison(e); }}>
              <path fill="White" stroke="black" strokeWidth="2px" d="M20.388,10.918L32,12.118l-8.735,7.749L25.914,31.4l-9.893-6.088L6.127,31.4l2.695-11.533L0,12.118
            l11.547-1.2L16.026,0.6L20.388,10.918z"/>
            </svg>
            :
            <div className="close-btn" onClick={() => { props.removeFromOutfit(props.product); }}></div>
          }

          <div className='product-card-description'>
            <div className='product-card-category'>{props.product.category}</div>
            <div className='product-card-name'>{props.product.name}</div>
            <div className='product-card-price'>${props.product.default_price}</div>
            <div className='product-card-stars'>{ stars }</div>
          </div>
        </div>
      </>
      : null
  );
};

export default ProductCard;

Test:测试:

  it('tests that clicking the open-comparison-btn opens the modal window', async () => {
    render(<RelatedProducts
      addToOutfit={() => { return; }}
      yourOutfit={() => { return; }}
      relatedProducts={relatedProducts}
      generateStars={ generateStars }
      isFetching={() => { return false; }}
      setIsFetching={() => { return; }}
    />, {wrapper: Router});
    fireEvent(
      screen.getByRole('button', {name: 'open comparison'}),
      new MouseEvent('click', {
        bubbles: true,
        cancelable: true,
      }),
    );
    const modal = screen.getByRole('dialog', {name: 'comparison window'});
    expect(modal).toBeInTheDocument();
  });

Any advice would be appreciated.任何意见,将不胜感激。

The answer ended up being simply using await before render...答案最终只是在渲染之前简单地使用 await ...

I think it might be because my child component was doing an API call, but I'm not sure why I did not need to use await when rendering and testing the child component separately.我想这可能是因为我的子组件正在执行 API 调用,但我不确定为什么在单独渲染和测试子组件时不需要使用 await。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM