reactjs - storybook + jest שיטת play נכשלת כאשר קוראים ל-shouldHaveBeenCalled על פונקציה


יש לי את הרכיב הזה:

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>
  );
};

כתבתי סיפור לזה:

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();
};

אני יכול לראות שהלחצן נלחץ, ו-onPageChange נקרא, מכיוון שהדפדוף משתנה בהתאם, אבל הבדיקה נכשלת:

מספר שיחות צפוי: >= 1

מספר שיחות שהתקבלו: 0

והדבר הכי מוזר בזה הוא שאם אני לוחץ על 'הרץ שוב' בלשונית האינטראקציות של Storybook, הכל עובד.

תשובה #1

במקום להשתמש ב-jest.spyOn האם אפשר לא להשתמש בזה ולהשתמש בארגומנטים הסטנדרטיים של סטוריבוק? משהו כזה:

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

כדי שהדבר שלמעלה יעבוד, עליך לוודא שהפעולות שלך נרשמות כראוי. בצילום המסך שצירפת, אני לא רואה מספר ליד כותרת הלשונית Actions (זה צריך להיראות כמו Actions (1)), מה שמוביל אותי להאמין שהפעולות שלך לא נרשמו כראוי ולכן לא תראה אותן בלשונית Actions.

לגבי איך לרשום פעולה, אתה יכול לראות את התיאור שלי בפוסט אחר כאן.

בעצם משהו כמו:

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

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

תשובה #2

הבעיה היא ש-jest.spyOn נקרא מאוחר מדי, אחרי שהצעדים שלך רוצו. לכן בריצת חזרה זה יעבור - כי הפעולה הקודמת קיימת וניתן לגשת אליה.
עזר לי להזיז את jest.spyOn למעלה מ-Default.play:

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

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