Remove user action from waitFor in stories (#11588)

As per title:
- waitFor is a loop that waits for a condition to be filled, it should
be use to expect or in rare case to wait for element to be present in
the page (in most cases, you can use findByXXX)
- user actions should not be in this loop, otherwise they will be
triggered multiple times
This commit is contained in:
Charles Bochet
2025-04-15 17:34:28 +02:00
committed by GitHub
parent c40baf036c
commit dee779179b
10 changed files with 77 additions and 56 deletions

View File

@ -146,8 +146,9 @@ export const Enter: Story = {
play: async () => { play: async () => {
expect(enterJestFn).toHaveBeenCalledTimes(0); expect(enterJestFn).toHaveBeenCalledTimes(0);
await userEvent.keyboard('{enter}');
await waitFor(() => { await waitFor(() => {
userEvent.keyboard('{enter}');
expect(enterJestFn).toHaveBeenCalledTimes(1); expect(enterJestFn).toHaveBeenCalledTimes(1);
}); });
}, },

View File

@ -139,9 +139,9 @@ export const Default: Story = {};
export const Enter: Story = { export const Enter: Story = {
play: async () => { play: async () => {
expect(enterJestFn).toHaveBeenCalledTimes(0); expect(enterJestFn).toHaveBeenCalledTimes(0);
await userEvent.keyboard('{enter}');
await waitFor(() => { await waitFor(() => {
userEvent.keyboard('{enter}');
expect(enterJestFn).toHaveBeenCalledTimes(1); expect(enterJestFn).toHaveBeenCalledTimes(1);
}); });
}, },
@ -151,8 +151,9 @@ export const Escape: Story = {
play: async () => { play: async () => {
expect(escapeJestfn).toHaveBeenCalledTimes(0); expect(escapeJestfn).toHaveBeenCalledTimes(0);
await userEvent.keyboard('{esc}');
await waitFor(() => { await waitFor(() => {
userEvent.keyboard('{esc}');
expect(escapeJestfn).toHaveBeenCalledTimes(1); expect(escapeJestfn).toHaveBeenCalledTimes(1);
}); });
}, },
@ -166,8 +167,9 @@ export const ClickOutside: Story = {
const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div'); const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
await userEvent.click(emptyDiv);
await waitFor(() => { await waitFor(() => {
userEvent.click(emptyDiv);
expect(clickOutsideJestFn).toHaveBeenCalledTimes(1); expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
}); });
}, },
@ -177,8 +179,9 @@ export const Tab: Story = {
play: async () => { play: async () => {
expect(tabJestFn).toHaveBeenCalledTimes(0); expect(tabJestFn).toHaveBeenCalledTimes(0);
await userEvent.keyboard('{tab}');
await waitFor(() => { await waitFor(() => {
userEvent.keyboard('{tab}');
expect(tabJestFn).toHaveBeenCalledTimes(1); expect(tabJestFn).toHaveBeenCalledTimes(1);
}); });
}, },
@ -188,8 +191,9 @@ export const ShiftTab: Story = {
play: async () => { play: async () => {
expect(shiftTabJestFn).toHaveBeenCalledTimes(0); expect(shiftTabJestFn).toHaveBeenCalledTimes(0);
await userEvent.keyboard('{shift>}{tab}');
await waitFor(() => { await waitFor(() => {
userEvent.keyboard('{shift>}{tab}');
expect(shiftTabJestFn).toHaveBeenCalledTimes(1); expect(shiftTabJestFn).toHaveBeenCalledTimes(1);
}); });
}, },

View File

@ -9,7 +9,6 @@ import { RecordFieldComponentInstanceContext } from '@/object-record/record-fiel
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2'; import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId'; import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { FieldRatingValue } from '../../../../types/FieldMetadata'; import { FieldRatingValue } from '../../../../types/FieldMetadata';
import { useRatingField } from '../../../hooks/useRatingField'; import { useRatingField } from '../../../hooks/useRatingField';
import { RatingFieldInput, RatingFieldInputProps } from '../RatingFieldInput'; import { RatingFieldInput, RatingFieldInputProps } from '../RatingFieldInput';
@ -118,11 +117,10 @@ export const Submit: Story = {
const input = canvas.getByRole('slider', { name: 'Rating' }); const input = canvas.getByRole('slider', { name: 'Rating' });
const firstStar = input.firstElementChild; const firstStar = input.firstElementChild;
await userEvent.click(firstStar);
await waitFor(() => { await waitFor(() => {
if (isDefined(firstStar)) { expect(submitJestFn).toHaveBeenCalledTimes(1);
userEvent.click(firstStar);
expect(submitJestFn).toHaveBeenCalledTimes(1);
}
}); });
}, },
}; };

View File

@ -111,8 +111,9 @@ export const Escape: Story = {
play: async () => { play: async () => {
expect(escapeJestFn).toHaveBeenCalledTimes(0); expect(escapeJestFn).toHaveBeenCalledTimes(0);
await userEvent.keyboard('{esc}');
await waitFor(() => { await waitFor(() => {
userEvent.keyboard('{esc}');
expect(escapeJestFn).toHaveBeenCalledTimes(1); expect(escapeJestFn).toHaveBeenCalledTimes(1);
}); });
}, },
@ -123,9 +124,11 @@ export const ClickOutside: Story = {
const canvas = within(canvasElement); const canvas = within(canvasElement);
expect(clickOutsideJestFn).toHaveBeenCalledTimes(0); expect(clickOutsideJestFn).toHaveBeenCalledTimes(0);
await waitFor(async () => { const outsideElement = await canvas.findByTestId('click-outside-element');
const outsideElement = await canvas.findByTestId('click-outside-element');
userEvent.click(outsideElement); await userEvent.click(outsideElement);
await waitFor(() => {
expect(clickOutsideJestFn).toHaveBeenCalledTimes(1); expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
}); });
}, },

View File

@ -11,6 +11,7 @@ import { FieldMetadataType } from '~/generated/graphql';
import { StorybookFieldInputDropdownFocusIdSetterEffect } from '~/testing/components/StorybookFieldInputDropdownFocusIdSetterEffect'; import { StorybookFieldInputDropdownFocusIdSetterEffect } from '~/testing/components/StorybookFieldInputDropdownFocusIdSetterEffect';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { sleep } from '~/utils/sleep';
import { useTextField } from '../../../hooks/useTextField'; import { useTextField } from '../../../hooks/useTextField';
import { TextFieldInput, TextFieldInputProps } from '../TextFieldInput'; import { TextFieldInput, TextFieldInputProps } from '../TextFieldInput';
const TextFieldValueSetterEffect = ({ value }: { value: string }) => { const TextFieldValueSetterEffect = ({ value }: { value: string }) => {
@ -131,9 +132,11 @@ export const Default: Story = {};
export const Enter: Story = { export const Enter: Story = {
play: async () => { play: async () => {
expect(enterJestFn).toHaveBeenCalledTimes(0); expect(enterJestFn).toHaveBeenCalledTimes(0);
await sleep(100);
await userEvent.keyboard('{enter}');
await waitFor(() => { await waitFor(() => {
userEvent.keyboard('{enter}');
expect(enterJestFn).toHaveBeenCalledTimes(1); expect(enterJestFn).toHaveBeenCalledTimes(1);
}); });
}, },
@ -142,9 +145,11 @@ export const Enter: Story = {
export const Escape: Story = { export const Escape: Story = {
play: async () => { play: async () => {
expect(escapeJestfn).toHaveBeenCalledTimes(0); expect(escapeJestfn).toHaveBeenCalledTimes(0);
await sleep(100);
await userEvent.keyboard('{esc}');
await waitFor(() => { await waitFor(() => {
userEvent.keyboard('{esc}');
expect(escapeJestfn).toHaveBeenCalledTimes(1); expect(escapeJestfn).toHaveBeenCalledTimes(1);
}); });
}, },
@ -153,13 +158,15 @@ export const Escape: Story = {
export const ClickOutside: Story = { export const ClickOutside: Story = {
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
await sleep(100);
expect(clickOutsideJestFn).toHaveBeenCalledTimes(0); expect(clickOutsideJestFn).toHaveBeenCalledTimes(0);
const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div'); const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
await userEvent.click(emptyDiv);
await waitFor(() => { await waitFor(() => {
userEvent.click(emptyDiv);
expect(clickOutsideJestFn).toHaveBeenCalled(); expect(clickOutsideJestFn).toHaveBeenCalled();
}); });
}, },
@ -168,9 +175,11 @@ export const ClickOutside: Story = {
export const Tab: Story = { export const Tab: Story = {
play: async () => { play: async () => {
expect(tabJestFn).toHaveBeenCalledTimes(0); expect(tabJestFn).toHaveBeenCalledTimes(0);
await sleep(100);
await userEvent.keyboard('{tab}');
await waitFor(() => { await waitFor(() => {
userEvent.keyboard('{tab}');
expect(tabJestFn).toHaveBeenCalledTimes(1); expect(tabJestFn).toHaveBeenCalledTimes(1);
}); });
}, },
@ -179,9 +188,11 @@ export const Tab: Story = {
export const ShiftTab: Story = { export const ShiftTab: Story = {
play: async () => { play: async () => {
expect(shiftTabJestFn).toHaveBeenCalledTimes(0); expect(shiftTabJestFn).toHaveBeenCalledTimes(0);
await sleep(100);
await userEvent.keyboard('{shift>}{tab}');
await waitFor(() => { await waitFor(() => {
userEvent.keyboard('{shift>}{tab}');
expect(shiftTabJestFn).toHaveBeenCalledTimes(1); expect(shiftTabJestFn).toHaveBeenCalledTimes(1);
}); });
}, },

View File

@ -206,7 +206,7 @@ describe('useRecordData', () => {
result.current.tableData.getTableData(); result.current.tableData.getTableData();
}); });
await waitFor(async () => { await waitFor(() => {
expect(callback).toHaveBeenCalledWith( expect(callback).toHaveBeenCalledWith(
[mockPerson], [mockPerson],
[ [
@ -297,7 +297,7 @@ describe('useRecordData', () => {
result.current.tableData.getTableData(); result.current.tableData.getTableData();
}); });
await waitFor(async () => { await waitFor(() => {
expect(callback).toHaveBeenCalledWith([mockPerson], []); expect(callback).toHaveBeenCalledWith([mockPerson], []);
}); });
}); });

View File

@ -5,8 +5,8 @@ import { expect, userEvent, within } from '@storybook/test';
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
import { sleep } from '~/utils/sleep'; import { sleep } from '~/utils/sleep';
import { IconPicker, IconPickerProps } from '../IconPicker';
import { ComponentDecorator } from 'twenty-ui/testing'; import { ComponentDecorator } from 'twenty-ui/testing';
import { IconPicker, IconPickerProps } from '../IconPicker';
type RenderProps = IconPickerProps; type RenderProps = IconPickerProps;
const Render = (args: RenderProps) => { const Render = (args: RenderProps) => {
@ -123,7 +123,7 @@ export const WithSearchAndClose: Story = {
name: 'Click to select icon (selected: IconBuildingSkyscraper)', name: 'Click to select icon (selected: IconBuildingSkyscraper)',
}); });
userEvent.click(iconPickerButton); await userEvent.click(iconPickerButton);
await sleep(500); await sleep(500);

View File

@ -6,6 +6,15 @@ import { useState } from 'react';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { Avatar, IconChevronLeft } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import {
MenuItem,
MenuItemMultiSelectAvatar,
MenuItemSelectAvatar,
} from 'twenty-ui/navigation';
import { ComponentDecorator } from 'twenty-ui/testing';
import { Dropdown } from '../Dropdown'; import { Dropdown } from '../Dropdown';
import { DropdownMenuHeader } from '../DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeader } from '../DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuInput } from '../DropdownMenuInput'; import { DropdownMenuInput } from '../DropdownMenuInput';
@ -13,26 +22,17 @@ import { DropdownMenuItemsContainer } from '../DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '../DropdownMenuSearchInput'; import { DropdownMenuSearchInput } from '../DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '../DropdownMenuSeparator'; import { DropdownMenuSeparator } from '../DropdownMenuSeparator';
import { StyledDropdownMenuSubheader } from '../StyledDropdownMenuSubheader'; import { StyledDropdownMenuSubheader } from '../StyledDropdownMenuSubheader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { Avatar, IconChevronLeft } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { ComponentDecorator } from 'twenty-ui/testing';
import {
MenuItem,
MenuItemMultiSelectAvatar,
MenuItemSelectAvatar,
} from 'twenty-ui/navigation';
const meta: Meta<typeof Dropdown> = { const meta: Meta<typeof Dropdown> = {
title: 'UI/Layout/Dropdown/Dropdown', title: 'UI/Layout/Dropdown/Dropdown',
component: Dropdown, component: Dropdown,
decorators: [ComponentDecorator, (Story) => <Story />], decorators: [ComponentDecorator, (Story) => <Story />],
args: { args: {
clickableComponent: <Button title="Open Dropdown" />, clickableComponent: <Button title="Open Dropdown" />,
dropdownHotkeyScope: { scope: 'testDropdownMenu' }, dropdownHotkeyScope: { scope: 'testDropdownMenu' },
dropdownOffset: { x: 0, y: 8 }, dropdownOffset: { x: 0, y: 8 },
dropdownId: 'test-dropdown-id', dropdownId: 'test-dropdown-id',
dropdownWidth: '200px',
}, },
argTypes: { argTypes: {
clickableComponent: { control: false }, clickableComponent: { control: false },
@ -84,24 +84,26 @@ export const Empty: Story = {
const canvas = within(document.body); const canvas = within(document.body);
const buttons = await canvas.findAllByRole('button'); const buttons = await canvas.findAllByRole('button');
userEvent.click(buttons[0]); await userEvent.click(buttons[0]);
await waitFor(async () => { const fakeMenu = await canvas.findByTestId('dropdown-content');
const fakeMenu = await canvas.findByTestId('dropdown-content');
await waitFor(() => {
expect(fakeMenu).toBeInTheDocument(); expect(fakeMenu).toBeInTheDocument();
}); });
userEvent.click(buttons[0]); await userEvent.click(buttons[0]);
await waitFor(async () => { await waitFor(() => {
const fakeMenu = canvas.queryByTestId('dropdown-content'); const fakeMenuBis = canvas.queryByTestId('dropdown-content');
expect(fakeMenu).not.toBeInTheDocument(); expect(fakeMenuBis).not.toBeInTheDocument();
}); });
userEvent.click(buttons[0]); await userEvent.click(buttons[0]);
await waitFor(async () => { const fakeMenuTer = await canvas.findByTestId('dropdown-content');
const fakeMenu = await canvas.findByTestId('dropdown-content');
expect(fakeMenu).toBeInTheDocument(); await waitFor(() => {
expect(fakeMenuTer).toBeInTheDocument();
}); });
}, },
}; };
@ -207,9 +209,9 @@ const playInteraction: PlayFunction<any, any> = async () => {
const canvas = within(document.body); const canvas = within(document.body);
const buttons = await canvas.findAllByRole('button'); const buttons = await canvas.findAllByRole('button');
userEvent.click(buttons[0]); await userEvent.click(buttons[0]);
await waitFor(async () => { await waitFor(() => {
expect(canvas.getByText('Company A')).toBeInTheDocument(); expect(canvas.getByText('Company A')).toBeInTheDocument();
}); });
}; };
@ -265,13 +267,15 @@ export const SearchWithLoadingMenu: Story = {
const buttons = await canvas.findAllByRole('button'); const buttons = await canvas.findAllByRole('button');
await userEvent.click(buttons[0]);
await waitFor(() => { await waitFor(() => {
userEvent.click(buttons[0]);
expect(canvas.getByDisplayValue('query')).toBeInTheDocument(); expect(canvas.getByDisplayValue('query')).toBeInTheDocument();
}); });
await userEvent.click(buttons[0]);
await waitFor(() => { await waitFor(() => {
userEvent.click(buttons[0]);
expect(canvas.queryByDisplayValue('query')).not.toBeInTheDocument(); expect(canvas.queryByDisplayValue('query')).not.toBeInTheDocument();
}); });
}, },

View File

@ -46,11 +46,11 @@ export const DateTimeSettingsTimeFormat: Story = {
const timeFormatSelect = await canvas.findByText('24h (08:33)'); const timeFormatSelect = await canvas.findByText('24h (08:33)');
userEvent.click(timeFormatSelect); await userEvent.click(timeFormatSelect);
const timeFormatOptions = await canvas.findByText('12h (8:33 AM)'); const timeFormatOptions = await canvas.findByText('12h (8:33 AM)');
userEvent.click(timeFormatOptions); await userEvent.click(timeFormatOptions);
await canvas.findByText('12h (8:33 AM)'); await canvas.findByText('12h (8:33 AM)');
}, },
@ -66,13 +66,13 @@ export const DateTimeSettingsTimezone: Story = {
'(GMT-04:00) Eastern Daylight Time - New York', '(GMT-04:00) Eastern Daylight Time - New York',
); );
userEvent.click(timezoneSelect); await userEvent.click(timezoneSelect);
const systemSettingsOptions = await canvas.findByText( const systemSettingsOptions = await canvas.findByText(
'(GMT-11:00) Niue Time', '(GMT-11:00) Niue Time',
); );
userEvent.click(systemSettingsOptions); await userEvent.click(systemSettingsOptions);
await canvas.findByText('(GMT-11:00) Niue Time'); await canvas.findByText('(GMT-11:00) Niue Time');
}, },
@ -86,11 +86,11 @@ export const DateTimeSettingsDateFormat: Story = {
const timeFormatSelect = await canvas.findByText('13 Jun, 2022'); const timeFormatSelect = await canvas.findByText('13 Jun, 2022');
userEvent.click(timeFormatSelect); await userEvent.click(timeFormatSelect);
const timeFormatOptions = await canvas.findByText('Jun 13, 2022'); const timeFormatOptions = await canvas.findByText('Jun 13, 2022');
userEvent.click(timeFormatOptions); await userEvent.click(timeFormatOptions);
await canvas.findByText('Jun 13, 2022'); await canvas.findByText('Jun 13, 2022');
}, },

View File

@ -24,6 +24,6 @@ export const Default: Story = {
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
const tooltip = await canvas.findByTestId('tooltip'); const tooltip = await canvas.findByTestId('tooltip');
userEvent.hover(tooltip); await userEvent.hover(tooltip);
}, },
}; };