Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/wojtekmaj/react-async-button. A button that handles Promises for your React app.
Something went wrong. Try again.
6.7 kB · 258 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259import React, { createRef } from 'react';import { act, render, screen } from '@testing-library/react';import userEvent from '@testing-library/user-event';
import AsyncButton from './index';
jest.useFakeTimers();
const pendingConfig = { children: 'Loading…',};
const successConfig = { children: 'Success!',};
const errorConfig = { children: 'Try again',};
describe('<AsyncButton /> component', () => { const defaultProps = { children: 'Click me', pendingConfig, successConfig, errorConfig, };
let user: ReturnType<typeof userEvent.setup>; beforeEach(() => { user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime, }); });
it('renders button properly', () => { render(<AsyncButton {...defaultProps} />);
expect(screen.queryByRole('button')).toBeInTheDocument(); });
it('passes ref correctly', () => { const ref = createRef<HTMLButtonElement>();
render(<AsyncButton {...defaultProps} ref={ref} />);
const button = screen.getByRole('button');
expect(ref.current).toBe(button); });
it('calls onClick properly', async () => { const onClick = jest.fn();
render(<AsyncButton {...defaultProps} onClick={onClick} />);
const button = screen.getByRole('button');
await user.click(button);
expect(onClick).toHaveBeenCalledTimes(1); expect(onClick).toHaveBeenCalledWith(expect.any(Object)); });
it('changes button state to success on click if onClick is synchronous', async () => { const onClick = () => { // Intentionally empty };
render(<AsyncButton {...defaultProps} onClick={onClick} />);
const button = screen.getByRole('button');
await user.click(button);
const button2 = screen.getByRole('button'); expect(button2).toHaveTextContent('Success!'); });
it('changes button state to default after refresh timeout has passed', async () => { const onClick = () => { // Intentionally empty };
render(<AsyncButton {...defaultProps} onClick={onClick} />);
const button = screen.getByRole('button');
await user.click(button);
const button2 = screen.getByRole('button'); expect(button2).toHaveTextContent('Success!');
act(() => { jest.advanceTimersByTime(1000); });
const button3 = screen.getByRole('button'); expect(button3).toHaveTextContent('Success!');
act(() => { jest.advanceTimersByTime(1000); });
const button4 = screen.getByRole('button'); expect(button4).toHaveTextContent('Click me'); });
it('changes button state to pending on click if onClick is asynchronous', async () => { let resolve: () => void; const onClick = () => new Promise<void>((res) => { resolve = res; });
render(<AsyncButton {...defaultProps} onClick={onClick} />);
const button = screen.getByRole('button');
await user.click(button);
const button2 = screen.getByRole('button'); expect(button2).toHaveTextContent('Loading…');
await act(async () => { resolve(); }); });
it('changes button state to success after asynchronous onClick is resolved', async () => { let resolve: () => void; const onClick = () => new Promise<void>((res) => { resolve = res; });
render(<AsyncButton {...defaultProps} onClick={onClick} />);
const button = screen.getByRole('button');
await user.click(button);
const button2 = screen.getByRole('button'); expect(button2).toHaveTextContent('Loading…');
await act(async () => { resolve(); });
const button3 = screen.getByRole('button'); expect(button3).toHaveTextContent('Success!'); });
it('changes button state to default after refresh timeout has passed', async () => { let resolve: () => void; const onClick = () => new Promise<void>((res) => { resolve = res; });
render(<AsyncButton {...defaultProps} onClick={onClick} />);
const button = screen.getByRole('button');
await user.click(button);
const button2 = screen.getByRole('button'); expect(button2).toHaveTextContent('Loading…');
await act(async () => { resolve(); });
const button3 = screen.getByRole('button'); expect(button3).toHaveTextContent('Success!');
act(() => { jest.advanceTimersByTime(1000); });
const button4 = screen.getByRole('button'); expect(button4).toHaveTextContent('Success!');
act(() => { jest.advanceTimersByTime(1000); });
const button5 = screen.getByRole('button'); expect(button5).toHaveTextContent('Click me'); });
it('should allow button props to be passed by default', () => { // @ts-expect-no-error <AsyncButton {...defaultProps} type="submit" />; });
it('should allow button props to be passed given as="button"', () => { // @ts-expect-no-error <AsyncButton {...defaultProps} as="button" disabled />; });
it('should not allow link props to be passed given as="button"', () => { // @ts-expect-error-next-line <AsyncButton {...defaultProps} as="button" href="https://example.com" />;
// Sanity check // @ts-expect-error-next-line <button href="https://example.com"></button>; });
it('should allow link props to be passed given as="a"', () => { // @ts-expect-no-error <AsyncButton {...defaultProps} as="a" href="https://example.com" />; });
it('should not allow button props to be passed given as="a"', () => { // @ts-expect-error-next-line <AsyncButton {...defaultProps} as="a" disabled href="https://example.com" />;
// Sanity check // @ts-expect-error-next-line <a disabled href="https://example.com"> Click me </a>; });
it('should not allow button props to be passed given as={MyButton}', () => { function MyButton() { return <button type="submit"></button>; }
// @ts-expect-error-next-line <AsyncButton {...defaultProps} as={MyButton} type="submit" />;
// Sanity check function MyCustomComponent({ as, ...otherProps }: { as: React.ElementType }) { const Component = as || 'div'; return <Component {...otherProps} />; }
// @ts-expect-error-next-line <MyCustomComponent as={MyButton} type="submit" />; });
it('should not allow invalid values for as', () => { // @ts-expect-error-next-line <AsyncButton {...defaultProps} as={5} type="submit" />;
// Sanity check function MyCustomComponent({ as }: { as: React.ElementType }) { const Component = as || 'div'; return <Component />; }
// @ts-expect-error-next-line <MyCustomComponent as={5} />; });});