使用外部组件库时的不变违规

时间:2018-05-03 14:37:48

标签: javascript reactjs typescript jestjs enzyme

我正在努力改变用打字稿编写的反应组件。我对代码库所做的更改涉及删除我们拥有的组件,以支持外部库react-dual-listbox

一旦导入依赖项,我现有的所有测试都开始失败,我不确定原因。我收到的错误是

Error: Uncaught [Invariant Violation: Element type is invalid: expected a string 
(for built-in components) or a class/function (for composite components) but got: 
undefined. You likely forgot to export your component from the file it's defined in, 
or you might have mixed up default and named imports.

Check the render method of `default_1`.

这是我的测试文件:

import { shallow, configure, mount } from 'enzyme';
import * as React from 'react';
import PolicyEditor, { PolicyEditorProps } from '../PolicyEditor';
import * as Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

const defaultProps: PolicyEditorProps = {
  id: 'id',
  serviceId: 'service id',
  data: { data: { policy: { id: '1', name: 'policy', permissions: [] } } },
  permissionsData: { data: { service: { id: '1', permissions: [] } } },
  create: jest.fn(),
  update: jest.fn(),
  updatePermissions: jest.fn(),
  delete: jest.fn(),
  back: jest.fn(),
};

describe('PolicyEditor.tsx', () => {
  it('sets the state when the name is changed', done => {
    const wrapper = mount(<PolicyEditor {...defaultProps} />);
    const input = wrapper.find('input[id="policyName"]').first();

    const event = { target: { value: 'new policy name' } };

    input.simulate('change', event);

    process.nextTick(() => {
      expect(wrapper.state().name).toEqual('new policy name');
      done();
    });
  });
});

组件:

import 'react-dual-listbox/lib/react-dual-listbox.css';

import * as React from 'react';
import { MutationFn } from 'react-apollo';
import { isQueryReady, isQueryLoading } from '../util/actionHelpers';
import {
  Permission,
  PolicyWithPermissions,
  ServicePermissionsQueryResult,
  PolicyQueryResult,
} from './types';
import AuthModal from '../shared/AuthModal';
import { Container, Row, Col, Label, Button, Form, FormGroup, Input } from 'reactstrap';
import DualListBox from 'react-dual-listbox';

export interface PolicyEditorProps {
  id?: string;
  serviceId: string;
  data: PolicyQueryResult;
  permissionsData: ServicePermissionsQueryResult;
  create: MutationFn;
  update: MutationFn;
  updatePermissions: MutationFn;
  delete: MutationFn;
  back(): void;
}

interface PolicyEditorState {
  name: string;
  selectedPermissions: Permission[];
}

export default class extends React.Component<PolicyEditorProps, PolicyEditorState> {
  static getDerivedStateFromProps(
    nextProps: PolicyEditorProps,
    prevState: PolicyEditorState
  ): Partial<PolicyEditorState> | null {
    if (nextProps.id && isQueryReady(nextProps.data)) {
      const policy: PolicyWithPermissions = nextProps.data.data!.policy;

      return {
        name: policy.name,
        selectedPermissions: policy.permissions,
      };
    }

    if (!nextProps.id) {
      return {
        name: '',
      };
    }

    return null;
  }

  constructor(props: PolicyEditorProps) {
    super(props);
    this.state = {
      name: '',
      selectedPermissions: [],
    };
  }

  render() {
    const isEdit: boolean = !!this.props.id;
    if (isEdit) {
      if (isQueryLoading(this.props.data)) {
        return <div>Loading...</div>;
      }
      if (this.props.data.error !== undefined) {
        return <div>Something went wrong!</div>;
      }
    }

    const { loading, error } = this.props.permissionsData;
    return (
      <AuthModal title="Policy Editor" isOpen={true} toggle={this.props.back}>
        <Form>
          <FormGroup>
            <Label for="policyName">Name</Label>
            <Input
              id="policyName"
              onChange={e => this.setState({ name: e.target.value })}
              value={this.state.name}
            />
          </FormGroup>
          {isEdit ? (
            <div>
              <hr />
              {loading ? (
                <div>Loading...</div>
              ) : error ? (
                <div>Error</div>
              ) : (
                <Container className="m-2">
                  <Row>
                    <Col className="text-center" xs="6">
                      Available Permissions
                    </Col>
                    <Col className="text-center" xs="6">
                      Current Permissions
                    </Col>
                  </Row>
                  <DualListBox
                    canFilter={true}
                    options={this.props.permissionsData.data!.service.permissions.map(p => ({
                      value: p.id,
                      label: p.name,
                    }))}
                    selected={this.state.selectedPermissions.map(p => p.id)}
                    onChange={this.updateSelectedPermissions}
                  />
                </Container>
              )}
              <FormGroup>
                <Row>
                  <Col xs={{ size: 1 }}>
                    <Button onClick={this.onUpdate} color="success">
                      Update
                    </Button>
                  </Col>
                  <Col xs={{ size: 1, offset: 1 }}>
                    <Button onClick={this.onDelete} color="danger">
                      Delete
                    </Button>
                  </Col>
                </Row>
              </FormGroup>
            </div>
          ) : (
            <FormGroup>
              <Button color="success" onClick={this.onCreate}>
                Create
              </Button>
            </FormGroup>
          )}
        </Form>
      </AuthModal>
    );
  }
}

错误消息似乎很神秘。它似乎告诉我,它希望某些东西是一个字符串,但未定义。我不确定究竟是什么未定义的。但是,如果我只是不渲染DualListBox,我可以通过测试,但这对我的问题没有帮助。

奇怪的是,它在浏览器中按预期工作,没有错误,问题只出在我的测试中。显然,拥有相同错误消息的人最终会成为导入问题,但如果它在浏览器中有效,则必须正确导入,不是吗?

我有什么遗漏或不理解的地方吗?

0 个答案:

没有答案