I have a working Preact component and a working test in Vitest. Working minimized samples:
Component
import { ChangeEvent } from 'preact/compat';
export function DateInput({
value,
setValue,
label,
}: {
value: string;
setValue: (value: string) => void;
label: string;
}) {
const storeValue = (e: ChangeEvent): void => {
// console.log('event fired'); --> Fires
const elem: HTMLInputElement = e.currentTarget as HTMLInputElement;
setValue(elem.value);
};
return (
<>
<label htmlFor="test-id">{label}</label>
<input type="date" value={value} id="test-id" onChange={storeValue} />
</>
);
}
Test (passing)
it.only('should update state when date is changed', async () => {
const setValue = vi.fn();
const { container } = render(
<DateInput
value={m.valueOutput1} // "2024-01-30"
setValue={setValue}
label={m.customLabel} // "Test label:"
/>
);
const htmlInput: HTMLInputElement | null = queryByLabelText(
container as HTMLElement,
m.customLabel
);
if (htmlInput) {
await act(() => {
fireEvent.change(htmlInput, { target: { value: m.valueOutput2 } }); // "2024-01-31"
});
await waitFor(() => {
expect(setValue).toHaveBeenCalledOnce(); // Passing without forwardRef, failing with it
});
}
});
Updating the test as seen below makes the test fail with message:
AssertionError: expected "spy" to be called once, but got 0 times
201| await waitFor(() => {
202| expect(setValue).toHaveBeenCalledOnce();
| ^
203| });
Component adjusted with forwardRef
import { Ref } from 'preact';
import { ForwardedRef, forwardRef, ChangeEvent } from 'preact/compat';
export const DateInput = forwardRef(function DateInput(
{
value,
setValue,
label,
}: {
value: string;
setValue: (value: string) => void;
label: string;
},
ref: ForwardedRef<HTMLElement>
) {
const storeValue = (e: ChangeEvent): void => {
// console.log('event fired'); --> Does not fire anymore
const elem: HTMLInputElement = e.currentTarget as HTMLInputElement;
setValue(elem.value);
};
return (
<>
<label htmlFor="test-id">{label}</label>
<input
type="date"
value={value}
id="test-id"
onChange={storeValue}
ref={ref as Ref<HTMLInputElement>}
/>
</>
);
});
act
and waitFor
) but nothing changed.React.createRef()
reference to the test component didn't solve my issue either.onChange
event does not trigger at all while using forwardRef
because I never reach it, even when putting a breakpoint directly inside the event:...
<input // <-- breakpoint here does trigger
type="date"
value={value}
id="test-id"
onChange={e => {
storeValue(e); // <-- breakpoint here does not trigger
}}
ref={ref as Ref<HTMLInputElement>}
/>
...
This is an issue with preact-testing-library
. Preact's preact/compat
library causes all events to be translated into names used in React (where they differ), but the testing library does not do this translation. There are in fact 16 events which are affected by this (mostly animation, composition and touch events).
To fix this immediately, just needed to replace onChange
to onInput
. This does not solve the underlying issue though.
https://preactjs.com/guide/v10/differences-to-react#use-oninput-instead-of-onchange