Vitest with Preact: Adding forwardRef prevents mocked useState hook from firing

27 Views Asked by At

Component without forwardRef passes the test

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
    });
  }
});

Adding forwardRef makes the exact same test not pass anymore

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>}
      />
    </>
  );
});

What I tried so far

  • Googling for 4 hours didn't yield anything related exactly to my problem.
  • I tried both with and without async functions in tests (act and waitFor) but nothing changed.
  • I verified (via logging all the steps) that all the operations in the test execute in the same order as written.
  • Adding a React.createRef() reference to the test component didn't solve my issue either.
  • It seems the 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>}
      />
...
0

There are 0 best solutions below