Я пытаюсь протестировать кнопку из компонента. У меня есть ссылка на событие щелчка. Ссылка является дочерним компонентом.
Компонент основного класса Speaker. js:
import React, {Component} from 'react';
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux';
import ActionCreators from '../state/actions';
import {Input, Col, Button, Icon} from 'antd';
import List from '../components/List';
import Partner from '../constants/Partner';
import Selector from '../components/Selector';
import {sortData} from '../utils/utils.js'
class SpeakersComponent extends Component {
state = {
speakers: this.props.event && this.props.event.speakers ? this.props.event.speakers : [],
};
constructor(props) {
super(props);
// if (props.event)
// console.log(props.event.speakers)
this.state = {
...this.state,
props : props,
speakers: props.event && props.event.speakers ? props.event.speakers : [],
};
this.show = React.createRef();
}
componentDidMount() {
this.props.actions.getAllSpeakers();
if (!this.props.companies) {
this.props.actions.getAllPartners();
}
}
UNSAFE_componentWillReceiveProps(props) {
if (props.event) {
this.setState({speakers: props.event.speakers || []});
}
}
sendToApi = (payload, callback) => {
this.props.actions.ajaxStart({
method: 'post',
endpoint: '/api/events',
body: {
...payload,
city: this.props.event.city.city,
year: this.props.event.year,
},
loadingMessage: true,
successMessage: true,
callback,
});
}
deleteSpeaker = (index) => {
this.state.speakers.splice(index, 1);
this.sendToApi({
speakers: this.state.speakers.map(s => s.id),
});
};
addSpeaker = async (_, speaker, __, update) => {
if (!speaker) {
return;
}
if (!update || update === undefined) {
await this.setState({speakers: [...this.state.speakers, speaker]});
}
this.sendToApi({
speakers: this.state.speakers.map(s => s.id),
}, () => {
this.props.actions.getAllSpeakers();
this.props.actions.getEvent(this.props.event.city.city, this.props.event.year);
});
};
render() {
// if (this.state.speakers)
// console.log(this.state.speakers)
const speaker = {
id: {
label: 'id',
disabled: true,
valuesForId: ["name"],
optional: true,
},
name: {},
title: {},
description: {
render: <Input.TextArea />,
},
company: {
render: 'selector',
props: {
data: this.props.companies,
config: Partner,
valueField: 'id',
displayField: 'name',
endpoint: '/api/partners',
default: item => item.company
},
},
picture: {
render: 'imagePicker',
optional: true,
props: {
renameFileTo: (_, formData) => formData.id,
mode: 'sizes',
sizes: [100, 190, 500],
prefix: 'event/speakers',
authorizedExts: ['jpg'],
},
},
};
return (
<Col offset={6} span={12} data-test="speakersComponent">
<List
dataSource={sortData(this.state.speakers)}
displayField='name'
onItemClick={console.info}
name='speaker'
onDelete={this.deleteSpeaker}
mainPage={true}
/>
<Button onClick={() => this.selector.show()} style={{marginTop: 10}}>
<Icon type='plus' /> Add or edit speaker
</Button>
<Selector
endpoint='/api/speakers'
_ref={ref => this.selector = ref}
value={this.addSpeaker}
data={sortData(this.props.speakers)}
config={speaker}
valueField='id'
displayField='name'
id='speaker'
/>
</Col>
);
}
}
const mapStateToProps = state => {
return {
speakers: state.data.speakers,
companies: state.data.partners,
};
};
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(ActionCreators, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(SpeakersComponent);
здесь у вас есть дочерний компонент Selector. js
import React, {Component} from 'react';
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux';
import ActionCreators from '../state/actions';
import {Modal} from 'antd';
import List from './List';
import Form from './Form';
class SelectorComponent extends Component {
state = {
visible: false,
value: null,
isEditing: false,
};
componentDidMount() {
this.update(this.props);
this.props._ref(this);
}
UNSAFE_componentWillReceiveProps(props) {
if (props.data && !this.props.data) {
this.update(props);
}
}
update = (props) => {
let value;
let defaultValue = this.props.default;
if (typeof defaultValue === 'function') {
defaultValue = defaultValue(this.props.item);
}
if (defaultValue) {
value = (props.data || []).find(e => e[this.props.valueField] === defaultValue[this.props.valueField]);
this.setState({value});
this.props.value(this.props.id, value);
}
}
clear = () => {
this.setState({value: null});
this.props.value(this.props.id, null);
}
show = () => this.setState({visible: true});
postSubmit = (values, apiValidated) => {
this.props.value(this.props.id, {
...this.state.value,
...values,
}, apiValidated);
}
onCustomizationOk = async () => {
this.setState({
customizationVisible: false,
});
if (typeof this.props.value === 'function') {
this.validate();
}
}
onOk = async (value, _, apiValidated, update) => {
await this.setState({
visible: false,
value,
isEditing : update,
});
if (this.props.customizationConfig) {
this.setState({customizationVisible: true});
} else if (typeof this.props.value === 'function') {
this.props.value(this.props.id, value, apiValidated, update);
}
};
onCancel = () => {
this.setState({
visible: false,
customizationVisible: false,
});
};
render() {
return (
<div>
<Modal
visible={this.state.visible}
closable={false}
title={<div>Select a {this.props.id}</div>}
footer={null}
onCancel={this.onCancel}
>
<List
endpoint={this.props.endpoint}
dataSource={(this.props.data || [])}
displayField={this.props.displayField}
valueField={this.props.valueField}
onItemClick={item => this.onOk(item)}
config={this.props.config}
name={this.props.id}
style={{maxHeight: 500, overflowY: 'scroll'}}
onOk={this.onOk}
/>
</Modal>
{
!this.props.customizationConfig || this.state.isEditing ? null : (
<Modal
visible={this.state.customizationVisible}
closable={false}
title={<div>Customize {this.props.id}</div>}
onOk={this.onCustomizationOk}
onCancel={this.onCancel}
>
<Form
loadingMessage={null}
successMessage={null}
payload={{[Object.keys(this.props.customizationConfig).find(k => this.props.customizationConfig[k] === null)]: this.state.value}}
config={this.props.customizationConfig}
substituteValidation={ref => this.validate = ref}
disableApiValidation={true}
postSubmit={this.postSubmit}
isEditing={this.state.customizationVisible}
/>
</Modal>
)
}
</div>
);
}
}
const mapStateToProps = state => {
return {
};
};
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(ActionCreators, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(SelectorComponent);
мой тестовый файл speaker.test. js
//https://www.youtube.com/watch?v=92F8_9UG04g
import Speakers from '../src/containers/Speakers';
import Adapter from 'enzyme-adapter-react-16';
import Selector from "../src/components/Selector";
import List from '../src/components/List';
import React from 'react';
import {eventData} from './event.data';
import { Provider } from 'react-redux';
import { shallow, configure, mount } from "enzyme";
import { findByTestAtrr, testStore } from '../src/utils/utilsTestJest';
import renderer from 'react-test-renderer';
import thunk from "redux-thunk";
import configureMockStore from 'redux-mock-store'
jest.mock('../src/components/Selector');
configure({ adapter: new Adapter() });
let middlewares = [ thunk ],
mockStore = configureMockStore(middlewares);
const setUpWrapper = (initialState={}) => {
const store = testStore(initialState);
const wrapper = shallow(
<Speakers store={store}/>
);
// console.log(wrapper.debug());
return wrapper;
};
const setUpSnapshot = (initialState={}) => {
const store = testStore(initialState);
const speakerComponent = renderer.create(
<Provider store={store}>
<Speakers event={eventData}/>
</Provider>
).toJSON();
// console.log(speakerComponent);
return speakerComponent;
};
const setUpMount = (initialState={}) => {
const store = testStore(initialState);
let speakerComponent = mount(<Provider store={store}><Speakers event={eventData.speakers}/></Provider>)
return speakerComponent;
};
describe('Speakers Container', () => {
let wrapperSpeakers;
let snapshotSpeakers;
let mountSpeakers;
let childContainerSelector;
beforeEach(() => {
const initialState = {
data: eventData.speakers
};
wrapperSpeakers = setUpWrapper(initialState);
snapshotSpeakers = setUpSnapshot(initialState);
mountSpeakers = setUpMount(initialState);
childContainerSelector = wrapperSpeakers.dive().dive().find('SelectorComponent');
})
it('Should render without errors', () => {
const component = findByTestAtrr(wrapperSpeakers.childAt(0).dive(), 'speakersComponent');
expect(component.length).toBe(1);
})
it('render correctly speaker component', () => {
expect(snapshotSpeakers).toMatchSnapshot();
});
it('check state', () => {
const wrapperSpeakersInstance = wrapperSpeakers.dive().dive().instance();
wrapperSpeakersInstance.componentDidMount();
wrapperSpeakersInstance.setState({speakers: eventData.speakers});
expect(wrapperSpeakersInstance.state.speakers).not.toBeNull();
expect(wrapperSpeakersInstance.state.speakers).toEqual(eventData.speakers);
})
it('button click', () => {
const instance = wrapperSpeakers.dive().dive();
let mockFn = jest.fn();
instance.show = mockFn
instance.update();
console.log(instance.debug())
instance.find('Button').simulate('click');
// let button = wrapperSpeakers.dive().dive().find('Button');
// console.log(wrapperSpeakers.dive().dive().find('Button').simulate('click'));
})
});
Когда я тестирую свой компонент, я получаю следующую ошибку:
✓ Should render without errors (61 ms)
✓ render correctly speaker component (17 ms)
✓ check state (17 ms)
✕ button click (58 ms)
● Speakers Container › button click
TypeError: Cannot read property 'show' of undefined
125 | mainPage={true}
126 | />
> 127 | <Button onClick={() => this.selector.show()} style={{marginTop: 10}}>
| ^
128 | <Icon type='plus' /> Add or edit speaker
129 | </Button>
130 | <Selector
at handler (src/containers/Speakers.js:127:46)
at fn (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:716:13)
at withSetStateAllowed (node_modules/enzyme-adapter-utils/src/Utils.js:99:18)
at Object.simulateEvent (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:712:11)
at ShallowWrapper.call (node_modules/enzyme/src/ShallowWrapper.js:1134:7)
at ShallowWrapper.single (node_modules/enzyme/src/ShallowWrapper.js:1654:21)
at ShallowWrapper.simulate (node_modules/enzyme/src/ShallowWrapper.js:1133:17)
at Object.<anonymous> (__test__/speakers2.test.js:87:29)
Test Suites: 1 failed, 1 passed, 2 total
Tests: 1 failed, 7 passed, 8 total
Snapshots: 1 passed, 1 total
Time: 2.578 s, estimated 3 s
Ran all test suites related to changed files.
Есть ли у вас идея решения моей проблемы? Я не видел топи c с этой ошибкой.