how to read the children function properties in jest or enzyme - reactjs

This is piece of my react component code:
<div style={{ width }}>
<FormField
label='Select a type'
labelPlacement='top'
className='cdsdrop'
>
{({ getButtonProps }) => (
<Dropdown
{...getButtonProps({
id: 'typeDropDown',
source: data,
onChange: this.handleInputChange,
options: data
})}
/>)}
</FormField>
</div>
Am new to jest framework. I started writing testcases for submit button and reset are disabled when dropdown value is empty, after selecting dropdown buttons should get enable.
When I use props().label am getting label but when I called children am getting error.
this is mytest component
describe('Buttons should be disabled on page load', () => {
it('submit and reset buttons are disabled when type is empty', () => {
const wrapper = shallow(<CdsNettingActions/>);
const submitButton = wrapper.find('WithStyles(Component).cdssubmit');
const resetButton = wrapper.find('WithStyles(Component).cdsreset');
const dropDown = wrapper.find('WithStyles(Component).cdsdrop');
const drop1=dropDown.props().children();
console.log('drop',drop1);
expect(submitButton.prop('disabled')).toEqual(true);
expect(resetButton.prop('disabled')).toEqual(true);
});
});
But am getting below error:
TypeError: Cannot read property 'getButtonProps' of undefined
className='cdsdrop'>
When I did the console logging the children function looks as below:
getButtonProps({
id: 'typeDropDown',
borderless: true,
buttonWidth: width,
source: data,
onChange: _this4.handleInputChange,
options: data
}))
Please help me how to read options from the dropdown.
I am using shollow strong textreact 16

So your FormField's children prop is a callback that expects an object with getButtonProps property:
{({ getButtonProps }) => (
That's why when you just do
const drop1=dropDown.props().children();
it crashes - there is no object with getButtonProps. You may pass this argument, but next you will find drop1 variable contains React object, not Enzyme's ShallowWrapper. So any checks like expect(drop1.prop('something')).toEqual(2) will fail "prop is not a function".
So you either use renderProp():
const drop1 = dropDown.renderProp('children')({
getButtonProps: () => ({
id: 'typeDropDown',
borderless: true,
buttonWidth: someWidth,
source: mockedSource,
onChange: mockedOnChange,
options: mockedOptions
})
});
Or maybe it's much easier to use mount() instead.

Related

MUI: The `getOptionLabel` method of Autocomplete returned undefined instead of a string

I am having trouble configuring this error (below), when I click the autocomplete search the display data is blank the error shows on the console
when i tried to type
the api fetch on when the user typing
where did i get wrong?
LocationSearch
const propTypes = {
getOptionLabel: PropTypes.func,
value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
}
const defaultProps = {
getOptionLabel: () => {},
value: '',
}
const LocationSearch = ({
getOptionLabel,
value,
})
....
<Autocomplete
getOptionLabel={getOptionLabel}
value={value}
/>
Parent
import {LocationSearch} from '../../component/LocationSearch'
......
<LocationSearch
getOptionLabel={(options) => options.title}
value={threatLocation}
/>
error
try providing an array of objects in the getOptionLabel parameter as autocomplete option maps data and find the key as you are looking for maybe that works.
<Autocomplete
getOptionLabel={[{title:'a', name: 'a'}, {title:'b', name: 'b'}]}
value={value}
/>

how to test that props are passed to child component with react testing library and jest? [duplicate]

My component looks something like this: (It has more functionality as well as columns, but I have not included that to make the example simpler)
const WeatherReport: FunctionComponent<Props> = ({ cityWeatherCollection, loading, rerender }) => {
/* some use effects skipped */
/* some event handlers skipped */
const columns = React.useMemo(() => [
{
header: 'City',
cell: ({ name, title }: EnhancedCityWeather) => <Link to={`/${name}`} className="city">{title}</Link>
},
{
header: 'Temp',
cell: ({ temperature }: EnhancedCityWeather) => (
<div className="temperature">
<span className="celcius">{`${temperature}°C`}</span>
<span className="fahrenheit">{` (~${Math.round(temperature * (9 / 5)) + 32}°F)`}</span>
</div>
)
},
{
header: '',
cell: ({ isFavorite } : EnhancedCityWeather) => isFavorite && (
<HeartIcon
fill="#6d3fdf"
height={20}
width={20}
/>
),
},
], []);
return (
<Table columns={columns} items={sortedItems} loading={loading} />
);
};
Now, I wrote some tests like this:
jest.mock('../../../components/Table', () => ({
__esModule: true,
default: jest.fn(() => <div data-testid="Table" />),
}));
let cityWeatherCollection: EnhancedCityWeather[];
let loading: boolean;
let rerender: () => {};
beforeEach(() => {
cityWeatherCollection = [/*...some objects...*/];
loading = true;
rerender = jest.fn();
render(
<BrowserRouter>
<WeatherReport
cityWeatherCollection={cityWeatherCollection}
loading={loading}
rerender={rerender}
/>
</BrowserRouter>
);
});
it('renders a Table', () => {
expect(screen.queryByTestId('Table')).toBeInTheDocument();
});
it('passes loading prop to Table', () => {
expect(Table).toHaveBeenCalledWith(
expect.objectContaining({ loading }),
expect.anything(),
);
});
it('passes items prop to Table after sorting by isFavorite and then alphabetically', () => {
expect(Table).toHaveBeenCalledWith(
expect.objectContaining({
items: cityWeatherCollection.sort((item1, item2) => (
+item2.isFavorite - +item1.isFavorite
|| item1.name.localeCompare(item2.name)
)),
}),
expect.anything(),
);
});
If you check my component, it has a variable called columns. I am assigning that variable to Table component.
I think, I should test that columns are being passed as props to the Table component. Am I thinking right? If so, can you please tell me how can I write a test case for that?
Also, it will be helpful if you can suggest me how can i test each cell declared inside columns property.
It is not recommended to test implementation details, such as component props, with React Testing Library. Instead you should be asserting on the screen content.
Recommended
expect(await screen.findByText('some city')).toBeInTheDocument();
expect(screen.queryByText('filtered out city')).not.toBeInTheDocument();
Not Recommended
If you want to test props anyways, you can try the sample code below. Source
import Table from './Table'
jest.mock('./Table', () => jest.fn(() => null))
// ... in your test
expect(Table).toHaveBeenCalledWith(props, context)
You might consider this approach mainly on the two following scenarios.
You already tried the recommended approach but you noticed the component is:
using legacy code and because of that it makes testing very hard. Refactoring the component would also take too long or be too risky.
is very slow and it drastically increases the testing time. The component is also already tested somewhere else.
have a look at a very similar question here
You can use the props() method, doing something like this:
expect(Table.props().propYouWantToCheck).toBeFalsy();
Just doing your component.props() then the prop you want, you can make any assert with it.

Testing component with lodash.debounce delay failing

I have a rich text editor input field that I wanted to wrap with a debounced component. Debounced input component looks like this:
import { useState, useCallback } from 'react';
import debounce from 'lodash.debounce';
const useDebounce = (callback, delay) => {
const debouncedFn = useCallback(
debounce((...args) => callback(...args), delay),
[delay] // will recreate if delay changes
);
return debouncedFn;
};
function DebouncedInput(props) {
const [value, setValue] = useState(props.value);
const debouncedSave = useDebounce((nextValue) => props.onChange(nextValue), props.delay);
const handleChange = (nextValue) => {
setValue(nextValue);
debouncedSave(nextValue);
};
return props.renderProps({ onChange: handleChange, value });
}
export default DebouncedInput;
I am using DebouncedInput as a wrapper component for MediumEditor:
<DebouncedInput
value={task.text}
onChange={(text) => onTextChange(text)}
delay={500}
renderProps={(props) => (
<MediumEditor
{...props}
id="task"
style={{ height: '100%' }}
placeholder="Task text…"
disabled={readOnly}
key={task.id}
/>
)}
/>;
MediumEditor component does some sanitation work that I would like to test, for example stripping html tags:
class MediumEditor extends React.Component {
static props = {
id: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func,
disabled: PropTypes.bool,
uniqueID: PropTypes.any,
placeholder: PropTypes.string,
style: PropTypes.object,
};
onChange(text) {
this.props.onChange(stripHtml(text) === '' ? '' : fixExcelPaste(text));
}
render() {
const {
id,
value,
onChange,
disabled,
placeholder,
style,
uniqueID,
...restProps
} = this.props;
return (
<div style={{ position: 'relative', height: '100%' }} {...restProps}>
{disabled && (
<div
style={{
position: 'absolute',
width: '100%',
height: '100%',
cursor: 'not-allowed',
zIndex: 1,
}}
/>
)}
<Editor
id={id}
data-testid="medium-editor"
options={{
toolbar: {
buttons: ['bold', 'italic', 'underline', 'subscript', 'superscript'],
},
spellcheck: false,
disableEditing: disabled,
placeholder: { text: placeholder || 'Skriv inn tekst...' },
}}
onChange={(text) => this.onChange(text)}
text={value}
style={{
...style,
background: disabled ? 'transparent' : 'white',
borderColor: disabled ? 'grey' : '#FF9600',
overflowY: 'auto',
color: '#444F55',
}}
/>
</div>
);
}
}
export default MediumEditor;
And this is how I am testing this:
it('not stripping html tags if there is text', async () => {
expect(editor.instance.state.text).toEqual('Lorem ipsum ...?');
const mediumEditor = editor.findByProps({ 'data-testid': 'medium-editor' });
const newText = '<p><b>New text, Flesk</b></p>';
mediumEditor.props.onChange(newText);
// jest.runAllTimers();
expect(editor.instance.state.text).toEqual(newText);
});
When I run this test I get:
Error: expect(received).toEqual(expected) // deep equality
Expected: "<p><b>New text, Flesk</b></p>"
Received: "Lorem ipsum ...?"
I have also tried running the test with jest.runAllTimers(); before checking the result, but then I get:
Error: Ran 100000 timers, and there are still more! Assuming we've hit an infinite recursion and bailing out...
I have also tried with:
jest.advanceTimersByTime(500);
But the test keeps failing, I get the old state of the text.
It seems like the state just doesn't change for some reason, which is weird since the component used to work and the test were green before I had them wrapped with DebounceInput component.
The parent component where I have MediumEditor has a method onTextChange that should be called from the DebounceInput component since that is the function that is being passed as the onChange prop to the DebounceInput, but in the test, I can see this method is never reached. In the browser, everything works fine, so I don't know why it is not working in the test?
onTextChange(text) {
console.log('text', text);
this.setState((state) => {
return {
task: { ...state.task, text },
isDirty: true,
};
});
}
On inspecting further I could see that the correct value is being passed in the test all the way to handleChange in DebouncedInput. So, I suspect, there are some problems with lodash.debounce in this test. I am not sure if I should mock this function or does mock come with jest?
const handleChange = (nextValue) => {
console.log(nextValue);
setValue(nextValue);
debouncedSave(nextValue);
};
This is where I suspect the problem is in the test:
const useDebounce = (callback, delay) => {
const debouncedFn = useCallback(
debounce((...args) => callback(...args), delay),
[delay] // will recreate if delay changes
);
return debouncedFn;
};
I have tried with mocking debounce like this:
import debounce from 'lodash.debounce'
jest.mock('lodash.debounce');
debounce.mockImplementation(() => jest.fn(fn => fn));
That gave me error:
TypeError: _lodash.default.mockImplementation is not a function
How should I fix this?
I'm guessing that you are using enzyme (from the props access).
In order to test some code that depends on timers in jest:
mark to jest to use fake timers with call to jest.useFakeTimers()
render your component
make your change (which will start the timers, in your case is the state change), pay attention that when you change the state from enzyme, you need to call componentWrapper.update()
advance the timers using jest.runOnlyPendingTimers()
This should work.
Few side notes regarding testing react components:
If you want to test the function of onChange, test the immediate component (in your case MediumEditor), there is no point of testing the entire wrapped component for testing the onChange functionality
Don't update the state from tests, it makes your tests highly couple to specific implementation, prove, rename the state variable name, the functionality of your component won't change, but your tests will fail, since they will try to update a state of none existing state variable.
Don't call onChange props (or any other props) from test. It makes your tests more implementation aware (=high couple with component implementation), and actually they doesn't check that your component works properly, think for example that for some reason you didn't pass the onChange prop to the input, your tests will pass (since your test is calling the onChange prop), but in real it won't work.
The best approach of component testing is to simulate actions on the component like your user will do, for example, in input component, simulate a change / input event on the component (this is what your user does in real app when he types).

How can I test if a prop is passed to child?

My component looks something like this: (It has more functionality as well as columns, but I have not included that to make the example simpler)
const WeatherReport: FunctionComponent<Props> = ({ cityWeatherCollection, loading, rerender }) => {
/* some use effects skipped */
/* some event handlers skipped */
const columns = React.useMemo(() => [
{
header: 'City',
cell: ({ name, title }: EnhancedCityWeather) => <Link to={`/${name}`} className="city">{title}</Link>
},
{
header: 'Temp',
cell: ({ temperature }: EnhancedCityWeather) => (
<div className="temperature">
<span className="celcius">{`${temperature}°C`}</span>
<span className="fahrenheit">{` (~${Math.round(temperature * (9 / 5)) + 32}°F)`}</span>
</div>
)
},
{
header: '',
cell: ({ isFavorite } : EnhancedCityWeather) => isFavorite && (
<HeartIcon
fill="#6d3fdf"
height={20}
width={20}
/>
),
},
], []);
return (
<Table columns={columns} items={sortedItems} loading={loading} />
);
};
Now, I wrote some tests like this:
jest.mock('../../../components/Table', () => ({
__esModule: true,
default: jest.fn(() => <div data-testid="Table" />),
}));
let cityWeatherCollection: EnhancedCityWeather[];
let loading: boolean;
let rerender: () => {};
beforeEach(() => {
cityWeatherCollection = [/*...some objects...*/];
loading = true;
rerender = jest.fn();
render(
<BrowserRouter>
<WeatherReport
cityWeatherCollection={cityWeatherCollection}
loading={loading}
rerender={rerender}
/>
</BrowserRouter>
);
});
it('renders a Table', () => {
expect(screen.queryByTestId('Table')).toBeInTheDocument();
});
it('passes loading prop to Table', () => {
expect(Table).toHaveBeenCalledWith(
expect.objectContaining({ loading }),
expect.anything(),
);
});
it('passes items prop to Table after sorting by isFavorite and then alphabetically', () => {
expect(Table).toHaveBeenCalledWith(
expect.objectContaining({
items: cityWeatherCollection.sort((item1, item2) => (
+item2.isFavorite - +item1.isFavorite
|| item1.name.localeCompare(item2.name)
)),
}),
expect.anything(),
);
});
If you check my component, it has a variable called columns. I am assigning that variable to Table component.
I think, I should test that columns are being passed as props to the Table component. Am I thinking right? If so, can you please tell me how can I write a test case for that?
Also, it will be helpful if you can suggest me how can i test each cell declared inside columns property.
It is not recommended to test implementation details, such as component props, with React Testing Library. Instead you should be asserting on the screen content.
Recommended
expect(await screen.findByText('some city')).toBeInTheDocument();
expect(screen.queryByText('filtered out city')).not.toBeInTheDocument();
Not Recommended
If you want to test props anyways, you can try the sample code below. Source
import Table from './Table'
jest.mock('./Table', () => jest.fn(() => null))
// ... in your test
expect(Table).toHaveBeenCalledWith(props, context)
You might consider this approach mainly on the two following scenarios.
You already tried the recommended approach but you noticed the component is:
using legacy code and because of that it makes testing very hard. Refactoring the component would also take too long or be too risky.
is very slow and it drastically increases the testing time. The component is also already tested somewhere else.
have a look at a very similar question here
You can use the props() method, doing something like this:
expect(Table.props().propYouWantToCheck).toBeFalsy();
Just doing your component.props() then the prop you want, you can make any assert with it.

Refactoring class component to functional component with hooks, getting Uncaught TypeError: func.apply is not a function

This is my first attempt to refactor code from a class component to a functional component using React hooks. The reason we're refactoring is that the component currently uses the soon-to-be-defunct componentWillReceiveProps lifecylcle method, and we haven't been able to make the other lifecycle methods work the way we want. For background, the original component had the aforementioned cWRP lifecycle method, a handleChange function, was using connect and mapStateToProps, and is linking to a repository of tableau dashboards via the tableau API. I am also breaking the component, which had four distinct features, into their own components. The code I'm having issues with is this:
const Parameter = (props) => {
let viz = useSelector(state => state.fetchDashboard);
const parameterSelect = useSelector(state => state.fetchParameter)
const parameterCurrent = useSelector(state => state.currentParameter)
const dispatch = useDispatch();
let parameterSelections = parameterCurrent;
useEffect(() => {
let keys1 = Object.keys(parameterCurrent);
if (
keys1.length > 0 //if parameters are available for a dashboard
) {
return ({
parameterSelections: parameterCurrent
});
}
}, [props.parameterCurrent])
const handleParameterChange = (event, valKey, index, key) => {
parameterCurrent[key] = event.target.value;
console.log(parameterCurrent[key]);
return (
prevState => ({
...prevState,
parameterSelections: parameterCurrent
}),
() => {
viz
.getWorkbook()
.changeParameterValueAsync(key, valKey)
.then(function () {
Swal.fire({
position: "center",
icon: "success",
title:
JSON.stringify(key) + " set to " + JSON.stringify(valKey),
font: "1em",
showConfirmButton: false,
timer: 2500,
heightAuto: false,
height: "20px"
});
})
.otherwise(function (err) {
alert(
Swal.fire({
position: "top-end",
icon: "error",
title: err,
showConfirmButton: false,
timer: 1500,
width: "16rem",
height: "5rem"
})
);
});
}
);
};
const classes = useStyles();
return (
<div>
{Object.keys(parameterSelect).map((key, index) => {
return (
<div>
<FormControl component="fieldset">
<FormLabel className={classes.label} component="legend">
{key}
</FormLabel>
{parameterSelect[key].map((valKey, valIndex) => {
console.log(parameterSelections[key])
return (
<RadioGroup
aria-label="parameter"
name="parameter"
value={parameterSelections[key]}
onChange={(e) => dispatch(
handleParameterChange(e, valKey, index, key)
)}
>
<FormControlLabel
className={classes.formControlparams}
value={valKey}
control={
<Radio
icon={
<RadioButtonUncheckedIcon fontSize="small" />
}
className={clsx(
classes.icon,
classes.checkedIcon
)}
/>
}
label={valKey}
/>
</RadioGroup>
);
})}
</FormControl>
<Divider className={classes.divider} />
</div>
);
})
}
</div >
)};
export default Parameter;
The classes const is defined separately, and all imports of reducers, etc. have been completed. parameterSelect in the code points to all available parameters, while parameterCurrent points to the default parameters chosen in the dashboard (i.e. what the viz initially loads with).
Two things are happening: 1. Everything loads fine on initial vizualization, and when I click on the Radio Button to change the parameter, I can see it update on the dashboard - however, it's not actually showing the radio button as being selected (it still shows whichever parameter the viz initialized with as being selected). 2. When I click outside of the Filterbar (where this component is imported to), I get Uncaught TypeError: func.apply is not a function. I refactored another component and didn't have this issue, and I can't seem to determine if I coded incorrectly in the useEffect hook, the handleParameterChange function, or somewhere in the return statement. Any help is greatly appreciated by this newbie!!!
This is a lot of code to take in without seeing the original class or having a code sandbox to load up. My initial thought is it might be your useEffect
In your refactored code, you tell your useEffect to only re-run when the props.parameterCurrent changes. However inside the useEffect you don't make use of props.parameterCurrent, you instead make use of parameterCurrent from the local lexical scope. General rule of thumb, any values used in the calculations inside a useEffect should be in the list of re-run dependencies.
useEffect(() => {
let keys1 = Object.keys(parameterCurrent);
if (
keys1.length > 0 //if parameters are available for a dashboard
) {
return ({
parameterSelections: parameterCurrent
});
}
}, [parameterCurrent])
However, this useEffect doesn't seem to do anything, so while its dependency list is incorrect, I don't think it'll solve the problem you are describing.
I would look at your dispatch and selector. Double check that the redux store is being updated as expected, and that the new value is making it from the change callback, to the store, and back down without being lost due to improper nesting, bad key names, etc...
I'd recommend posting a CodeSandbox.io link or the original class for further help debugging.

Resources