diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/__tests__/getRecordNodeFromRecord.test.ts b/packages/twenty-front/src/modules/object-record/cache/utils/__tests__/getRecordNodeFromRecord.test.ts new file mode 100644 index 000000000000..43267e148fb4 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/cache/utils/__tests__/getRecordNodeFromRecord.test.ts @@ -0,0 +1,80 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { + mockedObjectMetadataItems, + mockedPersonObjectMetadataItem, +} from '~/testing/mock-data/metadata'; +import { mockedPeopleData } from '~/testing/mock-data/people'; + +import { getRecordNodeFromRecord } from '../getRecordNodeFromRecord'; + +describe('getRecordNodeFromRecord', () => { + it('computes relation records cache references by default', () => { + // Given + const objectMetadataItems: ObjectMetadataItem[] = mockedObjectMetadataItems; + const objectMetadataItem: Pick< + ObjectMetadataItem, + 'fields' | 'namePlural' | 'nameSingular' + > = mockedPersonObjectMetadataItem; + const recordGqlFields = { + name: true, + company: true, + }; + const record = mockedPeopleData[0]; + + // When + const result = getRecordNodeFromRecord({ + objectMetadataItems, + objectMetadataItem, + recordGqlFields, + record, + }); + + // Then + expect(result).toEqual({ + __typename: 'Person', + company: { + __ref: 'Company:5c21e19e-e049-4393-8c09-3e3f8fb09ecb', + }, + name: { + __typename: 'FullName', + firstName: 'Alexandre', + lastName: 'Prot', + }, + }); + }); + + it('does not compute relation records cache references when `computeReferences` is false', () => { + // Given + const objectMetadataItems: ObjectMetadataItem[] = mockedObjectMetadataItems; + const objectMetadataItem: Pick< + ObjectMetadataItem, + 'fields' | 'namePlural' | 'nameSingular' + > = mockedPersonObjectMetadataItem; + const recordGqlFields = { + name: true, + company: true, + }; + const record = mockedPeopleData[0]; + const computeReferences = false; + + // When + const result = getRecordNodeFromRecord({ + objectMetadataItems, + objectMetadataItem, + recordGqlFields, + record, + computeReferences, + }); + + // Then + expect(result).toEqual({ + __typename: 'Person', + company: record.company, + name: { + __typename: 'FullName', + firstName: 'Alexandre', + lastName: 'Prot', + }, + }); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts b/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts index f807b880dd78..f162131f526c 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts @@ -11,7 +11,7 @@ import { RelationDefinitionType, } from '~/generated-metadata/graphql'; import { isDefined } from '~/utils/isDefined'; -import { lowerAndCapitalize } from '~/utils/string/lowerAndCapitalize'; +import { pascalCase } from '~/utils/string/pascalCase'; export const getRecordNodeFromRecord = ({ objectMetadataItems, @@ -129,6 +129,7 @@ export const getRecordNodeFromRecord = ({ ]; } case FieldMetadataType.Link: + case FieldMetadataType.Links: case FieldMetadataType.Address: case FieldMetadataType.FullName: case FieldMetadataType.Currency: { @@ -136,7 +137,7 @@ export const getRecordNodeFromRecord = ({ fieldName, { ...value, - __typename: lowerAndCapitalize(field.type), + __typename: pascalCase(field.type), }, ]; } diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts index 06da3c62372d..f3b8915a4259 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts @@ -49,11 +49,11 @@ export const responseData = { personDuplicates: { edges: [ { - node: { __typename: 'Person', ...mockedPeopleData[0], updatedAt: '' }, + node: { ...mockedPeopleData[0], updatedAt: '' }, cursor: 'cursor1', }, { - node: { __typename: 'Person', ...mockedPeopleData[1], updatedAt: '' }, + node: { ...mockedPeopleData[1], updatedAt: '' }, cursor: 'cursor2', }, ], diff --git a/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts b/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts index 7101d4deea93..3f6c58370c97 100644 --- a/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts +++ b/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts @@ -19,7 +19,7 @@ export const generateEmptyFieldValue = ( }; } case FieldMetadataType.Links: { - return { primaryLinkUrl: '', primaryLinkLabel: '' }; + return { primaryLinkUrl: '', primaryLinkLabel: '', secondaryLinks: null }; } case FieldMetadataType.FullName: { return { diff --git a/packages/twenty-front/src/testing/mock-data/people.ts b/packages/twenty-front/src/testing/mock-data/people.ts index b459b9b2518d..9db0b32ead31 100644 --- a/packages/twenty-front/src/testing/mock-data/people.ts +++ b/packages/twenty-front/src/testing/mock-data/people.ts @@ -8,6 +8,7 @@ type RequiredAndNotNull = { type MockedPerson = RequiredAndNotNull< Pick< Person, + | '__typename' | 'id' | 'name' | 'linkedinLink' @@ -20,12 +21,13 @@ type MockedPerson = RequiredAndNotNull< | 'createdAt' | 'companyId' > & { - company: Pick; + company: Pick; } >; export const mockedPeopleData: MockedPerson[] = [ { + __typename: 'Person', id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', name: { firstName: 'Alexandre', @@ -45,6 +47,7 @@ export const mockedPeopleData: MockedPerson[] = [ jobTitle: 'CEO', companyId: '5c21e19e-e049-4393-8c09-3e3f8fb09ecb', company: { + __typename: 'Company', id: '5c21e19e-e049-4393-8c09-3e3f8fb09ecb', name: 'Qonto', domainName: 'qonto.com', @@ -54,6 +57,7 @@ export const mockedPeopleData: MockedPerson[] = [ city: 'Paris', }, { + __typename: 'Person', id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6d', name: { firstName: 'John', lastName: 'Doe' }, linkedinLink: { @@ -69,6 +73,7 @@ export const mockedPeopleData: MockedPerson[] = [ email: 'john@linkedin.com', companyId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6e', company: { + __typename: 'Company', id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6e', name: 'LinkedIn', domainName: 'linkedin.com', @@ -78,6 +83,7 @@ export const mockedPeopleData: MockedPerson[] = [ city: 'Paris', }, { + __typename: 'Person', id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6f', name: { firstName: 'Jane', @@ -96,6 +102,7 @@ export const mockedPeopleData: MockedPerson[] = [ email: 'jane@sequoiacap.com', companyId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6g', company: { + __typename: 'Company', id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6g', name: 'Sequoia', domainName: 'sequoiacap.com', @@ -105,6 +112,7 @@ export const mockedPeopleData: MockedPerson[] = [ city: 'Paris', }, { + __typename: 'Person', id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6h', name: { firstName: 'Janice', @@ -123,6 +131,7 @@ export const mockedPeopleData: MockedPerson[] = [ jobTitle: 'CEO', companyId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6i', company: { + __typename: 'Company', id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6i', name: 'Facebook', domainName: 'facebook.com', diff --git a/packages/twenty-front/src/utils/string/__tests__/pascalCase.test.ts b/packages/twenty-front/src/utils/string/__tests__/pascalCase.test.ts new file mode 100644 index 000000000000..61d32f07cbe0 --- /dev/null +++ b/packages/twenty-front/src/utils/string/__tests__/pascalCase.test.ts @@ -0,0 +1,58 @@ +import { pascalCase } from '../pascalCase'; + +describe('pascalCase', () => { + it('converts a string to pascal case', () => { + // Given + const input = 'HELLO_WORLD'; + + // When + const result = pascalCase(input); + + // Then + expect(result).toBe('HelloWorld'); + }); + + it('handles empty strings', () => { + // Given + const input = ''; + + // When + const result = pascalCase(input); + + // Then + expect(result).toBe(''); + }); + + it('handles strings with only one word', () => { + // Given + const input = 'hello'; + + // When + const result = pascalCase(input); + + // Then + expect(result).toBe('Hello'); + }); + + it('handles strings with several words, spaces and special characters', () => { + // Given + const input = '& Hello world! How are you today? #'; + + // When + const result = pascalCase(input); + + // Then + expect(result).toBe('HelloWorldHowAreYouToday'); + }); + + it('handles strings with leading and trailing spaces', () => { + // Given + const input = ' hello_world '; + + // When + const result = pascalCase(input); + + // Then + expect(result).toBe('HelloWorld'); + }); +}); diff --git a/packages/twenty-front/src/utils/string/lowerAndCapitalize.ts b/packages/twenty-front/src/utils/string/lowerAndCapitalize.ts deleted file mode 100644 index f51325a09d9f..000000000000 --- a/packages/twenty-front/src/utils/string/lowerAndCapitalize.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { isNonEmptyString } from '@sniptt/guards'; - -export const lowerAndCapitalize = (stringToCapitalize: string) => { - if (!isNonEmptyString(stringToCapitalize)) return ''; - - const loweredString = stringToCapitalize.toLowerCase(); - - return loweredString[0].toUpperCase() + loweredString.slice(1); -}; diff --git a/packages/twenty-front/src/utils/string/pascalCase.ts b/packages/twenty-front/src/utils/string/pascalCase.ts new file mode 100644 index 000000000000..e25552af4a44 --- /dev/null +++ b/packages/twenty-front/src/utils/string/pascalCase.ts @@ -0,0 +1,5 @@ +import camelCase from 'lodash.camelcase'; + +import { capitalize } from '~/utils/string/capitalize'; + +export const pascalCase = (str: string) => capitalize(camelCase(str));