ws-nextjs-app/pages/[service]/send/[id]/FormField/index.test.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import React from 'react';
import {
  act,
  render,
} from '#app/components/react-testing-library-with-providers';
import * as FormContext from '../FormContext';
import FormField, { FormComponentProps } from '.';
import { Field } from '../types';
import { ContextProps } from '../FormContext';

jest.mock('next/router', () => ({
  useRouter: () => ({ query: { id: 'u1234' } }),
}));

const ComponentWithContext = ({
  props,
  fields,
}: {
  props: FormComponentProps;
  fields: Field[];
}) => (
  <FormContext.FormContextProvider fields={fields}>
    <FormField {...props} />
  </FormContext.FormContextProvider>
);

describe('FormField', () => {
  beforeEach(() => {
    jest.restoreAllMocks();
  });

  it('should render a text input with an associated label', async () => {
    const { container } = await act(() => {
      return render(
        <ComponentWithContext
          props={{
            id: 'testTextID',
            htmlType: 'text',
            label: 'This is a text field',
          }}
          fields={[
            {
              id: 'testTextID',
              validation: {},
            } as unknown as Field,
          ]}
        />,
      );
    });

    const label = container.querySelector('label[for=testTextID]');
    const text = container.querySelector('input[id=testTextID][type=text]');

    expect(label).toBeInTheDocument();
    expect(text).toBeInTheDocument();
    expect(container).toMatchSnapshot();
  });

  it('should render a textarea input with an associated label', async () => {
    const { container } = await act(() => {
      return render(
        <ComponentWithContext
          props={{
            id: 'testTextAreaID',
            htmlType: 'textarea',
            label: 'This is a text field',
          }}
          fields={[
            {
              id: 'testTextAreaID',
              validation: {},
            } as unknown as Field,
          ]}
        />,
      );
    });

    const label = container.querySelector('label[for=testTextAreaID]');
    const textArea = container.querySelector('textarea[id=testTextAreaID]');

    expect(label).toBeInTheDocument();
    expect(textArea).toBeInTheDocument();
    expect(container).toMatchSnapshot();
  });

  it('should render a textarea with a maxiumum word limit if provided', async () => {
    const mockFormState = {
      testAllyID: {
        value: '',
        htmlType: 'textarea',
        messageCode: null,
        required: true,
        isValid: true,
        wasInvalid: false,
        wordLimit: 500,
      },
    };

    jest.spyOn(FormContext, 'useFormContext').mockImplementationOnce(
      () =>
        ({
          formState: mockFormState,
          handleChange: () => null,
          hasAttemptedSubmit: false,
        }) as unknown as ContextProps,
    );

    const { container } = await act(() => {
      return render(
        <ComponentWithContext
          props={{
            id: 'testAllyID',
            htmlType: 'textarea',
            label: 'This is a text area field',
          }}
          fields={[]}
        />,
      );
    });
    const textareaWithCorrectAria = container.querySelector(
      `textarea[id=testAllyID][aria-describedby=testAllyID-wordLimit]`,
    );
    const maxWordLimit = container.querySelector('p[id=testAllyID-wordLimit]');

    expect(textareaWithCorrectAria).toBeInTheDocument();
    expect(maxWordLimit).toBeInTheDocument();
  });

  it('should render an email input with an associated label', async () => {
    const { container } = await act(() => {
      return render(
        <ComponentWithContext
          props={{
            id: 'testEmailID',
            htmlType: 'email',
            label: 'This is a text field',
          }}
          fields={[
            {
              id: 'testEmailID',
              validation: {},
            } as unknown as Field,
          ]}
        />,
      );
    });

    const label = container.querySelector('label[for=testEmailID]');
    const text = container.querySelector('input[id=testEmailID][type=email]');

    expect(label).toBeInTheDocument();
    expect(text).toBeInTheDocument();
    expect(container).toMatchSnapshot();
  });

  it('should render a checkbox input with an associated label', async () => {
    const { container } = await act(() => {
      return render(
        <ComponentWithContext
          props={{
            id: 'testCheckboxID',
            htmlType: 'checkbox',
            label: 'This is a text field',
          }}
          fields={[
            {
              id: 'testCheckboxID',
              validation: {},
            } as unknown as Field,
          ]}
        />,
      );
    });

    const label = container.querySelector('label[for=testCheckboxID]');
    const checkboxInput = container.querySelector(
      'input[id=testCheckboxID][type=checkbox]',
    );

    expect(label).toBeInTheDocument();
    expect(checkboxInput).toBeInTheDocument();
    expect(container).toMatchSnapshot();
  });

  it('should render a tel input with an associated label', async () => {
    const { container } = await act(() => {
      return render(
        <ComponentWithContext
          props={{
            id: 'testTelID',
            htmlType: 'phone',
            label: 'This is a text field',
          }}
          fields={[
            {
              id: 'testTelID',
              validation: {},
            } as unknown as Field,
          ]}
        />,
      );
    });

    const label = container.querySelector('label[for=testTelID]');
    const telephoneInput = container.querySelector(
      'input[id=testTelID][type=tel]',
    );

    expect(label).toBeInTheDocument();
    expect(telephoneInput).toBeInTheDocument();
    expect(container).toMatchSnapshot();
  });

  it('should render a required input with an associated label', async () => {
    const mockFormState = {
      testAllyID: {
        isValid: true,
        required: true,
        value: '',
        htmlType: 'text',
        messageCode: null,
        wasInvalid: false,
      },
    };

    jest.spyOn(FormContext, 'useFormContext').mockImplementationOnce(
      () =>
        ({
          formState: mockFormState,
          handleChange: () => null,
          attemptedSubmitCount: 0,
        }) as unknown as ContextProps,
    );

    const { container } = await act(() => {
      return render(
        <ComponentWithContext
          props={{
            id: 'testAllyID',
            htmlType: 'text',
            label: 'This is a required text field',
          }}
          fields={[]}
        />,
      );
    });

    expect(container).toMatchSnapshot();
  });

  it.each([
    {
      attemptedSubmitCount: 1,
      required: true,
      isValid: false,
      wasInvalid: true,
      expectedAria: '[aria-invalid=true][aria-required=true]',
    },
    {
      attemptedSubmitCount: 1,
      required: false,
      isValid: false,
      wasInvalid: true,
      expectedAria: '[aria-invalid=true]',
    },
    {
      attemptedSubmitCount: 1,
      required: false,
      isValid: true,
      wasInvalid: true,
      expectedAria: '[aria-invalid=false]',
    },
    {
      attemptedSubmitCount: 0,
      required: true,
      isValid: true,
      wasInvalid: false,
      expectedAria: '',
    },
  ])(
    `should apply these attributes: $expectedAria, when attempted submit count is is $attemptCount, required is $required, isValid is $isValid and when the 'previously submitted' flag is $wasInvalid`,
    async ({
      attemptedSubmitCount,
      required,
      isValid,
      expectedAria,
      wasInvalid,
    }) => {
      const mockFormState = {
        testAllyID: {
          isValid,
          required,
          value: '',
          htmlType: 'text',
          messageCode: null,
          wasInvalid,
        },
      };

      jest.spyOn(FormContext, 'useFormContext').mockImplementationOnce(
        () =>
          ({
            formState: mockFormState,
            handleChange: () => null,
            attemptedSubmitCount,
          }) as unknown as ContextProps,
      );

      const { container } = await act(() => {
        return render(
          <ComponentWithContext
            props={{
              id: 'testAllyID',
              htmlType: 'text',
              label: 'This is a text field',
            }}
            fields={[]}
          />,
        );
      });
      const text = container.querySelector(
        `input[id=testAllyID][type=text]${expectedAria}`,
      );

      expect(text).toBeInTheDocument();
    },
  );
});