1

I'm working with a custom React component that wraps an Office UI Fabric TextField within a Formik form. Despite following typical patterns for handling input fields with React and Formik, I'm encountering issues when testing the component using React Testing Library. The input's value does not update after simulating a change event in Jest.

Here's the relevant part of my component:

const FMIconButtonTextField: React.FC<Props> = props => {
  const { label, styles, name, callOutSpanText } = props;
  const [isCalloutVisible, setIsCalloutVisible] = useState(false);
  const inputId = getId(name);
  const [field, { touched, error }] = useField(name);

  return (
    <>
      <TextField
        {...field}
        {...props}
        label={label}
        errorMessage={touched && error ? error : null}
        id={inputId}
      />
    </>
  );
};

And here's how I'm testing it:

it('allows input into the voucher code field', async () => {
  const initialValues = { voucherCode: '' };
  const { container, findByDisplayValue } = render(
    <Formik initialValues={initialValues} onSubmit={jest.fn()}>
      <Form>
        <FMIconButtonTextField
          name="voucherCode"
          label="Voucher Code *"
          callOutSpanText="Voucher Code must contain at least 5 characters and no more than 20 characters, and is allowed A to Z, 0 to 9, and _."
          placeholder="Only A-Z or 0-9 or _"
        />
      </Form>
    </Formik>
  );

  const input = container.querySelector('input[name="voucherCode"]');
  fireEvent.change(input, { target: { value: 'ABC123' } });

  // Wait for React's state update to propagate
  await findByDisplayValue('ABC123'); // This ensures the input has received and displays the new value

  expect(input.value).toBe('ABC123');
});

Despite these efforts, the test fails with the input value not updating:

expect(received).toBe(expected) // Object.is equality

Expected: "ABC123"
Received: ""

2 Answers 2

1

Your CSS query selector expects an input element with the name "voucherCode." However, you assign the name prop to a which has an input as a child. Try changing your query to input *[name="voucherCode"] input or something similar instead to select the appropriate element.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for the suggestions. I tried updating my query selector as recommended to target the input more specifically, but the test still fails when attempting to verify the updated value of the input. Here's the error I'm getting - Unable to find an element with the display value: ABC123.
Can you check the value of the input right after findByDisplayVaue? I originally assumed that findByDisplayValue will wait for the change, and assumed therefore that the issue is that your event wasn't firing correctly. However, I think instead it might be that findByDisplayValue doesn't wait for the effect to occur– it simply checks the state (not yet updated) and responds that nothing matches the input.
0

After encountering issues with the input's value not updating when simulating change events in Jest, I found that the missing link was the onChange handler in the custom TextField component that integrates Formik with the Office UI Fabric UI library. The original component did not include an explicit onChange handler to manage state updates which are essential for Formik's setValue function to properly propagate the new value.

Here's the modified component with the onChange handler:

import React, { useState, useCallback } from 'react';
import { TextField, Label, Stack, IconButton, Callout } from '@fluentui/react';
import { useField, useFormikContext } from 'formik';
import { getId } from '@fluentui/react';

const FMIconButtonTextField = props => {
  const { label, styles, name, callOutSpanText } = props;
  const [isCalloutVisible, setIsCalloutVisible] = useState(false);
  const [field, meta, helpers] = useField(name);
  const { setValue } = helpers;

  const onRenderLabel = useCallback(() => (
    <Stack horizontal verticalAlign="center">
      <Label id={getId('label')} styles={styles.label}>
        {label}
      </Label>
      <IconButton
        id={getId('iconButton')}
        iconProps={{ iconName: 'Info' }}
        title="Info"
        ariaLabel="Info"
        onClick={() => setIsCalloutVisible(true)}
      />
      {isCalloutVisible && (
        <Callout target={`#${getId('iconButton')}`} onDismiss={() => setIsCalloutVisible(false)}>
          <Stack tokens={{ childrenGap: 20, maxWidth: 300 }} styles={styles.callout}>
            <span>{callOutSpanText}</span>
          </Stack>
        </Callout>
      )}
    </Stack>
  ), [label, styles, callOutSpanText, isCalloutVisible]);

  return (
    <TextField
      {...field}
      {...props}
      label={label}
      onRenderLabel={onRenderLabel}
      errorMessage={meta.touched && meta.error ? meta.error : undefined}
      onChange={(_, newValue) => setValue(newValue || '')}
    />
  );
};

export default FMIconButtonTextField;

Key Changes:

  • Added an onChange handler to the TextField component. This handler uses Formik's setValue to update the field's value, ensuring that changes in the input propagate back to Formik's state management.
  • Spread the field object before other props to ensure proper override if necessary.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.