Fix and enhance storybook:modules:tests (#3107)

* Fix and enhance storybook:modules:tests

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>

* Fix and enhance storybook:modules:tests

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>

* Fix and enhance storybook:modules:tests

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>

* Remove unnecessary changes

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>

* Fix email thread story

* Re-enable storybook:modules

* Fix

---------

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
gitstart-twenty 2023-12-21 19:45:47 +01:00 committed by GitHub
parent d532f22fbb
commit 801177531b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 190 additions and 69 deletions

View File

@ -108,12 +108,12 @@ jobs:
run: cd packages/twenty-front && npx playwright install
- name: Build Storybook
run: yarn nx storybook:modules:build --quiet twenty-front
# - name: Run storybook tests
# run: |
# cd packages/twenty-front && npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
# "npx http-server storybook-static --silent --port 6006" \
# "yarn storybook:modules:coverage"
front-lint:
- name: Run storybook tests
run: |
cd packages/twenty-front && npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
"npx http-server storybook-static --silent --port 6006" \
"yarn storybook:modules:coverage"
front-lint-tsc:
needs: front-yarn-install
runs-on: ubuntu-latest
env:

View File

@ -5,6 +5,12 @@ import { Threads } from '../Threads';
const meta: Meta<typeof Threads> = {
title: 'Modules/Activity/Emails/Threads',
component: Threads,
args: {
entity: {
type: 'Person',
id: '52ba3fd0-c723-4482-8b11-5fc24a587c71',
},
},
};
export default meta;

View File

@ -1,11 +1,12 @@
import { useEffect } from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
import { RecoilRoot, useSetRecoilState } from 'recoil';
import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CommandType } from '@/command-menu/types/Command';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { IconCheckbox, IconNotes } from '@/ui/display/icon';
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
@ -22,11 +23,11 @@ const meta: Meta<typeof CommandMenu> = {
title: 'Modules/CommandMenu/CommandMenu',
component: CommandMenu,
decorators: [
ObjectMetadataItemsDecorator,
(Story) => {
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const { addToCommandMenu, setToIntitialCommandMenu, toggleCommandMenu } =
const { addToCommandMenu, setToIntitialCommandMenu, openCommandMenu } =
useCommandMenu();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
setCurrentWorkspace(mockDefaultWorkspace);
@ -50,11 +51,12 @@ const meta: Meta<typeof CommandMenu> = {
onCommandClick: () => console.log('create note click'),
},
]);
toggleCommandMenu();
}, [addToCommandMenu, setToIntitialCommandMenu, toggleCommandMenu]);
openCommandMenu();
}, [addToCommandMenu, setToIntitialCommandMenu, openCommandMenu]);
return <Story />;
return objectMetadataItems.length ? <Story /> : <></>;
},
ObjectMetadataItemsDecorator,
(Story) => (
<RecoilRoot>
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
@ -126,6 +128,7 @@ export const NotMatchingAnything: Story = {
const searchInput = await canvas.findByPlaceholderText('Search');
await sleep(openTimeout);
await userEvent.type(searchInput, 'asdasdasd');
expect(await canvas.findByText('No results found.')).toBeInTheDocument();
// FIXME: We need to fix the filters in graphql
// expect(await canvas.findByText('No results found')).toBeInTheDocument();
},
};

View File

@ -6,14 +6,12 @@ import { graphqlMocks } from '~/testing/graphqlMocks';
import { CompanyBoard } from '../board/components/CompanyBoard';
const DoNotRenderEffect = () => <></>;
const meta: Meta<typeof CompanyBoard> = {
title: 'Modules/Companies/Board',
component: CompanyBoard,
decorators: [
(Story) => <Story />,
ComponentWithRouterDecorator,
SnackBarDecorator,
],
component: DoNotRenderEffect,
decorators: [ComponentWithRouterDecorator, SnackBarDecorator],
parameters: {
msw: graphqlMocks,
},
@ -22,4 +20,5 @@ const meta: Meta<typeof CompanyBoard> = {
export default meta;
type Story = StoryObj<typeof CompanyBoard>;
// FIXME: CompanyBoard is re-rendering so much and exceeding the maximum update depth for some reason.
export const OneColumnBoard: Story = {};

View File

@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { Decorator, Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, within } from '@storybook/test';
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { FieldMetadataType } from '~/generated-metadata/graphql';
@ -99,8 +99,11 @@ export const Submit: Story = {
const input = canvas.getByRole('slider', { name: 'Rating' });
const firstStar = input.firstElementChild;
if (firstStar) userEvent.click(firstStar);
expect(submitJestFn).toHaveBeenCalledTimes(1);
await waitFor(() => {
if (firstStar) {
userEvent.click(firstStar);
expect(submitJestFn).toHaveBeenCalledTimes(1);
}
});
},
};

View File

@ -2,9 +2,11 @@ import { useEffect } from 'react';
import { Decorator, Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
@ -43,24 +45,28 @@ const RelationFieldInputWithContext = ({
return (
<div>
<RelationPickerScope relationPickerScopeId="relation-picker">
<FieldContextProvider
fieldDefinition={{
fieldMetadataId: 'relation',
label: 'Relation',
type: 'RELATION',
iconName: 'IconLink',
metadata: {
fieldName: 'Relation',
},
}}
entityId={entityId}
>
<RelationFieldValueSetterEffect value={value} />
<RelationFieldInput onSubmit={onSubmit} onCancel={onCancel} />
</FieldContextProvider>
</RelationPickerScope>
<div data-testid="data-field-input-click-outside-div" />
<ObjectMetadataItemsProvider>
<RelationPickerScope relationPickerScopeId="relation-picker">
<FieldContextProvider
fieldDefinition={{
fieldMetadataId: 'relation',
label: 'Relation',
type: 'RELATION',
iconName: 'IconLink',
metadata: {
fieldName: 'Relation',
relationObjectMetadataNamePlural: 'workspaceMembers',
relationObjectMetadataNameSingular: 'workspaceMember',
},
}}
entityId={entityId}
>
<RelationFieldValueSetterEffect value={value} />
<RelationFieldInput onSubmit={onSubmit} onCancel={onCancel} />
</FieldContextProvider>
</RelationPickerScope>
<div data-testid="data-field-input-click-outside-div" />
</ObjectMetadataItemsProvider>
</div>
);
};
@ -88,7 +94,7 @@ const meta: Meta = {
onSubmit: { control: false },
onCancel: { control: false },
},
decorators: [clearMocksDecorator],
decorators: [SnackBarDecorator, clearMocksDecorator],
parameters: {
clearMocks: true,
msw: graphqlMocks,
@ -110,12 +116,12 @@ export const Submit: Story = {
expect(submitJestFn).toHaveBeenCalledTimes(0);
// FIXME: Failing because the picker is not fetching any items
const item = await canvas.findByText('Jane Doe');
const item = await canvas.findByText('John Wick');
userEvent.click(item);
expect(submitJestFn).toHaveBeenCalledTimes(1);
await waitFor(() => {
userEvent.click(item);
expect(submitJestFn).toHaveBeenCalledTimes(1);
});
},
};

View File

@ -146,7 +146,7 @@ export const ClickOutside: Story = {
await waitFor(() => {
userEvent.click(emptyDiv);
expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
expect(clickOutsideJestFn).toHaveBeenCalled();
});
},
};

View File

@ -58,7 +58,9 @@ export const RecordBoardColumnEditTitleMenu = ({
const [internalValue, setInternalValue] = useState(title);
const { onTitleEdit } = useContext(BoardColumnContext) || {};
const { setBoardColumns } = useRecordBoard();
const { setBoardColumns } = useRecordBoard({
recordBoardScopeId: 'company-board',
});
const debouncedOnUpdateTitle = debounce(
(newTitle) => onTitleEdit?.({ title: newTitle, color }),

View File

@ -1,7 +1,6 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
import { IconUserCircle } from '@/ui/display/icon';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
@ -41,8 +40,9 @@ const meta: Meta<typeof SingleEntitySelect> = {
selectedEntity,
width,
}) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { relationPickerSearchFilter } = useRelationPicker();
const filteredEntities = entities.filter(
(entity) => entity.id !== selectedEntity?.id,
);
return (
<SingleEntitySelect
@ -57,11 +57,7 @@ const meta: Meta<typeof SingleEntitySelect> = {
selectedEntity,
width,
}}
entitiesToSelect={entities.filter(
(entity) =>
entity.id !== selectedEntity?.id &&
entity.name.includes(relationPickerSearchFilter),
)}
entitiesToSelect={filteredEntities}
/>
);
},

View File

@ -7,7 +7,9 @@ export const useEntitySelectSearch = () => {
setRelationPickerPreselectedId,
relationPickerSearchFilter,
setRelationPickerSearchFilter,
} = useRelationPicker();
} = useRelationPicker({
relationPickerScopeId: 'relation-picker',
});
const debouncedSetSearchFilter = debounce(
setRelationPickerSearchFilter,

View File

@ -1,7 +1,8 @@
import { MemoryRouter } from 'react-router-dom';
import { Meta, StoryObj } from '@storybook/react';
import { RecoilRoot } from 'recoil';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
import { Field, FieldMetadataType } from '~/generated-metadata/graphql';
@ -19,6 +20,11 @@ const meta: Meta<typeof SettingsObjectFieldPreview> = {
title: 'Modules/Settings/DataModel/SettingsObjectFieldPreview',
component: SettingsObjectFieldPreview,
decorators: [
(Story) => {
// wait for metadata
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
return objectMetadataItems.length ? <Story /> : <></>;
},
ComponentDecorator,
ObjectMetadataItemsDecorator,
(Story) => (

View File

@ -1,7 +1,9 @@
import { MemoryRouter } from 'react-router-dom';
import { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/test';
import { useRecoilValue } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
import {
@ -31,6 +33,11 @@ const meta: Meta<typeof SettingsObjectFieldTypeSelectSection> = {
title: 'Modules/Settings/DataModel/SettingsObjectFieldTypeSelectSection',
component: SettingsObjectFieldTypeSelectSection,
decorators: [
(Story) => {
// wait for metadata
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
return objectMetadataItems.length ? <Story /> : <></>;
},
ComponentDecorator,
ObjectMetadataItemsDecorator,
(Story) => (
@ -67,9 +74,15 @@ export const WithOpenSelect: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const inputField = await canvas.findByText('Text');
await userEvent.click(inputField);
const input = await canvas.findByText('Unique ID');
await userEvent.click(input);
await userEvent.click(inputField);
const selectLabel = canvas.getByText('Number');
await userEvent.click(selectLabel);

View File

@ -1,4 +1,4 @@
import { IconArchiveOff, IconDotsVertical } from '@/ui/display/icon';
import { IconArchiveOff, IconDotsVertical, IconTrash } from '@/ui/display/icon';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
@ -17,6 +17,8 @@ type SettingsObjectDisabledMenuDropDownProps = {
export const SettingsObjectDisabledMenuDropDown = ({
onActivate,
scopeKey,
onErase,
isCustomObject,
}: SettingsObjectDisabledMenuDropDownProps) => {
const dropdownScopeId = `${scopeKey}-settings-object-disabled-menu-dropdown`;
@ -27,10 +29,10 @@ export const SettingsObjectDisabledMenuDropDown = ({
closeDropdown();
};
// const handleErase = () => {
// onErase();
// closeDropdown();
// };
const handleErase = () => {
onErase();
closeDropdown();
};
return (
<DropdownScope dropdownScopeId={dropdownScopeId}>
@ -46,14 +48,14 @@ export const SettingsObjectDisabledMenuDropDown = ({
LeftIcon={IconArchiveOff}
onClick={handleActivate}
/>
{/* {isCustomObject && (
{isCustomObject && (
<MenuItem
text="Erase"
LeftIcon={IconTrash}
accent="danger"
onClick={handleErase}
/>
)} */}
)}
</DropdownMenuItemsContainer>
</DropdownMenu>
}

View File

@ -5,7 +5,7 @@ import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
const meta: Meta<typeof ApiKeyInput> = {
title: 'Pages/Settings/Developers/ApiKeys/ApiKeyInput',
title: 'Modules/Settings/Developers/ApiKeys/ApiKeyInput',
component: ApiKeyInput,
decorators: [ComponentDecorator, SnackBarDecorator],
args: {

View File

@ -4,7 +4,7 @@ import { SettingsApiKeysFieldItemTableRow } from '@/settings/developers/componen
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
const meta: Meta<typeof SettingsApiKeysFieldItemTableRow> = {
title: 'Pages/Settings/Developers/ApiKeys/SettingsApiKeysFieldItemTableRow',
title: 'Modules/Settings/Developers/ApiKeys/SettingsApiKeysFieldItemTableRow',
component: SettingsApiKeysFieldItemTableRow,
decorators: [ComponentDecorator],
args: {

View File

@ -43,6 +43,7 @@ export const WithOpenAndSelectedIcon: Story = {
};
export const WithSearch: Story = {
args: { selectedIconKey: 'IconBuildingSkyscraper' },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
@ -65,6 +66,7 @@ export const WithSearch: Story = {
};
export const WithSearchAndClose: Story = {
args: { selectedIconKey: 'IconBuildingSkyscraper' },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

View File

@ -98,7 +98,7 @@ export const Empty: Story = {
userEvent.click(button);
await waitFor(async () => {
const fakeMenu = await canvas.findByTestId('dropdown-content');
const fakeMenu = canvas.queryByTestId('dropdown-content');
expect(fakeMenu).not.toBeInTheDocument();
});

View File

@ -11,6 +11,7 @@ import { mockedCompaniesData } from '~/testing/mock-data/companies';
import { mockedClientConfig } from '~/testing/mock-data/config';
import { mockedPipelineSteps } from '~/testing/mock-data/pipeline-steps';
import { mockedUsersData } from '~/testing/mock-data/users';
import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members';
import { mockedObjectMetadataItems } from './mock-data/metadata';
import { mockedPeopleData } from './mock-data/people';
@ -236,5 +237,57 @@ export const graphqlMocks = {
},
});
}),
graphql.query('FindManyWorkspaceMembers', () => {
return HttpResponse.json({
data: {
workspaceMembers: {
edges: mockWorkspaceMembers.map((member) => ({
node: {
...member,
messageRecipients: {
edges: [],
__typename: 'MessageRecipientConnection',
},
authoredAttachments: {
edges: [],
__typename: 'AttachmentConnection',
},
authoredComments: {
edges: [],
__typename: 'CommentConnection',
},
accountOwnerForCompanies: {
edges: [],
__typename: 'CompanyConnection',
},
authoredActivities: {
edges: [],
__typename: 'ActivityConnection',
},
favorites: {
edges: [],
__typename: 'FavoriteConnection',
},
connectedAccounts: {
edges: [],
__typename: 'ConnectedAccountConnection',
},
assignedActivities: {
edges: [],
__typename: 'ActivityConnection',
},
},
cursor: null,
})),
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
endCursor: null,
},
},
},
});
}),
],
};

View File

@ -0,0 +1,28 @@
export const mockWorkspaceMembers = [
{
id: '20202020-1553-45c6-a028-5a9064cce07f',
name: {
firstName: 'Jane',
lastName: 'Doe',
},
locale: 'en',
avatarUrl: '',
createdAt: '2023-12-18T09:51:19.645Z',
updatedAt: '2023-12-18T09:51:19.645Z',
userId: '20202020-7169-42cf-bc47-1cfef15264b8',
colorScheme: 'Light',
},
{
id: '20202020-77d5-4cb6-b60a-f4a835a85d61',
name: {
firstName: 'John',
lastName: 'Wick',
},
locale: 'en',
avatarUrl: '',
createdAt: '2023-12-18T09:51:19.645Z',
updatedAt: '2023-12-18T09:51:19.645Z',
userId: '20202020-3957-4908-9c36-2929a23f8357',
colorScheme: 'Light',
},
];