strictly-typed icon indicating copy to clipboard operation
strictly-typed copied to clipboard

Unable to get input value of wrapped component in <TypedController /> on Jest

Open redshoga opened this issue 3 years ago • 1 comments

Unable to get input value of wrapped component in <TypedController /> on Jest.

Both codes work correctly in the browser. (Chrome 85.0.4183.102)

use <TypedController /> sample test code

Only the last test will not pass.

import React from 'react';
import { render, fireEvent, act } from '@testing-library/react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { useTypedController } from '@hookform/strictly-typed';

const FORM_ID = 'test-textarea-id';
const SUBMIT_ID = 'test-submit-id';

type CustomeTextareaProps = {
  testId: string;
  name: string;
} & React.TextareaHTMLAttributes<HTMLTextAreaElement>;
const CustomeTextarea: React.FC<CustomeTextareaProps> = (props: CustomeTextareaProps) => {
  const { testId, ...textAreaProps } = props;

  return (
    <div>
      <textarea data-testid={testId} {...textAreaProps} />
    </div>
  );
};

type SampleFormValue = {
  sample: string;
};
type SampleFormProps = {
  onSubmit: (value: SampleFormValue) => void;
};
const SampleForm: React.FC<SampleFormProps> = (props: SampleFormProps) => {
  const { handleSubmit, control } = useForm<SampleFormValue>({
    criteriaMode: 'all',
  });
  const TypedController = useTypedController<SampleFormValue>({ control });
  const onSubmit: SubmitHandler<SampleFormValue> = (data) => {
    props.onSubmit(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <TypedController
        name={'sample'}
        render={(formProps) => <CustomeTextarea testId={FORM_ID} name={'sample'} {...formProps} />}
      />
      <input type="submit" data-testid={SUBMIT_ID} />
    </form>
  );
};

describe('SampleForm', () => {
  test('Input value should be equal to the form value', async () => {
    const mockOnSubmit = jest.fn();
    await act(async () => {
      const renderResult = render(<SampleForm onSubmit={mockOnSubmit} />);
      const input = renderResult.getByTestId(FORM_ID) as HTMLTextAreaElement;
      fireEvent.change(input, { target: { value: 'test-value' } });
      expect(input.value).toBe('test-value'); // No Error
    });
  });

  test('Submitted data should be equal to the input data', async () => {
    const mockOnSubmit = jest.fn();
    await act(async () => {
      const renderResult = render(<SampleForm onSubmit={mockOnSubmit} />);
      const input = renderResult.getByTestId(FORM_ID) as HTMLTextAreaElement;
      fireEvent.change(input, { target: { value: 'test-value' } });
      expect(input.value).toBe('test-value');
      fireEvent.submit(renderResult.getByTestId(SUBMIT_ID));
    });
    expect(mockOnSubmit).toBeCalled(); // No Error
    expect(mockOnSubmit).toBeCalledWith({}); // No Error
    expect(mockOnSubmit).toBeCalledWith({ sample: 'test-value' }); // -> Error
  });
});

Error log

SampleForm
    √ Input value should be equal to the form value (122 ms)
    × Submitted data should be equal to the input data (89 ms)

  ● SampleForm › Submitted data should be equal to the input data

    expect(jest.fn()).toBeCalledWith(...expected)

    - Expected
    + Received

    - Object {
    -   "sample": "test-value",
    - }
    + Object {},

    Number of calls: 1

      71 |     expect(mockOnSubmit).toBeCalled(); // No Error
      72 |     expect(mockOnSubmit).toBeCalledWith({}); // No Error
    > 73 |     expect(mockOnSubmit).toBeCalledWith({ sample: 'test-value' }); // -> Error
         |                          ^
      74 |   });
      75 | });
      76 | 

without <TypedController /> sample test code

All tests pass.

import React from 'react';
import { render, fireEvent, act } from '@testing-library/react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { useTypedController } from '@hookform/strictly-typed';

const FORM_ID = 'test-textarea-id';
const SUBMIT_ID = 'test-submit-id';

type CustomeTextareaProps = {
  testId: string;
  name: string;
} & React.TextareaHTMLAttributes<HTMLTextAreaElement>;

const CustomeTextarea = React.forwardRef<HTMLTextAreaElement, CustomeTextareaProps>(
  (props: CustomeTextareaProps, ref) => {
    const { testId, ...textAreaProps } = props;
    return (
      <div>
        <textarea data-testid={testId} ref={ref} {...textAreaProps} />
      </div>
    );
  }
);

type SampleFormValue = {
  sample: string;
};
type SampleFormProps = {
  onSubmit: (value: SampleFormValue) => void;
};
const SampleForm: React.FC<SampleFormProps> = (props: SampleFormProps) => {
  const { handleSubmit, register } = useForm<SampleFormValue>({
    criteriaMode: 'all',
  });
  const onSubmit: SubmitHandler<SampleFormValue> = (data) => {
    props.onSubmit(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <CustomeTextarea testId={FORM_ID} name={'sample'} ref={register()} />
      <input type="submit" data-testid={SUBMIT_ID} />
    </form>
  );
};

describe('SampleForm', () => {
  test('Input value should be equal to the form value', async () => {
    const mockOnSubmit = jest.fn();
    await act(async () => {
      const renderResult = render(<SampleForm onSubmit={mockOnSubmit} />);
      const input = renderResult.getByTestId(FORM_ID) as HTMLTextAreaElement;
      fireEvent.change(input, { target: { value: 'test-value' } });
      expect(input.value).toBe('test-value'); // -> No Error
    });
  });

  test('Submitted data should be equal to the input data', async () => {
    const mockOnSubmit = jest.fn();
    await act(async () => {
      const renderResult = render(<SampleForm onSubmit={mockOnSubmit} />);
      const input = renderResult.getByTestId(FORM_ID) as HTMLTextAreaElement;
      fireEvent.change(input, { target: { value: 'test-value' } });
      expect(input.value).toBe('test-value'); // -> No Error
      fireEvent.submit(renderResult.getByTestId(SUBMIT_ID));
    });
    expect(mockOnSubmit).toBeCalled(); // -> No Error
    expect(mockOnSubmit).toBeCalledWith({ sample: 'test-value' }); // -> No Error
  });
});

dependencies (package.json)

{
  ...
  "dependencies": {
    "@aspida/axios": "^0.9.4",
    "@emotion/core": "^10.0.28",
    "@hookform/resolvers": "^0.1.0",
    "@hookform/strictly-typed": "^0.0.4",
    "axios": "^0.19.2",
    "css-element-queries": "^1.2.3",
    "dayjs": "^1.8.36",
    "next": "^9.4.4",
    "next-images": "^1.4.0",
    "nprogress": "^0.2.0",
    "qrcode.react": "^1.0.0",
    "query-string": "^6.13.1",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-hook-form": "^6.0.2",
    "react-loading-skeleton": "^2.1.1",
    "react-modal": "^3.11.2",
    "react-toastify": "^6.0.8",
    "react-tooltip": "^4.2.8",
    "swr": "^0.3.0",
    "ts-node": "^8.10.2",
    "vanilla-lazyload": "^17.1.0"
  },
  "devDependencies": {
    "@babel/core": "^7.10.4",
    "@next/bundle-analyzer": "^9.4.4",
    "@stoplight/prism-cli": "^3.3.7",
    "@storybook/addon-a11y": "^5.3.19",
    "@storybook/addon-actions": "^5.3.19",
    "@storybook/addon-backgrounds": "^5.3.19",
    "@storybook/addon-docs": "^5.3.19",
    "@storybook/addon-knobs": "^5.3.19",
    "@storybook/react": "^5.3.19",
    "@testing-library/jest-dom": "^5.11.0",
    "@testing-library/react": "^10.4.4",
    "@testing-library/react-hooks": "^3.4.1",
    "@types/jest": "^26.0.4",
    "@types/node": "^14.0.14",
    "@types/nprogress": "^0.2.0",
    "@types/qrcode.react": "^1.0.1",
    "@types/react": "^16.9.41",
    "@types/react-modal": "^3.10.6",
    "@types/react-tooltip": "^4.2.4",
    "@types/testing-library__jest-dom": "^5.9.1",
    "@typescript-eslint/eslint-plugin": "^3.5.0",
    "@typescript-eslint/parser": "^3.5.0",
    "babel-loader": "^8.1.0",
    "babel-preset-react-app": "^9.1.2",
    "cross-env": "^7.0.2",
    "eslint": "^7.4.0",
    "eslint-config-prettier": "^6.11.0",
    "eslint-plugin-prettier": "^3.1.4",
    "eslint-plugin-react": "^7.20.3",
    "husky": "^4.2.5",
    "jest": "^26.1.0",
    "openapi2aspida": "^0.9.0",
    "prettier": "^2.0.5",
    "react-is": "^16.13.1",
    "react-test-renderer": "16.9.0",
    "stylelint": "^13.6.1",
    "stylelint-config-prettier": "^8.0.2",
    "stylelint-config-standard": "^20.0.0",
    "ts-jest": "^26.1.1",
    "typescript": "3.8.3"
  }
}

redshoga avatar Sep 24 '20 03:09 redshoga

@redshoga Thanks for your feedback! I haven't tried it, but it looks like fireEvent.change doesn't fire an onChange event and an onChange callback passed by TypedController is not being executed. Can you verify this? 🙏

You may also be able to resolve using userEvent.type instead of fireEvent.change.

kotarella1110 avatar Sep 28 '20 15:09 kotarella1110