diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx index f2d72df15..c139d1b6e 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx @@ -132,8 +132,9 @@ export const MultiItemFieldInput = ({ }; const handleSubmitInput = () => { + const sanitizedInput = inputValue.trim(); if (validateInput !== undefined) { - const validationData = validateInput(inputValue) ?? { isValid: true }; + const validationData = validateInput(sanitizedInput) ?? { isValid: true }; if (!validationData.isValid) { onError?.(true, items); setErrorData(validationData); @@ -141,18 +142,18 @@ export const MultiItemFieldInput = ({ } } - if (inputValue === '' && isAddingNewItem) { + if (sanitizedInput === '' && isAddingNewItem) { return; } - if (inputValue === '' && !isAddingNewItem) { + if (sanitizedInput === '' && !isAddingNewItem) { handleDeleteItem(itemToEditIndex); return; } const newItem = formatInput - ? formatInput(inputValue) - : (inputValue as unknown as T); + ? formatInput(sanitizedInput) + : (sanitizedInput as unknown as T); if (!isAddingNewItem && newItem === items[itemToEditIndex]) { setIsInputDisplayed(false); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/ArrayFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/ArrayFieldInput.stories.tsx index 9ae911027..6e75dcafb 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/ArrayFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/ArrayFieldInput.stories.tsx @@ -1,6 +1,6 @@ import { expect } from '@storybook/jest'; import { Meta, StoryObj } from '@storybook/react'; -import { fn, userEvent, within } from '@storybook/test'; +import { fn, userEvent, waitFor, within } from '@storybook/test'; import { useEffect } from 'react'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; @@ -125,3 +125,39 @@ export const Default: Story = { expect(tag3Element).toBeVisible(); }, }; + +export const TrimInput: Story = { + args: { + value: ['tag1', 'tag2'], + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const addButton = await canvas.findByText('Add Item'); + await userEvent.click(addButton); + + const input = await canvas.findByPlaceholderText('Enter value'); + await userEvent.type(input, ' tag2 {enter}'); + + await waitFor(() => { + const tag2Elements = canvas.queryAllByText('tag2'); + expect(tag2Elements).toHaveLength(2); + }); + + await waitFor(() => { + expect(updateRecord).toHaveBeenCalledWith({ + variables: { + where: { id: 'record-id' }, + updateOneRecordInput: { + tags: [ + 'tag1', + 'tag2', + 'tag2', // The second tag2 is not trimmed, so it remains as a duplicate + ], + }, + }, + }); + }); + expect(updateRecord).toHaveBeenCalledTimes(1); + }, +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/EmailsFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/EmailsFieldInput.stories.tsx index a25d89eba..943c1e89f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/EmailsFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/EmailsFieldInput.stories.tsx @@ -1,6 +1,6 @@ import { expect } from '@storybook/jest'; import { Meta, StoryObj } from '@storybook/react'; -import { fn, userEvent, within } from '@storybook/test'; +import { fn, userEvent, waitFor, within } from '@storybook/test'; import { useEffect } from 'react'; import { getCanvasElementForDropdownTesting } from 'twenty-ui/testing'; @@ -131,7 +131,42 @@ export const Default: Story = { }, }; -// FIXME: We will have to fix that behavior, we should only be able to set a secondary email as the primary email +export const TrimInput: Story = { + args: { + value: { + primaryEmail: 'john@example.com', + additionalEmails: [], + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const addButton = await canvas.findByText('Add Email'); + await userEvent.click(addButton); + + const input = await canvas.findByPlaceholderText('Email'); + await userEvent.type(input, ' new.email@example.com {enter}'); + + const newEmailElement = await canvas.findByText('new.email@example.com'); + expect(newEmailElement).toBeVisible(); + + await waitFor(() => { + expect(updateRecord).toHaveBeenCalledWith({ + variables: { + where: { id: 'record-id' }, + updateOneRecordInput: { + emails: { + primaryEmail: 'john@example.com', + additionalEmails: ['new.email@example.com'], + }, + }, + }, + }); + }); + expect(updateRecord).toHaveBeenCalledTimes(1); + }, +}; + export const CanNotSetPrimaryLinkAsPrimaryLink: Story = { args: { value: { diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/LinksFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/LinksFieldInput.stories.tsx index 3a2a46bf7..601580f2a 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/LinksFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/LinksFieldInput.stories.tsx @@ -253,6 +253,36 @@ export const CreatePrimaryLink: Story = { }, }; +export const TrimInput: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const input = await canvas.findByPlaceholderText('URL'); + await userEvent.type(input, ' https://www.twenty.com {enter}'); + + const linkDisplay = await canvas.findByText('twenty.com'); + expect(linkDisplay).toBeVisible(); + + await waitFor(() => { + expect(updateRecord).toHaveBeenCalledWith({ + variables: { + where: { id: '123' }, + updateOneRecordInput: { + links: { + primaryLinkUrl: 'https://www.twenty.com', + primaryLinkLabel: null, + secondaryLinks: [], + }, + }, + }, + }); + }); + expect(updateRecord).toHaveBeenCalledTimes(1); + + expect(getPrimaryLinkBookmarkIcon(canvasElement)).not.toBeInTheDocument(); + }, +}; + export const AddSecondaryLink: Story = { args: { value: { diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/PhonesFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/PhonesFieldInput.stories.tsx index 59b9a1e4b..a55b44b4a 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/PhonesFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/__stories__/PhonesFieldInput.stories.tsx @@ -1,6 +1,6 @@ import { expect } from '@storybook/jest'; import { Meta, StoryObj } from '@storybook/react'; -import { fn, userEvent, within } from '@storybook/test'; +import { fn, userEvent, waitFor, within } from '@storybook/test'; import { useEffect } from 'react'; import { getCanvasElementForDropdownTesting } from 'twenty-ui/testing'; @@ -140,7 +140,64 @@ export const Default: Story = { }, }; -// FIXME: We will have to fix that behavior, we should only be able to set an additional phone as the primary phone +export const TrimInput: Story = { + args: { + value: { + primaryPhoneCountryCode: 'FR', + primaryPhoneNumber: '642646272', + primaryPhoneCallingCode: '+33', + additionalPhones: [ + { + countryCode: 'FR', + number: '642646273', + callingCode: '+33', + }, + ], + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const addButton = await canvas.findByText('Add Phone'); + await userEvent.click(addButton); + + const input = await canvas.findByPlaceholderText('Phone'); + await userEvent.type(input, '+33642646274 {enter}'); + + const newPhoneElement = await canvas.findByText('+33 6 42 64 62 74'); + expect(newPhoneElement).toBeVisible(); + + // Verify the update was called with swapped phones + await waitFor(() => { + expect(updateRecord).toHaveBeenCalledWith({ + variables: { + where: { id: 'record-id' }, + updateOneRecordInput: { + phones: { + primaryPhoneCallingCode: '+33', + primaryPhoneCountryCode: 'FR', + primaryPhoneNumber: '642646272', + additionalPhones: [ + { + countryCode: 'FR', + number: '642646273', + callingCode: '+33', + }, + { + countryCode: 'FR', + number: '642646274', + callingCode: '+33', + }, + ], + }, + }, + }, + }); + }); + expect(updateRecord).toHaveBeenCalledTimes(1); + }, +}; + export const CanNotSetPrimaryLinkAsPrimaryLink: Story = { args: { value: {