Ферментный модульный тест на сбой компонента формы - PullRequest
0 голосов
/ 23 мая 2019

Я новичок во фронт-тестировании и довольно свеж с экосистемой React, так что терпите меня.

У меня есть несколько юнит-тестов для компонента CustomerSection.Когда я запускаю yarn spec, я получаю следующую ошибку:

➜  client git:(ConvertToRO_Test) ✗ yarn spec
yarn run v1.3.2
warning package.json: No license field
warning ../package.json: No license field
$ NODE_ENV=test mocha './spec/**/*.spec.js*' --compilers js:babel-register --recursive
Warning: Failed prop type: The prop `submitting` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: The prop `role` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: The prop `search` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: The prop `manager` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: The prop `id` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: The prop `close` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: The prop `handleSubmit` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: Invalid prop `fields` of type `array` supplied to `LineItemFields`, expected `object`.
    in LineItemFields
Warning: Failed prop type: The prop `company.name` is marked as required in `CompanyStatsTooltip`, but its value is `undefined`.
    in CompanyStatsTooltip (created by CustomerSection)
    in CustomerSection (created by ConnectedFields)
    in ConnectedFields (created by Connect(ConnectedFields))
    in Connect(ConnectedFields) (created by Fields)
    in Fields (created by WithFormFields)
    in WithFormFields (created by Form(WithFormFields))
    in Form(WithFormFields) (created by Connect(Form(WithFormFields)))
    in Connect(Form(WithFormFields)) (created by ReduxForm)
    in ReduxForm
    in Provider (created by WrapperComponent)
    in WrapperComponent
The above error occurred in the <CustomerSection> component:
    in CustomerSection (created by ConnectedFields)
    in ConnectedFields (created by Connect(ConnectedFields))
    in Connect(ConnectedFields) (created by Fields)
    in Fields (created by WithFormFields)
    in WithFormFields (created by Form(WithFormFields))
    in Form(WithFormFields) (created by Connect(Form(WithFormFields)))
    in Connect(Form(WithFormFields)) (created by ReduxForm)
    in ReduxForm
    in Provider (created by WrapperComponent)
    in WrapperComponent

Consider adding an error boundary to your tree to customize error handling behavior.
/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20106
    throw error;
    ^

TypeError: Cannot read property 'label' of undefined
    at /Users/danielbonnell/Development/acme-api/client/app/lib/forms.js:3:30
    at Array.map (<anonymous>)
    at toOptions (/Users/danielbonnell/Development/acme-api/client/app/lib/forms.js:2:27)
    at CustomerSection.render (/Users/danielbonnell/Development/acme-api/client/app/modules/SalesOrders/components/shared/FormSections/CustomerSection/CustomerSection.jsx:149:24)
    at finishClassComponent (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:14535:31)
    at updateClassComponent (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:14490:24)
    at beginWork (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:15438:16)
    at performUnitOfWork (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:19106:12)
    at workLoop (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:19146:24)
    at renderRoot (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:19229:7)
    at performWorkOnRoot (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20136:7)
    at performWork (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20048:7)
    at performSyncWork (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20022:3)
    at requestWork (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:19891:5)
    at scheduleWork (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:19705:5)
    at scheduleRootUpdate (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20366:3)
    at updateContainerAtExpirationTime (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20394:10)
    at updateContainer (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20451:10)
    at ReactRoot.render (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20747:3)
    at /Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20884:14
    at unbatchedUpdates (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20253:10)
    at legacyRenderSubtreeIntoContainer (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20880:5)
    at Object.render (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20949:12)
    at Object.render (/Users/danielbonnell/Development/acme-api/client/node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:352:114)
    at new ReactWrapper (/Users/danielbonnell/Development/acme-api/client/node_modules/enzyme/build/ReactWrapper.js:98:16)
    at mount (/Users/danielbonnell/Development/acme-api/client/node_modules/enzyme/build/mount.js:19:10)
    at Suite.<anonymous> (/Users/danielbonnell/Development/acme-api/client/spec/modules/SalesOrders/components/shared/FormSections/CustomerSection/CustomerSection.spec.jsx:26:19)
    at Object.create (/Users/danielbonnell/Development/acme-api/client/node_modules/mocha/lib/interfaces/common.js:114:19)
    at context.describe.context.context (/Users/danielbonnell/Development/acme-api/client/node_modules/mocha/lib/interfaces/bdd.js:44:27)
    at Object.<anonymous> (/Users/danielbonnell/Development/acme-api/client/spec/modules/SalesOrders/components/shared/FormSections/CustomerSection/CustomerSection.spec.jsx:10:1)
    at Module._compile (module.js:652:30)
    at loader (/Users/danielbonnell/Development/acme-api/client/node_modules/babel-register/lib/node.js:144:5)
    at Object.require.extensions.(anonymous function) [as .jsx] (/Users/danielbonnell/Development/acme-api/client/node_modules/babel-register/lib/node.js:154:7)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Module.require (module.js:596:17)
    at require (internal/module.js:11:18)
    at /Users/danielbonnell/Development/acme-api/client/node_modules/mocha/lib/mocha.js:222:27
    at Array.forEach (<anonymous>)
    at Mocha.loadFiles (/Users/danielbonnell/Development/acme-api/client/node_modules/mocha/lib/mocha.js:219:14)
    at Mocha.run (/Users/danielbonnell/Development/acme-api/client/node_modules/mocha/lib/mocha.js:487:10)
    at Object.<anonymous> (/Users/danielbonnell/Development/acme-api/client/node_modules/mocha/bin/_mocha:459:18)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Function.Module.runMain (module.js:693:10)
    at startup (bootstrap_node.js:191:16)
    at bootstrap_node.js:612:3
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Если я использую shallow рендеринг, тесты запускаются, но не выполняются, потому что дочерние компоненты не отображаются.Я не уверен, в чем проблема.

Вот мой CustomerSection компонент:

import React, { Component } from "react";
import T from "prop-types";
import { Row, Col } from "react-bootstrap";
import { Field } from "redux-form";
import api from "APP_ROOT/api";
import pojo from "LIB/pojos";
import { toOptions } from "LIB/forms";
import {
  Select,
  FormInput,
  CheckGroup,
  Check
} from "MODULES/shared/components/Forms";
import { CompanyStatsTooltip } from "MODULES/shared/components";
import AutoCompleteField from "MODULES/shared/components/Forms/AutoCompleteField/AutoCompleteField";
import {
  WIRE_IN_ADVANCE,
  CHECK_IN_ADVANCE,
  CREDIT_CARD,
  VENDOR_TERMS
} from "MODULES/companies/constants";
import * as F from "MODULES/shared/components/Forms/FormTypes";
import withFormFields from "MODULES/shared/decorators/withFormFields";

const baseOptions = [WIRE_IN_ADVANCE, CHECK_IN_ADVANCE, CREDIT_CARD];

class CustomerSection extends Component {
  state = {
    contacts: [],
    terms: baseOptions,
    selectedCompany: null,
    afterHours: this.props.afterHours
  };

  componentWillMount() {
    const { company, creditTerms } = this.props;

    if (company) {
      this.setState({ selectedCompany: company });
      this.searchContacts(company.id);
    }

    if (creditTerms) {
      this.addOption(creditTerms);
    }
  }

  searchCompanies = filter =>
    api.companies
      .search({ filter, page: 1, limit: 20 })
      .then(resp => resp.data.results);

  searchContacts = companyId => {
    if (!companyId) {
      this.setState({ contacts: [] });
    } else {
      api.companies.contacts
        .search(companyId, {
          active: true,
          page: 1,
          limit: 1000,
          sort: "name",
          reverse: false
        })
        .then(resp => {
          this.setState({ contacts: resp.data.results });
        });
    }
  };

  handleCompanySelected = company => {
    this.setState({ selectedCompany: company });
    this.resetTerms();
    this.props.contact_id.input.onChange(null);
    if (company.customer_credit_terms) {
      this.updateTerms(company.customer_credit_terms);
    }
    if (this.props.onCompanySelected) {
      this.props.onCompanySelected(company);
    }
    this.searchContacts(company.id);
  };

  resetTerms = () => {
    this.props.credit_terms.input.onChange(null);
    this.setState({ terms: baseOptions });
  };

  addOption = option => {
    this.setState({
      terms: baseOptions.concat(option)
    });
  };

  updateTerms = term => {
    this.addOption(term);
    this.props.credit_terms.input.onChange(term);
  };

  render() {
    const { contacts, terms } = this.state;
    const {
      canEditCompany = true,
      contact_id: { input: { value } }
    } = this.props;

    const options = pojo(VENDOR_TERMS).pick(...terms);
    const contactOptions = (contacts || [])
      .map(c => ({
        value: c.id,
        text: c.name
      }))
      .concat({
        text: "+ Add a Contact",
        value: "Add"
      });

    return (
      <div>
        <Row className="row-col-gutters-half">
          <Col md={8}>
            <Field
              name="company_id"
              label="Customer"
              labelChildren={
                this.state.selectedCompany && (
                  <React.Fragment>
                    &nbsp;&nbsp;<CompanyStatsTooltip
                      company={this.state.selectedCompany}
                    />
                  </React.Fragment>
                )
              }
              placeholder="Search by typing a company's name or code"
              component={AutoCompleteField}
              search={this.searchCompanies}
              formatItem={result => result && result.name}
              onItemSelected={i => i && this.handleCompanySelected(i)}
              selectedItem={this.state.selectedCompany}
              disabled={!canEditCompany}
              required
            />
          </Col>
          <Col md={4}>
            <Field
              name="credit_terms"
              component={Select}
              label="Customer Credit Terms"
              options={toOptions(options)}
              required
            />
          </Col>
        </Row>
        <Row className="row-col-gutters-half">
          <Col md={4}>
            <Field
              name="contact_id"
              component={Select}
              label="Customer Contact"
              options={contactOptions}
              required
            />
          </Col>
          <Col md={4}>
            <Field
              name="cc_email_address"
              component={FormInput}
              label="Contact Email #2"
            />
          </Col>
          <Col md={4}>
            <Field
              required
              name="customer_purchase_order_number"
              component={FormInput}
              label="Customer PO#"
            />
          </Col>
        </Row>
        {value === "Add" && (
          <div className="row row-col-gutters-half">
            <div className="col-md-6">
              <div className="form-group">
                <Field
                  name="contact.name"
                  component={FormInput}
                  label="Contact Name"
                  required
                />
              </div>
            </div>
            <div className="col-md-6">
              <div className="form-group">
                <Field
                  name="contact.email"
                  component={FormInput}
                  label="Contact Email"
                  required
                />
              </div>
            </div>
          </div>
        )}
        <Row className="row-col-gutters-half">
          <Col md={4}>
            <CheckGroup justified title="After Hours">
              <Field name="after_hours" component={Check} label="Yes" />
            </CheckGroup>
          </Col>
          <Col md={4}>
            <CheckGroup justified title="Send SO Acknowledgement">
              <Field
                name="send_sales_order_acknowledgement"
                component={Check}
                label="Yes"
                icon="fa-eye-slash text-success"
              />
            </CheckGroup>
          </Col>
        </Row>
      </div>
    );
  }
}

CustomerSection.propTypes = {
  company: T.shape({
    id: T.number.isRequired,
    customer_credit_terms: T.string
  }),
  creditTerms: T.string,
  onCompanySelected: T.func,
  credit_terms: F.formField(F.stringInput).isRequired,
  canEditCompany: T.bool,
  contact_id: F.formField(F.malleable).isRequired,
  afterHours: T.bool.isRequired
};

export default withFormFields(CustomerSection, [
  "credit_terms",
  "contact_id",
  "after_hours",
  "send_sales_order_acknowledgement"
]);

А вот мой тест:

import React from "react";
import expect from "expect";
import { mount } from "enzyme";
import { createStore } from "redux";
import { Provider } from "react-redux";
import { Field, reduxForm } from "redux-form";
import { CheckGroup } from "MODULES/shared/components/Forms";
import CustomerSection from "MODULES/SalesOrders/components/shared/FormSections/CustomerSection/CustomerSection";

describe("<CustomerSection />", () => {
  const props = {
    company: {
      id: 1,
      customer_credit_terms: "Net-30"
    },
    creditTerms: "Net-30",
    onCompanySelected: () => {},
    canEditCompany: true,
    afterHours: true
  };

  const store = createStore(() => ({}));
  const DecoratedCustomerSection = reduxForm({ form: "testForm" })(
    CustomerSection
  );
  const wrapper = mount(
    <Provider store={store}>
      <DecoratedCustomerSection {...props} />
    </Provider>
  );

  afterEach(() => {
    expect.restoreSpies();
  });

  it("renders the correct Field components", () => {
    const fields = wrapper.find(Field);

    expect(fields.length).toEqual(9);

    expect(
      fields.findWhere(n => n.prop("label") === "Customer").length
    ).toEqual(1);
    expect(
      fields.findWhere(n => n.prop("label") === "Customer Credit Terms").length
    ).toEqual(1);
    expect(
      fields.findWhere(n => n.prop("label") === "Customer Contact").length
    ).toEqual(1);
    expect(
      fields.findWhere(n => n.prop("label") === "Contact Email #2").length
    ).toEqual(1);
    expect(
      fields.findWhere(n => n.prop("label") === "Customer PO#").length
    ).toEqual(1);
    expect(
      fields.findWhere(n => n.prop("label") === "Contact Name").length
    ).toEqual(1);
    expect(
      fields.findWhere(n => n.prop("label") === "Contact Email").length
    ).toEqual(1);
    expect(fields.findWhere(n => n.prop("label") === "Yes").length).toEqual(2);
  });

  it("renders the correct CheckGroup components", () => {
    const checkGroup = wrapper.find(CheckGroup);

    expect(
      checkGroup.findWhere(n => n.prop("title") === "After Hours").length
    ).toEqual(1);
    expect(
      checkGroup.findWhere(n => n.prop("title") === "Send SO Acknowledgement")
        .length
    ).toEqual(1);
  });
});

Воткод в lib/forms.js для toOptions():

export function toOptions(map, textField = "label") {
  return Object.keys(map).map(t => {
    const text = textField ? map[t][textField] : map[t];
    return { value: t, text };
  });
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...