Basically, I'm trying to test a component that have a select.
When trying to test the component, the test fails by returning the default value instead of the changed value.
But when I take the HTML of the rendered component (from screen.debug()) it works.
The component:
export function SelectFile({
fileList,
handleChange,
selected,
}) {
return (
<select
className="bg-slate-600 rounded w-auto"
onChange={onChange}
value={selected}
>
<option value="">Select an option</option>
<TodayOptions />
<AllOptions />
</select>
);
function AllOptions() {
return (
<>
{Object.entries(groups).map(([key, value]) => {
return (
<optgroup key={key} label={key.toLocaleUpperCase()}>
{[...value].sort(sortByDateFromLogs).map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</optgroup>
);
})}
</>
);
}
function TodayOptions() {
const todayFiles = Object.values(groups)
.map((group) => {
const today = new Date().toLocaleDateString().replace(/\//g, '-');
return group.filter((file) => file.includes(today));
})
.flat();
if (todayFiles.length === 0) {
return null;
}
return (
<optgroup label="Today">
{todayFiles.map((item) => (
<option key={item}>{item}</option>
))}
</optgroup>
);
}
}
The original test:
it('should change option', () => {
render(
<SelectFile
fileList={fileList}
handleChange={handleChange}
selected=""
/>,
);
const selectElement = screen.getByDisplayValue('Select an option');
const allOptions = screen.getAllByRole('option');
const optionSelected = fileList.adonis[1];
expect(selectElement).toHaveValue('');
act(() => {
userEvent.selectOptions(selectElement, optionSelected);
});
expect(handleChange).toHaveBeenCalledTimes(1);
expect(selectElement).toHaveValue(optionSelected); // returns "" (default value)
expect((allOptions[0] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[1] as HTMLOptionElement).selected).toBe(true);
expect((allOptions[2] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[3] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[4] as HTMLOptionElement).selected).toBe(false);
});
And the modified test with the rendered html:
it('should change option', () => {
render(
<div>
<div className="flex mr-10">
<h3 className="text-lg font-bold mr-4">Select a file</h3>
<select className="bg-slate-600 rounded w-auto">
<option value="">Select an option</option>
<optgroup label="ADONIS">
<option value="adonis-03-02-2022.json">
adonis-03-02-2022.json
</option>
<option value="adonis-02-02-2022.json">
adonis-02-02-2022.json
</option>
</optgroup>
<optgroup label="ERRORS">
<option value="http_errors-03-03-2022.log">
http_errors-03-03-2022.log
</option>
<option value="http_errors-04-02-2022.log">
http_errors-04-02-2022.log
</option>
</optgroup>
</select>
</div>
</div>,
);
const selectElement = screen.getByDisplayValue('Select an option');
const allOptions = screen.getAllByRole('option');
const optionSelected = fileList.adonis[1];
expect(selectElement).toHaveValue('');
act(() => {
userEvent.selectOptions(selectElement, optionSelected);
});
expect(selectElement).toHaveValue(optionSelected); // this returns the optionSelected value
expect((allOptions[0] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[1] as HTMLOptionElement).selected).toBe(true);
expect((allOptions[2] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[3] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[4] as HTMLOptionElement).selected).toBe(false);
});
Considering it works with the modified test, I can't make it why it doesn't on the original. I've considered it was due to the optgroup, but it doesn't seems the case, so now I'm at a loss as to why.
Edit: the final version of the test:
it('should change option', () => {
const mockHandleChange = handleChange.mockImplementation(
(cb) => (e) => cb(e.target.value),
);
render(
<SelectWrapper fileList={fileList} handleChange={mockHandleChange} />,
);
const selectElement = screen.getByDisplayValue('Select an option');
const optionSelected = fileList.adonis[1];
expect(selectElement).toHaveValue('');
act(() => {
userEvent.selectOptions(selectElement, optionSelected);
});
expect(handleChange).toHaveBeenCalledTimes(2); // 1 for cb wrapper, 1 for select
expect(selectElement).toHaveValue(optionSelected);
});
});
const SelectWrapper = ({ handleChange, fileList }) => {
const [selected, setSelected] = useState('');
const mockHandleChange = handleChange(setSelected);
return (
<SelectFile
fileList={fileList}
handleChange={mockHandleChange}
selected={selected}
/>
);
};
I've created a wrapper to make it like you would use in another component, wrapped the mock function and now it changes the value and you have access to the mock.