Skip to content

onChangeText is trigger but there is no validation #922

@Benoit-ROBIN

Description

@Benoit-ROBIN

Ask your Question

Hello,
I'm working on a "react-native" app that works with "formik" and "yupjs".
I created a login form that works fine in Android and iOS emulators, as well as on the web via react-native-web.
I test this application with jest and react-native-testing-library for the Android and iOS part.
It's the first time I use react-native-testing-library,
I have a problem when I want to test that an error message is displayed when a user enters a non-compliant email and/or password, on the native part.
This validation is done with formik and yupjs, and is automatically triggered during an onChange event on an input.
However, this event does not trigger and the error message does not appear.

I removed the "isDisabled" props on the submit button so when I fire a press event on the submit button the validation is triggered and all the tests works fine but it's not a solution because the specifications do not allow to submit if the form is invalid.

Tell me if you need more information
BTW I'm not native english speaker, please be indulgent.

Login form

export default function LoginForm() {
  const intl = useIntl();
  const login = useLoginUser();

  const initialValues = {
    email: '',
    password: '',
  };

  return (
    <Formik
      initialValues={initialValues}
      validateOnChange
      validateOnBlur
      validateOnMount
      validationSchema={validationSchema(intl)}
      onSubmit={(
        values: FormikFields,
        formikBag: FormikHelpers<FormikFields>,
      ) => login(values, formikBag)}
    >
      {({
        handleSubmit,
      }) => (
        <>
          <InputText<FormikFields>
            name="email"
            type="email"
            label={intl.formatMessage(translations.emailLabel)}
            placeholder={intl.formatMessage(translations.emailLabel)}
          />

          <InputPassword<FormikFields>
            name="password"
            label={intl.formatMessage(translations.passwordLabel)}
          />

          <Button
            onPress={() => handleSubmit()}
            alignSelf="center"
          >
            {intl.formatMessage(translations.submitButton)}
          </Button>
        </>
      )}
    </Formik>
  );
}

My input text wrapper

export default function InputText<T extends {
  [key: string]: string | undefined,
}>({
  name,
  label,
  type = 'text',
  placeholder = undefined,
  autoCapitalize = 'none',
  autoComplete = undefined,
  autoCorrect = false,
  autoFocus = false,
  isRequired = false,
}: {
  name: string;
  label: string;
  type?: 'email' | 'text' | 'number';
  placeholder?: string;
  autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters' | undefined;
  autoComplete?: TextInputAndroidProps['autoComplete'] | undefined;
  autoCorrect?: boolean;
  autoFocus?: boolean;
  isRequired?: boolean;
}) {
  const {
    values,
    errors,
    touched,
    handleChange,
    handleBlur,
  } = useFormikContext<T>();

  return (
    <FormControl
      isInvalid={name in errors && !!touched[name]}
      isRequired={isRequired}
    >
      <FormControl.Label>
        {label}
      </FormControl.Label>
      <Input
        type={type}
        onChangeText={handleChange(name)}
        onBlur={handleBlur(name)}
        value={values[name]}
        autoCapitalize={autoCapitalize}
        autoComplete={autoComplete}
        autoCorrect={autoCorrect}
        autoFocus={autoFocus}
        placeholder={placeholder ?? label}
        accessibilityLabel={label}
      />
      <FormControl.ErrorMessage>
        {errors[name]}
      </FormControl.ErrorMessage>
    </FormControl>
  );
}

THE BROKEN TEST

test('Mandatory fields - display error', async () => {
    const {
      getByLabelText,
      getByText,
      container,
    } = componentRenderer({
      Component: LoginForm,
    });

    await waitFor(() => container.instance); // wait for the auth provider to remove loading

    const emailInput = getByLabelText(/Email/i);
    const passwordInput = getByLabelText(/Mot de passe/i);

    act(() => {
      fireEvent(emailInput, 'onChangeText', 'invlidemail');
      fireEvent(passwordInput, 'onChangeText', 'invalidpassword');
      //   fireEvent.changeText(emailInput, { target: { name: 'email', value: 'invlidemail' } });
      //   fireEvent.changeText(passwordInput, { target: { name: 'password', value: 'invalidpassword' } });
    });

    await waitFor(() => container.instance);

    expect(emailInput.props.value).toBe('invlidemail');
    expect(passwordInput.props.value).toBe('invalidpassword');

    expect(getByText(/Veuillez saisir un email valide/i)).toBeDefined();
    expect(getByText(/Veuillez saisir un mot de passe valide/i)).toBeDefined();
  });

the test results
Capture d’écran 2022-02-22 à 17 51 23

versions

"react-native": "0.66.4",  
"yup": "^0.32.11"  
"formik": "^2.2.9",  
"@testing-library/jest-native": "^4.0.4",  
"@testing-library/react": "^12.1.2",  
"@testing-library/react-native": "^9.0.0",  
"react-test-renderer": "17.0.2",  

Jest config

module.exports = () => {
  const isAndroid = process.env.PLATFORM === 'android';

  return {
    preset: '@testing-library/react-native',
    setupFiles: ['./jestSetupFile.js'],
    setupFilesAfterEnv: [
      '@testing-library/jest-native/extend-expect',
    ],
    globals: {
      'ts-jest': {
        tsconfig: './tsconfig.spec.json',
      },
    },
    transform: {
      '^.+\\.jsx?$': 'babel-jest',
      '^.+\\.tsx?$': 'ts-jest',
    },
    testEnvironment: 'node',
    moduleFileExtensions: [
      'ts',
      'tsx',
      'js',
      'jsx',
      'json',
      'node',
    ],
    testMatch: [
      '**/?(*.)+(spec|test).[jt]s?(x)',
      '!**/?(*.web.)+(spec|test).[jt]s?(x)',
      isAndroid
        ? '!**/?(*.ios.)+(spec|test).[jt]s?(x)'
        : '!**/?(*.android.)+(spec|test).[jt]s?(x)',
    ],
    transformIgnorePatterns: [
      'node_modules/(?!(jest-)?react-native|@react-native)',
    ],
    moduleNameMapper: {
      '^.+\\.(png)$': 'identity-obj-proxy',
    },
    haste: {
      defaultPlatform: isAndroid ? 'android' : 'ios',
      platforms: [
        'android',
        'ios',
        'native',
      ],
    },
    collectCoverageFrom: [
      'src/**/*.{js,jsx,ts,tsx}',
      '!src/**/*.d.ts',
      '!src/**/@types/graphql.ts',
      '!src/**/@types/graphql-errors.ts',
      '!src/bundles/**/*.translations.ts',
      '!src/reportWebVitals.ts'
    ],
    snapshotResolver: './config/snapshotResolver.native.js',
    coverageDirectory: './coverage',
  };
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions