我正在使用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;
答案 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}.`)
反映由组件分配的默认值。