酶+反应本机+开玩笑:如何对<text>的内容进行console.log()?

时间:2018-08-13 18:42:44

标签: react-native jestjs enzyme

我正在使用Enzyme和Jest在React Native中测试<Text />标签的内容。我的问题是测试失败(即使一切都凭经验进行,即使我觉得我写的测试正确)。这是测试:

        describe("when less than minimum mandatory chosen", () => {
          it("should render a label saying choose at least X items", () => {
            console.log(wrapper);
            expect(
              wrapper
                .find(Text)
                .at(1)
                .contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${props.minChoices}.`)
            ).toBe(true);
          });
        });

我想检查<Text />标记内的实际字符串是什么。我该如何实现?

根据Brian的请求,以下是完整的测试代码:

import React from "react";
import { shallow } from "enzyme";
import { Text, View } from "react-native";

import {
  SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY,
  SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL,
  SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO,
  SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST
} from "../../config/constants/screenTexts";
import { AccordionList } from "./AccordionList";
import styles from "./styles";

const createTestProps = props => ({
  header: "Kekse",
  items: [
    {
      uuid: "1057e751-8ef1-4524-a743-1b4ba7b33d7b",
      name: "Haferkeks",
      price: "2.00",
      priceCurrency: "EUR"
    },
    {
      uuid: "f41f8e1a-b526-490e-ba4a-3d6acb3f3c16",
      name: "Schokosojakeks",
      price: "1.50",
      priceCurrency: "EUR"
    }
  ],
  chosenItems: [],
  onItemPressed: jest.fn(),
  ...props
});

describe("AccordionList", () => {
  describe("rendering", () => {
    let wrapper;
    let props;
    beforeEach(() => {
      props = createTestProps();
      wrapper = shallow(<AccordionList {...props} />);
    });

    it("should render container", () => {
      expect(
        wrapper
          .find(View)
          .at(0)
          .prop("style")
      ).toContain(styles.container);
    });

    it("should render a <Collapsible />", () => {
      expect(wrapper.find("Collapsible")).toHaveLength(1);
    });

    it("should give the header's <TouchableOpacity /> the headerbutton style", () => {
      expect(
        wrapper
          .find("TouchableOpacity")
          .at(0)
          .prop("style")
      ).toEqual(styles.headerButton);
    });

    it("should render a header", () => {
      expect(
        wrapper
          .find(Text)
          .at(0)
          .contains(props.header)
      ).toBe(true);
    });

    it("should give the header the header style", () => {
      expect(
        wrapper
          .find(Text)
          .at(0)
          .prop("style")
      ).toEqual(styles.header);
    });

    it("should render a subheader", () => {
      expect(
        wrapper
          .find(Text)
          .at(1)
          .prop("style")
      ).toContain(styles.subHeader);
    });

    it("should render a <TouchableOpacity /> for each of it's items", () => {
      expect(wrapper.find("TouchableOpacity")).toHaveLength(props.items.length + 1);
    });

    describe("folded", () => {
      it("should render an arrow pointing to the right", () => {
        expect(wrapper.find("Image").prop("source")).toEqual(
          require("../../assets/icons/rightArrow.png")
        );
      });

      it("should render the folded arrow with the default style", () => {
        expect(wrapper.find("Image").prop("style")).toEqual([styles.arrowIcon, styles.inActive]);
      });

      describe("mandatory", () => {
        beforeEach(() => {
          props = createTestProps({ minChoices: 1 });
          wrapper = shallow(<AccordionList {...props} />);
        });

        it("should render a mandatory label with the minimum number of mandatory items", () => {
          expect(
            wrapper
              .find(Text)
              .at(1)
              .contains(`(${SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY}, ${props.minChoices})`)
          ).toBe(true);
        });
      });

      describe("optional", () => {
        it("should render an optional label", () => {
          expect(
            wrapper
              .find(Text)
              .at(1)
              .contains(`(${SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL})`)
          ).toBe(true);
        });
      });
    });

    describe("expanded", () => {
      beforeEach(() => {
        wrapper.setState({ collapsed: false });
      });

      it("should render an arrow pointing down", () => {
        expect(wrapper.find("Image").prop("source")).toEqual(
          require("../../assets/icons/downArrow.png")
        );
      });

      it("should render the expanded arrow with the default style", () => {
        expect(wrapper.find("Image").prop("style")).toEqual([styles.arrowIcon, styles.inActive]);
      });

      // FIXME: These tests should also work but don't for some reason.
      describe("mandatory", () => {
        beforeEach(() => {
          props = createTestProps({ minChoices: 1 });
          wrapper = shallow(<AccordionList {...props} />);
          wrapper.setState({ collapsed: false });
        });

        describe("when less than minimum mandatory chosen", () => {
          it("should render a label saying choose at least X items", () => {
            expect(
              wrapper
                .find(Text)
                .at(1)
                .contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${props.minChoices}.`)
            ).toBe(true);
          });
        });

        describe("when more than minimum mandatory chosen", () => {
          beforeEach(() => {
            props = createTestProps({
              minChoices: 1,
              chosenItems: ["1057e751-8ef1-4524-a743-1b4ba7b33d7b"]
            });
            wrapper = shallow(<AccordionList {...props} />);
            wrapper.setState({ collapsed: false });
          });

          it("should render a label saying choose up to X items", () => {
            expect(
              wrapper
                .find(Text)
                .at(1)
                .contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices}.`)
            ).toBe(true);
          });
        });
      });

      describe("optional", () => {
        it("should render a label saying choose up to X items", () => {
          expect(
            wrapper
              .find(Text)
              .at(1)
              .contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices}.`)
          ).toBe(true);
        });
      });
    });

    describe("item chosen", () => {
      beforeEach(() => {
        props = createTestProps({ chosenItems: ["1057e751-8ef1-4524-a743-1b4ba7b33d7b"] });
        wrapper = shallow(<AccordionList {...props} />);
        wrapper.setState({ collapsed: false });
      });

      it("should render a checkmark for the item", () => {
        expect(
          wrapper
            .find("Image")
            .at(1)
            .prop("source")
        ).toEqual(require("../../assets/icons/checkmark.png"));
      });

      it("should render the checkmark with the checkmarkIcon and active style", () => {
        expect(
          wrapper
            .find("Image")
            .at(1)
            .prop("style")
        ).toEqual([styles.checkmarkIcon, styles.active]);
      });

      it("should render the folded arrow with the primary style", () => {
        expect(
          wrapper
            .find("Image")
            .at(0)
            .prop("style")
        ).toContain(styles.active);
      });

      it("should render the expanded arrow with the primary style", () => {
        wrapper.setState({ collapsed: false });
        expect(
          wrapper
            .find("Image")
            .at(0)
            .prop("style")
        ).toContain(styles.active);
      });
    });

    describe("max items chosen", () => {
      beforeEach(() => {
        props = createTestProps({
          maxChoices: 1,
          chosenItems: ["1057e751-8ef1-4524-a743-1b4ba7b33d7b"]
        });
        wrapper = shallow(<AccordionList {...props} />);
        wrapper.setState({ collapsed: false });
      });

      it("should disable all items but the chosen", () => {
        expect(
          wrapper
            .find("TouchableOpacity")
            .at(2)
            .prop("disabled")
        ).toEqual(true);
      });
    });
  });

  describe("interaction", () => {
    let wrapper;
    let props;
    beforeEach(() => {
      props = createTestProps();
      wrapper = shallow(<AccordionList {...props} />);
    });

    // FIXME: This test does not work for some reason...
    // describe("pressing the header", () => {
    //   beforeEach(() => {
    //     wrapper.instance().toggleExpanded = jest.fn();
    //     wrapper
    //       .find("TouchableOpacity")
    //       .first()
    //       .prop("onPress")();
    //   });
    //
    //   it("should call the toggleExpanded() instance function", () => {
    //     expect(wrapper.instance().toggleExpanded).toHaveBeenCalledTimes(1);
    //   });
    // });

    describe("pressing an item", () => {
      beforeEach(() => {
        wrapper
          .find("TouchableOpacity")
          .at(1)
          .prop("onPress")();
      });

      it("should call the onItemPressed callback", () => {
        expect(props.onItemPressed).toHaveBeenCalledTimes(1);
      });
    });
  });

  describe("component methods", () => {
    describe("toggleExpanded", () => {
      let wrapper;
      let props;
      beforeEach(() => {
        props = createTestProps();
        wrapper = shallow(<AccordionList {...props} />);
        wrapper.instance().toggleExpanded();
      });

      it("should change the state of the component to collapsed=false", () => {
        expect(wrapper.instance().state.collapsed).toBe(false);
      });
    });
  });
});

这是组件的完整代码:

import React, { PureComponent } from "react";
import { Image, Text, TouchableOpacity, View } from "react-native";
import Collapsible from "react-native-collapsible";
import PropTypes from "prop-types";

import {
  SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY,
  SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL,
  SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO,
  SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST
} from "../../config/constants/screenTexts";
import styles from "./styles";

export class AccordionList extends PureComponent {
  static propTypes = {
    header: PropTypes.string.isRequired,
    items: PropTypes.array.isRequired,
    chosenItems: PropTypes.array.isRequired,
    onItemPressed: PropTypes.func.isRequired,
    minChoices: PropTypes.number,
    maxChoices: PropTypes.number,
    borderTop: PropTypes.bool,
    borderBottom: PropTypes.bool
  };

  static defaultProps = {
    minChoices: 0,
    maxChoices: 1,
    borderTop: false,
    borderBottom: false
  };

  state = { collapsed: true };

  toggleExpanded = () => {
    this.setState(state => ({ collapsed: !state.collapsed }));
  };

  renderContent = () => {
    const { items, onItemPressed, chosenItems, maxChoices } = this.props;
    return (
      <View>
        {items.map(item => {
          const disabled =
            !chosenItems.includes(item.uuid) &&
            chosenItems.filter(item => items.map(item => item.uuid).includes(item)).length ===
              maxChoices;
          return (
            <View style={styles.itemContainer} key={item.uuid}>
              <TouchableOpacity
                onPress={() => onItemPressed(item.uuid)}
                style={[styles.itemButton, disabled ? styles.opaque : null]}
                disabled={disabled}
              >
                {item.name && <Text style={styles.itemText}>{item.name}</Text>}
                {item.price && (
                  <View style={styles.priceContainer}>
                    <Text style={styles.sizeText}>{item.label ? `${item.label} ` : ""}</Text>
                    <Text style={styles.sizeText}>
                      {item.size ? `${item.size.size}${item.size.unit}: ` : ""}
                    </Text>
                    <Text style={styles.priceText}>{item.price} €</Text>
                  </View>
                )}
                <View style={styles.checkMarkContainer}>
                  {chosenItems.includes(item.uuid) ? (
                    <Image
                      source={require("../../assets/icons/checkmark.png")}
                      resizeMode="contain"
                      style={[styles.checkmarkIcon, styles.active]}
                    />
                  ) : null}
                </View>
              </TouchableOpacity>
            </View>
          );
        })}
      </View>
    );
  };

  render() {
    const {
      header,
      items,
      maxChoices,
      minChoices,
      chosenItems,
      borderTop,
      borderBottom
    } = this.props;
    const { collapsed } = this.state;
    return (
      <View
        style={[
          styles.container,
          borderTop ? styles.borderTop : null,
          borderBottom ? styles.borderBottom : null
        ]}
      >
        <TouchableOpacity onPress={this.toggleExpanded} style={styles.headerButton}>
          <Text style={styles.header}>{header}</Text>
          <Text style={[styles.subHeader, minChoices > 0 ? styles.mandatory : styles.optional]}>
            {minChoices > 0
              ? collapsed
                ? `(${SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY}, ${minChoices})`
                : chosenItems.filter(item => items.map(item => item.uuid).includes(item)).length >=
                  maxChoices
                  ? `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${maxChoices}.`
                  : `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${minChoices}`
              : collapsed
                ? `(${SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL})`
                : `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${maxChoices}.`}
          </Text>
          <Image
            source={
              collapsed
                ? require("../../assets/icons/rightArrow.png")
                : require("../../assets/icons/downArrow.png")
            }
            resizeMode="contain"
            style={[
              styles.arrowIcon,
              chosenItems.length > 0 &&
              chosenItems.some(item => items.map(item => item.uuid).includes(item))
                ? styles.active
                : styles.inActive
            ]}
          />
        </TouchableOpacity>
        <Collapsible collapsed={collapsed}>{this.renderContent()}</Collapsible>
      </View>
    );
  }
}

export default AccordionList;

1 个答案:

答案 0 :(得分:1)

看起来只是一个错字,您在组件代码中缺少了.

更改此行:

: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${minChoices}`

...对此:

: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${minChoices}.`

我注意到的另一件事是,您的组件使用maxChoices的默认值1,但是在您的测试中有两个地方引用了props.maxChoices,但尚未设置。您可能需要像这样更改两行:

.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices}.`)

..对此:

.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices || 1}.`)

反映由组件分配的默认值。