code is not covered by testing-library-react - reactjs

I want to test a select change function,here is the code :
import React, { useEffect, useState } from 'react';
import Select from 'react-select';
function Component1(props) {
const [content, setContent] = useState('initialized Value');
const [color, setColor] = useState('initialized Value');
const options = [
{ value: 'red', label: 'Red' },
{ value: 'green', label: 'Green' },
{ value: 'blue', label: 'Blue' },
];
useEffect(async () => {
fetchSomeData();
// onclickButton();
}, []);
const fetchSomeData = async () => {
console.log('fetchSomeData');
};
const onclickButton = () => {
console.log('do something here onclickButton');
setContent('updated Value');
};
const resetColor = (value) => {
console.log(value);
setColor(value);
};
return (
<div data-testid='Component1'>
Component1
<button data-testid='button' onClick={onclickButton}>
Button
</button>
<div>Button Test :{content}</div>
<Select aria-label='select-Label' data-testid='select' options={options} value={color} onChange={resetColor} />
<div data-testid='color-value'>Current Color:{color}</div>
</div>
);
}
I did some reasearches , and they said the best way is mocked a select and test it:
beforeEach(() => {
render(<Component1 />);
});
test('should 3', () => {
jest.doMock('react-select', () => ({ options, value, onChange }) => {
function handleChange(event) {
const option = options.find((option) => option.value === event.currentTarget.value);
onChange(option);
}
return (
<select data-testid='custom-select' value={value} onChange={handleChange}>
{options.map(({ label, value }) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
);
});
fireEvent.change(screen.getByTestId('select'), {
target: { value: 'green' },
});
test('should 2', () => {
// screen.debug()
const onclickButton = jest.fn();
// render(<Component1 onclickButton={onclickButton} />);
fireEvent.click(screen.getByTestId('button'), {
// target: { value: 'JavaScript' },
});
});
after I run the test, I got this :
TestingLibraryElementError: Unable to find an element by: [data-testid="select"]
can some one help me? I just want below codes can be covered by unit test
update:
I tried to use queryByLabelText, and it works, but still ,it seems not trigger the onChange event. I got this:
Expected element to have text content:
Current Color:green
Received:
Current Color:red
fireEvent.select(screen.queryByLabelText('select-Label'),{target:{value:'green'}})
expect(screen.queryByTestId('color-value')).toHaveTextContent('Current Color:green');

I resolved it by below code:
const DOWN_ARROW = { keyCode: 40 };
fireEvent.keyDown(screen.getByLabelText('select-Label'), DOWN_ARROW);
fireEvent.click(screen.getByText('Green'));
these code will trigger onChange event.
also refter to:
how to test react-select with react-testing-library

Related

How do I check if multiple text contents are in an element?

I have a component like this:
export const MyComponent = props => {
return (
{
props.options.map(option =>
<>
<div>
<input type="radio" id={option.id} name="group" value={option.id} />
<label htmlFor={option.id}>{option.label}</label>
</div>
<span>Some other text</span>
</>
)
}
)
}
And in my test, I want to make sure that both that all the radio buttons are rendered with the right label text and the extra text in the span are present.
import { render, screen, within } from '#testing-library/react'
describe('MyComponent', () => {
const props = {
options: [
{ id: 1, label: 'Apple' },
{ id: 2, label: 'Banana' },
{ id: 3, label: 'Cherry' },
]
}
it('Renders the component', () => {
render(<MyComponent {...props} />)
const options = screen.queryAllByRole('radio')
expect(options).toBeArrayOfSize(3)
options.forEach((option, index) => {
const { getByText } = within(option)
expect(getByText(props.options[index])).toBeInTheDocument() // Assertion fails
expect(getByText("Some other text")).toBeInTheDocument() // Also fails
})
})
})
However, I'm getting errors on the two expect assertions.
You can try the following:
import { render, screen } from "#testing-library/react"
import { MyComponent } from "./MyComponent"
describe("MyComponent", () => {
const props = {
options: [
{ id: 1, label: "Apple" },
{ id: 2, label: "Banana" },
{ id: 3, label: "Cherry" },
],
}
it("Renders the component", () => {
render(<MyComponent {...props} />)
const options = screen.queryAllByRole("radio")
expect(options).toHaveLength(3)
props.options.forEach((option) => {
const label = screen.getByLabelText(option.label)
const radioBtn = screen.getByRole("radio", { name: option.label })
// Need to use getAllByText query since the string "Some other text" is repeated... getByText will throw because of multiple matches
const [someOtherText] = screen.getAllByText("Some other text")
expect(label).toBeInTheDocument()
expect(radioBtn).toBeInTheDocument()
expect(someOtherText).toHaveTextContent(someOtherText.textContent)
})
})
})

Ant Design & React Testing Library - Testing Form with Select

I'm attempting to test a Select input inside an Ant Design Form filled with initialValues and the test is failing because the Select does not receive a value. Is there a best way to test a "custom" rendered select?
Test Output:
Error: expect(element).toHaveValue(chocolate)
Expected the element to have value:
chocolate
Received:
Example Test:
import { render, screen } from '#testing-library/react';
import { Form, Select } from 'antd';
const customRender = (ui: React.ReactElement, options = {}) => render(ui, {
wrapper: ({ children }) => children,
...options,
});
describe('select tests', () => {
it('renders select', () => {
const options = [
{ label: 'Chocolate', value: 'chocolate' },
{ label: 'Strawberry', value: 'strawberry' },
{ label: 'Vanilla', value: 'vanilla' },
];
const { value } = options[0];
customRender(
<Form initialValues={{ formSelectItem: value }}>
<Form.Item label="Form Select Label" name="formSelectItem">
<Select options={options} />
</Form.Item>
</Form>,
);
expect(screen.getByLabelText('Form Select Label')).toHaveValue(value);
});
});
testing a library component may be harsh sometimes because it hides internal complexity.
for testing antd select i suggest to mock it and use normal select in your tests like this:
jest.mock('antd', () => {
const antd = jest.requireActual('antd');
const Select = ({ children, onChange, ...rest }) => {
return <select role='combobox' onChange={e => onChange(e.target.value)}>
{children}
</select>;
};
Select.Option = ({ children, ...otherProps }) => {
return <option role='option' {...otherProps}}>{children}</option>;
}
return {
...antd,
Select,
}
})
this way you can test the select component as a normal select (use screen.debug to check that the antd select is mocked)
I mocked a normal select and was able to get everything working.
The following example utilizes Vitest for a test runner but should apply similar to Jest.
antd-mock.tsx
import React from 'react';
import { vi } from 'vitest';
vi.mock('antd', async () => {
const antd = await vi.importActual('antd');
const Select = props => {
const [text, setText] = React.useState('');
const multiple = ['multiple', 'tags'].includes(props.mode);
const handleOnChange = e => props.onChange(
multiple
? Array.from(e.target.selectedOptions)
.map(option => option.value)
: e.target.value,
);
const handleKeyDown = e => {
if (e.key === 'Enter') {
props.onChange([text]);
setText('');
}
};
return (
<>
<select
// add value in custom attribute to handle async selector,
// where no option exists on load (need to type to fetch option)
className={props.className}
data-testid={props['data-testid']}
data-value={props.value || undefined}
defaultValue={props.defaultValue || undefined}
disabled={props.disabled || undefined}
id={props.id || undefined}
multiple={multiple || undefined}
onChange={handleOnChange}
value={props.value || undefined}
>
{props.children}
</select>
{props.mode === 'tags' && (
<input
data-testid={`${props['data-testid']}Input`}
onChange={e => setText(e.target.value)}
onKeyDown={handleKeyDown}
type="text"
value={text}
/>
)}
</>
);
};
Select.Option = ({ children, ...otherProps }) => (
<option {...otherProps}>{children}</option>
);
Select.OptGroup = ({ children, ...otherProps }) => (
<optgroup {...otherProps}>{children}</optgroup>
);
return { ...antd, Select };
});
utils.tsx
import { render } from '#testing-library/react';
import { ConfigProvider } from 'antd';
const customRender = (ui: React.ReactElement, options = {}) => render(ui, {
wrapper: ({ children }) => <ConfigProvider prefixCls="bingo">{children}</ConfigProvider>,
...options,
});
export * from '#testing-library/react';
export { default as userEvent } from '#testing-library/user-event';
export { customRender as render };
Select.test.tsx
import { Form } from 'antd';
import { render, screen, userEvent } from '../../../test/utils';
import Select from './Select';
const options = [
{ label: 'Chocolate', value: 'chocolate' },
{ label: 'Strawberry', value: 'strawberry' },
{ label: 'Vanilla', value: 'vanilla' },
];
const { value } = options[0];
const initialValues = { selectFormItem: value };
const renderSelect = () => render(
<Form initialValues={initialValues}>
<Form.Item label="Label" name="selectFormItem">
<Select options={options} />
</Form.Item>
</Form>,
);
describe('select tests', () => {
it('renders select', () => {
render(<Select options={options} />);
expect(screen.getByRole('combobox')).toBeInTheDocument();
});
it('renders select with initial values', () => {
renderSelect();
expect(screen.getByLabelText('Label')).toHaveValue(value);
});
it('handles select change', () => {
renderSelect();
expect(screen.getByLabelText('Label')).toHaveValue(value);
userEvent.selectOptions(screen.getByLabelText('Label'), 'vanilla');
expect(screen.getByLabelText('Label')).toHaveValue('vanilla');
});
});

React/Jest: Simulate users tampering with the HTML

In a React vanilla form, I need to solve an issue where users edit manually the value of an option to make the form submit bad values.
To reproduce, the users inspect the element, and manually change the value of an option to an invalid value (domain-wise).
The fix is easy, but I want to create a failing unit test before fixing, in a TDD fashion, and cannot figure how to model this in a test. I use jest and react-testing-library.
Here is the form code:
export const CreateTripForm = ({ countries, onSubmit }) => {
const [countryId, setCountryId] = useState()
const submit = async (e) => {
e.preventDefault()
if (countryId === undefined) return
await onSubmit(countryId)
}
return (
<form onSubmit={submit}>
<legend>Choose a country</legend>
<label htmlFor="countryId">Country</label>
<select name="countryId" required value={countryId} onChange={(e) => setCountryId(e.target.value)}>
<option value=""></option>
{countries.map((country) =>
<option key={country.id} value={country.id}>{country.label}</option>
)}
</select>
<input type="submit" value="Create a trip" />
</form>
)
}
Here is what I tried to do, but the test passes instead of failing:
it('keeps previous countryId if the selected one has been tampered with', () => {
const onSubmit = jest.fn(() => Promise.resolve())
const countries = [
{ id: 'fr', label: 'France' },
{ id: 'en', label: 'England' },
]
const { container } = render(
<CreateTripForm countries={countries} onSubmit={onSubmit} />
)
const select = container.querySelector('select[name=countryId]')
const submitButton = container.querySelector('input[type=select]')
// Select the 'fr' option, it works.
fireEvent.change(select, { target: { value: 'fr' } })
submitButton.click()
expect(onSubmit).toHaveBeenCalledWith('fr')
// Edit an option to have an incorrect value, it should keep the previous value.
elements.unitSelect.options[2].value = 'asgard'
fireEvent.change(select, { target: { value: 'asgard' } })
submitButton.click()
expect(onSubmit).toHaveBeenCalledWith('fr')
})
I've had some bad experiences with expect() like this before.
Have you tried to separate the tests? One for success and one for failure?
From what I can see, the first expect() doesn't reset what he have been called already so I guess that's why your test have passed on the second expect().
Try it like this:
it('keeps previous countryId if the selected one has been tampered with', () => {
const onSubmit = jest.fn(() => Promise.resolve())
const countries = [
{ id: 'fr', label: 'France' },
{ id: 'en', label: 'England' },
]
const { container } = render(
<CreateTripForm countries={countries} onSubmit={onSubmit} />
)
const select = container.querySelector('select[name=countryId]')
const submitButton = container.querySelector('input[type=select]')
// Select the 'fr' option, it works.
fireEvent.change(select, { target: { value: 'fr' } })
submitButton.click()
expect(onSubmit).toHaveBeenCalledWith('fr')
})
it('should prevent sending with tampered select', () => {
const onSubmit = jest.fn(() => Promise.resolve())
const countries = [
{ id: 'fr', label: 'France' },
{ id: 'en', label: 'England' },
]
const { container } = render(
<CreateTripForm countries={countries} onSubmit={onSubmit} />
)
const select = container.querySelector('select[name=countryId]')
const submitButton = container.querySelector('input[type=select]')
elements.unitSelect.options[2].value = 'asgard'
fireEvent.change(select, { target: { value: 'asgard' } })
submitButton.click()
expect(onSubmit).not.toHaveBeenCalled()
})

How to add a function in const Target = props => {

How do I add a function to connect to one of my components onChange? Creating a function like this returns an error code of 'cardActionResponse' is not defined.
What the benefit of using a const class like this?
const Target = props => {
const { markAsDone } = useContext(ItemContext);
const [{ isOver }, drop] = useDrop({
accept: 'Item',
drop: (item, monitor) => console.log(item),
collect: monitor => ({
isOver: !!monitor.isOver()
})
})
//Cannot do this. How else can I make a function to connect to CreateVideoCard?
cardActionResponse = (event) => {
console.log(event);
}
return (
<div className="target top80 right30" ref={drop} style={{ backgroundColor: isOver ? 'black' : '' }} >
<TitleDescription class="z1"/>
<div class="right10 left10">
<CreateVideoCard onChange={this.cardActionResponse} />
<CreateDescriptionCard></CreateDescriptionCard>
<CreateAudioCard></CreateAudioCard>
<CreateTermsCard></CreateTermsCard>
</div>
</div>
);
};
export default Target;
Functional components don't have it's own context (this), so you should simply use const variable.
Please use
const cardActionResponse = (event) => {
console.log(event);
}
and then
<CreateVideoCard onChange={cardActionResponse} />

I am trying to change the input value but its not changing, may i know the reason

I am trying to change the input value but its not changing.
Onchange the values are not changing
May i know the the reason
any suggestion?
please refer below snippet
// snippets
import React, {useState} from 'react';
const StackOverFlow = () => {
let rowData = [
{ header: "first" },
{ header: "second" },
{ header: "third" }
];
const [name, setName] = useState({ fn: "test" });
const [data, setData] = useState(rowData);
const getOnchange = (e) => {
console.log('--e--',e.target.value)
setName({ ...name, fn: e.target.value })
}
let updateValue = () => {
setData([
...data,
{
header: (
<input
type="text"
value={name.fn}
onChange={getOnchange}
/>
)
}
]);
};
return (
<div>
{data.map(val => (
<h6>{val.header}</h6>
))}
<button onClick={updateValue}> Click </button>
</div>
);
};
export default StackOverFlow
The value is not changing because once you set a JSX in state data it will take the value during you call setData and does not change when getOnChange is executed,
import React, { useState } from "react";
const StackOverFlow = () => {
let rowData = [
{ header: "first" },
{ header: "second" },
{ header: "third" }
];
const [name, setName] = useState({ fn: "test" });
const [data, setData] = useState(rowData);
const getOnchange = e => {
console.log("--e--", e.target.value);
setName({ ...name, fn: e.target.value });
};
let updateValue = () => {
setData([
...data,
{
header: name.fn
}
]);
setName({ ...name, fn: e.target.value });
};
return (
<div>
{data.map(val => (
<h6 key={val.header}>{val.header}</h6>
))}
<input type="text" value={name.fn} onChange={getOnchange} />
<button onClick={updateValue}> Click </button>
</div>
);
};
export default StackOverFlow;
this is an alternate approach on what you're trying to achieve.

Resources