Sending value with fireEvent - reactjs

I'm using the fireEvent from #testing-library/react.
I declared a react component as follows:
const CustomParamTest = () => {
onButtonClick(customParam) {
console.log(customParam);
}
return (
<>
<button
data-testid="custom-param-button"
onClick={(customParam) => onButtonClick(customParam) }
>
Custom param
</button>
</>
);
}
I have this test:
describe('basic test', () => {
test('items should be empty', () => {
const { getByTestId } = render(<CustomParamTest />);
const customButton = getByTestId('custom-param-button');
fireEvent.click(customButton);
});
});
What I want to achive is to send a custom parameter with the click event, something like this:
fireEvent.click(customButton, myCustomParameter);
I want to access the myCustomParameter inside my onButtonClick function. I could make it work as follow but I want a better solution:
I created a custom click event like this:
const customEvent = createEvent.click(customButton, { button: 2 });
customEvent.myCustomProp = 'customValue';
fireEvent.click(customButton, customEvent);
And in my component I can access this value like this:
onClick={(e) => console.log(e.nativeEvent.myCustomProp)}
I feel like there should be a better way to do this.

Related

React Highcharts - how to use drillUp from parent component?

I am trying to drillUp from the parent component
This is the father component:
<button onClick={() => {
drillUp();
}}></button>
<TreeMap dataLabel={dataLabel} height={blockHeight} />
And in the child component I am rendering the Highchart
<ReactHighcharts
highcharts={Highcharts}
options={treeConfig}
></ReactHighcharts>
This is my treeConfig
const treeConfig = {
chart: {
height: height,
events: {
render() {
const elements = document.getElementsByClassName(
"highcharts-point highcharts-color-0 highcharts-internal-node-interactive",
);
const drillButton = document.getElementsByClassName(
"highcharts-drillup-button",
)[0];
if (drillButton && drillButton.id !== "drill") {
drillButton.setAttribute("id", "drill");
}
if (elements.length) {
const sortElements = [...elements].sort((a, b) => {
return a.width.baseVal.value * a.height.baseVal.value >
b.width.baseVal.value * b.height.baseVal.value
? -1
: 1;
});
sortElements.forEach((el, i) => {
el.style.setProperty("fill", colorPallete[i], "important");
el.setAttribute("fill", colorPallete[i]);
});
}
}
},
},
I found this post -> Manually Triggering drillup event, highcharts but it's in jQuery and I tried that and it didn't work
How can I cause drillUp event from the parent component in React?
Thanks in advance!
Ok, this was more complicated than I thought because the documentation is dreadful but I was able to get it running on this stackblitz
To be able to call events manually or programmatically on the chart we need to get the reference to the chart object. To get the ref on the child component we can do something like this:
const Child = ({ setChart }) => {
...
const chartComponent = useRef(null);
useEffect(() => {
setChart(chartComponent.current.chart);
}, []);
highchartsDrillDown(Highcharts);
return (
<HighchartsReact
highcharts={Highcharts}
options={options}
ref={chartComponent}
/>
);
}
setChart is a prop from the parent that sets the chart object on the parent
On the parent you can manually call drilldown and drillup like this:
const Parent = () => {
const [chart, setChart] = useState(null);
const handleDrillUp = () => {
chart.drillUp();
};
const handleDrillDown = () => {
chart.series[0].data[0].doDrilldown(); // you can chose the series and data
};
return (
<div>
<button onClick={handleDrillUp}> drill up</button>
<button onClick={handleDrillDown}> drill down</button>
<Child setChart={setChart} />;
</div>
);
};
EDIT:
The previous solution is for the highcharts-react-official lib.
Just noticed you are using a different lib for highcharts than the one I used, so for react-highcharts the only difference would be:
const afterRender = (chart) => {setChart(chart)};
<ReactHighcharts config = {config} callback = {afterRender}>.
</ReactHighcharts>
the only difference is that one uses the useRef hook and the other has a callback prop

Create trigger onChange in React Component

I have a component in react and I want to create an event "onChange" who can be detect by react by an other component.
const MyFirstComponent = () => {
//doSomething
//How can I create event here who can be detect by the onChange in the MySecondComponent
return (
<p>
Something
</p>
)
}
const MySecondComponent = () => {
const handleOnChange = (event) => {//I want this can be launch by MyFirstComponent
//doSomething
}
return (
<MyFirstComponent onChange={handleOnChange}>
)
}
Maybe there are better ways to get information out ?
Pass the HandleOnChange as props as you did it's a good idea . And then you can get it in your first composant like this :
const MyFirstComponent = (props) => {
return (
<p onChange={props.OnChange}></p>
)
}

Testing React component built with a function

Experimenting testing for React component built with a function. Before that I was used to do :
const animalsTable = shallow(<Animals/*props*//>);
animalsTable.instance().functionToTest();
ShallowWrapper.instance() returns null for a function so now following Alex Answer I am testing the DOM directly.
Below a simplified version of my React component and some tests :
React component :
import React, {useState, useEffect} from 'react';
import ReactTable from 'react-table';
const AnimalsTable = ({animals}) => {
const [animals, setAnimals] = useState(animals);
const [messageHelper, setMessageHelper] = useState('');
//some functions
useEffect(() => {
//some functions calls
}, []);
return (
<div>
<ReactTable id="animals-table"
//some props
getTrProps= {(state, rowInfo) => {
return {
onClick: (_event) => {
handleRowSelection(rowInfo);
}
};
}}
/>
<p id="message-helper">{messageHelper}</p>
</div>
);
};
export default AnimalsTable;
Test :
//imports
describe('AnimalsTable.computeMessageHelper', () => {
it('It should display the correct message', () => {
const expectedResult = 'Select the correct animal';
const animalsTable = mount(<AnimalsTable //props/>);
const message = animalsTable.find('#message-helper').props().children;
expect(message).to.equal(expectedResult);
});
});
This one is working well.
My issue is how to test a row click on the ReactTable component to test the handleRowSelection method?
My current test is :
describe('AnimalsTable.handleRowSelection', () => {
it('When a selection occurs should change the animal state', () => {
const animalsTable = mount(<AnimalsTable //props/>);
const getTrProps = channelsSelectionTable.find('#animals-table').props().getTrProps;
//what to do from here to trigger onClick() ?
});
});
EDIT :
I think the correct way would be like that but handleRowSelection is not triggered :
const animalsTable= mount(<AnimalsTable //props />);
const rows = animalsTable.find('div.rt-tr-group');
rows.at(0).simulate('click');
I will try to add a simple codeSandBox
Here the solution I found out, I had to inspect the classes names with Chrome then use it to simulate a click on the first row of the table :
it('When an animal selection occurs, it change the checkbox state', () => {
const animalsTable = mount(<AnimalsTable //props/>);
const rows = animalsTable.find('div.rt-tr.-odd');
rows.at(0).simulate('click');
const checkBoxResult = animalsTable.find('input[type="checkbox"]')
.at(0).props().checked;
expect(checkBoxResult).to.equal(true);
});
Might not be the correct way to test it but this one is working.

How to verify method is not invoked on mock passed as prop

I am developing a React application with jest and TypeMoq.
I can't test the negative path of a decision tree when the mocked call is a method on the object which needs to be undefined. Is there a method on TypeMoq that can help me verify that the provided method is not called?
type TopicComponentProps = {
topic: Topic
history?: History<any>
}
export const TopicComponent = ({topic, history} : TopicComponentProps) => {
const { Id, Name } = topic;
const filterTopic = () => {
if (history) { // <-- this is my problem
history.push(`/topic/overview/${Id}`);
}
}
return(
<Fragment>
<span
style={topicStyle}
onClick={() => filterTopic()}
className="topic">
{Name}
</span>
</Fragment>
)
}
The positive test case looks like this:
it('should trigger the navigation when clicked', () => {
const mockHistory = Mock.ofType<History<any>>();
const wrapper = mount(
<TopicComponent topic={testTopic} history={mockHistory.object} />
);
wrapper.simulate('click');
mockHistory.verify(x => x.push(It.isAnyString()), Times.once());
});
How do I setup the mock object, so i can test that no navigation happens when no history is provided?
it('should not trigger the navigation when history is undefined', () => {
let mockHistory = Mock.ofType<History<any>>();
???
const wrapper = mount(
<TopicComponent topic={testTopic} history={???} />
);
wrapper.simulate('click');
mockHistory.verify(x => x.push(It.isAnyString()), Times.never());
});

how to test react-select with react-testing-library

App.js
import React, { Component } from "react";
import Select from "react-select";
const SELECT_OPTIONS = ["FOO", "BAR"].map(e => {
return { value: e, label: e };
});
class App extends Component {
state = {
selected: SELECT_OPTIONS[0].value
};
handleSelectChange = e => {
this.setState({ selected: e.value });
};
render() {
const { selected } = this.state;
const value = { value: selected, label: selected };
return (
<div className="App">
<div data-testid="select">
<Select
multi={false}
value={value}
options={SELECT_OPTIONS}
onChange={this.handleSelectChange}
/>
</div>
<p data-testid="select-output">{selected}</p>
</div>
);
}
}
export default App;
App.test.js
import React from "react";
import {
render,
fireEvent,
cleanup,
waitForElement,
getByText
} from "react-testing-library";
import App from "./App";
afterEach(cleanup);
const setup = () => {
const utils = render(<App />);
const selectOutput = utils.getByTestId("select-output");
const selectInput = document.getElementById("react-select-2-input");
return { selectOutput, selectInput };
};
test("it can change selected item", async () => {
const { selectOutput, selectInput } = setup();
getByText(selectOutput, "FOO");
fireEvent.change(selectInput, { target: { value: "BAR" } });
await waitForElement(() => getByText(selectOutput, "BAR"));
});
This minimal example works as expected in the browser but the test fails. I think the onChange handler in is not invoked. How can I trigger the onChange callback in the test? What is the preferred way to find the element to fireEvent at? Thank you
In my project, I'm using react-testing-library and jest-dom.
I ran into same problem - after some investigation I found solution, based on thread: https://github.com/airbnb/enzyme/issues/400
Notice that the top-level function for render has to be async, as well as individual steps.
There is no need to use focus event in this case, and it will allow to select multiple values.
Also, there has to be async callback inside getSelectItem.
const DOWN_ARROW = { keyCode: 40 };
it('renders and values can be filled then submitted', async () => {
const {
asFragment,
getByLabelText,
getByText,
} = render(<MyComponent />);
( ... )
// the function
const getSelectItem = (getByLabelText, getByText) => async (selectLabel, itemText) => {
fireEvent.keyDown(getByLabelText(selectLabel), DOWN_ARROW);
await waitForElement(() => getByText(itemText));
fireEvent.click(getByText(itemText));
}
// usage
const selectItem = getSelectItem(getByLabelText, getByText);
await selectItem('Label', 'Option');
( ... )
}
This got to be the most asked question about RTL :D
The best strategy is to use jest.mock (or the equivalent in your testing framework) to mock the select and render an HTML select instead.
For more info on why this is the best approach, I wrote something that applies to this case too. The OP asked about a select in Material-UI but the idea is the same.
Original question and my answer:
Because you have no control over that UI. It's defined in a 3rd party module.
So, you have two options:
You can figure out what HTML the material library creates and then use container.querySelector to find its elements and interact with it. It takes a while but it should be possible. After you have done all of that you have to hope that at every new release they don't change the DOM structure too much or you might have to update all your tests.
The other option is to trust that Material-UI is going to make a component that works and that your users can use. Based on that trust you can simply replace that component in your tests for a simpler one.
Yes, option one tests what the user sees but option two is easier to maintain.
In my experience the second option is just fine but of course, your use-case might be different and you might have to test the actual component.
This is an example of how you could mock a select:
jest.mock("react-select", () => ({ options, value, onChange }) => {
function handleChange(event) {
const option = options.find(
option => option.value === event.currentTarget.value
);
onChange(option);
}
return (
<select data-testid="select" value={value} onChange={handleChange}>
{options.map(({ label, value }) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
);
});
You can read more here.
Finally, there is a library that helps us with that: https://testing-library.com/docs/ecosystem-react-select-event. Works perfectly for both single select or select-multiple:
From #testing-library/react docs:
import React from 'react'
import Select from 'react-select'
import { render } from '#testing-library/react'
import selectEvent from 'react-select-event'
const { getByTestId, getByLabelText } = render(
<form data-testid="form">
<label htmlFor="food">Food</label>
<Select options={OPTIONS} name="food" inputId="food" isMulti />
</form>
)
expect(getByTestId('form')).toHaveFormValues({ food: '' }) // empty select
// select two values...
await selectEvent.select(getByLabelText('Food'), ['Strawberry', 'Mango'])
expect(getByTestId('form')).toHaveFormValues({ food: ['strawberry', 'mango'] })
// ...and add a third one
await selectEvent.select(getByLabelText('Food'), 'Chocolate')
expect(getByTestId('form')).toHaveFormValues({
food: ['strawberry', 'mango', 'chocolate'],
})
Thanks https://github.com/romgain/react-select-event for such an awesome package!
Similar to #momimomo's answer, I wrote a small helper to pick an option from react-select in TypeScript.
Helper file:
import { getByText, findByText, fireEvent } from '#testing-library/react';
const keyDownEvent = {
key: 'ArrowDown',
};
export async function selectOption(container: HTMLElement, optionText: string) {
const placeholder = getByText(container, 'Select...');
fireEvent.keyDown(placeholder, keyDownEvent);
await findByText(container, optionText);
fireEvent.click(getByText(container, optionText));
}
Usage:
export const MyComponent: React.FunctionComponent = () => {
return (
<div data-testid="day-selector">
<Select {...reactSelectOptions} />
</div>
);
};
it('can select an option', async () => {
const { getByTestId } = render(<MyComponent />);
// Open the react-select options then click on "Monday".
await selectOption(getByTestId('day-selector'), 'Monday');
});
An easy way to test is by doing what the user should do
Click on the select field.
Click on one of the items in the dropdown list.
function CustomSelect() {
const colourOptions = [
{ value: 'orange', label: 'Orange', color: '#FF8B00' },
{ value: 'yellow', label: 'Yellow', color: '#FFC400' }
]
return <Select
aria-label="my custom select"
options={colourOptions}
//... props
/>
}
import { act, render, screen } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
// another imports
test('show selected item...', async () => {
const { getByText, getByLabelText } = render(<CustomSelect />);
expect(getByText('Orange')).not.toBeInTheDocument();
const myCustomSelect = getByLabelText(/my custom select/i);
await act(async () => userEvent.click(myCustomSelect));
const selectedItem = getByText('Orange');
await act(async () => userEvent.click(selectedItem));
expect(getByText('Orange')).toBeInTheDocument();
});
This solution worked for me.
fireEvent.change(getByTestId("select-test-id"), { target: { value: "1" } });
Hope it might help strugglers.
In case you are not using a label element, the way to go with react-select-event is:
const select = screen.container.querySelector(
"input[name='select']"
);
selectEvent.select(select, "Value");
export async function selectOption(container: HTMLElement, optionText: string) {
let listControl: any = '';
await waitForElement(
() => (listControl = container.querySelector('.Select-control')),
);
fireEvent.mouseDown(listControl);
await wait();
const option = getByText(container, optionText);
fireEvent.mouseDown(option);
await wait();
}
NOTE:
container: container for select box ( eg: container = getByTestId('seclectTestId') )
An alternative solution which worked for my use case and requires no react-select mocking or separate library (thanks to #Steve Vaughan) found on the react-testing-library spectrum chat.
The downside to this is we have to use container.querySelector which RTL advises against in favour of its more resillient selectors.
if for whatever reason there is a label with the same name use this
const [firstLabel, secondLabel] = getAllByLabelText('State');
await act(async () => {
fireEvent.focus(firstLabel);
fireEvent.keyDown(firstLabel, {
key: 'ArrowDown',
keyCode: 40,
code: 40,
});
await waitFor(() => {
fireEvent.click(getByText('Alabama'));
});
fireEvent.focus(secondLabel);
fireEvent.keyDown(secondLabel, {
key: 'ArrowDown',
keyCode: 40,
code: 40,
});
await waitFor(() => {
fireEvent.click(getByText('Alaska'));
});
});
or If you have a way to query your section—for example with a data-testid—you could use within:
within(getByTestId('id-for-section-A')).getByLabelText('Days')
within(getByTestId('id-for-section-B')).getByLabelText('Days')
Because I wanted to test a component that wrapped react-select, mocking it with a regular <select> element wouldn't have worked. So I ended up using the same approach that the package's own tests use, which is supplying a className in props and then using it with querySelector() to access the rendered element in the test:
const BASIC_PROPS: BasicProps = {
className: 'react-select',
classNamePrefix: 'react-select',
// ...
};
let { container } = render(
<Select {...props} menuIsOpen escapeClearsValue isClearable />
);
fireEvent.keyDown(container.querySelector('.react-select')!, {
keyCode: 27,
key: 'Escape',
});
expect(
container.querySelector('.react-select__single-value')!.textContent
).toEqual('0');
To anyone out there - I got mine to select by doing fireEvent.mouseDown on the option instead of click.

Resources