Búsqueda de sitios web

reactjs - el método play de storybook + jest falla al llamar a shouldHaveBeenCalled en una función


Tengo este componente:

import {
  FC,
  ReactElement,
  useState,
  MouseEvent as ReactMouseEvent,
  ChangeEvent as ReactChangeEvent,
} from 'react';

import { Stack, TablePagination } from '@mui/material';

export const Pagination: FC<IPaginationProps> = ({
  total,
  page = 1,
  rowsPerPage = 25,
  position = 'start',
  rowsPerPageOptions = [10, 25, 50, 100],
  onPageChange,
  onRowsPerPageChange,
}): ReactElement => {
  const [localPage, setPage] = useState(page);
  const [localRowsPerPage, setRowsPerPage] = useState(rowsPerPage);

  const handleChangePage = (event: ReactMouseEvent<HTMLButtonElement> | null, newPage: number) => {
    setPage(newPage);
    onPageChange({ page: newPage, total, rowsPerPage: localRowsPerPage });
  };

  const handleChangeRowsPerPage = (
    event: ReactChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
  ) => {
    const perPage = parseInt(event.target.value, 10);
    setRowsPerPage(perPage);
    setPage(0);
    onRowsPerPageChange({ page: 0, total, rowsPerPage: perPage });
  };

  return (
    <Stack direction="row" justifyContent={`flex-${position}`}>
      <TablePagination
        component="div"
        showFirstButton
        showLastButton
        count={total}
        page={localPage}
        onPageChange={handleChangePage}
        rowsPerPage={localRowsPerPage}
        onRowsPerPageChange={handleChangeRowsPerPage}
        rowsPerPageOptions={rowsPerPageOptions}
      />
    </Stack>
  );
};

Escribí una historia para ello:

export default {
  title: 'Components/Pagination',
  component: Pagination,
  parameters: {
    actions: {
      handles: ['click'],
    },
  },
  args: {
    total: 100,
    page: 1,
    onPageChange: ({ page }) => {
      // eslint-disable-next-line no-console
      console.log({ page });
    },
    onRowsPerPageChange: ({ rowsPerPage }) => {
      // eslint-disable-next-line no-console
      console.log({ rowsPerPage });
    },
  },
  decorators: [
    (Story) => (
      <div style={{ margin: '3em', boxShadow: '7px 5px 14px -8px rgba(0,0,0,0.55)' }}>
        {Story()}
      </div>
    ),
  ],
} as ComponentMeta<typeof Pagination>;


const Template: ComponentStory<typeof Pagination> = ({ ...args }) => <Pagination {...args} />;

export const Default = Template.bind({});
Default.play = async ({ canvasElement, args }) => {
  const onPageChangeSpy = jest.spyOn(args, 'onPageChange');
  const canvas = await within(canvasElement);

  const prevBtn = canvas.getByLabelText('Go to previous page');
  await userEvent.click(prevBtn);
  await expect(onPageChangeSpy).toHaveBeenCalled();
};

Puedo ver que se hizo clic en el botón y se llamó a onPageChange, ya que la paginación cambia en consecuencia, pero la prueba falla:

Número esperado de llamadas: >= 1

Número de llamadas recibidas: 0

Y lo más extraño de todo es que si hago clic en volver a ejecutar en la pestaña de interacciones de Storybook, todo funciona.

Respuesta #1

En lugar de usar jest.spyOn, ¿no puedes usar los args estándar de Storybook? Algo así:

await waitFor(() => expect(args.onPageChange).toHaveBeenCalledTimes(1));

Para que lo anterior funcione, necesitas asegurarte de que tus acciones se estén registrando correctamente. En la captura de pantalla que adjuntaste, no veo ningún número junto al título de la pestaña Actions (debería verse como Actions (1)), lo cual me lleva a creer que tus acciones no se han registrado correctamente y, por lo tanto, no las verás en la pestaña Actions.

Para saber cómo registrar una acción, puedes ver mi descripción en otra publicación aquí.

Esencialmente algo como:

import { action } from '@storybook/addon-actions';

// ...
args: {
  onPageChange: action('onPageChange'),
  //...
},
argTypes: {
  onPageChange: { action: 'onPageChange' },
  //...
},

Respuesta #2

El problema es que jest.spyOn se llamó demasiado tarde, después de que se ejecutaron tus pasos. Por eso, al ejecutarlo nuevamente pasará, porque la acción anterior existe y se puede acceder a ella.
Me ayudó mover jest.spyOn arriba de Default.play:

const onPageChangeSpy = jest.spyOn(args, 'onPageChange'); // replase 'args' with your arguments

Default.play = async ({ canvasElement }) => {
  
  ...
  onPageChangeSpy.mockClear(); //remember to stop spying
};