From 3660bec01deaeb3d6be7e874e3cde4d227b656fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Wed, 26 Mar 2025 17:31:48 +0100 Subject: [PATCH] New TitleInput UI component for side panel (#11192) # Description I previously introduced the `RecordTitleCell` component, but it was coupled with the field context, so it was only usable for record fields. This PR: - Introduces a new component `TitleInput` for side panel pages which needed to have an editable title which wasn't a record field. - Fixes the hotkey scope problem with the workflow step page title - Introduces a new hook `useUpdateCommandMenuPageInfo`, to update the side panel page title and icon. - Fixes workflow side panel UI - Adds jest tests and stories # Video https://github.com/user-attachments/assets/c501245c-4492-4351-b761-05b5abc4bd14 --- .../useUpdateCommandMenuPageInfo.test.tsx | 135 +++++++++++++ .../hooks/useUpdateCommandMenuPageInfo.ts | 57 ++++++ .../ui/input/components/TitleInput.tsx | 182 ++++++++++++++++++ .../__stories__/TitleInput.stories.tsx | 64 ++++++ .../components/WorkflowStepHeader.tsx | 43 +++-- .../WorkflowStepHeader.stories.tsx | 36 ++-- ...WorkflowEditActionCreateRecord.stories.tsx | 9 +- ...WorkflowEditActionDeleteRecord.stories.tsx | 27 ++- .../WorkflowEditActionFindRecords.stories.tsx | 9 +- ...WorkflowEditActionUpdateRecord.stories.tsx | 27 ++- .../WorkflowEditActionFormBuilder.stories.tsx | 12 +- 11 files changed, 551 insertions(+), 50 deletions(-) create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/__tests__/useUpdateCommandMenuPageInfo.test.tsx create mode 100644 packages/twenty-front/src/modules/command-menu/hooks/useUpdateCommandMenuPageInfo.ts create mode 100644 packages/twenty-front/src/modules/ui/input/components/TitleInput.tsx create mode 100644 packages/twenty-front/src/modules/ui/input/components/__stories__/TitleInput.stories.tsx diff --git a/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useUpdateCommandMenuPageInfo.test.tsx b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useUpdateCommandMenuPageInfo.test.tsx new file mode 100644 index 000000000..90d80cbe3 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/hooks/__tests__/useUpdateCommandMenuPageInfo.test.tsx @@ -0,0 +1,135 @@ +import { useUpdateCommandMenuPageInfo } from '@/command-menu/hooks/useUpdateCommandMenuPageInfo'; +import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState'; +import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState'; +import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { RecoilRoot, useRecoilValue } from 'recoil'; +import { IconArrowDown, IconDotsVertical } from 'twenty-ui'; + +const mockedPageInfo = { + title: 'Initial Title', + Icon: IconDotsVertical, + instanceId: 'test-instance', +}; + +const mockedNavigationStack = [ + { + page: CommandMenuPages.Root, + pageTitle: 'Initial Title', + pageIcon: IconDotsVertical, + pageId: 'test-page-id', + }, +]; + +const Wrapper = ({ children }: { children: React.ReactNode }) => ( + { + set(commandMenuNavigationStackState, mockedNavigationStack); + set(commandMenuPageInfoState, mockedPageInfo); + }} + > + {children} + +); + +describe('useUpdateCommandMenuPageInfo', () => { + const renderHooks = () => { + const { result } = renderHook( + () => { + const { updateCommandMenuPageInfo } = useUpdateCommandMenuPageInfo(); + const commandMenuNavigationStack = useRecoilValue( + commandMenuNavigationStackState, + ); + const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState); + + return { + updateCommandMenuPageInfo, + commandMenuNavigationStack, + commandMenuPageInfo, + }; + }, + { wrapper: Wrapper }, + ); + + return { + result, + }; + }; + + it('should update command menu page info with new title and icon', () => { + const { result } = renderHooks(); + + act(() => { + result.current.updateCommandMenuPageInfo({ + pageTitle: 'New Title', + pageIcon: IconArrowDown, + }); + }); + + expect(result.current.commandMenuNavigationStack).toEqual([ + { + page: CommandMenuPages.Root, + pageTitle: 'New Title', + pageIcon: IconArrowDown, + pageId: 'test-page-id', + }, + ]); + + expect(result.current.commandMenuPageInfo).toEqual({ + title: 'New Title', + Icon: IconArrowDown, + instanceId: 'test-instance', + }); + }); + + it('should update command menu page info with new title', () => { + const { result } = renderHooks(); + + act(() => { + result.current.updateCommandMenuPageInfo({ + pageTitle: 'New Title', + }); + }); + + expect(result.current.commandMenuNavigationStack).toEqual([ + { + page: CommandMenuPages.Root, + pageTitle: 'New Title', + pageIcon: IconDotsVertical, + pageId: 'test-page-id', + }, + ]); + + expect(result.current.commandMenuPageInfo).toEqual({ + title: 'New Title', + Icon: IconDotsVertical, + instanceId: 'test-instance', + }); + }); + + it('should update command menu page info with new icon', () => { + const { result } = renderHooks(); + + act(() => { + result.current.updateCommandMenuPageInfo({ + pageIcon: IconArrowDown, + }); + }); + + expect(result.current.commandMenuNavigationStack).toEqual([ + { + page: CommandMenuPages.Root, + pageTitle: 'Initial Title', + pageIcon: IconArrowDown, + pageId: 'test-page-id', + }, + ]); + + expect(result.current.commandMenuPageInfo).toEqual({ + title: 'Initial Title', + Icon: IconArrowDown, + instanceId: 'test-instance', + }); + }); +}); diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useUpdateCommandMenuPageInfo.ts b/packages/twenty-front/src/modules/command-menu/hooks/useUpdateCommandMenuPageInfo.ts new file mode 100644 index 000000000..896547b2a --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/hooks/useUpdateCommandMenuPageInfo.ts @@ -0,0 +1,57 @@ +import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState'; +import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState'; +import { useRecoilCallback } from 'recoil'; +import { IconComponent, IconDotsVertical } from 'twenty-ui'; + +export const useUpdateCommandMenuPageInfo = () => { + const updateCommandMenuPageInfo = useRecoilCallback( + ({ snapshot, set }) => + ({ + pageTitle, + pageIcon, + }: { + pageTitle?: string; + pageIcon?: IconComponent; + }) => { + const commandMenuPageInfo = snapshot + .getLoadable(commandMenuPageInfoState) + .getValue(); + + const newCommandMenuPageInfo = { + ...commandMenuPageInfo, + title: pageTitle ?? commandMenuPageInfo.title ?? '', + Icon: pageIcon ?? commandMenuPageInfo.Icon ?? IconDotsVertical, + }; + + set(commandMenuPageInfoState, newCommandMenuPageInfo); + + const commandMenuNavigationStack = snapshot + .getLoadable(commandMenuNavigationStackState) + .getValue(); + + const lastCommandMenuNavigationStackItem = + commandMenuNavigationStack.at(-1); + + if (!lastCommandMenuNavigationStackItem) { + return; + } + + const newCommandMenuNavigationStack = [ + ...commandMenuNavigationStack.slice(0, -1), + { + page: lastCommandMenuNavigationStackItem.page, + pageTitle: newCommandMenuPageInfo.title, + pageIcon: newCommandMenuPageInfo.Icon, + pageId: lastCommandMenuNavigationStackItem.pageId, + }, + ]; + + set(commandMenuNavigationStackState, newCommandMenuNavigationStack); + }, + [], + ); + + return { + updateCommandMenuPageInfo, + }; +}; diff --git a/packages/twenty-front/src/modules/ui/input/components/TitleInput.tsx b/packages/twenty-front/src/modules/ui/input/components/TitleInput.tsx new file mode 100644 index 000000000..f24aadf22 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/TitleInput.tsx @@ -0,0 +1,182 @@ +import { + TextInputV2, + TextInputV2Size, +} from '@/ui/input/components/TextInputV2'; +import { useRef, useState } from 'react'; +import { isDefined } from 'twenty-shared/utils'; + +import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents'; +import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; +import styled from '@emotion/styled'; +import { OverflowingTextWithTooltip } from 'twenty-ui'; + +type InputProps = { + value?: string; + onChange: (value: string) => void; + placeholder?: string; + hotkeyScope?: string; + onEnter?: () => void; + onEscape?: () => void; + onClickOutside?: () => void; + onTab?: () => void; + onShiftTab?: () => void; + sizeVariant?: TextInputV2Size; +}; + +export type TitleInputProps = { + disabled?: boolean; +} & InputProps; + +const StyledDiv = styled.div<{ + sizeVariant: TextInputV2Size; + disabled?: boolean; +}>` + background: inherit; + border: none; + border-radius: ${({ theme }) => theme.border.radius.sm}; + color: ${({ theme }) => theme.font.color.primary}; + cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')}; + overflow: hidden; + height: ${({ sizeVariant }) => + sizeVariant === 'xs' + ? '20px' + : sizeVariant === 'sm' + ? '24px' + : sizeVariant === 'md' + ? '28px' + : '32px'}; + padding: ${({ theme }) => theme.spacing(0, 1.25)}; + box-sizing: border-box; + display: flex; + align-items: center; + :hover { + background: ${({ theme, disabled }) => + disabled ? 'inherit' : theme.background.transparent.light}; + } +`; + +const Input = ({ + value, + onChange, + placeholder, + hotkeyScope = 'title-input', + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, + setIsOpened, + sizeVariant, +}: InputProps & { setIsOpened: (isOpened: boolean) => void }) => { + const wrapperRef = useRef(null); + + const [draftValue, setDraftValue] = useState(value ?? ''); + + const handleFocus = (event: React.FocusEvent) => { + if (isDefined(value)) { + event.target.select(); + } + }; + + const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope(); + + const handleLeaveFocus = () => { + setIsOpened(false); + goBackToPreviousHotkeyScope(); + }; + + useRegisterInputEvents({ + inputRef: wrapperRef, + inputValue: draftValue, + onEnter: () => { + handleLeaveFocus(); + onEnter?.(); + }, + onEscape: () => { + handleLeaveFocus(); + onEscape?.(); + }, + onClickOutside: (event) => { + event.stopImmediatePropagation(); + handleLeaveFocus(); + onClickOutside?.(); + }, + onTab: () => { + handleLeaveFocus(); + onTab?.(); + }, + onShiftTab: () => { + handleLeaveFocus(); + onShiftTab?.(); + }, + hotkeyScope: hotkeyScope, + }); + + return ( + { + setDraftValue(text); + onChange?.(text); + }} + placeholder={placeholder} + onFocus={handleFocus} + autoFocus + /> + ); +}; + +export const TitleInput = ({ + disabled, + value, + sizeVariant = 'md', + onChange, + placeholder, + hotkeyScope = 'title-input', + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, +}: TitleInputProps) => { + const [isOpened, setIsOpened] = useState(false); + + const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); + + return ( + <> + {isOpened ? ( + + ) : ( + { + if (!disabled) { + setIsOpened(true); + setHotkeyScopeAndMemorizePreviousScope(hotkeyScope); + } + }} + > + + + )} + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/input/components/__stories__/TitleInput.stories.tsx b/packages/twenty-front/src/modules/ui/input/components/__stories__/TitleInput.stories.tsx new file mode 100644 index 000000000..4e4a6b331 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/__stories__/TitleInput.stories.tsx @@ -0,0 +1,64 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { ComponentDecorator } from 'twenty-ui'; + +import { TitleInput } from '@/ui/input/components/TitleInput'; + +const meta: Meta = { + title: 'UI/Input/TitleInput', + component: TitleInput, + decorators: [ComponentDecorator], + args: { + placeholder: 'Enter title', + hotkeyScope: 'titleInput', + sizeVariant: 'md', + }, + argTypes: { + hotkeyScope: { control: false }, + sizeVariant: { control: false }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithValue: Story = { + args: { value: 'Sample Title' }, +}; + +export const Disabled: Story = { + args: { disabled: true, value: 'Disabled Title' }, +}; + +export const ExtraSmall: Story = { + args: { sizeVariant: 'xs', value: 'Extra Small Title' }, +}; + +export const Small: Story = { + args: { sizeVariant: 'sm', value: 'Small Title' }, +}; + +export const Medium: Story = { + args: { sizeVariant: 'md', value: 'Medium Title' }, +}; + +export const Large: Story = { + args: { sizeVariant: 'lg', value: 'Large Title' }, +}; + +export const WithLongText: Story = { + args: { + value: + 'This is a very long title that will likely overflow and demonstrate the tooltip behavior of the component', + }, + parameters: { + container: { + width: 250, + }, + }, +}; + +export const WithCustomPlaceholder: Story = { + args: { placeholder: 'Custom placeholder example' }, +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepHeader.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepHeader.tsx index 05ad3fda7..8ed5d7c64 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepHeader.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepHeader.tsx @@ -1,10 +1,9 @@ -import { TextInput } from '@/ui/field/input/components/TextInput'; +import { useUpdateCommandMenuPageInfo } from '@/command-menu/hooks/useUpdateCommandMenuPageInfo'; +import { TitleInput } from '@/ui/input/components/TitleInput'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useState } from 'react'; import { IconComponent } from 'twenty-ui'; -import { useDebouncedCallback } from 'use-debounce'; - const StyledHeader = styled.div` background-color: ${({ theme }) => theme.background.secondary}; border-bottom: 1px solid ${({ theme }) => theme.border.color.medium}; @@ -17,6 +16,7 @@ const StyledHeader = styled.div` const StyledHeaderInfo = styled.div` display: flex; flex-direction: column; + width: 100%; gap: ${({ theme }) => theme.spacing(2)}; `; @@ -24,9 +24,8 @@ const StyledHeaderTitle = styled.div` color: ${({ theme }) => theme.font.color.primary}; font-weight: ${({ theme }) => theme.font.weight.semiBold}; font-size: ${({ theme }) => theme.font.size.xl}; - width: 420px; - overflow: hidden; - + width: fit-content; + max-width: 420px; & > input:disabled { color: ${({ theme }) => theme.font.color.primary}; } @@ -34,7 +33,7 @@ const StyledHeaderTitle = styled.div` const StyledHeaderType = styled.div` color: ${({ theme }) => theme.font.color.tertiary}; - padding-left: ${({ theme }) => theme.spacing(2)}; + padding-left: ${({ theme }) => theme.spacing(1)}; `; const StyledHeaderIconContainer = styled.div` @@ -75,13 +74,18 @@ export const WorkflowStepHeader = ({ const [title, setTitle] = useState(initialTitle); - const debouncedOnTitleChange = useDebouncedCallback((newTitle: string) => { - onTitleChange?.(newTitle); - }, 100); + const { updateCommandMenuPageInfo } = useUpdateCommandMenuPageInfo(); const handleChange = (newTitle: string) => { setTitle(newTitle); - debouncedOnTitleChange(newTitle); + }; + + const saveTitle = () => { + onTitleChange?.(title); + updateCommandMenuPageInfo({ + pageTitle: title, + pageIcon: Icon, + }); }; return ( @@ -95,15 +99,20 @@ export const WorkflowStepHeader = ({ - { + setTitle(initialTitle); + }} + onClickOutside={saveTitle} + onTab={saveTitle} + onShiftTab={saveTitle} /> {headerType} diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/__stories__/WorkflowStepHeader.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/__stories__/WorkflowStepHeader.stories.tsx index 574efaa80..81606fd37 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/__stories__/WorkflowStepHeader.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/__stories__/WorkflowStepHeader.stories.tsx @@ -27,7 +27,8 @@ export const Default: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - expect(await canvas.findByDisplayValue('Create Record')).toBeVisible(); + // TitleInput shows text in a div when not being edited + expect(await canvas.findByText('Create Record')).toBeVisible(); expect(await canvas.findByText('Action')).toBeVisible(); }, }; @@ -43,24 +44,25 @@ export const EditableTitle: Story = { play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); + // First find the div with the text, then click it to activate the input + const titleText = await canvas.findByText('Create Record'); + await userEvent.click(titleText); + + // Now find the input that appears after clicking const titleInput = await canvas.findByDisplayValue('Create Record'); const NEW_TITLE = 'New Title'; await userEvent.clear(titleInput); - - await waitFor(() => { - expect(args.onTitleChange).toHaveBeenCalledWith(''); - }); - await userEvent.type(titleInput, NEW_TITLE); + // Press Enter to submit the edit + await userEvent.keyboard('{Enter}'); + + // Wait for the callback to be called await waitFor(() => { expect(args.onTitleChange).toHaveBeenCalledWith(NEW_TITLE); }); - - expect(args.onTitleChange).toHaveBeenCalledTimes(2); - expect(titleInput).toHaveValue(NEW_TITLE); }, }; @@ -76,14 +78,20 @@ export const Disabled: Story = { play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); - const titleInput = await canvas.findByDisplayValue('Create Record'); - expect(titleInput).toBeDisabled(); + // When disabled, TitleInput just shows text in a div, not an input + const titleText = await canvas.findByText('Create Record'); - const NEW_TITLE = 'New Title'; + // Check if the element has the disabled styling (cursor: default) + expect(window.getComputedStyle(titleText).cursor).toBe('default'); - await userEvent.type(titleInput, NEW_TITLE); + // Try to click it - nothing should happen + await userEvent.click(titleText); + // Confirm there is no input field + const titleInput = canvas.queryByDisplayValue('Create Record'); + expect(titleInput).not.toBeInTheDocument(); + + // Confirm the callback is not called expect(args.onTitleChange).not.toHaveBeenCalled(); - expect(titleInput).toHaveValue('Create Record'); }, }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionCreateRecord.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionCreateRecord.stories.tsx index 32db820d1..de0a68a78 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionCreateRecord.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionCreateRecord.stories.tsx @@ -73,9 +73,14 @@ export const Disabled: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const titleInput = await canvas.findByDisplayValue('Create Record'); + const titleText = await canvas.findByText('Create Record'); - expect(titleInput).toBeDisabled(); + expect(window.getComputedStyle(titleText).cursor).toBe('default'); + + await userEvent.click(titleText); + + const titleInput = canvas.queryByDisplayValue('Create Record'); + expect(titleInput).not.toBeInTheDocument(); const objectSelectCurrentValue = await canvas.findByText('People'); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionDeleteRecord.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionDeleteRecord.stories.tsx index f37a0fb8d..aaee9d16d 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionDeleteRecord.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionDeleteRecord.stories.tsx @@ -77,9 +77,14 @@ export const DisabledWithEmptyValues: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const titleInput = await canvas.findByDisplayValue('Delete Record'); + const titleText = await canvas.findByText('Delete Record'); - expect(titleInput).toBeDisabled(); + expect(window.getComputedStyle(titleText).cursor).toBe('default'); + + await userEvent.click(titleText); + + const titleInput = canvas.queryByDisplayValue('Delete Record'); + expect(titleInput).not.toBeInTheDocument(); const objectSelectCurrentValue = await canvas.findByText('People'); @@ -123,9 +128,14 @@ export const DisabledWithDefaultStaticValues: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const titleInput = await canvas.findByDisplayValue('Delete Record'); + const titleText = await canvas.findByText('Delete Record'); - expect(titleInput).toBeDisabled(); + expect(window.getComputedStyle(titleText).cursor).toBe('default'); + + await userEvent.click(titleText); + + const titleInput = canvas.queryByDisplayValue('Delete Record'); + expect(titleInput).not.toBeInTheDocument(); const objectSelectCurrentValue = await canvas.findByText('People'); @@ -173,9 +183,14 @@ export const DisabledWithDefaultVariableValues: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const titleInput = await canvas.findByDisplayValue('Delete Record'); + const titleText = await canvas.findByText('Delete Record'); - expect(titleInput).toBeDisabled(); + expect(window.getComputedStyle(titleText).cursor).toBe('default'); + + await userEvent.click(titleText); + + const titleInput = canvas.queryByDisplayValue('Delete Record'); + expect(titleInput).not.toBeInTheDocument(); const objectSelectCurrentValue = await canvas.findByText('People'); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionFindRecords.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionFindRecords.stories.tsx index d3e3f3bca..16e3c26b4 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionFindRecords.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionFindRecords.stories.tsx @@ -76,9 +76,14 @@ export const DisabledWithEmptyValues: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const titleInput = await canvas.findByDisplayValue('Search Records'); + const titleText = await canvas.findByText('Search Records'); - expect(titleInput).toBeDisabled(); + expect(window.getComputedStyle(titleText).cursor).toBe('default'); + + await userEvent.click(titleText); + + const titleInput = canvas.queryByDisplayValue('Search Records'); + expect(titleInput).not.toBeInTheDocument(); const objectSelectCurrentValue = await canvas.findByText('People'); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionUpdateRecord.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionUpdateRecord.stories.tsx index 2f65208b5..6b74ffcc6 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionUpdateRecord.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionUpdateRecord.stories.tsx @@ -90,9 +90,14 @@ export const DisabledWithEmptyValues: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const titleInput = await canvas.findByDisplayValue('Update Record'); + const titleText = await canvas.findByText('Update Record'); - expect(titleInput).toBeDisabled(); + expect(window.getComputedStyle(titleText).cursor).toBe('default'); + + await userEvent.click(titleText); + + const titleInput = canvas.queryByDisplayValue('Update Record'); + expect(titleInput).not.toBeInTheDocument(); const objectSelectCurrentValue = await canvas.findByText('People'); @@ -151,9 +156,14 @@ export const DisabledWithDefaultStaticValues: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const titleInput = await canvas.findByDisplayValue('Update Record'); + const titleText = await canvas.findByText('Update Record'); - expect(titleInput).toBeDisabled(); + expect(window.getComputedStyle(titleText).cursor).toBe('default'); + + await userEvent.click(titleText); + + const titleInput = canvas.queryByDisplayValue('Update Record'); + expect(titleInput).not.toBeInTheDocument(); const objectSelectCurrentValue = await canvas.findByText('People'); @@ -214,9 +224,14 @@ export const DisabledWithDefaultVariableValues: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const titleInput = await canvas.findByDisplayValue('Update Record'); + const titleText = await canvas.findByText('Update Record'); - expect(titleInput).toBeDisabled(); + expect(window.getComputedStyle(titleText).cursor).toBe('default'); + + await userEvent.click(titleText); + + const titleInput = canvas.queryByDisplayValue('Update Record'); + expect(titleInput).not.toBeInTheDocument(); const objectSelectCurrentValue = await canvas.findByText('People'); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/__stories__/WorkflowEditActionFormBuilder.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/__stories__/WorkflowEditActionFormBuilder.stories.tsx index 3eacec391..194a7ff6b 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/__stories__/WorkflowEditActionFormBuilder.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/form-action/components/__stories__/WorkflowEditActionFormBuilder.stories.tsx @@ -2,12 +2,13 @@ import { WorkflowFormAction } from '@/workflow/types/Workflow'; import { WorkflowEditActionFormBuilder } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder'; import { Meta, StoryObj } from '@storybook/react'; import { expect, fn, within } from '@storybook/test'; +import { userEvent } from '@storybook/testing-library'; +import { FieldMetadataType } from 'twenty-shared/types'; import { ComponentDecorator, RouterDecorator } from 'twenty-ui'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; import { getWorkflowNodeIdMock } from '~/testing/mock-data/workflow'; -import { FieldMetadataType } from 'twenty-shared/types'; const DEFAULT_ACTION = { id: getWorkflowNodeIdMock(), @@ -89,9 +90,14 @@ export const DisabledWithEmptyValues: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const titleInput = await canvas.findByDisplayValue('Form'); + const titleText = await canvas.findByText('Form'); - expect(titleInput).toBeDisabled(); + expect(window.getComputedStyle(titleText).cursor).toBe('default'); + + await userEvent.click(titleText); + + const titleInput = canvas.queryByDisplayValue('Form'); + expect(titleInput).not.toBeInTheDocument(); await canvas.findByText('Company');