Create stories for action display components (#11547)

Create stories for:
- ActionButton
- ActionComponent
- ActionDisplay
- ActionDropdownItem
- ActionListItem
This commit is contained in:
Raphaël Bosi
2025-04-14 18:42:49 +02:00
committed by GitHub
parent 0249cf3b34
commit 8f2dddaa5f
6 changed files with 456 additions and 7 deletions

View File

@ -0,0 +1,66 @@
import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKeys';
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
import { createMockActionMenuActions } from '@/action-menu/mock/action-menu-actions.mock';
import { getActionLabel } from '@/action-menu/utils/getActionLabel';
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { fn, userEvent, within } from '@storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
import { ActionButton } from '../ActionButton';
const meta: Meta<typeof ActionButton> = {
title: 'Modules/ActionMenu/Actions/Display/ActionButton',
component: ActionButton,
decorators: [ComponentDecorator, RouterDecorator],
};
export default meta;
type Story = StoryObj<typeof ActionButton>;
const deleteMock = fn();
const addToFavoritesMock = fn();
const mockActions = createMockActionMenuActions({
deleteMock,
addToFavoritesMock,
});
const addToFavoritesAction = mockActions.find(
(action) => action.key === SingleRecordActionKeys.ADD_TO_FAVORITES,
);
const goToPeopleAction = mockActions.find(
(action) => action.key === NoSelectionRecordActionKeys.GO_TO_PEOPLE,
);
export const Default: Story = {
args: {
action: addToFavoritesAction,
onClick: addToFavoritesMock,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getActionLabel(addToFavoritesAction?.shortLabel ?? ''),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
},
};
export const WithLink: Story = {
args: {
action: goToPeopleAction,
to: '/objects/people',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const menuItem = await canvas.findByText(
getActionLabel(goToPeopleAction?.shortLabel ?? ''),
);
expect(menuItem).toBeVisible();
expect(canvas.getByRole('link')).toHaveAttribute('href', '/objects/people');
},
};

View File

@ -0,0 +1,67 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, within } from '@storybook/test';
import { ActionComponent } from '@/action-menu/actions/display/components/ActionComponent';
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { createMockActionMenuActions } from '@/action-menu/mock/action-menu-actions.mock';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { getActionLabel } from '@/action-menu/utils/getActionLabel';
import { ComponentDecorator } from 'twenty-ui/testing';
const mockActions = createMockActionMenuActions({});
const addToFavoritesAction = mockActions.find(
(action) => action.key === SingleRecordActionKeys.ADD_TO_FAVORITES,
);
if (!addToFavoritesAction) {
throw new Error('Add to favorites action not found');
}
const meta: Meta<typeof ActionComponent> = {
title: 'Modules/ActionMenu/Actions/Display/ActionComponent',
component: ActionComponent,
decorators: [
ComponentDecorator,
(Story) => (
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: 'story' }}
>
<ActionMenuContext.Provider
value={{
isInRightDrawer: false,
actionMenuType: 'index-page-action-menu',
displayType: 'button',
actions: [addToFavoritesAction],
}}
>
<Story />
</ActionMenuContext.Provider>
</ActionMenuComponentInstanceContext.Provider>
),
],
args: {
action: addToFavoritesAction,
},
parameters: {
container: {
width: 'auto',
},
},
};
export default meta;
type Story = StoryObj<typeof ActionComponent>;
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(
await canvas.findByText(
getActionLabel(addToFavoritesAction?.shortLabel ?? ''),
),
).toBeVisible();
},
};

View File

@ -0,0 +1,139 @@
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
import { ActionConfigContext } from '@/action-menu/contexts/ActionConfigContext';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { createMockActionMenuActions } from '@/action-menu/mock/action-menu-actions.mock';
import { getActionLabel } from '@/action-menu/utils/getActionLabel';
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { fn, userEvent, within } from '@storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
import { ActionDisplay } from '../ActionDisplay';
type Story = StoryObj<typeof ActionDisplay>;
const deleteMock = fn();
const addToFavoritesMock = fn();
const mockActions = createMockActionMenuActions({
deleteMock,
addToFavoritesMock,
});
const addToFavoritesAction = mockActions.find(
(action) => action.key === SingleRecordActionKeys.ADD_TO_FAVORITES,
);
if (!addToFavoritesAction) {
throw new Error('addToFavoritesAction not found');
}
const meta: Meta<typeof ActionDisplay> = {
title: 'Modules/ActionMenu/Actions/Display/ActionDisplay',
component: ActionDisplay,
decorators: [
(Story) => (
<ActionConfigContext.Provider value={addToFavoritesAction}>
<Story />
</ActionConfigContext.Provider>
),
ComponentDecorator,
RouterDecorator,
],
};
export default meta;
export const AsButton: Story = {
args: {
onClick: addToFavoritesMock,
},
decorators: [
(Story) => (
<ActionMenuContext.Provider
value={{
isInRightDrawer: false,
actionMenuType: 'command-menu',
displayType: 'button',
actions: [],
}}
>
<Story />
</ActionMenuContext.Provider>
),
],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getActionLabel(addToFavoritesAction?.shortLabel ?? ''),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
},
};
export const AsListItem: Story = {
args: {
onClick: addToFavoritesMock,
},
decorators: [
(Story) => (
<SelectableListComponentInstanceContext.Provider
value={{ instanceId: 'story' }}
>
<Story />
</SelectableListComponentInstanceContext.Provider>
),
(Story) => (
<ActionMenuContext.Provider
value={{
isInRightDrawer: false,
actionMenuType: 'command-menu',
displayType: 'listItem',
actions: [],
}}
>
<Story />
</ActionMenuContext.Provider>
),
],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getActionLabel(addToFavoritesAction?.label ?? ''),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
},
};
export const AsDropdownItem: Story = {
args: {
onClick: addToFavoritesMock,
},
decorators: [
(Story) => (
<ActionMenuContext.Provider
value={{
isInRightDrawer: false,
actionMenuType: 'command-menu',
displayType: 'dropdownItem',
actions: [],
}}
>
<Story />
</ActionMenuContext.Provider>
),
],
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getActionLabel(addToFavoritesAction?.label ?? ''),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
},
};

View File

@ -0,0 +1,65 @@
import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKeys';
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
import { createMockActionMenuActions } from '@/action-menu/mock/action-menu-actions.mock';
import { getActionLabel } from '@/action-menu/utils/getActionLabel';
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { fn, userEvent, within } from '@storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
import { ActionDropdownItem } from '../ActionDropdownItem';
const meta: Meta<typeof ActionDropdownItem> = {
title: 'Modules/ActionMenu/Actions/Display/ActionDropdownItem',
component: ActionDropdownItem,
decorators: [ComponentDecorator, RouterDecorator],
};
export default meta;
type Story = StoryObj<typeof ActionDropdownItem>;
const deleteMock = fn();
const addToFavoritesMock = fn();
const mockActions = createMockActionMenuActions({
deleteMock,
addToFavoritesMock,
});
const addToFavoritesAction = mockActions.find(
(action) => action.key === SingleRecordActionKeys.ADD_TO_FAVORITES,
);
const goToPeopleAction = mockActions.find(
(action) => action.key === NoSelectionRecordActionKeys.GO_TO_PEOPLE,
);
export const Default: Story = {
args: {
action: addToFavoritesAction,
onClick: addToFavoritesMock,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getActionLabel(addToFavoritesAction?.label ?? ''),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
},
};
export const WithLink: Story = {
args: {
action: goToPeopleAction,
to: '/objects/people',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const dropdownItem = await canvas.findByText(
getActionLabel(goToPeopleAction?.label ?? ''),
);
expect(dropdownItem).toBeVisible();
},
};

View File

@ -0,0 +1,76 @@
import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKeys';
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
import { createMockActionMenuActions } from '@/action-menu/mock/action-menu-actions.mock';
import { getActionLabel } from '@/action-menu/utils/getActionLabel';
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { expect } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { fn, userEvent, within } from '@storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
import { ActionListItem } from '../ActionListItem';
type Story = StoryObj<typeof ActionListItem>;
const deleteMock = fn();
const addToFavoritesMock = fn();
const mockActions = createMockActionMenuActions({
deleteMock,
addToFavoritesMock,
});
const addToFavoritesAction = mockActions.find(
(action) => action.key === SingleRecordActionKeys.ADD_TO_FAVORITES,
);
const goToPeopleAction = mockActions.find(
(action) => action.key === NoSelectionRecordActionKeys.GO_TO_PEOPLE,
);
const meta: Meta<typeof ActionListItem> = {
title: 'Modules/ActionMenu/Actions/Display/ActionListItem',
component: ActionListItem,
decorators: [
(Story) => (
<SelectableListComponentInstanceContext.Provider
value={{ instanceId: 'story' }}
>
<Story />
</SelectableListComponentInstanceContext.Provider>
),
ComponentDecorator,
RouterDecorator,
],
};
export default meta;
export const Default: Story = {
args: {
action: addToFavoritesAction,
onClick: addToFavoritesMock,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByText(
getActionLabel(addToFavoritesAction?.label ?? ''),
),
);
expect(addToFavoritesMock).toHaveBeenCalled();
},
};
export const WithLink: Story = {
args: {
action: goToPeopleAction,
to: '/objects/people',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const listItem = await canvas.findByText(
getActionLabel(goToPeopleAction?.label ?? ''),
);
expect(listItem).toBeVisible();
},
};

View File

@ -1,20 +1,30 @@
import { Action } from '@/action-menu/actions/components/Action';
import { ActionLink } from '@/action-menu/actions/components/ActionLink';
import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKeys';
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
import { ActionConfig } from '@/action-menu/actions/types/ActionConfig';
import { ActionScope } from '@/action-menu/actions/types/ActionScope';
import { ActionType } from '@/action-menu/actions/types/ActionType';
import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { AppPath } from '@/types/AppPath';
import { msg } from '@lingui/core/macro';
import { IconFileExport, IconHeart, IconTrash } from 'twenty-ui/display';
import {
IconFileExport,
IconHeart,
IconTrash,
IconUser,
} from 'twenty-ui/display';
export const createMockActionMenuActions = ({
deleteMock,
addToFavoritesMock,
exportMock,
deleteMock = () => {},
addToFavoritesMock = () => {},
exportMock = () => {},
}: {
deleteMock: () => void;
addToFavoritesMock: () => void;
exportMock: () => void;
deleteMock?: () => void;
addToFavoritesMock?: () => void;
exportMock?: () => void;
}): ActionConfig[] => [
{
type: ActionType.Standard,
@ -66,4 +76,30 @@ export const createMockActionMenuActions = ({
],
component: <Action onClick={deleteMock} />,
},
{
type: ActionType.Navigation,
scope: ActionScope.Global,
key: NoSelectionRecordActionKeys.GO_TO_PEOPLE,
label: msg`Go to People`,
shortLabel: msg`People`,
position: 19,
Icon: IconUser,
isPinned: false,
availableOn: [
ActionViewType.INDEX_PAGE_NO_SELECTION,
ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
ActionViewType.INDEX_PAGE_BULK_SELECTION,
ActionViewType.SHOW_PAGE,
],
shouldBeRegistered: ({ objectMetadataItem, viewType }) =>
objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Person ||
viewType === ActionViewType.SHOW_PAGE,
component: (
<ActionLink
to={AppPath.RecordIndexPage}
params={{ objectNamePlural: CoreObjectNamePlural.Person }}
/>
),
hotKeys: ['G', 'P'],
},
];