From 2e4c59664488bfbe5342f8f73d0ea17c366102dc Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Mon, 3 Mar 2025 10:22:26 +0100 Subject: [PATCH] [TEST] Covering `useDeleteOne` relations optimistic cache behavior (#10238) ## Introduction Added coverage on the `useDeleteOneRecord` hooks, especially its optimistic behavior feature. Introduced a new testing tool `InMemoryTestingCacheInstance` that has builtin very basic expectors in order to avoid future duplication when covering others record hooks `update, create, destroy` etc etc ## Notes Added few comments in this PR regarding some builtin functions I've created around companies and people mocked object model and that I think could be cool to spread and centralize within a dedicated "class template" Also put in light that unless I'm mistaken some tests are running on `RecordNode` and not `RecordObject` Took few directions on my own that as I always I would suggestion nor remarks on them ! Let me know ## Misc - Should we refactor `useDeleteOneRecord` tests to follow `eachTesting` pattern ? => I feel like this is inappropriate as this hooks is already high level, the only plus value would be less tests code despite readability IMO --- .../useDeleteMultipleRecordsAction.test.tsx | 4 +- .../useDestroyMultipleRecordsAction.test.tsx | 12 +- .../useExportMultipleRecordsAction.test.tsx | 4 +- ...eAddToFavoritesSingleRecordAction.test.tsx | 4 +- .../useDeleteSingleRecordAction.test.tsx | 4 +- ...veFromFavoritesSingleRecordAction.test.tsx | 4 +- .../__tests__/useDefaultHomePagePath.test.ts | 4 +- .../__tests__/getRecordNodeFromRecord.test.ts | 4 +- ...eDepthOneRecordGqlFieldsFromRecord.test.ts | 8 +- .../generateDepthOneRecordGqlFields.test.ts | 4 +- .../hooks/__mocks__/useDeleteManyRecords.ts | 4 +- .../hooks/__mocks__/useDeleteOneRecord.ts | 12 +- .../__mocks__/useFindDuplicateRecords.ts | 4 +- .../useDeleteOneRecord.test.tsx.snap | 624 ++++++ .../__tests__/useDeleteManyRecords.test.tsx | 4 +- .../__tests__/useDeleteOneRecord.test.tsx | 233 +- .../useLazyLoadRecordIndexTable.test.tsx | 5 +- .../SingleRecordPicker.stories.tsx | 20 +- .../RecordDetailRelationSection.stories.tsx | 8 +- .../computeOptimisticRecordFromInput.test.ts | 28 +- ...flowEditActionFormDeleteRecord.stories.tsx | 6 +- ...flowEditActionFormUpdateRecord.stories.tsx | 6 +- .../__stories__/RecordShowPage.stories.tsx | 12 +- .../cache/inMemoryTestingCacheInstance.ts | 113 + .../decorators/ContextStoreDecorator.tsx | 4 +- .../testing/decorators/getFieldDecorator.tsx | 4 +- .../twenty-front/src/testing/graphqlMocks.ts | 4 +- .../src/testing/mock-data/companies.ts | 119 +- .../mock-data/companiesWithRelations.ts | 1882 +++++++++++++++++ .../src/testing/mock-data/people.ts | 134 +- 30 files changed, 2989 insertions(+), 289 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/hooks/__tests__/__snapshots__/useDeleteOneRecord.test.tsx.snap create mode 100644 packages/twenty-front/src/testing/cache/inMemoryTestingCacheInstance.ts create mode 100644 packages/twenty-front/src/testing/mock-data/companiesWithRelations.ts diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx index 495ce98b4..716f96d4a 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx @@ -4,14 +4,14 @@ import { renderHook, waitFor } from '@testing-library/react'; import { act } from 'react'; import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people'; import { useDeleteMultipleRecordsAction } from '../useDeleteMultipleRecordsAction'; const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'person', )!; -const peopleMock = getPeopleMock(); +const peopleMock = getPeopleRecordConnectionMock(); const deleteManyRecordsMock = jest.fn(); const resetTableRowSelectionMock = jest.fn(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDestroyMultipleRecordsAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDestroyMultipleRecordsAction.test.tsx index 4817da899..a2b8010f6 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDestroyMultipleRecordsAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDestroyMultipleRecordsAction.test.tsx @@ -9,7 +9,7 @@ import { getJestMetadataAndApolloMocksAndActionMenuWrapper, } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people'; import { useDestroyMultipleRecordsAction } from '../useDestroyMultipleRecordsAction'; const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( @@ -20,10 +20,12 @@ const personMockObjectMetadataItemDeletedAtField = if (personMockObjectMetadataItemDeletedAtField === undefined) throw new Error('Should never occur'); -const [firstPeopleMock, secondPeopleMock] = getPeopleMock().map((record) => ({ - ...record, - deletedAt: new Date().toISOString(), -})); +const [firstPeopleMock, secondPeopleMock] = getPeopleRecordConnectionMock().map( + (record) => ({ + ...record, + deletedAt: new Date().toISOString(), + }), +); const destroyManyRecordsMock = jest.fn(); const resetTableRowSelectionMock = jest.fn(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx index 4317a51e8..ad3891698 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx @@ -3,14 +3,14 @@ import { renderHook, waitFor } from '@testing-library/react'; import { act } from 'react'; import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people'; import { useExportMultipleRecordsAction } from '../useExportMultipleRecordsAction'; const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'person', )!; -const peopleMock = getPeopleMock(); +const peopleMock = getPeopleRecordConnectionMock(); const downloadMock = jest.fn(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx index 8c7ec34e7..974dd2b63 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useAddToFavoritesSingleRecordAction.test.tsx @@ -6,14 +6,14 @@ import { getJestMetadataAndApolloMocksAndActionMenuWrapper, } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people'; import { useAddToFavoritesSingleRecordAction } from '../useAddToFavoritesSingleRecordAction'; const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'person', )!; -const peopleMock = getPeopleMock(); +const peopleMock = getPeopleRecordConnectionMock(); const favoritesMock = [ { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx index f357c0234..167d60ca5 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx @@ -3,14 +3,14 @@ import { renderHook } from '@testing-library/react'; import { act } from 'react'; import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people'; import { useDeleteSingleRecordAction } from '../useDeleteSingleRecordAction'; const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'person', )!; -const peopleMock = getPeopleMock(); +const peopleMock = getPeopleRecordConnectionMock(); const deleteOneRecordMock = jest.fn(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx index 702f45d98..c22406e15 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useRemoveFromFavoritesSingleRecordAction.test.tsx @@ -6,14 +6,14 @@ import { getJestMetadataAndApolloMocksAndActionMenuWrapper, } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people'; import { useRemoveFromFavoritesSingleRecordAction } from '../useRemoveFromFavoritesSingleRecordAction'; const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'person', )!; -const peopleMock = getPeopleMock(); +const peopleMock = getPeopleRecordConnectionMock(); const favoritesMock = [ { diff --git a/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts b/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts index 0e294fc66..23774ae8a 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts @@ -9,7 +9,7 @@ import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState'; import { AppPath } from '@/types/AppPath'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; import { ViewType } from '@/views/types/ViewType'; -import { getCompanyObjectMetadataItem } from '~/testing/mock-data/companies'; +import { getMockCompanyObjectMetadataItem } from '~/testing/mock-data/companies'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { mockedUserData } from '~/testing/mock-data/users'; @@ -35,7 +35,7 @@ const renderHooks = ({ { id: 'viewId', name: 'Test View', - objectMetadataId: getCompanyObjectMetadataItem().id, + objectMetadataId: getMockCompanyObjectMetadataItem().id, type: ViewType.Table, key: null, isCompact: false, 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 index 4641974a0..75fb1dcf0 100644 --- 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 @@ -1,11 +1,11 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { getRecordNodeFromRecord } from '../getRecordNodeFromRecord'; -const peopleMock = getPeopleMock(); +const peopleMock = getPeopleRecordConnectionMock(); describe('getRecordNodeFromRecord', () => { it('computes relation records cache references by default', () => { diff --git a/packages/twenty-front/src/modules/object-record/graphql/utils/__tests__/computeDepthOneRecordGqlFieldsFromRecord.test.ts b/packages/twenty-front/src/modules/object-record/graphql/utils/__tests__/computeDepthOneRecordGqlFieldsFromRecord.test.ts index 5f5de3f64..87eb9c254 100644 --- a/packages/twenty-front/src/modules/object-record/graphql/utils/__tests__/computeDepthOneRecordGqlFieldsFromRecord.test.ts +++ b/packages/twenty-front/src/modules/object-record/graphql/utils/__tests__/computeDepthOneRecordGqlFieldsFromRecord.test.ts @@ -1,13 +1,13 @@ import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord'; import { - getPersonObjectMetadataItem, - getPersonRecord, + allMockPersonRecords, + getMockPersonObjectMetadataItem, } from '~/testing/mock-data/people'; describe('computeDepthOneRecordGqlFieldsFromRecord', () => { - const objectMetadataItem = getPersonObjectMetadataItem(); + const objectMetadataItem = getMockPersonObjectMetadataItem(); it('Should handle basic call', () => { - const personRecord = getPersonRecord(); + const personRecord = allMockPersonRecords[0]; const result = computeDepthOneRecordGqlFieldsFromRecord({ objectMetadataItem, record: personRecord, diff --git a/packages/twenty-front/src/modules/object-record/graphql/utils/__tests__/generateDepthOneRecordGqlFields.test.ts b/packages/twenty-front/src/modules/object-record/graphql/utils/__tests__/generateDepthOneRecordGqlFields.test.ts index 78f7b9692..8bde1d235 100644 --- a/packages/twenty-front/src/modules/object-record/graphql/utils/__tests__/generateDepthOneRecordGqlFields.test.ts +++ b/packages/twenty-front/src/modules/object-record/graphql/utils/__tests__/generateDepthOneRecordGqlFields.test.ts @@ -1,8 +1,8 @@ import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; -import { getPersonObjectMetadataItem } from '~/testing/mock-data/people'; +import { getMockPersonObjectMetadataItem } from '~/testing/mock-data/people'; describe('generateDepthOneRecordGqlFields', () => { - const objectMetadataItem = getPersonObjectMetadataItem(); + const objectMetadataItem = getMockPersonObjectMetadataItem(); it('Should handle basic call with standalone objectMetadataItem', () => { const result = generateDepthOneRecordGqlFields({ objectMetadataItem, diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteManyRecords.ts index 7228945ac..dba5c1fef 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteManyRecords.ts @@ -1,6 +1,6 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { gql } from '@apollo/client'; -import { getPersonRecord } from '~/testing/mock-data/people'; +import { getMockPersonRecord } from '~/testing/mock-data/people'; export const query = gql` mutation DeleteManyPeople($filter: PersonFilterInput!) { @@ -17,7 +17,7 @@ export const personIds = [ ]; export const personRecords = personIds.map((personId, index) => - getPersonRecord({ id: personId, deletedAt: null }, index), + getMockPersonRecord({ id: personId, deletedAt: null }, index), ); export const variables = { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts index b04513123..8c042b848 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts @@ -8,14 +8,4 @@ export const query = gql` id } } -`; - -export const variables = { - idToDelete: 'a7286b9a-c039-4a89-9567-2dfa7953cda9', -}; - -export const responseData = { - __typename: 'Person', - deletedAt: '2024-02-14T09:45:00Z', - id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9', -}; +`; \ No newline at end of file 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 784e178fc..929c016a3 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 @@ -1,8 +1,8 @@ import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { gql } from '@apollo/client'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people'; -const peopleMock = getPeopleMock(); +const peopleMock = getPeopleRecordConnectionMock(); export const query = gql` query FindDuplicatePerson($ids: [ID!]!) { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/__snapshots__/useDeleteOneRecord.test.tsx.snap b/packages/twenty-front/src/modules/object-record/hooks/__tests__/__snapshots__/useDeleteOneRecord.test.tsx.snap new file mode 100644 index 000000000..aeecd9676 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/__snapshots__/useDeleteOneRecord.test.tsx.snap @@ -0,0 +1,624 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`useDeleteOneRecord A. Starting from empty cache 1. Should successfully delete record and update record cache entry 1`] = ` +{ + "__typename": "Person", + "deletedAt": "2024-02-14T09:45:00Z", + "id": "da3c2c4b-da01-4b81-9734-226069eb4cd0", +} +`; + +exports[`useDeleteOneRecord B. Starting from filled cache 1. Should handle successfull record deletion 1`] = ` +{ + "__typename": "Person", + "city": "ASd", + "company": { + "__typename": "Company", + "address": { + "__typename": "Address", + "addressCity": "Dublin", + "addressCountry": "Ireland", + "addressLat": null, + "addressLng": null, + "addressPostcode": null, + "addressState": null, + "addressStreet1": "Eutaw Street", + "addressStreet2": null, + }, + "createdAt": "2025-02-16T08:21:51.715Z", + "createdBy": { + "__typename": "Actor", + "context": {}, + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": null, + "domainName": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "https://linkedin.com", + "secondaryLinks": [], + }, + "employees": null, + "id": "20202020-3ec3-4fe3-8997-b76aa0bfa408", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": "Linkedin", + "position": 1, + }, + "createdAt": "2024-08-02T09:52:46.814Z", + "createdBy": { + "__typename": "Actor", + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": "2024-02-14T09:45:00Z", + "id": "da3c2c4b-da01-4b81-9734-226069eb4cd0", + "jobTitle": "", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": { + "__typename": "FullName", + "firstName": "Test", + "lastName": "Test", + }, + "noteTargets": [], + "phones": { + "primaryPhoneCallingCode": "+33", + "primaryPhoneCountryCode": "FR", + "primaryPhoneNumber": "781234562", + }, + "position": 0, + "taskTargets": [], + "xLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, +} +`; + +exports[`useDeleteOneRecord B. Starting from filled cache 1. Should handle successfull record deletion 2`] = ` +{ + "__typename": "Company", + "accountOwner": null, + "address": { + "__typename": "Address", + "addressCity": "Dublin", + "addressCountry": "Ireland", + "addressLat": null, + "addressLng": null, + "addressPostcode": null, + "addressState": null, + "addressStreet1": "Eutaw Street", + "addressStreet2": null, + }, + "createdAt": "2025-02-16T08:21:51.715Z", + "createdBy": { + "__typename": "Actor", + "context": {}, + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": null, + "domainName": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "https://linkedin.com", + "secondaryLinks": [], + }, + "employees": null, + "id": "20202020-3ec3-4fe3-8997-b76aa0bfa408", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": "Linkedin", + "noteTargets": [], + "opportunities": [ + {}, + ], + "people": [ + { + "__typename": "Person", + "city": "Seattle", + "createdAt": "2024-08-01T09:50:00.000Z", + "createdBy": { + "__typename": "Actor", + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": null, + "id": "20202020-1c0e-494c-a1b6-85b1c6fefaa5", + "jobTitle": "", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": { + "__typename": "FullName", + "firstName": "Christoph", + "lastName": "Callisto", + }, + "phones": { + "primaryPhoneCallingCode": "+33", + "primaryPhoneCountryCode": "FR", + "primaryPhoneNumber": "781234562", + }, + "position": 1, + "xLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + }, + { + "__typename": "Person", + "city": "Los Angeles", + "createdAt": "2024-08-02T09:48:36.193Z", + "createdBy": { + "__typename": "Actor", + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": null, + "id": "20202020-ac73-4797-824e-87a1f5aea9e0", + "jobTitle": "", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": { + "__typename": "FullName", + "firstName": "Sylvie", + "lastName": "Palmer", + }, + "phones": { + "primaryPhoneCallingCode": "+33", + "primaryPhoneCountryCode": "FR", + "primaryPhoneNumber": "781234576", + }, + "position": 2, + "xLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + }, + ], + "position": 1, + "taskTargets": [], +} +`; + +exports[`useDeleteOneRecord B. Starting from filled cache 2. Should handle optimistic cache on record deletion 1`] = ` +{ + "__typename": "Person", + "city": "ASd", + "company": { + "__typename": "Company", + "address": { + "__typename": "Address", + "addressCity": "Dublin", + "addressCountry": "Ireland", + "addressLat": null, + "addressLng": null, + "addressPostcode": null, + "addressState": null, + "addressStreet1": "Eutaw Street", + "addressStreet2": null, + }, + "createdAt": "2025-02-16T08:21:51.715Z", + "createdBy": { + "__typename": "Actor", + "context": {}, + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": null, + "domainName": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "https://linkedin.com", + "secondaryLinks": [], + }, + "employees": null, + "id": "20202020-3ec3-4fe3-8997-b76aa0bfa408", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": "Linkedin", + "position": 1, + }, + "createdAt": "2024-08-02T09:52:46.814Z", + "createdBy": { + "__typename": "Actor", + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": Any, + "id": "da3c2c4b-da01-4b81-9734-226069eb4cd0", + "jobTitle": "", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": { + "__typename": "FullName", + "firstName": "Test", + "lastName": "Test", + }, + "noteTargets": [], + "phones": { + "primaryPhoneCallingCode": "+33", + "primaryPhoneCountryCode": "FR", + "primaryPhoneNumber": "781234562", + }, + "position": 0, + "taskTargets": [], + "xLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, +} +`; + +exports[`useDeleteOneRecord B. Starting from filled cache 2. Should handle optimistic cache on record deletion 2`] = ` +{ + "__typename": "Company", + "accountOwner": null, + "address": { + "__typename": "Address", + "addressCity": "Dublin", + "addressCountry": "Ireland", + "addressLat": null, + "addressLng": null, + "addressPostcode": null, + "addressState": null, + "addressStreet1": "Eutaw Street", + "addressStreet2": null, + }, + "createdAt": "2025-02-16T08:21:51.715Z", + "createdBy": { + "__typename": "Actor", + "context": {}, + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": null, + "domainName": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "https://linkedin.com", + "secondaryLinks": [], + }, + "employees": null, + "id": "20202020-3ec3-4fe3-8997-b76aa0bfa408", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": "Linkedin", + "noteTargets": [], + "opportunities": [ + {}, + ], + "people": [ + { + "__typename": "Person", + "city": "Seattle", + "createdAt": "2024-08-01T09:50:00.000Z", + "createdBy": { + "__typename": "Actor", + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": null, + "id": "20202020-1c0e-494c-a1b6-85b1c6fefaa5", + "jobTitle": "", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": { + "__typename": "FullName", + "firstName": "Christoph", + "lastName": "Callisto", + }, + "phones": { + "primaryPhoneCallingCode": "+33", + "primaryPhoneCountryCode": "FR", + "primaryPhoneNumber": "781234562", + }, + "position": 1, + "xLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + }, + { + "__typename": "Person", + "city": "Los Angeles", + "createdAt": "2024-08-02T09:48:36.193Z", + "createdBy": { + "__typename": "Actor", + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": null, + "id": "20202020-ac73-4797-824e-87a1f5aea9e0", + "jobTitle": "", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": { + "__typename": "FullName", + "firstName": "Sylvie", + "lastName": "Palmer", + }, + "phones": { + "primaryPhoneCallingCode": "+33", + "primaryPhoneCountryCode": "FR", + "primaryPhoneNumber": "781234576", + }, + "position": 2, + "xLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + }, + ], + "position": 1, + "taskTargets": [], +} +`; + +exports[`useDeleteOneRecord B. Starting from filled cache 3. Should handle optimistic cache rollback on record deletion failure 1`] = ` +{ + "__typename": "Person", + "city": "ASd", + "company": { + "__typename": "Company", + "address": { + "__typename": "Address", + "addressCity": "Dublin", + "addressCountry": "Ireland", + "addressLat": null, + "addressLng": null, + "addressPostcode": null, + "addressState": null, + "addressStreet1": "Eutaw Street", + "addressStreet2": null, + }, + "createdAt": "2025-02-16T08:21:51.715Z", + "createdBy": { + "__typename": "Actor", + "context": {}, + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": null, + "domainName": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "https://linkedin.com", + "secondaryLinks": [], + }, + "employees": null, + "id": "20202020-3ec3-4fe3-8997-b76aa0bfa408", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": "Linkedin", + "position": 1, + }, + "createdAt": "2024-08-02T09:52:46.814Z", + "createdBy": { + "__typename": "Actor", + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": null, + "id": "da3c2c4b-da01-4b81-9734-226069eb4cd0", + "jobTitle": "", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": { + "__typename": "FullName", + "firstName": "Test", + "lastName": "Test", + }, + "noteTargets": [], + "phones": { + "primaryPhoneCallingCode": "+33", + "primaryPhoneCountryCode": "FR", + "primaryPhoneNumber": "781234562", + }, + "position": 0, + "taskTargets": [], + "xLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, +} +`; + +exports[`useDeleteOneRecord B. Starting from filled cache 3. Should handle optimistic cache rollback on record deletion failure 2`] = ` +{ + "__typename": "Company", + "accountOwner": null, + "address": { + "__typename": "Address", + "addressCity": "Dublin", + "addressCountry": "Ireland", + "addressLat": null, + "addressLng": null, + "addressPostcode": null, + "addressState": null, + "addressStreet1": "Eutaw Street", + "addressStreet2": null, + }, + "createdAt": "2025-02-16T08:21:51.715Z", + "createdBy": { + "__typename": "Actor", + "context": {}, + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": null, + "domainName": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "https://linkedin.com", + "secondaryLinks": [], + }, + "employees": null, + "id": "20202020-3ec3-4fe3-8997-b76aa0bfa408", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": "Linkedin", + "noteTargets": [], + "opportunities": [ + {}, + ], + "people": [ + { + "__typename": "Person", + "city": "Seattle", + "createdAt": "2024-08-01T09:50:00.000Z", + "createdBy": { + "__typename": "Actor", + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": null, + "id": "20202020-1c0e-494c-a1b6-85b1c6fefaa5", + "jobTitle": "", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": { + "__typename": "FullName", + "firstName": "Christoph", + "lastName": "Callisto", + }, + "phones": { + "primaryPhoneCallingCode": "+33", + "primaryPhoneCountryCode": "FR", + "primaryPhoneNumber": "781234562", + }, + "position": 1, + "xLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + }, + { + "__typename": "Person", + "city": "Los Angeles", + "createdAt": "2024-08-02T09:48:36.193Z", + "createdBy": { + "__typename": "Actor", + "name": "Tim Apple", + "source": "MANUAL", + "workspaceMemberId": "20202020-0687-4c41-b707-ed1bfca972a7", + }, + "deletedAt": null, + "id": "20202020-ac73-4797-824e-87a1f5aea9e0", + "jobTitle": "", + "linkedinLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + "name": { + "__typename": "FullName", + "firstName": "Sylvie", + "lastName": "Palmer", + }, + "phones": { + "primaryPhoneCallingCode": "+33", + "primaryPhoneCountryCode": "FR", + "primaryPhoneNumber": "781234576", + }, + "position": 2, + "xLink": { + "__typename": "Links", + "primaryLinkLabel": "", + "primaryLinkUrl": "", + "secondaryLinks": [], + }, + }, + ], + "position": 1, + "taskTargets": [], +} +`; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx index 11e43b351..c2d8ae980 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx @@ -17,7 +17,7 @@ import { InMemoryCache } from '@apollo/client'; import { MockedResponse } from '@apollo/client/testing'; import { act } from 'react'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; -import { getPersonObjectMetadataItem } from '~/testing/mock-data/people'; +import { getMockPersonObjectMetadataItem } from '~/testing/mock-data/people'; const getDefaultMocks = ( overrides?: Partial, ): MockedResponse[] => [ @@ -40,7 +40,7 @@ const mockRefetchAggregateQueries = jest.fn(); (useRefetchAggregateQueries as jest.Mock).mockReturnValue({ refetchAggregateQueries: mockRefetchAggregateQueries, }); -const objectMetadataItem = getPersonObjectMetadataItem(); +const objectMetadataItem = getMockPersonObjectMetadataItem(); const objectMetadataItems = [objectMetadataItem]; const expectedCachedRecordsWithDeletedAt = personRecords.map( (personRecord) => ({ diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecord.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecord.test.tsx index 50704f96b..1f3ec8974 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecord.test.tsx @@ -1,24 +1,24 @@ import { renderHook, waitFor } from '@testing-library/react'; import { act } from 'react'; -import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache'; -import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache'; -import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord'; -import { - query, - responseData, - variables, -} from '@/object-record/hooks/__mocks__/useDeleteOneRecord'; +import { query } from '@/object-record/hooks/__mocks__/useDeleteOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { InMemoryCache } from '@apollo/client'; import { MockedResponse } from '@apollo/client/testing'; import { expect } from '@storybook/jest'; +import { InMemoryTestingCacheInstance } from '~/testing/cache/inMemoryTestingCacheInstance'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { getMockCompanyObjectMetadataItem } from '~/testing/mock-data/companies'; import { - getPersonObjectMetadataItem, - getPersonRecord, + allMockCompanyRecordsWithRelation, + findMockCompanyWithRelationRecord, +} from '~/testing/mock-data/companiesWithRelations'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { + allMockPersonRecords, + getMockPersonObjectMetadataItem, + getMockPersonRecord, } from '~/testing/mock-data/people'; jest.mock('@/object-record/hooks/useRefetchAggregateQueries'); @@ -27,64 +27,63 @@ const mockRefetchAggregateQueries = jest.fn(); refetchAggregateQueries: mockRefetchAggregateQueries, }); -// TODO Should test relation deletion cache hydratation describe('useDeleteOneRecord', () => { - let cache: InMemoryCache; + const personRecord = getMockPersonRecord({ + deletedAt: null, + }); + const relatedCompanyRecord = findMockCompanyWithRelationRecord({ + id: personRecord.company.id, + }); + const personObjectMetadataItem = getMockPersonObjectMetadataItem(); + const companyObjectMetadataItem = getMockCompanyObjectMetadataItem(); + const objectMetadataItems = generatedMockObjectMetadataItems; + const getDefaultMocks = ( overrides?: Partial, - ): MockedResponse[] => [ - { + ): MockedResponse[] => { + const deleteOneQueryMock: MockedResponse< + Record, + Record<'idToDelete', string> + > = { request: { + variables: { idToDelete: personRecord.id }, query, - variables, }, - result: jest.fn(() => ({ + result: jest.fn((variables) => ({ data: { - deletePerson: responseData, + deletePerson: { + __typename: 'Person', + deletedAt: '2024-02-14T09:45:00Z', + id: variables.idToDelete, + }, }, })), ...overrides, - }, - ]; - const defaultMocks = getDefaultMocks(); - const personRecord = getPersonRecord({ - id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9', - deletedAt: null, - }); - const objectMetadataItem = getPersonObjectMetadataItem(); - const objectMetadataItems = [objectMetadataItem]; - const assertCachedRecordMatch = (expectedRecord: ObjectRecord) => { - const cachedRecord = getRecordFromCache({ - cache, - objectMetadataItem, - objectMetadataItems, - recordId: personRecord.id, - }); - expect(cachedRecord).not.toBeNull(); - if (cachedRecord === null) throw new Error('Should never occur'); - // Find a way to reverse assertion - expect(expectedRecord).toMatchObject(cachedRecord); + }; + return [deleteOneQueryMock]; }; - const assertCachedRecordIsNull = () => - expect( - getRecordFromCache({ - cache, - objectMetadataItem, - objectMetadataItems, - recordId: personRecord.id, - }), - ).toBeNull(); + const defaultMocks = getDefaultMocks(); + beforeEach(() => { jest.clearAllMocks(); - cache = new InMemoryCache(); }); describe('A. Starting from empty cache', () => { + const { + cache, + assertCachedRecordIsNull, + assertCachedRecordMatchSnapshot, + restoreCacheToInitialState, + } = new InMemoryTestingCacheInstance({ + objectMetadataItems, + }); + beforeEach(async () => restoreCacheToInitialState()); + it('1. Should successfully delete record and update record cache entry', async () => { const { result } = renderHook( () => useDeleteOneRecord({ - objectNameSingular: objectMetadataItem.nameSingular, + objectNameSingular: personObjectMetadataItem.nameSingular, }), { wrapper: getJestMetadataAndApolloMocksWrapper({ @@ -98,13 +97,22 @@ describe('useDeleteOneRecord', () => { const deleteOneResult = await result.current.deleteOneRecord( personRecord.id, ); - const expectedResult: ObjectRecord = { + expect(deleteOneResult).toStrictEqual({ __typename: personRecord.__typename, deletedAt: expect.any(String), id: personRecord.id, - }; - expect(deleteOneResult).toStrictEqual(expectedResult); - assertCachedRecordMatch(expectedResult); + }); + assertCachedRecordMatchSnapshot({ + recordId: personRecord.id, + objectMetadataItem: personObjectMetadataItem, + matchObject: { + deletedAt: expect.any(String), + }, + }); + assertCachedRecordIsNull({ + objectMetadataItem: companyObjectMetadataItem, + recordId: personRecord.company.id, + }); }); expect(defaultMocks[0].result).toHaveBeenCalled(); @@ -115,10 +123,11 @@ describe('useDeleteOneRecord', () => { const apolloMocks: MockedResponse[] = getDefaultMocks({ delay: Number.POSITIVE_INFINITY, }); + expect(personRecord).toHaveProperty('company'); const { result } = renderHook( () => useDeleteOneRecord({ - objectNameSingular: objectMetadataItem.nameSingular, + objectNameSingular: personObjectMetadataItem.nameSingular, }), { wrapper: getJestMetadataAndApolloMocksWrapper({ @@ -131,7 +140,14 @@ describe('useDeleteOneRecord', () => { await act(async () => { result.current.deleteOneRecord(personRecord.id); await waitFor(() => { - assertCachedRecordIsNull(); + assertCachedRecordIsNull({ + recordId: personRecord.id, + objectMetadataItem: personObjectMetadataItem, + }); + assertCachedRecordIsNull({ + recordId: personRecord.company.id, + objectMetadataItem: companyObjectMetadataItem, + }); }); }); @@ -146,7 +162,7 @@ describe('useDeleteOneRecord', () => { const { result } = renderHook( () => useDeleteOneRecord({ - objectNameSingular: objectMetadataItem.nameSingular, + objectNameSingular: personObjectMetadataItem.nameSingular, }), { wrapper: getJestMetadataAndApolloMocksWrapper({ @@ -161,32 +177,47 @@ describe('useDeleteOneRecord', () => { await result.current.deleteOneRecord(personRecord.id); fail('Should have thrown an error'); } catch (e) { - assertCachedRecordIsNull(); + assertCachedRecordIsNull({ + recordId: personRecord.id, + objectMetadataItem: personObjectMetadataItem, + }); + assertCachedRecordIsNull({ + recordId: relatedCompanyRecord.id, + objectMetadataItem: companyObjectMetadataItem, + }); } }); }); }); describe('B. Starting from filled cache', () => { + const { + assertCachedRecordMatchSnapshot, + cache, + restoreCacheToInitialState, + } = new InMemoryTestingCacheInstance({ + objectMetadataItems, + initialRecordsInCache: [ + { + objectMetadataItem: companyObjectMetadataItem, + records: allMockCompanyRecordsWithRelation, + }, + { + objectMetadataItem: personObjectMetadataItem, + records: allMockPersonRecords, + }, + ], + }); + beforeEach(() => { - const recordGqlFields = computeDepthOneRecordGqlFieldsFromRecord({ - objectMetadataItem, - record: personRecord, - }); - updateRecordFromCache({ - cache, - objectMetadataItem, - objectMetadataItems, - record: personRecord, - recordGqlFields, - }); + restoreCacheToInitialState(); }); it('1. Should handle successfull record deletion', async () => { const { result } = renderHook( () => useDeleteOneRecord({ - objectNameSingular: objectMetadataItem.nameSingular, + objectNameSingular: personObjectMetadataItem.nameSingular, }), { wrapper: getJestMetadataAndApolloMocksWrapper({ @@ -198,15 +229,23 @@ describe('useDeleteOneRecord', () => { await act(async () => { const res = await result.current.deleteOneRecord(personRecord.id); - expect(res).toBeDefined(); - expect(res.deletedAt).toBeDefined(); - expect(res).toHaveProperty('id', personRecord.id); - - const personRecordWithDeletedAt = { - ...personRecord, + expect(res).toMatchObject({ + __typename: 'Person', + id: personRecord.id, deletedAt: expect.any(String), - }; - assertCachedRecordMatch(personRecordWithDeletedAt); + }); + + assertCachedRecordMatchSnapshot({ + recordId: personRecord.id, + objectMetadataItem: personObjectMetadataItem, + matchObject: { + deletedAt: expect.any(String), + }, + }); + assertCachedRecordMatchSnapshot({ + objectMetadataItem: companyObjectMetadataItem, + recordId: personRecord.company.id, + }); }); expect(defaultMocks[0].result).toHaveBeenCalled(); @@ -221,7 +260,7 @@ describe('useDeleteOneRecord', () => { const { result } = renderHook( () => useDeleteOneRecord({ - objectNameSingular: objectMetadataItem.nameSingular, + objectNameSingular: personObjectMetadataItem.nameSingular, }), { wrapper: getJestMetadataAndApolloMocksWrapper({ @@ -234,11 +273,21 @@ describe('useDeleteOneRecord', () => { await act(async () => { result.current.deleteOneRecord(personRecord.id); await waitFor(() => { - const personRecordWithDeletedAt = { - ...personRecord, - deletedAt: expect.any(String), - }; - assertCachedRecordMatch(personRecordWithDeletedAt); + assertCachedRecordMatchSnapshot({ + recordId: personRecord.id, + objectMetadataItem: personObjectMetadataItem, + snapshotPropertyMatchers: { + // Request is paused then the cached get filled with optmistic deletedAt + deletedAt: expect.any(String), + }, + matchObject: { + deletedAt: expect.any(String), + }, + }); + assertCachedRecordMatchSnapshot({ + objectMetadataItem: companyObjectMetadataItem, + recordId: personRecord.company.id, + }); }); }); @@ -253,7 +302,7 @@ describe('useDeleteOneRecord', () => { const { result } = renderHook( () => useDeleteOneRecord({ - objectNameSingular: objectMetadataItem.nameSingular, + objectNameSingular: personObjectMetadataItem.nameSingular, }), { wrapper: getJestMetadataAndApolloMocksWrapper({ @@ -268,11 +317,17 @@ describe('useDeleteOneRecord', () => { await result.current.deleteOneRecord(personRecord.id); fail('Should have thrown an error'); } catch (e) { - const personRecordWithDeletedAt = { - ...personRecord, - deletedAt: null, - }; - assertCachedRecordMatch(personRecordWithDeletedAt); + assertCachedRecordMatchSnapshot({ + recordId: personRecord.id, + objectMetadataItem: personObjectMetadataItem, + matchObject: { + deletedAt: null, + }, + }); + assertCachedRecordMatchSnapshot({ + objectMetadataItem: companyObjectMetadataItem, + recordId: personRecord.company.id, + }); } }); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyLoadRecordIndexTable.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyLoadRecordIndexTable.test.tsx index bafd1762a..a004fc0de 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyLoadRecordIndexTable.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyLoadRecordIndexTable.test.tsx @@ -9,7 +9,8 @@ import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewCompon import { MockedResponse } from '@apollo/client/testing'; import gql from 'graphql-tag'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people'; + const recordTableId = 'people'; const objectNameSingular = 'person'; const onColumnsChange = jest.fn(); @@ -422,7 +423,7 @@ const mocks: MockedResponse[] = [ }, result: jest.fn(() => ({ data: { - people: getPeopleMock(), + people: getPeopleRecordConnectionMock(), }, })), }, diff --git a/packages/twenty-front/src/modules/object-record/record-picker/components/__stories__/SingleRecordPicker.stories.tsx b/packages/twenty-front/src/modules/object-record/record-picker/components/__stories__/SingleRecordPicker.stories.tsx index 4e75d91f9..df638799c 100644 --- a/packages/twenty-front/src/modules/object-record/record-picker/components/__stories__/SingleRecordPicker.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-picker/components/__stories__/SingleRecordPicker.stories.tsx @@ -7,21 +7,21 @@ import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/Componen import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { allMockPersonRecords } from '~/testing/mock-data/people'; import { sleep } from '~/utils/sleep'; import { SingleRecordPicker } from '@/object-record/record-picker/components/SingleRecordPicker'; import { SingleRecordPickerRecord } from '../../types/SingleRecordPickerRecord'; -const peopleMock = getPeopleMock(); - -const records = peopleMock.map((person) => ({ - id: person.id, - name: person.name.firstName + ' ' + person.name.lastName, - avatarUrl: 'https://picsum.photos/200', - avatarType: 'rounded', - record: { ...person, __typename: 'Person' }, -})); +const records = allMockPersonRecords.map( + (person) => ({ + id: person.id, + name: person.name.firstName + ' ' + person.name.lastName, + avatarUrl: 'https://picsum.photos/200', + avatarType: 'rounded', + record: { ...person, __typename: 'Person' }, + }), +); const meta: Meta = { title: 'UI/RecordPicker/SingleRecordPicker', diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx index 783f84ec9..7d212b39c 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx @@ -9,16 +9,14 @@ import { RecordStoreDecorator } from '~/testing/decorators/RecordStoreDecorator' import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; import { getCompaniesMock } from '~/testing/mock-data/companies'; -import { getPeopleMock } from '~/testing/mock-data/people'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { allMockPersonRecords } from '~/testing/mock-data/people'; import { RecordDetailRelationSection } from '../RecordDetailRelationSection'; const companiesMock = getCompaniesMock(); -const peopleMock = getPeopleMock(); - const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'company', ); @@ -72,9 +70,9 @@ export const WithRecords: Story = { records: [ { ...companiesMock[0], - people: peopleMock, + people: allMockPersonRecords, }, - ...peopleMock, + ...allMockPersonRecords, ], }, }; diff --git a/packages/twenty-front/src/modules/object-record/utils/__tests__/computeOptimisticRecordFromInput.test.ts b/packages/twenty-front/src/modules/object-record/utils/__tests__/computeOptimisticRecordFromInput.test.ts index cfb00f9c9..2f1d1c388 100644 --- a/packages/twenty-front/src/modules/object-record/utils/__tests__/computeOptimisticRecordFromInput.test.ts +++ b/packages/twenty-front/src/modules/object-record/utils/__tests__/computeOptimisticRecordFromInput.test.ts @@ -4,9 +4,9 @@ import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphq import { FieldActorForInputValue } from '@/object-record/record-field/types/FieldMetadata'; import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput'; import { InMemoryCache } from '@apollo/client'; -import { getCompanyObjectMetadataItem } from '~/testing/mock-data/companies'; +import { getMockCompanyObjectMetadataItem } from '~/testing/mock-data/companies'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -import { getPersonObjectMetadataItem } from '~/testing/mock-data/people'; +import { getMockPersonObjectMetadataItem } from '~/testing/mock-data/people'; import { mockCurrentWorkspaceMembers } from '~/testing/mock-data/workspace-members'; describe('computeOptimisticRecordFromInput', () => { @@ -14,7 +14,7 @@ describe('computeOptimisticRecordFromInput', () => { const currentWorkspaceMemberFullname = `${currentWorkspaceMember.name.firstName} ${currentWorkspaceMember.name.lastName}`; it('should generate correct optimistic record if no relation field is present', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = getPersonObjectMetadataItem(); + const personObjectMetadataItem = getMockPersonObjectMetadataItem(); const result = computeOptimisticRecordFromInput({ currentWorkspaceMember, @@ -33,7 +33,7 @@ describe('computeOptimisticRecordFromInput', () => { it('should generate correct optimistic record with actor field', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = getPersonObjectMetadataItem(); + const personObjectMetadataItem = getMockPersonObjectMetadataItem(); const actorFieldValueForInput: FieldActorForInputValue = { context: {}, source: 'API', @@ -62,7 +62,7 @@ describe('computeOptimisticRecordFromInput', () => { it('should generate correct optimistic record createdBy when recordInput contains id', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = getPersonObjectMetadataItem(); + const personObjectMetadataItem = getMockPersonObjectMetadataItem(); const result = computeOptimisticRecordFromInput({ currentWorkspaceMember, objectMetadataItems: generatedMockObjectMetadataItems, @@ -90,7 +90,7 @@ describe('computeOptimisticRecordFromInput', () => { it('should generate correct optimistic record if relation field is present but cache is empty', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = getPersonObjectMetadataItem(); + const personObjectMetadataItem = getMockPersonObjectMetadataItem(); const result = computeOptimisticRecordFromInput({ currentWorkspaceMember, @@ -109,8 +109,8 @@ describe('computeOptimisticRecordFromInput', () => { it('should generate correct optimistic record even if recordInput contains field __typename', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = getPersonObjectMetadataItem(); - const companyObjectMetadataItem = getCompanyObjectMetadataItem(); + const personObjectMetadataItem = getMockPersonObjectMetadataItem(); + const companyObjectMetadataItem = getMockCompanyObjectMetadataItem(); const companyRecord = { id: '123', @@ -154,8 +154,8 @@ describe('computeOptimisticRecordFromInput', () => { it('should generate correct optimistic record if relation field is present and cache is not empty', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = getPersonObjectMetadataItem(); - const companyObjectMetadataItem = getCompanyObjectMetadataItem(); + const personObjectMetadataItem = getMockPersonObjectMetadataItem(); + const companyObjectMetadataItem = getMockCompanyObjectMetadataItem(); const companyRecord = { id: '123', @@ -198,7 +198,7 @@ describe('computeOptimisticRecordFromInput', () => { it('should generate correct optimistic record if relation field is null and cache is empty', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = getPersonObjectMetadataItem(); + const personObjectMetadataItem = getMockPersonObjectMetadataItem(); const result = computeOptimisticRecordFromInput({ currentWorkspaceMember, @@ -218,7 +218,7 @@ describe('computeOptimisticRecordFromInput', () => { it('should throw an error if recordInput contains fields unrelated to the current objectMetadata', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = getPersonObjectMetadataItem(); + const personObjectMetadataItem = getMockPersonObjectMetadataItem(); expect(() => computeOptimisticRecordFromInput({ @@ -240,7 +240,7 @@ describe('computeOptimisticRecordFromInput', () => { it('should throw an error if recordInput contains both the relationFieldId and relationField', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = getPersonObjectMetadataItem(); + const personObjectMetadataItem = getMockPersonObjectMetadataItem(); expect(() => computeOptimisticRecordFromInput({ @@ -260,7 +260,7 @@ describe('computeOptimisticRecordFromInput', () => { it('should throw an error if recordInput contains both the relationFieldId and relationField even if null', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = getPersonObjectMetadataItem(); + const personObjectMetadataItem = getMockPersonObjectMetadataItem(); expect(() => computeOptimisticRecordFromInput({ diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionFormDeleteRecord.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionFormDeleteRecord.stories.tsx index 436bb07df..2e0316f73 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionFormDeleteRecord.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionFormDeleteRecord.stories.tsx @@ -6,11 +6,11 @@ import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadat import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator'; import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator'; +import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { allMockPersonRecords } from '~/testing/mock-data/people'; import { getWorkflowNodeIdMock } from '~/testing/mock-data/workflow'; import { WorkflowEditActionFormDeleteRecord } from '../WorkflowEditActionFormDeleteRecord'; -import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator'; const DEFAULT_ACTION = { id: getWorkflowNodeIdMock(), @@ -100,7 +100,7 @@ export const DisabledWithEmptyValues: Story = { }, }; -const peopleMock = getPeopleMock()[0]; +const peopleMock = allMockPersonRecords[0]; export const DisabledWithDefaultStaticValues: Story = { args: { diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionFormUpdateRecord.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionFormUpdateRecord.stories.tsx index c422fd47e..752806cba 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionFormUpdateRecord.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/__stories__/WorkflowEditActionFormUpdateRecord.stories.tsx @@ -6,11 +6,11 @@ import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadat import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator'; import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator'; +import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { allMockPersonRecords } from '~/testing/mock-data/people'; import { getWorkflowNodeIdMock } from '~/testing/mock-data/workflow'; import { WorkflowEditActionFormUpdateRecord } from '../WorkflowEditActionFormUpdateRecord'; -import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator'; const DEFAULT_ACTION = { id: getWorkflowNodeIdMock(), @@ -128,7 +128,7 @@ export const DisabledWithEmptyValues: Story = { }, }; -const peopleMock = getPeopleMock()[0]; +const peopleMock = allMockPersonRecords[0]; export const DisabledWithDefaultStaticValues: Story = { args: { diff --git a/packages/twenty-front/src/pages/object-record/__stories__/RecordShowPage.stories.tsx b/packages/twenty-front/src/pages/object-record/__stories__/RecordShowPage.stories.tsx index 2bf0fbb87..16570cdd0 100644 --- a/packages/twenty-front/src/pages/object-record/__stories__/RecordShowPage.stories.tsx +++ b/packages/twenty-front/src/pages/object-record/__stories__/RecordShowPage.stories.tsx @@ -8,13 +8,15 @@ import { PageDecoratorArgs, } from '~/testing/decorators/PageDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; -import { getPeopleMock, peopleQueryResult } from '~/testing/mock-data/people'; +import { + allMockPersonRecords, + peopleQueryResult, +} from '~/testing/mock-data/people'; import { mockedWorkspaceMemberData } from '~/testing/mock-data/users'; import { RecordShowPage } from '../RecordShowPage'; -const peopleMock = getPeopleMock(); - +const personRecord = allMockPersonRecords[0]; const meta: Meta = { title: 'Pages/ObjectRecord/RecordShowPage', component: RecordShowPage, @@ -22,7 +24,7 @@ const meta: Meta = { routePath: '/object/:objectNameSingular/:objectRecordId', routeParams: { ':objectNameSingular': 'person', - ':objectRecordId': peopleMock[0].id, + ':objectRecordId': personRecord.id, }, }, parameters: { @@ -36,7 +38,7 @@ const meta: Meta = { graphql.query('FindOnePerson', () => { return HttpResponse.json({ data: { - person: peopleMock[0], + person: personRecord, }, }); }), diff --git a/packages/twenty-front/src/testing/cache/inMemoryTestingCacheInstance.ts b/packages/twenty-front/src/testing/cache/inMemoryTestingCacheInstance.ts new file mode 100644 index 000000000..34004de23 --- /dev/null +++ b/packages/twenty-front/src/testing/cache/inMemoryTestingCacheInstance.ts @@ -0,0 +1,113 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache'; +import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache'; +import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { InMemoryCache, NormalizedCacheObject } from '@apollo/client'; +import { expect } from '@storybook/jest'; +import { isDefined } from 'twenty-shared'; + +type ObjectMetadataItemAndRecordId = { + recordId: string; + objectMetadataItem: ObjectMetadataItem; +}; +type RecordsWithObjectMetadataItem = { + records: ObjectRecord[]; + objectMetadataItem: ObjectMetadataItem; +}[]; + +type GetMockCachedRecord = { + recordId: string; + objectMetadataItem: ObjectMetadataItem; + matchObject?: Record; + snapshotPropertyMatchers?: { + deletedAt?: any; + updatedAt?: any; + createdAt?: any; + }; +}; +type InMemoryTestingCacheInstanceArgs = { + objectMetadataItems: ObjectMetadataItem[]; + initialRecordsInCache?: RecordsWithObjectMetadataItem; +}; + +export class InMemoryTestingCacheInstance { + private _cache: InMemoryCache; + private objectMetadataItems: ObjectMetadataItem[]; + private initialStateExtract: NormalizedCacheObject; + + constructor({ + objectMetadataItems, + initialRecordsInCache = [], + }: InMemoryTestingCacheInstanceArgs) { + this.objectMetadataItems = objectMetadataItems; + this._cache = new InMemoryCache(); + + this.populateRecordsInCache(initialRecordsInCache); + this.initialStateExtract = this._cache.extract(); + } + + public populateRecordsInCache = ( + recordsWithObjectMetadataItem: RecordsWithObjectMetadataItem, + ) => { + recordsWithObjectMetadataItem.forEach(({ objectMetadataItem, records }) => + records.forEach((record) => + updateRecordFromCache({ + cache: this._cache, + objectMetadataItem, + objectMetadataItems: this.objectMetadataItems, + record, + recordGqlFields: computeDepthOneRecordGqlFieldsFromRecord({ + objectMetadataItem, + record, + }), + }), + ), + ); + }; + + public assertCachedRecordIsNull = ({ + objectMetadataItem, + recordId, + }: ObjectMetadataItemAndRecordId) => { + const cachedRecord = getRecordFromCache({ + cache: this._cache, + objectMetadataItem, + objectMetadataItems: this.objectMetadataItems, + recordId, + }); + expect(cachedRecord).toBeNull(); + }; + + public assertCachedRecordMatchSnapshot = ({ + objectMetadataItem, + recordId, + matchObject, + snapshotPropertyMatchers, + }: GetMockCachedRecord) => { + const cachedRecord = getRecordFromCache({ + cache: this._cache, + objectMetadataItem, + objectMetadataItems: this.objectMetadataItems, + recordId, + }); + expect(cachedRecord).not.toBeNull(); + + if (cachedRecord === null) { + throw new Error('Should never occurs, cachedRecord is null'); + } + + if (isDefined(matchObject)) { + expect(cachedRecord).toMatchObject(matchObject); + } + expect(cachedRecord).toMatchSnapshot(snapshotPropertyMatchers ?? {}); + }; + + public restoreCacheToInitialState = async () => { + return this._cache.restore(this.initialStateExtract); + }; + + public get cache() { + return this._cache; + } +} diff --git a/packages/twenty-front/src/testing/decorators/ContextStoreDecorator.tsx b/packages/twenty-front/src/testing/decorators/ContextStoreDecorator.tsx index 42af801a0..7750990ac 100644 --- a/packages/twenty-front/src/testing/decorators/ContextStoreDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/ContextStoreDecorator.tsx @@ -6,7 +6,7 @@ import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-s import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { isUndefined } from '@sniptt/guards'; -import { getCompanyObjectMetadataItem } from '~/testing/mock-data/companies'; +import { getMockCompanyObjectMetadataItem } from '~/testing/mock-data/companies'; export const ContextStoreDecorator: Decorator = (Story, context) => { const { contextStore } = context.parameters; @@ -24,7 +24,7 @@ export const ContextStoreDecorator: Decorator = (Story, context) => { const [isLoaded, setIsLoaded] = useState(false); - const objectMetadataItem = getCompanyObjectMetadataItem(); + const objectMetadataItem = getMockCompanyObjectMetadataItem(); useEffect(() => { setCurrentObjectMetadataItem(objectMetadataItem); diff --git a/packages/twenty-front/src/testing/decorators/getFieldDecorator.tsx b/packages/twenty-front/src/testing/decorators/getFieldDecorator.tsx index 84df810b2..36fde3e75 100644 --- a/packages/twenty-front/src/testing/decorators/getFieldDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/getFieldDecorator.tsx @@ -14,7 +14,7 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { isDefined } from 'twenty-shared'; import { getCompaniesMock } from '~/testing/mock-data/companies'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people'; import { mockedTasks } from '~/testing/mock-data/tasks'; const RecordMockSetterEffect = ({ @@ -71,7 +71,7 @@ export const getFieldDecorator = ] : companiesMock; - const peopleMock = getPeopleMock(); + const peopleMock = getPeopleRecordConnectionMock(); const people = objectNameSingular === 'person' && isDefined(fieldValue) diff --git a/packages/twenty-front/src/testing/graphqlMocks.ts b/packages/twenty-front/src/testing/graphqlMocks.ts index 00e5679e8..4d3f3a850 100644 --- a/packages/twenty-front/src/testing/graphqlMocks.ts +++ b/packages/twenty-front/src/testing/graphqlMocks.ts @@ -14,7 +14,7 @@ import { mockedClientConfig } from '~/testing/mock-data/config'; import { mockedFavoritesData } from '~/testing/mock-data/favorite'; import { mockedFavoriteFoldersData } from '~/testing/mock-data/favorite-folders'; import { mockedNotes } from '~/testing/mock-data/notes'; -import { getPeopleMock } from '~/testing/mock-data/people'; +import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people'; import { mockedRemoteTables } from '~/testing/mock-data/remote-tables'; import { mockedUserData } from '~/testing/mock-data/users'; import { mockedViewsData } from '~/testing/mock-data/views'; @@ -33,7 +33,7 @@ import { import { mockedRemoteServers } from './mock-data/remote-servers'; import { mockedViewFieldsData } from './mock-data/view-fields'; -const peopleMock = getPeopleMock(); +const peopleMock = getPeopleRecordConnectionMock(); const companiesMock = getCompaniesMock(); const duplicateCompanyMock = getCompanyDuplicateMock(); diff --git a/packages/twenty-front/src/testing/mock-data/companies.ts b/packages/twenty-front/src/testing/mock-data/companies.ts index bb56a3ff6..75c6accc5 100644 --- a/packages/twenty-front/src/testing/mock-data/companies.ts +++ b/packages/twenty-front/src/testing/mock-data/companies.ts @@ -1,50 +1,7 @@ +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { isDefined } from 'twenty-shared'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -export const getCompaniesMock = () => { - return companiesQueryResult.companies.edges.map((edge) => edge.node); -}; - -export const getCompanyObjectMetadataItem = () => { - const companyObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'company', - ); - - if (!companyObjectMetadataItem) { - throw new Error('Company object metadata item not found'); - } - - return companyObjectMetadataItem; -}; -export const getCompanyDuplicateMock = () => { - return { - ...companiesQueryResult.companies.edges[0].node, - id: '8b40856a-2ec9-4c03-8bc0-c032c89e1824', - }; -}; - -export const getEmptyCompanyMock = () => { - return { - id: '9231e6ee-4cc2-4c7b-8c55-dff16f4d968a', - name: '', - domainName: { - __typename: 'Links', - primaryLinkUrl: '', - primaryLinkLabel: '', - secondaryLinks: [], - }, - address: {}, - accountOwner: null, - createdAt: null, - updatedAt: null, - employees: null, - idealCustomerProfile: null, - linkedinLink: null, - xLink: null, - _activityCount: null, - __typename: 'Company', - }; -}; - export const companiesQueryResult = { companies: { __typename: 'CompanyConnection', @@ -774,3 +731,75 @@ export const companiesQueryResult = { ], }, }; + +const allMockedCompanyRecords = companiesQueryResult.companies.edges.map( + (edge) => edge.node, +); +export const getCompaniesMock = () => { + return [...allMockedCompanyRecords]; +}; + +export const getMockCompanyObjectMetadataItem = () => { + const companyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', + ); + + if (!companyObjectMetadataItem) { + throw new Error('Company object metadata item not found'); + } + + return companyObjectMetadataItem; +}; +export const getCompanyDuplicateMock = () => { + return { + ...companiesQueryResult.companies.edges[0].node, + id: '8b40856a-2ec9-4c03-8bc0-c032c89e1824', + }; +}; + +export const getMockCompanyRecord = ( + overrides?: Partial, + index = 0, +) => { + return { + ...allMockedCompanyRecords[index], + ...overrides, + }; +}; + +export const findMockCompanyRecord = ({ + id: queriedCompanyId, +}: Pick) => { + const company = allMockedCompanyRecords.find( + ({ id: currentCompanyId }) => currentCompanyId === queriedCompanyId, + ); + + if (!isDefined(company)) { + throw new Error(`Could not find company with id, ${queriedCompanyId}`); + } + + return company; +}; + +export const getEmptyCompanyMock = () => { + return { + id: '9231e6ee-4cc2-4c7b-8c55-dff16f4d968a', + name: '', + domainName: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + address: {}, + accountOwner: null, + createdAt: null, + updatedAt: null, + employees: null, + idealCustomerProfile: null, + linkedinLink: null, + xLink: null, + _activityCount: null, + __typename: 'Company', + }; +}; diff --git a/packages/twenty-front/src/testing/mock-data/companiesWithRelations.ts b/packages/twenty-front/src/testing/mock-data/companiesWithRelations.ts new file mode 100644 index 000000000..0377acf74 --- /dev/null +++ b/packages/twenty-front/src/testing/mock-data/companiesWithRelations.ts @@ -0,0 +1,1882 @@ +import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { isDefined } from 'twenty-shared'; + +const FIND_MANY_COMPANIES_WITH_RELATION_QUERY_RESULT = { + data: { + companies: { + __typename: 'CompanyConnection', + totalCount: 13, + pageInfo: { + __typename: 'PageInfo', + hasNextPage: false, + hasPreviousPage: false, + startCursor: + 'eyJwb3NpdGlvbiI6MSwiaWQiOiIyMDIwMjAyMC0zZWMzLTRmZTMtODk5Ny1iNzZhYTBiZmE0MDgifQ==', + endCursor: + 'eyJwb3NpdGlvbiI6MTMsImlkIjoiMjAyMDIwMjAtMTQ1NS00YzU3LWFmYWYtZGQ1ZGMwODYzNjFkIn0=', + }, + edges: [ + { + __typename: 'CompanyEdge', + cursor: + 'eyJwb3NpdGlvbiI6MSwiaWQiOiIyMDIwMjAyMC0zZWMzLTRmZTMtODk5Ny1iNzZhYTBiZmE0MDgifQ==', + node: { + __typename: 'Company', + accountOwner: null, + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + employees: null, + id: '20202020-3ec3-4fe3-8997-b76aa0bfa408', + name: 'Linkedin', + position: 1, + address: { + __typename: 'Address', + addressStreet1: 'Eutaw Street', + addressStreet2: null, + addressCity: 'Dublin', + addressState: null, + addressCountry: 'Ireland', + addressPostcode: null, + addressLat: null, + addressLng: null, + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + domainName: { + __typename: 'Links', + primaryLinkUrl: 'https://linkedin.com', + primaryLinkLabel: '', + secondaryLinks: [], + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + noteTargets: { + __typename: 'NoteTargetConnection', + edges: [], + }, + taskTargets: { + __typename: 'TaskTargetConnection', + edges: [], + }, + opportunities: { + __typename: 'OpportunityConnection', + edges: [ + { + __typename: 'OpportunityEdge', + node: { + __typename: 'Opportunity', + closeDate: '2025-02-16T08:21:51.764Z', + companyId: '20202020-3ec3-4fe3-8997-b76aa0bfa408', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-be10-422b-a663-16bd3c2228e1', + name: 'Opportunity 1', + pointOfContactId: '20202020-1c0e-494c-a1b6-85b1c6fefaa5', + position: 1, + stage: 'NEW', + updatedAt: '2025-02-16T08:21:51.715Z', + amount: { + __typename: 'Currency', + amountMicros: 100000, + currencyCode: 'USD', + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Cook', + context: {}, + }, + }, + }, + ], + }, + people: { + __typename: 'PersonConnection', + edges: [ + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'Seattle', + companyId: '20202020-3ec3-4fe3-8997-b76aa0bfa408', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-1c0e-494c-a1b6-85b1c6fefaa5', + intro: '', + jobTitle: '', + performanceRating: null, + position: 1, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'christoph.calisto@linkedin.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Christoph', + lastName: 'Callisto', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '789012345', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '789012345', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'Los Angeles', + companyId: '20202020-3ec3-4fe3-8997-b76aa0bfa408', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-ac73-4797-824e-87a1f5aea9e0', + intro: '', + jobTitle: '', + performanceRating: null, + position: 2, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'sylvie.palmer@linkedin.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Sylvie', + lastName: 'Palmer', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '780123456', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '780123456', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + ], + }, + }, + }, + { + __typename: 'CompanyEdge', + cursor: + 'eyJwb3NpdGlvbiI6MiwiaWQiOiIyMDIwMjAyMC01ZDgxLTQ2ZDYtYmY4My1mN2ZkMzNlYTYxMDIifQ==', + node: { + __typename: 'Company', + accountOwner: null, + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + employees: null, + id: '20202020-5d81-46d6-bf83-f7fd33ea6102', + name: 'Facebook', + position: 2, + people: { + __typename: 'PersonConnection', + edges: [], + }, + address: { + __typename: 'Address', + addressStreet1: null, + addressStreet2: null, + addressCity: null, + addressState: null, + addressCountry: null, + addressPostcode: null, + addressLat: null, + addressLng: null, + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + domainName: { + __typename: 'Links', + primaryLinkUrl: 'https://facebook.com', + primaryLinkLabel: '', + secondaryLinks: [], + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + noteTargets: { + __typename: 'NoteTargetConnection', + edges: [], + }, + taskTargets: { + __typename: 'TaskTargetConnection', + edges: [], + }, + opportunities: { + __typename: 'OpportunityConnection', + edges: [ + { + __typename: 'OpportunityEdge', + node: { + __typename: 'Opportunity', + closeDate: '2025-02-16T08:21:51.764Z', + companyId: '20202020-5d81-46d6-bf83-f7fd33ea6102', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-0543-4cc2-9f96-95cc699960f2', + name: 'Opportunity 2', + pointOfContactId: '20202020-f517-42fd-80ae-14173b3b70ae', + position: 2, + stage: 'MEETING', + updatedAt: '2025-02-16T08:21:51.715Z', + amount: { + __typename: 'Currency', + amountMicros: 2000000, + currencyCode: 'USD', + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Cook', + context: {}, + }, + }, + }, + ], + }, + }, + }, + { + __typename: 'CompanyEdge', + cursor: + 'eyJwb3NpdGlvbiI6MywiaWQiOiIyMDIwMjAyMC0wNzEzLTQwYTUtODIxNi04MjgwMjQwMWQzM2UifQ==', + node: { + __typename: 'Company', + accountOwner: null, + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + employees: null, + id: '20202020-0713-40a5-8216-82802401d33e', + name: 'Qonto', + position: 3, + opportunities: { + __typename: 'OpportunityConnection', + edges: [], + }, + address: { + __typename: 'Address', + addressStreet1: '18 rue de navarrin', + addressStreet2: null, + addressCity: 'Paris', + addressState: null, + addressCountry: 'France', + addressPostcode: '75009', + addressLat: null, + addressLng: null, + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + domainName: { + __typename: 'Links', + primaryLinkUrl: 'https://qonto.com', + primaryLinkLabel: '', + secondaryLinks: [], + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + noteTargets: { + __typename: 'NoteTargetConnection', + edges: [], + }, + taskTargets: { + __typename: 'TaskTargetConnection', + edges: [], + }, + people: { + __typename: 'PersonConnection', + edges: [ + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'Seattle', + companyId: '20202020-0713-40a5-8216-82802401d33e', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-f517-42fd-80ae-14173b3b70ae', + intro: '', + jobTitle: '', + performanceRating: null, + position: 3, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'christopher.gonzalez@qonto.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Christopher', + lastName: 'Gonzalez', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '789012345', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '789012345', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'Los Angeles', + companyId: '20202020-0713-40a5-8216-82802401d33e', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-eee1-4690-ad2c-8619e5b56a2e', + intro: '', + jobTitle: '', + performanceRating: null, + position: 4, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'ashley.parker@qonto.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Ashley', + lastName: 'Parker', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '780123456', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '780123456', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + ], + }, + }, + }, + { + __typename: 'CompanyEdge', + cursor: + 'eyJwb3NpdGlvbiI6NCwiaWQiOiIyMDIwMjAyMC1lZDg5LTQxM2EtYjMxYS05NjI5ODZlNjdiYjQifQ==', + node: { + __typename: 'Company', + accountOwner: null, + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + employees: null, + id: '20202020-ed89-413a-b31a-962986e67bb4', + name: 'Microsoft', + position: 4, + address: { + __typename: 'Address', + addressStreet1: null, + addressStreet2: null, + addressCity: null, + addressState: null, + addressCountry: null, + addressPostcode: null, + addressLat: null, + addressLng: null, + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + domainName: { + __typename: 'Links', + primaryLinkUrl: 'https://microsoft.com', + primaryLinkLabel: '', + secondaryLinks: [], + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + noteTargets: { + __typename: 'NoteTargetConnection', + edges: [], + }, + taskTargets: { + __typename: 'TaskTargetConnection', + edges: [], + }, + opportunities: { + __typename: 'OpportunityConnection', + edges: [ + { + __typename: 'OpportunityEdge', + node: { + __typename: 'Opportunity', + closeDate: '2025-02-16T08:21:51.764Z', + companyId: '20202020-ed89-413a-b31a-962986e67bb4', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-2f89-406f-90ea-180f433b2445', + name: 'Opportunity 3', + pointOfContactId: '20202020-6784-4449-afdf-dc62cb8702f2', + position: 3, + stage: 'PROPOSAL', + updatedAt: '2025-02-16T08:21:51.715Z', + amount: { + __typename: 'Currency', + amountMicros: 300000, + currencyCode: 'USD', + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Cook', + context: {}, + }, + }, + }, + { + __typename: 'OpportunityEdge', + node: { + __typename: 'Opportunity', + closeDate: '2025-02-16T08:21:51.764Z', + companyId: '20202020-ed89-413a-b31a-962986e67bb4', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-35b1-4045-9cde-42f715148954', + name: 'Opportunity 4', + pointOfContactId: '20202020-80f1-4dff-b570-a74942528de3', + position: 4, + stage: 'PROPOSAL', + updatedAt: '2025-02-16T08:21:51.715Z', + amount: { + __typename: 'Currency', + amountMicros: 4000000, + currencyCode: 'USD', + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: null, + name: '', + context: {}, + }, + }, + }, + ], + }, + people: { + __typename: 'PersonConnection', + edges: [ + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'Seattle', + companyId: '20202020-ed89-413a-b31a-962986e67bb4', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-6784-4449-afdf-dc62cb8702f2', + intro: '', + jobTitle: '', + performanceRating: null, + position: 5, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'nicholas.wright@microsoft.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Nicholas', + lastName: 'Wright', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '781234567', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '781234567', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'New York', + companyId: '20202020-ed89-413a-b31a-962986e67bb4', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-490f-4466-8391-733cfd66a0c8', + intro: '', + jobTitle: '', + performanceRating: null, + position: 6, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'isabella.scott@microsoft.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Isabella', + lastName: 'Scott', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '782345678', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '782345678', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'Seattle', + companyId: '20202020-ed89-413a-b31a-962986e67bb4', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-80f1-4dff-b570-a74942528de3', + intro: '', + jobTitle: '', + performanceRating: null, + position: 7, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'matthew.green@microsoft.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Matthew', + lastName: 'Green', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '783456789', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '783456789', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + ], + }, + }, + }, + { + __typename: 'CompanyEdge', + cursor: + 'eyJwb3NpdGlvbiI6NSwiaWQiOiIyMDIwMjAyMC0xNzFlLTRiY2MtOWNmNy00MzQ0OGQ2ZmIyNzgifQ==', + node: { + __typename: 'Company', + accountOwner: null, + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + employees: null, + id: '20202020-171e-4bcc-9cf7-43448d6fb278', + name: 'Airbnb', + position: 5, + opportunities: { + __typename: 'OpportunityConnection', + edges: [], + }, + address: { + __typename: 'Address', + addressStreet1: '888 Brannan St', + addressStreet2: null, + addressCity: 'San Francisco', + addressState: 'CA', + addressCountry: 'United States', + addressPostcode: '94103', + addressLat: null, + addressLng: null, + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + domainName: { + __typename: 'Links', + primaryLinkUrl: 'https://airbnb.com', + primaryLinkLabel: '', + secondaryLinks: [], + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + noteTargets: { + __typename: 'NoteTargetConnection', + edges: [], + }, + taskTargets: { + __typename: 'TaskTargetConnection', + edges: [], + }, + people: { + __typename: 'PersonConnection', + edges: [ + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'New York', + companyId: '20202020-171e-4bcc-9cf7-43448d6fb278', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-338b-46df-8811-fa08c7d19d35', + intro: '', + jobTitle: '', + performanceRating: null, + position: 8, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'elizabeth.baker@airbnb.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Elizabeth', + lastName: 'Baker', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '784567890', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '784567890', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'San Francisco', + companyId: '20202020-171e-4bcc-9cf7-43448d6fb278', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-64ad-4b0e-bbfd-e9fd795b7016', + intro: '', + jobTitle: '', + performanceRating: null, + position: 9, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'christopher.nelson@airbnb.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Christopher', + lastName: 'Nelson', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '785678901', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '785678901', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'New York', + companyId: '20202020-171e-4bcc-9cf7-43448d6fb278', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-5d54-41b7-ba36-f0d20e1417ae', + intro: '', + jobTitle: '', + performanceRating: null, + position: 10, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'avery.carter@airbnb.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Avery', + lastName: 'Carter', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '786789012', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '786789012', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + ], + }, + }, + }, + { + __typename: 'CompanyEdge', + cursor: + 'eyJwb3NpdGlvbiI6NiwiaWQiOiIyMDIwMjAyMC1jMjFlLTRlYzItODczYi1kZTQyNjRkODkwMjUifQ==', + node: { + __typename: 'Company', + accountOwner: null, + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + employees: null, + id: '20202020-c21e-4ec2-873b-de4264d89025', + name: 'Google', + position: 6, + opportunities: { + __typename: 'OpportunityConnection', + edges: [], + }, + address: { + __typename: 'Address', + addressStreet1: '760 Market St', + addressStreet2: 'Floor 10', + addressCity: 'San Francisco', + addressState: null, + addressCountry: 'United States', + addressPostcode: '94102', + addressLat: null, + addressLng: null, + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + domainName: { + __typename: 'Links', + primaryLinkUrl: 'https://google.com', + primaryLinkLabel: '', + secondaryLinks: [], + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + noteTargets: { + __typename: 'NoteTargetConnection', + edges: [], + }, + taskTargets: { + __typename: 'TaskTargetConnection', + edges: [], + }, + people: { + __typename: 'PersonConnection', + edges: [ + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'Los Angeles', + companyId: '20202020-c21e-4ec2-873b-de4264d89025', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-623d-41fe-92e7-dd45b7c568e1', + intro: '', + jobTitle: '', + performanceRating: null, + position: 11, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'ethan.mitchell@google.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Ethan', + lastName: 'Mitchell', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '787890123', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '787890123', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'Seattle', + companyId: '20202020-c21e-4ec2-873b-de4264d89025', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-2d40-4e49-8df4-9c6a049190ef', + intro: '', + jobTitle: '', + performanceRating: null, + position: 12, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'madison.perez@google.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Madison', + lastName: 'Perez', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '788901234', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '788901234', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'Seattle', + companyId: '20202020-c21e-4ec2-873b-de4264d89025', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-2d40-4e49-8df4-9c6a049190df', + intro: '', + jobTitle: '', + performanceRating: null, + position: 13, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'bertrand.voulzy@google.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Bertrand', + lastName: 'Voulzy', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '788901234', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '788901234', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'Seattle', + companyId: '20202020-c21e-4ec2-873b-de4264d89025', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-2d40-4e49-8df4-9c6a049191de', + intro: '', + jobTitle: '', + performanceRating: null, + position: 14, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'louis.duss@google.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Louis', + lastName: 'Duss', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '789012345', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '789012345', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + { + __typename: 'PersonEdge', + node: { + __typename: 'Person', + avatarUrl: '', + city: 'Seattle', + companyId: '20202020-c21e-4ec2-873b-de4264d89025', + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + id: '20202020-2d40-4e49-8df4-9c6a049191df', + intro: '', + jobTitle: '', + performanceRating: null, + position: 15, + updatedAt: '2025-02-16T08:21:51.715Z', + workPreference: null, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + emails: { + __typename: 'Emails', + primaryEmail: 'lorie.vladim@google.com', + additionalEmails: null, + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + name: { + __typename: 'FullName', + firstName: 'Lorie', + lastName: 'Vladim', + }, + phones: { + __typename: 'Phones', + primaryPhoneNumber: '788901235', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + whatsapp: { + __typename: 'Phones', + primaryPhoneNumber: '788901235', + primaryPhoneCountryCode: 'FR', + primaryPhoneCallingCode: '+33', + additionalPhones: null, + }, + xLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + }, + }, + ], + }, + }, + }, + { + __typename: 'CompanyEdge', + cursor: + 'eyJwb3NpdGlvbiI6NywiaWQiOiIyMDIwMjAyMC03MDdlLTQ0ZGMtYTFkMi0zMDAzMGJmMWE5NDQifQ==', + node: { + __typename: 'Company', + accountOwner: null, + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + employees: null, + id: '20202020-707e-44dc-a1d2-30030bf1a944', + name: 'Netflix', + position: 7, + opportunities: { + __typename: 'OpportunityConnection', + edges: [], + }, + people: { + __typename: 'PersonConnection', + edges: [], + }, + address: { + __typename: 'Address', + addressStreet1: '2300 Harrison St', + addressStreet2: null, + addressCity: 'San Francisco', + addressState: 'CA', + addressCountry: 'United States', + addressPostcode: '94110', + addressLat: null, + addressLng: null, + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + domainName: { + __typename: 'Links', + primaryLinkUrl: 'https://netflix.com', + primaryLinkLabel: '', + secondaryLinks: [], + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + noteTargets: { + __typename: 'NoteTargetConnection', + edges: [], + }, + taskTargets: { + __typename: 'TaskTargetConnection', + edges: [], + }, + }, + }, + { + __typename: 'CompanyEdge', + cursor: + 'eyJwb3NpdGlvbiI6OCwiaWQiOiIyMDIwMjAyMC0zZjc0LTQ5MmQtYTEwMS0yYTcwZjUwYTE2NDUifQ==', + node: { + __typename: 'Company', + accountOwner: null, + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + employees: null, + id: '20202020-3f74-492d-a101-2a70f50a1645', + name: 'Libeo', + position: 8, + opportunities: { + __typename: 'OpportunityConnection', + edges: [], + }, + people: { + __typename: 'PersonConnection', + edges: [], + }, + address: { + __typename: 'Address', + addressStreet1: null, + addressStreet2: null, + addressCity: null, + addressState: null, + addressCountry: null, + addressPostcode: null, + addressLat: null, + addressLng: null, + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + domainName: { + __typename: 'Links', + primaryLinkUrl: 'https://libeo.io', + primaryLinkLabel: '', + secondaryLinks: [], + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + noteTargets: { + __typename: 'NoteTargetConnection', + edges: [], + }, + taskTargets: { + __typename: 'TaskTargetConnection', + edges: [], + }, + }, + }, + { + __typename: 'CompanyEdge', + cursor: + 'eyJwb3NpdGlvbiI6OSwiaWQiOiIyMDIwMjAyMC1jZmJmLTQxNTYtYTc5MC1lMzk4NTRkY2Q0ZWIifQ==', + node: { + __typename: 'Company', + accountOwner: null, + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + employees: null, + id: '20202020-cfbf-4156-a790-e39854dcd4eb', + name: 'Claap', + position: 9, + opportunities: { + __typename: 'OpportunityConnection', + edges: [], + }, + people: { + __typename: 'PersonConnection', + edges: [], + }, + address: { + __typename: 'Address', + addressStreet1: null, + addressStreet2: null, + addressCity: null, + addressState: null, + addressCountry: null, + addressPostcode: null, + addressLat: null, + addressLng: null, + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + domainName: { + __typename: 'Links', + primaryLinkUrl: 'https://claap.io', + primaryLinkLabel: '', + secondaryLinks: [], + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + noteTargets: { + __typename: 'NoteTargetConnection', + edges: [], + }, + taskTargets: { + __typename: 'TaskTargetConnection', + edges: [], + }, + }, + }, + { + __typename: 'CompanyEdge', + cursor: + 'eyJwb3NpdGlvbiI6MTAsImlkIjoiMjAyMDIwMjAtZjg2Yi00MTlmLWI3OTQtMDIzMTlhYmU4NjM3In0=', + node: { + __typename: 'Company', + accountOwner: null, + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + employees: null, + id: '20202020-f86b-419f-b794-02319abe8637', + name: 'Hasura', + position: 10, + opportunities: { + __typename: 'OpportunityConnection', + edges: [], + }, + people: { + __typename: 'PersonConnection', + edges: [], + }, + address: { + __typename: 'Address', + addressStreet1: null, + addressStreet2: null, + addressCity: null, + addressState: null, + addressCountry: null, + addressPostcode: null, + addressLat: null, + addressLng: null, + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + domainName: { + __typename: 'Links', + primaryLinkUrl: 'https://hasura.io', + primaryLinkLabel: '', + secondaryLinks: [], + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + noteTargets: { + __typename: 'NoteTargetConnection', + edges: [], + }, + taskTargets: { + __typename: 'TaskTargetConnection', + edges: [], + }, + }, + }, + { + __typename: 'CompanyEdge', + cursor: + 'eyJwb3NpdGlvbiI6MTEsImlkIjoiMjAyMDIwMjAtNTUxOC00NTUzLTk0MzMtNDJkOGViODI4MzRiIn0=', + node: { + __typename: 'Company', + accountOwner: null, + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + employees: null, + id: '20202020-5518-4553-9433-42d8eb82834b', + name: 'Wework', + position: 11, + opportunities: { + __typename: 'OpportunityConnection', + edges: [], + }, + people: { + __typename: 'PersonConnection', + edges: [], + }, + address: { + __typename: 'Address', + addressStreet1: null, + addressStreet2: null, + addressCity: null, + addressState: null, + addressCountry: null, + addressPostcode: null, + addressLat: null, + addressLng: null, + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + domainName: { + __typename: 'Links', + primaryLinkUrl: 'https://wework.com', + primaryLinkLabel: '', + secondaryLinks: [], + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + noteTargets: { + __typename: 'NoteTargetConnection', + edges: [], + }, + taskTargets: { + __typename: 'TaskTargetConnection', + edges: [], + }, + }, + }, + { + __typename: 'CompanyEdge', + cursor: + 'eyJwb3NpdGlvbiI6MTIsImlkIjoiMjAyMDIwMjAtZjc5ZS00MGRkLWJkMDYtYzM2ZTZhYmI0Njc4In0=', + node: { + __typename: 'Company', + accountOwner: null, + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + employees: null, + id: '20202020-f79e-40dd-bd06-c36e6abb4678', + name: 'Samsung', + position: 12, + opportunities: { + __typename: 'OpportunityConnection', + edges: [], + }, + people: { + __typename: 'PersonConnection', + edges: [], + }, + address: { + __typename: 'Address', + addressStreet1: null, + addressStreet2: null, + addressCity: null, + addressState: null, + addressCountry: null, + addressPostcode: null, + addressLat: null, + addressLng: null, + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + domainName: { + __typename: 'Links', + primaryLinkUrl: 'https://samsung.com', + primaryLinkLabel: '', + secondaryLinks: [], + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + noteTargets: { + __typename: 'NoteTargetConnection', + edges: [], + }, + taskTargets: { + __typename: 'TaskTargetConnection', + edges: [], + }, + }, + }, + { + __typename: 'CompanyEdge', + cursor: + 'eyJwb3NpdGlvbiI6MTMsImlkIjoiMjAyMDIwMjAtMTQ1NS00YzU3LWFmYWYtZGQ1ZGMwODYzNjFkIn0=', + node: { + __typename: 'Company', + accountOwner: null, + createdAt: '2025-02-16T08:21:51.715Z', + deletedAt: null, + employees: null, + id: '20202020-1455-4c57-afaf-dd5dc086361d', + name: 'Algolia', + position: 13, + opportunities: { + __typename: 'OpportunityConnection', + edges: [], + }, + people: { + __typename: 'PersonConnection', + edges: [], + }, + address: { + __typename: 'Address', + addressStreet1: null, + addressStreet2: null, + addressCity: null, + addressState: null, + addressCountry: null, + addressPostcode: null, + addressLat: null, + addressLng: null, + }, + createdBy: { + __typename: 'Actor', + source: 'MANUAL', + workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7', + name: 'Tim Apple', + context: {}, + }, + domainName: { + __typename: 'Links', + primaryLinkUrl: 'https://algolia.com', + primaryLinkLabel: '', + secondaryLinks: [], + }, + linkedinLink: { + __typename: 'Links', + primaryLinkUrl: '', + primaryLinkLabel: '', + secondaryLinks: [], + }, + noteTargets: { + __typename: 'NoteTargetConnection', + edges: [], + }, + taskTargets: { + __typename: 'TaskTargetConnection', + edges: [], + }, + }, + }, + ], + }, + }, + loading: false, + networkStatus: 7, +} as const; + +export const allMockCompanyRecordsWithRelation = + FIND_MANY_COMPANIES_WITH_RELATION_QUERY_RESULT.data.companies.edges.map( + (edge) => getRecordFromRecordNode({ recordNode: edge.node }), + ); + +export const getMockCompanyWithRelationRecord = ( + overrides?: Partial, + index = 0, +) => { + return { + ...allMockCompanyRecordsWithRelation[index], + ...overrides, + }; +}; + +export const findMockCompanyWithRelationRecord = ({ + id: queriedCompanyId, +}: Pick) => { + const company = allMockCompanyRecordsWithRelation.find( + ({ id: currentCompanyId }) => currentCompanyId === queriedCompanyId, + ); + + if (!isDefined(company)) { + throw new Error(`Could not find company with id, ${queriedCompanyId}`); + } + + return company; +}; diff --git a/packages/twenty-front/src/testing/mock-data/people.ts b/packages/twenty-front/src/testing/mock-data/people.ts index bb02de9a2..aaad09421 100644 --- a/packages/twenty-front/src/testing/mock-data/people.ts +++ b/packages/twenty-front/src/testing/mock-data/people.ts @@ -1,73 +1,9 @@ +import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode'; import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { FieldMetadataType } from 'twenty-shared'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -export const getPeopleMock = (): ObjectRecord[] => { - const peopleMock = peopleQueryResult.people.edges.map((edge) => edge.node); - - return peopleMock; -}; - -export const getPersonObjectMetadataItem = () => { - const personObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'person', - ); - - if (!personObjectMetadataItem) { - throw new Error('Person object metadata item not found'); - } - - return personObjectMetadataItem; -}; - -export const getPersonFieldMetadataItem = ( - fieldMetadataType: FieldMetadataType, - objectMetadataItem = getPersonObjectMetadataItem(), -) => { - const result = objectMetadataItem.fields.find( - (field) => field.type === fieldMetadataType, - ); - if (!result) { - throw new Error( - `Person fieldmetadata item type ${fieldMetadataType} not found`, - ); - } - - return result; -}; - -export const getPersonRecord = ( - overrides?: Partial, - index = 0, -) => { - const personRecords = getPeopleMock(); - return { - ...personRecords[index], - ...overrides, - }; -}; - -export const mockedEmptyPersonData = { - id: 'ce7f0a37-88d7-4cd8-8b41-6721c57195b5', - firstName: '', - lastName: '', - phone: null, - email: null, - city: null, - createdBy: null, - displayName: null, - avatarUrl: null, - createdAt: null, - jobTitle: null, - linkedinUrl: null, - xUrl: null, - _activityCount: null, - company: null, - deletedAt: null, - __typename: 'Person', -}; - export const peopleQueryResult = { people: { __typename: 'PersonConnection', @@ -1762,3 +1698,71 @@ export const peopleQueryResult = { ], }, } satisfies { people: RecordGqlConnection }; + +export const allMockPersonRecords = peopleQueryResult.people.edges.map((edge) => + getRecordFromRecordNode({ recordNode: edge.node }), +); + +export const getPeopleRecordConnectionMock = () => { + const peopleMock = peopleQueryResult.people.edges.map((edge) => edge.node); + + return peopleMock; +}; + +export const getMockPersonObjectMetadataItem = () => { + const personObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', + ); + + if (!personObjectMetadataItem) { + throw new Error('Person object metadata item not found'); + } + + return personObjectMetadataItem; +}; + +export const getMockPersonFieldMetadataItem = ( + fieldMetadataType: FieldMetadataType, + objectMetadataItem = getMockPersonObjectMetadataItem(), +) => { + const result = objectMetadataItem.fields.find( + (field) => field.type === fieldMetadataType, + ); + if (!result) { + throw new Error( + `Person fieldmetadata item type ${fieldMetadataType} not found`, + ); + } + + return result; +}; + +export const getMockPersonRecord = ( + overrides?: Partial, + index = 0, +) => { + return { + ...allMockPersonRecords[index], + ...overrides, + }; +}; + +export const mockedEmptyPersonData = { + id: 'ce7f0a37-88d7-4cd8-8b41-6721c57195b5', + firstName: '', + lastName: '', + phone: null, + email: null, + city: null, + createdBy: null, + displayName: null, + avatarUrl: null, + createdAt: null, + jobTitle: null, + linkedinUrl: null, + xUrl: null, + _activityCount: null, + company: null, + deletedAt: null, + __typename: 'Person', +};