npm package: @secondcloset/frontend-test-utils
Description: Utilities for testing front-end applications
Latest version: 1.0.2

# @secondcloset/frontend-test-utils

Utilities for testing front-end applications

# Installation

```shell
npm install @secondcloset/frontend-test-utils --save-dev
```

In your test suite:

```ts
import { lens, componentLens } from "@secondcloset/frontend-test-utils";
```

# Utilities

- [lens](#lens)
- [componentLens](#componentlens)

## lens

Allows to set and read value of Recoil state

```ts
const { render, result } = lens(node);
```

### Options

- `node: RecoilState<T>` - Recoil state

### Returns

- `render: () => JSX.Element` - render this function inside of `RecoilRoot`
- `result`
  - `set: SetterOrUpdater<T>` - this functions is used to set Recoil state value
  - `reset: Resetter` - this function is used to reset Recoil state value
  - `value: T | undefined` - current value of Recoil state

### Example

```tsx
// usernameState.ts
export const usernameState = atom({
  key: "usernameState",
  default: "Anonymous",
});

// User.tsx
export const User = () => {
  const username = useRecoilValue(usernameState);

  return <div>{username}</div>;
};

// User.test.tsx
test("renders username", () => {
  const { render: renderUsernameState, result: usernameStateLens } = lens(usernameState);

  render(
    <RecoilRoot>
      {renderUsernameState()}
      <User />
    </RecoilRoot>
  );

  let username = screen.queryByText(/anonymous/i);
  expect(username).toBeInTheDocument();

  act(() => {
    usernameStateLens.set("John Doe");
  });

  username = screen.queryByText(/john doe/i);
  expect(username).toBeInTheDocument();
});

// UsernameInput.tsx
export const UsernameInput = () => {
  const setUsername = useSetRecoilState(usernameState);

  return (
    <div>
      <input
        onChange={(e) => {
          setUsername(e.target.value);
        }}
      />
    </div>
  );
};

// UsernameInput.test.tsx
test("updates username", () => {
  const { render: renderUsernameState, result: usernameStateLens } = lens(usernameState);

  render(
    <RecoilRoot>
      {renderUsernameState()}
      <UsernameInput />
    </RecoilRoot>
  );

  const usernameInput = screen.getByRole("textbox");
  userEvent.type(usernameInput, "Michael Scott");

  expect(usernameStateLens.value).toBe("Michael Scott");
});
```

## componentLens

Allows to read the prop values of a mocked component that have passed to it during rendering. It is also possible to call functions (usually event handlers) that have been passed as props to that component

```ts
const { props } = componentLens(ComponentMock, render?);
```

### Options

- `componentMock: jest.MockedFunction<React.FC<T>>` - mocked component
- `render: (props: PropsWithChildren<T>) => ReactNode` - custom render function

### Returns

- `props: { current: PropsWithChildren<T> }` - component props (including `children`)

### Notes

- It is possible to use `jest.Mock` instead of `jest.MockedFunction` when mocking component, but you will lose type support for component props

### Example

```tsx
// User.tsx
interface UserProps {
  initialUsername: string;
}
export const User: React.FC<UserProps> = ({ initialUsername }) => {
  const [username, setUsername] = useState(initialUsername);

  return (
    <div>
      {username}

      <UsernameInput
        value={username}
        onChange={(value) => {
          setUsername(value);
        }}
      />
    </div>
  );
};

jest.mock("./UsernameInput");
const UsernameInputMock = UsernameInput as jest.MockedFunction<typeof UsernameInput>;

test("updates username", () => {
  const { props: UsernameInputProps } = componentLens(
    UsernameInputMock,
    ({ children }) => <div>{children}</div>
  ));

  render(<User initialUsername="Anonymous" />);

  expect(UsernameInputProps.current).toMatchObject({ value: "Anonymous" });

  act(() => {
    UsernameInputProps.current.onChange("John Doe");
  });

  expect(UsernameInputProps.current).toMatchObject({ value: "John Doe" });
});
```