diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivities.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivities.test.tsx
new file mode 100644
index 000000000..56730f9c8
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivities.test.tsx
@@ -0,0 +1,242 @@
+import { ReactNode } from 'react';
+import { gql } from '@apollo/client';
+import { MockedProvider, MockedResponse } from '@apollo/client/testing';
+import { act, renderHook, waitFor } from '@testing-library/react';
+import { RecoilRoot, useSetRecoilState } from 'recoil';
+
+import { useActivities } from '@/activities/hooks/useActivities';
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
+import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
+import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members';
+
+const mockActivityTarget = {
+ __typename: 'ActivityTarget',
+ updatedAt: '2021-08-03T19:20:06.000Z',
+ createdAt: '2021-08-03T19:20:06.000Z',
+ personId: '1',
+ activityId: '234',
+ companyId: '1',
+ id: '123',
+};
+
+const mockActivity = {
+ __typename: 'Activity',
+ activityTargets: [],
+ updatedAt: '2021-08-03T19:20:06.000Z',
+ createdAt: '2021-08-03T19:20:06.000Z',
+ completedAt: '2021-08-03T19:20:06.000Z',
+ reminderAt: '2021-08-03T19:20:06.000Z',
+ title: 'title',
+ authorId: '1',
+ body: 'body',
+ comments: [],
+ dueAt: '2021-08-03T19:20:06.000Z',
+ type: 'type',
+ assigneeId: '1',
+ id: '234',
+};
+
+const defaultResponseData = {
+ pageInfo: {
+ hasNextPage: false,
+ startCursor: '',
+ endCursor: '',
+ },
+ totalCount: 1,
+};
+
+const mocks: MockedResponse[] = [
+ {
+ request: {
+ query: gql`
+ query FindManyActivityTargets(
+ $filter: ActivityTargetFilterInput
+ $orderBy: ActivityTargetOrderByInput
+ $lastCursor: String
+ $limit: Float
+ ) {
+ activityTargets(
+ filter: $filter
+ orderBy: $orderBy
+ first: $limit
+ after: $lastCursor
+ ) {
+ edges {
+ node {
+ __typename
+ updatedAt
+ createdAt
+ personId
+ activityId
+ companyId
+ id
+ }
+ cursor
+ }
+ pageInfo {
+ hasNextPage
+ startCursor
+ endCursor
+ }
+ totalCount
+ }
+ }
+ `,
+ variables: {
+ filter: { activityTargetId: { eq: '123' } },
+ limit: undefined,
+ orderBy: undefined,
+ },
+ },
+ result: jest.fn(() => ({
+ data: {
+ activityTargets: {
+ ...defaultResponseData,
+ edges: [
+ {
+ node: mockActivityTarget,
+ cursor: '1',
+ },
+ ],
+ },
+ },
+ })),
+ },
+ {
+ request: {
+ query: gql`
+ query FindManyActivities(
+ $filter: ActivityFilterInput
+ $orderBy: ActivityOrderByInput
+ $lastCursor: String
+ $limit: Float
+ ) {
+ activities(
+ filter: $filter
+ orderBy: $orderBy
+ first: $limit
+ after: $lastCursor
+ ) {
+ edges {
+ node {
+ __typename
+ createdAt
+ reminderAt
+ authorId
+ title
+ completedAt
+ updatedAt
+ body
+ dueAt
+ type
+ id
+ assigneeId
+ }
+ cursor
+ }
+ pageInfo {
+ hasNextPage
+ startCursor
+ endCursor
+ }
+ totalCount
+ }
+ }
+ `,
+ variables: {
+ filter: { id: { in: ['234'] } },
+ limit: undefined,
+ orderBy: {},
+ },
+ },
+ result: jest.fn(() => ({
+ data: {
+ activities: {
+ ...defaultResponseData,
+ edges: [
+ {
+ node: mockActivity,
+ cursor: '1',
+ },
+ ],
+ },
+ },
+ })),
+ },
+];
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+
+
+ {children}
+
+
+
+);
+
+describe('useActivities', () => {
+ it('returns default response', () => {
+ const { result } = renderHook(
+ () =>
+ useActivities({
+ targetableObjects: [],
+ activitiesFilters: {},
+ activitiesOrderByVariables: {},
+ skip: false,
+ skipActivityTargets: false,
+ }),
+ { wrapper: Wrapper },
+ );
+
+ expect(result.current).toEqual({
+ activities: [],
+ loading: false,
+ initialized: true,
+ noActivities: true,
+ });
+ });
+
+ it('fetches activities', async () => {
+ const { result } = renderHook(
+ () => {
+ const setCurrentWorkspaceMember = useSetRecoilState(
+ currentWorkspaceMemberState(),
+ );
+
+ const activities = useActivities({
+ targetableObjects: [
+ { targetObjectNameSingular: 'activityTarget', id: '123' },
+ ],
+ activitiesFilters: {},
+ activitiesOrderByVariables: {},
+ skip: false,
+ skipActivityTargets: false,
+ });
+ return { activities, setCurrentWorkspaceMember };
+ },
+ { wrapper: Wrapper },
+ );
+
+ act(() => {
+ result.current.setCurrentWorkspaceMember(mockWorkspaceMembers[0]);
+ });
+
+ expect(result.current.activities.loading).toBe(true);
+
+ // Wait for activityTargets to complete fetching
+ await waitFor(() => !result.current.activities.loading);
+
+ expect(result.current.activities.loading).toBe(false);
+
+ // Wait for request to fetch activities to be made
+ await waitFor(() => result.current.activities.loading);
+
+ // Wait for activities to complete fetching
+ await waitFor(() => !result.current.activities.loading);
+
+ const { activities } = result.current;
+
+ expect(activities.activities).toEqual([mockActivity]);
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityById.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityById.test.tsx
new file mode 100644
index 000000000..4db2793e0
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityById.test.tsx
@@ -0,0 +1,80 @@
+import { ReactNode } from 'react';
+import { MockedProvider, MockedResponse } from '@apollo/client/testing';
+import { renderHook, waitFor } from '@testing-library/react';
+import gql from 'graphql-tag';
+import { RecoilRoot } from 'recoil';
+
+import { useActivityById } from '@/activities/hooks/useActivityById';
+import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
+import { mockedActivities } from '~/testing/mock-data/activities';
+
+const mocks: MockedResponse[] = [
+ {
+ request: {
+ query: gql`
+ query FindOneActivity($objectRecordId: UUID!) {
+ activity(filter: { id: { eq: $objectRecordId } }) {
+ __typename
+ createdAt
+ reminderAt
+ authorId
+ title
+ completedAt
+ updatedAt
+ body
+ dueAt
+ type
+ id
+ assigneeId
+ }
+ }
+ `,
+ variables: { objectRecordId: '1234' },
+ },
+ result: jest.fn(() => ({
+ data: {
+ activity: mockedActivities[0],
+ },
+ })),
+ },
+];
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+
+
+ {children}
+
+
+
+);
+
+describe('useActivityById', () => {
+ it('works as expected', async () => {
+ const { result } = renderHook(
+ () => useActivityById({ activityId: '1234' }),
+ { wrapper: Wrapper },
+ );
+
+ expect(result.current.loading).toBe(true);
+
+ await waitFor(() => !result.current.loading);
+
+ expect(result.current.activity).toEqual({
+ __typename: 'Activity',
+ assigneeId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
+ authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
+ body: '',
+ comments: [],
+ completedAt: null,
+ createdAt: '2023-04-26T10:12:42.33625+00:00',
+ activityTargets: [],
+ dueAt: '2023-04-26T10:12:42.33625+00:00',
+ id: '3ecaa1be-aac7-463a-a38e-64078dd451d5',
+ reminderAt: null,
+ title: 'My very first note',
+ type: 'Note',
+ updatedAt: '2023-04-26T10:23:42.33625+00:00',
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityConnectionUtils.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityConnectionUtils.test.tsx
index 88da90b3a..ddda904ee 100644
--- a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityConnectionUtils.test.tsx
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityConnectionUtils.test.tsx
@@ -104,14 +104,6 @@ describe('useActivityConnectionUtils', () => {
expect(activityWithConnection).toBeDefined();
- console.log(
- JSON.stringify({
- mockActivityWithConnectionRelation,
- activityWithConnection,
- mockActivityWithArrayRelation,
- }),
- );
-
expect(activityWithConnection.activityTargets.edges[0].node.id).toEqual(
mockActivityWithConnectionRelation.activityTargets.edges[0].node.id,
);
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx
new file mode 100644
index 000000000..0a1b8f7b4
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx
@@ -0,0 +1,223 @@
+import { ReactNode } from 'react';
+import { MockedProvider, MockedResponse } from '@apollo/client/testing';
+import { act, renderHook, waitFor } from '@testing-library/react';
+import gql from 'graphql-tag';
+import { RecoilRoot, useSetRecoilState } from 'recoil';
+
+import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
+import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
+import { mockedActivities } from '~/testing/mock-data/activities';
+import { mockedCompaniesData } from '~/testing/mock-data/companies';
+import { mockedPeopleData } from '~/testing/mock-data/people';
+import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members';
+
+const defaultResponseData = {
+ pageInfo: {
+ hasNextPage: false,
+ startCursor: '',
+ endCursor: '',
+ },
+ totalCount: 1,
+};
+
+const mockActivityTarget = {
+ __typename: 'ActivityTarget',
+ updatedAt: '2021-08-03T19:20:06.000Z',
+ createdAt: '2021-08-03T19:20:06.000Z',
+ personId: '1',
+ activityId: '234',
+ companyId: '1',
+ id: '123',
+ person: { ...mockedPeopleData[0], __typename: 'Person', updatedAt: '' },
+ company: { ...mockedCompaniesData[0], __typename: 'Company', updatedAt: '' },
+ activity: mockedActivities[0],
+};
+
+const mocks: MockedResponse[] = [
+ {
+ request: {
+ query: gql`
+ query FindManyActivityTargets(
+ $filter: ActivityTargetFilterInput
+ $orderBy: ActivityTargetOrderByInput
+ $lastCursor: String
+ $limit: Float
+ ) {
+ activityTargets(
+ filter: $filter
+ orderBy: $orderBy
+ first: $limit
+ after: $lastCursor
+ ) {
+ edges {
+ node {
+ __typename
+ updatedAt
+ createdAt
+ company {
+ __typename
+ xLink {
+ label
+ url
+ }
+ linkedinLink {
+ label
+ url
+ }
+ domainName
+ annualRecurringRevenue {
+ amountMicros
+ currencyCode
+ }
+ createdAt
+ address
+ updatedAt
+ name
+ accountOwnerId
+ employees
+ id
+ idealCustomerProfile
+ }
+ personId
+ activityId
+ companyId
+ id
+ activity {
+ __typename
+ createdAt
+ reminderAt
+ authorId
+ title
+ completedAt
+ updatedAt
+ body
+ dueAt
+ type
+ id
+ assigneeId
+ }
+ person {
+ __typename
+ xLink {
+ label
+ url
+ }
+ id
+ createdAt
+ city
+ email
+ jobTitle
+ name {
+ firstName
+ lastName
+ }
+ phone
+ linkedinLink {
+ label
+ url
+ }
+ updatedAt
+ avatarUrl
+ companyId
+ }
+ }
+ cursor
+ }
+ pageInfo {
+ hasNextPage
+ startCursor
+ endCursor
+ }
+ totalCount
+ }
+ }
+ `,
+ variables: {
+ filter: { activityId: { eq: '1234' } },
+ limit: undefined,
+ orderBy: undefined,
+ },
+ },
+ result: jest.fn(() => ({
+ data: {
+ activityTargets: {
+ ...defaultResponseData,
+ edges: [
+ {
+ node: mockActivityTarget,
+ cursor: '1',
+ },
+ ],
+ },
+ },
+ })),
+ },
+];
+
+const mockObjectMetadataItems = getObjectMetadataItemsMock();
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+
+
+ {children}
+
+
+
+);
+
+describe('useActivityTargetObjectRecords', () => {
+ it('returns default response', () => {
+ const { result } = renderHook(
+ () => useActivityTargetObjectRecords({ activityId: '1234' }),
+ { wrapper: Wrapper },
+ );
+
+ expect(result.current).toEqual({
+ activityTargetObjectRecords: [],
+ loadingActivityTargets: false,
+ });
+ });
+
+ it('fetches records', async () => {
+ const { result } = renderHook(
+ () => {
+ const setCurrentWorkspaceMember = useSetRecoilState(
+ currentWorkspaceMemberState(),
+ );
+ const setObjectMetadataItems = useSetRecoilState(
+ objectMetadataItemsState(),
+ );
+
+ const { activityTargetObjectRecords, loadingActivityTargets } =
+ useActivityTargetObjectRecords({ activityId: '1234' });
+ return {
+ activityTargetObjectRecords,
+ loadingActivityTargets,
+ setCurrentWorkspaceMember,
+ setObjectMetadataItems,
+ };
+ },
+ { wrapper: Wrapper },
+ );
+
+ act(() => {
+ result.current.setCurrentWorkspaceMember(mockWorkspaceMembers[0]);
+ result.current.setObjectMetadataItems(mockObjectMetadataItems);
+ });
+
+ expect(result.current.loadingActivityTargets).toBe(true);
+
+ // Wait for activityTargets to complete fetching
+ await waitFor(() => !result.current.loadingActivityTargets);
+
+ expect(mocks[0].result).toHaveBeenCalled();
+ expect(result.current.activityTargetObjectRecords).toHaveLength(1);
+ expect(
+ result.current.activityTargetObjectRecords[0].targetObjectNameSingular,
+ ).toBe('person');
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetsForTargetableObject.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetsForTargetableObject.test.tsx
new file mode 100644
index 000000000..e1e55e9c3
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetsForTargetableObject.test.tsx
@@ -0,0 +1,129 @@
+import { ReactNode } from 'react';
+import { MockedProvider, MockedResponse } from '@apollo/client/testing';
+import { act, renderHook, waitFor } from '@testing-library/react';
+import gql from 'graphql-tag';
+import { RecoilRoot, useSetRecoilState } from 'recoil';
+
+import { useActivityTargetsForTargetableObject } from '@/activities/hooks/useActivityTargetsForTargetableObject';
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
+import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
+import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members';
+
+const mockActivityTarget = {
+ __typename: 'ActivityTarget',
+ updatedAt: '2021-08-03T19:20:06.000Z',
+ createdAt: '2021-08-03T19:20:06.000Z',
+ personId: '1',
+ activityId: '234',
+ companyId: '1',
+ id: '123',
+};
+
+const defaultResponseData = {
+ pageInfo: {
+ hasNextPage: false,
+ startCursor: '',
+ endCursor: '',
+ },
+ totalCount: 1,
+};
+
+const mocks: MockedResponse[] = [
+ {
+ request: {
+ query: gql`
+ query FindManyActivityTargets(
+ $filter: ActivityTargetFilterInput
+ $orderBy: ActivityTargetOrderByInput
+ $lastCursor: String
+ $limit: Float
+ ) {
+ activityTargets(
+ filter: $filter
+ orderBy: $orderBy
+ first: $limit
+ after: $lastCursor
+ ) {
+ edges {
+ node {
+ __typename
+ updatedAt
+ createdAt
+ personId
+ activityId
+ companyId
+ id
+ }
+ cursor
+ }
+ pageInfo {
+ hasNextPage
+ startCursor
+ endCursor
+ }
+ totalCount
+ }
+ }
+ `,
+ variables: {
+ filter: { personId: { eq: '1234' } },
+ limit: undefined,
+ orderBy: undefined,
+ },
+ },
+ result: jest.fn(() => ({
+ data: {
+ activityTargets: {
+ ...defaultResponseData,
+ edges: [
+ {
+ node: mockActivityTarget,
+ cursor: '1',
+ },
+ ],
+ },
+ },
+ })),
+ },
+];
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+
+
+ {children}
+
+
+
+);
+
+describe('useActivityTargetsForTargetableObject', () => {
+ it('works as expected', async () => {
+ const { result } = renderHook(
+ () => {
+ const setCurrentWorkspaceMember = useSetRecoilState(
+ currentWorkspaceMemberState(),
+ );
+
+ const res = useActivityTargetsForTargetableObject({
+ targetableObject: {
+ id: '1234',
+ targetObjectNameSingular: 'person',
+ },
+ });
+ return { ...res, setCurrentWorkspaceMember };
+ },
+ { wrapper: Wrapper },
+ );
+
+ act(() => {
+ result.current.setCurrentWorkspaceMember(mockWorkspaceMembers[0]);
+ });
+
+ expect(result.current.loadingActivityTargets).toBe(true);
+
+ await waitFor(() => !result.current.loadingActivityTargets);
+
+ expect(result.current.activityTargets).toEqual([mockActivityTarget]);
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useAttachRelationInBothDirections.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useAttachRelationInBothDirections.test.tsx
new file mode 100644
index 000000000..e55118b7a
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useAttachRelationInBothDirections.test.tsx
@@ -0,0 +1,76 @@
+import { ReactNode } from 'react';
+import { MockedProvider, MockedResponse } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import { RecoilRoot, useSetRecoilState } from 'recoil';
+
+import { useAttachRelationInBothDirections } from '@/activities/hooks/useAttachRelationInBothDirections';
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
+import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
+import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members';
+
+const mocks: MockedResponse[] = [];
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+
+
+ {children}
+
+
+
+);
+
+const mockObjectMetadataItems = getObjectMetadataItemsMock();
+
+describe('useAttachRelationInBothDirections', () => {
+ it('works as expected', () => {
+ const { result } = renderHook(
+ () => {
+ const setCurrentWorkspaceMember = useSetRecoilState(
+ currentWorkspaceMemberState(),
+ );
+ const setObjectMetadataItems = useSetRecoilState(
+ objectMetadataItemsState(),
+ );
+
+ const res = useAttachRelationInBothDirections();
+ return {
+ ...res,
+ setCurrentWorkspaceMember,
+ setObjectMetadataItems,
+ };
+ },
+ { wrapper: Wrapper },
+ );
+
+ act(() => {
+ result.current.setCurrentWorkspaceMember(mockWorkspaceMembers[0]);
+ result.current.setObjectMetadataItems(mockObjectMetadataItems);
+ });
+ const targetRecords = [
+ { id: '5678', person: { id: '1234' } },
+ { id: '91011', person: { id: '1234' } },
+ ];
+
+ const forEachSpy = jest.spyOn(targetRecords, 'forEach');
+
+ act(() => {
+ result.current.attachRelationInBothDirections({
+ sourceRecord: {
+ id: '1234',
+ company: { id: '5678' },
+ },
+ targetRecords,
+ sourceObjectNameSingular: 'person',
+ targetObjectNameSingular: 'company',
+ fieldNameOnSourceRecord: 'company',
+ fieldNameOnTargetRecord: 'person',
+ });
+ });
+
+ // expect forEach to have been called on targetRecords
+ expect(forEachSpy).toHaveBeenCalled();
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInCache.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInCache.test.tsx
new file mode 100644
index 000000000..08a571a2e
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInCache.test.tsx
@@ -0,0 +1,99 @@
+import { ReactNode } from 'react';
+import { MockedProvider, MockedResponse } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import gql from 'graphql-tag';
+import { RecoilRoot, useSetRecoilState } from 'recoil';
+
+import { useCreateActivityInCache } from '@/activities/hooks/useCreateActivityInCache';
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
+import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
+import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members';
+
+const mocks: MockedResponse[] = [
+ {
+ request: {
+ query: gql`
+ query FindOneWorkspaceMember($objectRecordId: UUID!) {
+ workspaceMember(filter: { id: { eq: $objectRecordId } }) {
+ __typename
+ colorScheme
+ name {
+ firstName
+ lastName
+ }
+ locale
+ userId
+ avatarUrl
+ createdAt
+ updatedAt
+ id
+ }
+ }
+ `,
+ variables: { objectRecordId: '20202020-1553-45c6-a028-5a9064cce07f' },
+ },
+ result: jest.fn(() => ({
+ data: {
+ workspaceMember: mockWorkspaceMembers[0],
+ },
+ })),
+ },
+];
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+
+
+ {children}
+
+
+
+);
+
+const mockObjectMetadataItems = getObjectMetadataItemsMock();
+
+describe('useCreateActivityInCache', () => {
+ it('Should create activity in cache', async () => {
+ const { result } = renderHook(
+ () => {
+ const setCurrentWorkspaceMember = useSetRecoilState(
+ currentWorkspaceMemberState(),
+ );
+ const setObjectMetadataItems = useSetRecoilState(
+ objectMetadataItemsState(),
+ );
+
+ const res = useCreateActivityInCache();
+ return {
+ ...res,
+ setCurrentWorkspaceMember,
+ setObjectMetadataItems,
+ };
+ },
+ { wrapper: Wrapper },
+ );
+
+ act(() => {
+ result.current.setCurrentWorkspaceMember(mockWorkspaceMembers[0]);
+ result.current.setObjectMetadataItems(mockObjectMetadataItems);
+ });
+
+ act(() => {
+ const res = result.current.createActivityInCache({
+ type: 'Note',
+ targetableObjects: [
+ {
+ targetObjectNameSingular: 'person',
+ id: '1234',
+ },
+ ],
+ });
+
+ expect(res.createdActivityInCache).toHaveProperty('id');
+ expect(res.createdActivityInCache).toHaveProperty('__typename');
+ expect(res.createdActivityInCache).toHaveProperty('activityTargets');
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInDB.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInDB.test.tsx
new file mode 100644
index 000000000..5c982afaa
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInDB.test.tsx
@@ -0,0 +1,90 @@
+import { ReactNode } from 'react';
+import { MockedProvider, MockedResponse } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import gql from 'graphql-tag';
+import pick from 'lodash/pick';
+import { RecoilRoot } from 'recoil';
+
+import { useCreateActivityInDB } from '@/activities/hooks/useCreateActivityInDB';
+import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
+import { mockedActivities } from '~/testing/mock-data/activities';
+
+const mockedDate = '2024-03-15T12:00:00.000Z';
+const toISOStringMock = jest.fn(() => mockedDate);
+global.Date.prototype.toISOString = toISOStringMock;
+
+const mockedActivity = {
+ ...pick(mockedActivities[0], [
+ 'id',
+ 'title',
+ 'body',
+ 'type',
+ 'completedAt',
+ 'dueAt',
+ ]),
+ updatedAt: mockedDate,
+};
+
+const mocks: MockedResponse[] = [
+ {
+ request: {
+ query: gql`
+ mutation CreateOneActivity($input: ActivityCreateInput!) {
+ createActivity(data: $input) {
+ __typename
+ createdAt
+ reminderAt
+ authorId
+ title
+ completedAt
+ updatedAt
+ body
+ dueAt
+ type
+ id
+ assigneeId
+ }
+ }
+ `,
+ variables: {
+ input: mockedActivity,
+ },
+ },
+ result: jest.fn(() => ({
+ data: {
+ createActivity: {
+ ...mockedActivity,
+ __typename: 'Activity',
+ assigneeId: '',
+ authorId: '1',
+ reminderAt: null,
+ createdAt: mockedDate,
+ },
+ },
+ })),
+ },
+];
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+
+
+ {children}
+
+
+
+);
+
+describe('useCreateActivityInDB', () => {
+ it('Should create activity in DB', async () => {
+ const { result } = renderHook(() => useCreateActivityInDB(), {
+ wrapper: Wrapper,
+ });
+
+ await act(async () => {
+ await result.current.createActivityInDB(mockedActivity);
+ });
+
+ expect(mocks[0].result).toHaveBeenCalled();
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useDeleteActivityFromCache.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useDeleteActivityFromCache.test.tsx
new file mode 100644
index 000000000..e999f6394
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useDeleteActivityFromCache.test.tsx
@@ -0,0 +1,53 @@
+import { ReactNode } from 'react';
+import { MockedProvider } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import pick from 'lodash.pick';
+import { RecoilRoot } from 'recoil';
+
+import { useDeleteActivityFromCache } from '@/activities/hooks/useDeleteActivityFromCache';
+import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
+import { mockedActivities } from '~/testing/mock-data/activities';
+
+const triggerDeleteRecordsOptimisticEffectMock = jest.fn();
+
+// mock the triggerDeleteRecordsOptimisticEffect function
+jest.mock(
+ '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect',
+ () => ({
+ triggerDeleteRecordsOptimisticEffect: jest.fn(),
+ }),
+);
+
+(triggerDeleteRecordsOptimisticEffect as jest.Mock).mockImplementation(
+ triggerDeleteRecordsOptimisticEffectMock,
+);
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+);
+
+describe('useDeleteActivityFromCache', () => {
+ it('works as expected', () => {
+ const { result } = renderHook(() => useDeleteActivityFromCache(), {
+ wrapper: Wrapper,
+ });
+
+ act(() => {
+ result.current.deleteActivityFromCache(
+ pick(mockedActivities[0], [
+ 'id',
+ 'title',
+ 'body',
+ 'type',
+ 'completedAt',
+ 'dueAt',
+ 'updatedAt',
+ ]),
+ );
+
+ expect(triggerDeleteRecordsOptimisticEffectMock).toHaveBeenCalledTimes(1);
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useInjectIntoActivitiesQueries.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useInjectIntoActivitiesQueries.test.tsx
new file mode 100644
index 000000000..f965c403f
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useInjectIntoActivitiesQueries.test.tsx
@@ -0,0 +1,60 @@
+import { ReactNode } from 'react';
+import { MockedProvider } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import { RecoilRoot } from 'recoil';
+
+import { useInjectIntoActivitiesQueries } from '@/activities/hooks/useInjectIntoActivitiesQueries';
+import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
+import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
+import { mockedActivities } from '~/testing/mock-data/activities';
+
+const upsertFindManyRecordsQueryInCacheMock = jest.fn();
+
+jest.mock(
+ '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache',
+ () => ({
+ useUpsertFindManyRecordsQueryInCache: jest.fn(),
+ }),
+);
+
+(useUpsertFindManyRecordsQueryInCache as jest.Mock).mockImplementation(() => ({
+ upsertFindManyRecordsQueryInCache: upsertFindManyRecordsQueryInCacheMock,
+}));
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+
+
+ {children}
+
+
+
+);
+
+describe('useInjectIntoActivitiesQueries', () => {
+ it('works as expected', () => {
+ const { result } = renderHook(() => useInjectIntoActivitiesQueries(), {
+ wrapper: Wrapper,
+ });
+
+ act(() => {
+ result.current.injectActivitiesQueries({
+ activityToInject: mockedActivities[0],
+ activityTargetsToInject: [],
+ targetableObjects: [{ id: '123', targetObjectNameSingular: 'person' }],
+ });
+
+ expect(upsertFindManyRecordsQueryInCacheMock).toHaveBeenCalledTimes(1);
+ });
+
+ act(() => {
+ result.current.injectActivitiesQueries({
+ activityToInject: mockedActivities[0],
+ activityTargetsToInject: [],
+ targetableObjects: [],
+ });
+
+ expect(upsertFindManyRecordsQueryInCacheMock).toHaveBeenCalledTimes(2);
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useInjectIntoActivityTargetsQueries.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useInjectIntoActivityTargetsQueries.test.tsx
new file mode 100644
index 000000000..969b63943
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useInjectIntoActivityTargetsQueries.test.tsx
@@ -0,0 +1,64 @@
+import { ReactNode } from 'react';
+import { MockedProvider } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import { RecoilRoot } from 'recoil';
+
+import { useInjectIntoActivityTargetsQueries } from '@/activities/hooks/useInjectIntoActivityTargetsQueries';
+import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
+import { mockedActivities } from '~/testing/mock-data/activities';
+
+const upsertFindManyRecordsQueryInCacheMock = jest.fn();
+
+jest.mock(
+ '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache',
+ () => ({
+ useUpsertFindManyRecordsQueryInCache: jest.fn(),
+ }),
+);
+
+(useUpsertFindManyRecordsQueryInCache as jest.Mock).mockImplementation(() => ({
+ upsertFindManyRecordsQueryInCache: upsertFindManyRecordsQueryInCacheMock,
+}));
+
+const mockActivityTarget = {
+ __typename: 'ActivityTarget',
+ updatedAt: '2021-08-03T19:20:06.000Z',
+ createdAt: '2021-08-03T19:20:06.000Z',
+ personId: '1',
+ activityId: '234',
+ companyId: '1',
+ id: '123',
+ activity: mockedActivities[0],
+};
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+);
+
+describe('useInjectIntoActivityTargetsQueries', () => {
+ it('works as expected', () => {
+ const { result } = renderHook(() => useInjectIntoActivityTargetsQueries(), {
+ wrapper: Wrapper,
+ });
+
+ act(() => {
+ result.current.injectActivityTargetsQueries({
+ activityTargetsToInject: [mockActivityTarget],
+ targetableObjects: [{ id: '123', targetObjectNameSingular: 'person' }],
+ });
+
+ expect(upsertFindManyRecordsQueryInCacheMock).toHaveBeenCalledTimes(1);
+ });
+
+ act(() => {
+ result.current.injectActivityTargetsQueries({
+ activityTargetsToInject: [mockActivityTarget],
+ targetableObjects: [],
+ });
+
+ expect(upsertFindManyRecordsQueryInCacheMock).toHaveBeenCalledTimes(1);
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useModifyActivityOnActivityTargetsCache.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useModifyActivityOnActivityTargetsCache.test.tsx
new file mode 100644
index 000000000..b5858858b
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useModifyActivityOnActivityTargetsCache.test.tsx
@@ -0,0 +1,44 @@
+import { ReactNode } from 'react';
+import { MockedProvider } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import { RecoilRoot } from 'recoil';
+
+import { useModifyActivityOnActivityTargetsCache } from '@/activities/hooks/useModifyActivityOnActivityTargetCache';
+import { useModifyRecordFromCache } from '@/object-record/cache/hooks/useModifyRecordFromCache';
+import { mockedActivities } from '~/testing/mock-data/activities';
+
+const useModifyRecordFromCacheMock = jest.fn();
+
+jest.mock('@/object-record/cache/hooks/useModifyRecordFromCache', () => ({
+ useModifyRecordFromCache: jest.fn(),
+}));
+
+(useModifyRecordFromCache as jest.Mock).mockImplementation(
+ () => useModifyRecordFromCacheMock,
+);
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+);
+
+describe('useModifyActivityOnActivityTargetsCache', () => {
+ it('works as expected', () => {
+ const { result } = renderHook(
+ () => useModifyActivityOnActivityTargetsCache(),
+ {
+ wrapper: Wrapper,
+ },
+ );
+
+ act(() => {
+ result.current.modifyActivityOnActivityTargetsCache({
+ activity: mockedActivities[0],
+ activityTargetIds: ['123', '456'],
+ });
+ });
+
+ expect(useModifyRecordFromCacheMock).toHaveBeenCalled();
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useModifyActivityTargetsOnActivityCache.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useModifyActivityTargetsOnActivityCache.test.tsx
new file mode 100644
index 000000000..cfc4bce55
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useModifyActivityTargetsOnActivityCache.test.tsx
@@ -0,0 +1,43 @@
+import { ReactNode } from 'react';
+import { MockedProvider } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import { RecoilRoot } from 'recoil';
+
+import { useModifyActivityTargetsOnActivityCache } from '@/activities/hooks/useModifyActivityTargetsOnActivityCache';
+import { useModifyRecordFromCache } from '@/object-record/cache/hooks/useModifyRecordFromCache';
+
+const useModifyRecordFromCacheMock = jest.fn();
+
+jest.mock('@/object-record/cache/hooks/useModifyRecordFromCache', () => ({
+ useModifyRecordFromCache: jest.fn(),
+}));
+
+(useModifyRecordFromCache as jest.Mock).mockImplementation(
+ () => useModifyRecordFromCacheMock,
+);
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+);
+
+describe('useModifyActivityTargetsOnActivityCache', () => {
+ it('works as expected', () => {
+ const { result } = renderHook(
+ () => useModifyActivityTargetsOnActivityCache(),
+ {
+ wrapper: Wrapper,
+ },
+ );
+
+ act(() => {
+ result.current.modifyActivityTargetsOnActivityCache({
+ activityId: '1234',
+ activityTargets: [],
+ });
+ });
+
+ expect(useModifyRecordFromCacheMock).toHaveBeenCalled();
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenActivityRightDrawer.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenActivityRightDrawer.test.tsx
new file mode 100644
index 000000000..2a323f3bd
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenActivityRightDrawer.test.tsx
@@ -0,0 +1,42 @@
+import { ReactNode } from 'react';
+import { MockedProvider } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import { RecoilRoot, useRecoilValue } from 'recoil';
+
+import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
+import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
+import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+);
+
+describe('useOpenActivityRightDrawer', () => {
+ it('works as expected', () => {
+ const { result } = renderHook(
+ () => {
+ const openActivityRightDrawer = useOpenActivityRightDrawer();
+ const viewableActivityId = useRecoilValue(viewableActivityIdState());
+ const activityIdInDrawer = useRecoilValue(activityIdInDrawerState());
+ return {
+ openActivityRightDrawer,
+ activityIdInDrawer,
+ viewableActivityId,
+ };
+ },
+ {
+ wrapper: Wrapper,
+ },
+ );
+
+ expect(result.current.activityIdInDrawer).toBeNull();
+ expect(result.current.viewableActivityId).toBeNull();
+ act(() => {
+ result.current.openActivityRightDrawer('123');
+ });
+ expect(result.current.activityIdInDrawer).toBe('123');
+ expect(result.current.viewableActivityId).toBe('123');
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx
new file mode 100644
index 000000000..ee294657c
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx
@@ -0,0 +1,63 @@
+import { ReactNode } from 'react';
+import { MockedProvider } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil';
+
+import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
+import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
+import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
+
+const mockUUID = '37873e04-2f83-4468-9ab7-3f87da6cafad';
+
+jest.mock('uuid', () => ({
+ v4: () => mockUUID,
+}));
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+);
+
+const mockObjectMetadataItems = getObjectMetadataItemsMock();
+
+describe('useOpenCreateActivityDrawer', () => {
+ it('works as expected', async () => {
+ const { result } = renderHook(
+ () => {
+ const openActivityRightDrawer = useOpenCreateActivityDrawer();
+ const viewableActivityId = useRecoilValue(viewableActivityIdState());
+ const activityIdInDrawer = useRecoilValue(activityIdInDrawerState());
+ const setObjectMetadataItems = useSetRecoilState(
+ objectMetadataItemsState(),
+ );
+ return {
+ openActivityRightDrawer,
+ activityIdInDrawer,
+ viewableActivityId,
+ setObjectMetadataItems,
+ };
+ },
+ {
+ wrapper: Wrapper,
+ },
+ );
+
+ act(() => {
+ result.current.setObjectMetadataItems(mockObjectMetadataItems);
+ });
+
+ expect(result.current.activityIdInDrawer).toBeNull();
+ expect(result.current.viewableActivityId).toBeNull();
+ await act(async () => {
+ result.current.openActivityRightDrawer({
+ type: 'Note',
+ targetableObjects: [],
+ });
+ });
+ expect(result.current.activityIdInDrawer).toBe(mockUUID);
+ expect(result.current.viewableActivityId).toBe(mockUUID);
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawerForSelectedRowIds.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawerForSelectedRowIds.test.tsx
new file mode 100644
index 000000000..6a4ecc83d
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawerForSelectedRowIds.test.tsx
@@ -0,0 +1,110 @@
+import { ReactNode } from 'react';
+import { MockedProvider } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil';
+
+import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
+import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
+import { activityIdInDrawerState } from '@/activities/states/activityIdInDrawerState';
+import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
+import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
+import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
+import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState';
+
+const useOpenCreateActivityDrawerMock = jest.fn();
+jest.mock('@/activities/hooks/useOpenCreateActivityDrawer', () => ({
+ useOpenCreateActivityDrawer: jest.fn(),
+}));
+
+(useOpenCreateActivityDrawer as jest.Mock).mockImplementation(
+ () => useOpenCreateActivityDrawerMock,
+);
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+);
+
+const mockObjectMetadataItems = getObjectMetadataItemsMock();
+const recordTableId = 'recordTableId';
+const tableRowIds = ['123', '456'];
+const recordObject = {
+ id: '789',
+};
+
+describe('useOpenCreateActivityDrawerForSelectedRowIds', () => {
+ it('works as expected', async () => {
+ const { result } = renderHook(
+ () => {
+ const openCreateActivityDrawerForSelectedRowIds =
+ useOpenCreateActivityDrawerForSelectedRowIds(recordTableId);
+ const viewableActivityId = useRecoilValue(viewableActivityIdState());
+ const activityIdInDrawer = useRecoilValue(activityIdInDrawerState());
+ const setObjectMetadataItems = useSetRecoilState(
+ objectMetadataItemsState(),
+ );
+ const scopeId = `${recordTableId}-scope`;
+ const setTableRowIds = useSetRecoilState(
+ tableRowIdsComponentState({ scopeId }),
+ );
+ const setIsRowSelectedComponentFamilyState = useSetRecoilState(
+ isRowSelectedComponentFamilyState({
+ scopeId,
+ familyKey: tableRowIds[0],
+ }),
+ );
+ const setRecordStoreFamilyState = useSetRecoilState(
+ recordStoreFamilyState(tableRowIds[0]),
+ );
+ return {
+ openCreateActivityDrawerForSelectedRowIds,
+ activityIdInDrawer,
+ viewableActivityId,
+ setObjectMetadataItems,
+ setTableRowIds,
+ setIsRowSelectedComponentFamilyState,
+ setRecordStoreFamilyState,
+ };
+ },
+ {
+ wrapper: Wrapper,
+ },
+ );
+
+ act(() => {
+ result.current.setTableRowIds(tableRowIds);
+ result.current.setRecordStoreFamilyState(recordObject);
+ result.current.setIsRowSelectedComponentFamilyState(true);
+ result.current.setObjectMetadataItems(mockObjectMetadataItems);
+ });
+
+ expect(result.current.activityIdInDrawer).toBeNull();
+ expect(result.current.viewableActivityId).toBeNull();
+ await act(async () => {
+ result.current.openCreateActivityDrawerForSelectedRowIds(
+ 'Note',
+ 'person',
+ [{ id: '176', targetObjectNameSingular: 'person' }],
+ );
+ });
+
+ expect(useOpenCreateActivityDrawerMock).toHaveBeenCalledWith({
+ type: 'Note',
+ targetableObjects: [
+ {
+ type: 'Custom',
+ targetObjectNameSingular: 'person',
+ id: '123',
+ targetObjectRecord: { id: '789' },
+ },
+ {
+ id: '176',
+ targetObjectNameSingular: 'person',
+ },
+ ],
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useRemoveFromActivitiesQueries.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useRemoveFromActivitiesQueries.test.tsx
new file mode 100644
index 000000000..6e2f5a516
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useRemoveFromActivitiesQueries.test.tsx
@@ -0,0 +1,63 @@
+import { ReactNode } from 'react';
+import { MockedProvider } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import { RecoilRoot } from 'recoil';
+
+import { useRemoveFromActivitiesQueries } from '@/activities/hooks/useRemoveFromActivitiesQueries';
+import { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache';
+import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
+
+const upsertFindManyRecordsQueryInCacheMock = jest.fn();
+const useReadFindManyRecordsQueryInCacheMock = jest.fn(() => [
+ { activityId: '981' },
+ { activityId: '345' },
+]);
+jest.mock(
+ '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache',
+ () => ({
+ useReadFindManyRecordsQueryInCache: jest.fn(),
+ }),
+);
+jest.mock(
+ '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache',
+ () => ({
+ useUpsertFindManyRecordsQueryInCache: jest.fn(),
+ }),
+);
+
+(useReadFindManyRecordsQueryInCache as jest.Mock).mockImplementation(() => ({
+ readFindManyRecordsQueryInCache: useReadFindManyRecordsQueryInCacheMock,
+}));
+
+(useUpsertFindManyRecordsQueryInCache as jest.Mock).mockImplementation(() => ({
+ upsertFindManyRecordsQueryInCache: upsertFindManyRecordsQueryInCacheMock,
+}));
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+);
+
+describe('useRemoveFromActivitiesQueries', () => {
+ it('works as expected', () => {
+ const { result } = renderHook(() => useRemoveFromActivitiesQueries(), {
+ wrapper: Wrapper,
+ });
+
+ act(() => {
+ result.current.removeFromActivitiesQueries({
+ activityIdToRemove: '123',
+ targetableObjects: [],
+ });
+ });
+
+ expect(upsertFindManyRecordsQueryInCacheMock).toHaveBeenCalledWith({
+ objectRecordsToOverwrite: [{ activityId: '981' }, { activityId: '345' }],
+ queryVariables: {
+ filter: { id: { in: ['345', '981'] } },
+ orderBy: undefined,
+ },
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useRemoveFromActivityTargetsQueries.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useRemoveFromActivityTargetsQueries.test.tsx
new file mode 100644
index 000000000..79e8ff6c8
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useRemoveFromActivityTargetsQueries.test.tsx
@@ -0,0 +1,72 @@
+import { ReactNode } from 'react';
+import { MockedProvider } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import { RecoilRoot } from 'recoil';
+
+import { useRemoveFromActivityTargetsQueries } from '@/activities/hooks/useRemoveFromActivityTargetsQueries';
+import { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache';
+import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
+import { mockedActivities } from '~/testing/mock-data/activities';
+
+const upsertFindManyRecordsQueryInCacheMock = jest.fn();
+const useReadFindManyRecordsQueryInCacheMock = jest.fn(() => [
+ { id: '981' },
+ { id: '345' },
+]);
+jest.mock(
+ '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache',
+ () => ({
+ useReadFindManyRecordsQueryInCache: jest.fn(),
+ }),
+);
+jest.mock(
+ '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache',
+ () => ({
+ useUpsertFindManyRecordsQueryInCache: jest.fn(),
+ }),
+);
+
+(useReadFindManyRecordsQueryInCache as jest.Mock).mockImplementation(() => ({
+ readFindManyRecordsQueryInCache: useReadFindManyRecordsQueryInCacheMock,
+}));
+(useUpsertFindManyRecordsQueryInCache as jest.Mock).mockImplementation(() => ({
+ upsertFindManyRecordsQueryInCache: upsertFindManyRecordsQueryInCacheMock,
+}));
+
+const Wrapper = ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+);
+
+const mockActivityTarget = {
+ __typename: 'ActivityTarget',
+ updatedAt: '2021-08-03T19:20:06.000Z',
+ createdAt: '2021-08-03T19:20:06.000Z',
+ personId: '1',
+ activityId: '234',
+ companyId: '1',
+ id: '123',
+ activity: mockedActivities[0],
+};
+
+describe('useRemoveFromActivityTargetsQueries', () => {
+ it('works as expected', () => {
+ const { result } = renderHook(() => useRemoveFromActivityTargetsQueries(), {
+ wrapper: Wrapper,
+ });
+
+ act(() => {
+ result.current.removeFromActivityTargetsQueries({
+ activityTargetsToRemove: [mockActivityTarget],
+ targetableObjects: [],
+ });
+ });
+
+ expect(upsertFindManyRecordsQueryInCacheMock).toHaveBeenCalledWith({
+ objectRecordsToOverwrite: [{ id: '981' }, { id: '345' }],
+ queryVariables: { filter: {} },
+ depth: 2,
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useUpsertActivity.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useUpsertActivity.test.tsx
new file mode 100644
index 000000000..fcfb2c94c
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useUpsertActivity.test.tsx
@@ -0,0 +1,187 @@
+import { ReactNode } from 'react';
+import { MemoryRouter } from 'react-router-dom';
+import { MockedProvider, MockedResponse } from '@apollo/client/testing';
+import { act, renderHook } from '@testing-library/react';
+import gql from 'graphql-tag';
+import { RecoilRoot, useSetRecoilState } from 'recoil';
+
+import { useCreateActivityInDB } from '@/activities/hooks/useCreateActivityInDB';
+import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
+import { currentNotesQueryVariablesState } from '@/activities/notes/states/currentNotesQueryVariablesState';
+import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
+import { currentCompletedTaskQueryVariablesState } from '@/activities/tasks/states/currentCompletedTaskQueryVariablesState';
+import { currentIncompleteTaskQueryVariablesState } from '@/activities/tasks/states/currentIncompleteTaskQueryVariablesState';
+import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectIdState';
+import { Activity } from '@/activities/types/Activity';
+import { mockedActivities } from '~/testing/mock-data/activities';
+
+const newId = 'new-id';
+const activity = mockedActivities[0];
+const input: Partial = { id: newId };
+
+const mockedDate = '2024-03-15T12:00:00.000Z';
+const toISOStringMock = jest.fn(() => mockedDate);
+global.Date.prototype.toISOString = toISOStringMock;
+
+const useCreateActivityInDBMock = jest.fn();
+
+jest.mock('@/activities/hooks/useCreateActivityInDB', () => ({
+ useCreateActivityInDB: jest.fn(),
+}));
+(useCreateActivityInDB as jest.Mock).mockImplementation(() => ({
+ createActivityInDB: useCreateActivityInDBMock,
+}));
+
+const mocks: MockedResponse[] = [
+ {
+ request: {
+ query: gql`
+ mutation UpdateOneActivity(
+ $idToUpdate: ID!
+ $input: ActivityUpdateInput!
+ ) {
+ updateActivity(id: $idToUpdate, data: $input) {
+ __typename
+ createdAt
+ reminderAt
+ authorId
+ title
+ completedAt
+ updatedAt
+ body
+ dueAt
+ type
+ id
+ assigneeId
+ }
+ }
+ `,
+ variables: {
+ idToUpdate: activity.id,
+ input: { id: 'new-id' },
+ },
+ },
+ result: jest.fn(() => ({
+ data: {
+ updateActivity: { ...activity, ...input },
+ },
+ })),
+ },
+];
+
+const getWrapper =
+ (initialIndex: 0 | 1) =>
+ ({ children }: { children: ReactNode }) => (
+
+
+
+ {children}
+
+
+
+ );
+
+describe('useUpsertActivity', () => {
+ it('updates an activity', async () => {
+ const { result } = renderHook(() => useUpsertActivity(), {
+ wrapper: getWrapper(0),
+ });
+
+ await act(async () => {
+ await result.current.upsertActivity({
+ activity,
+ input,
+ });
+ });
+
+ expect(mocks[0].result).toHaveBeenCalled();
+ });
+
+ it('creates an activity on tasks page', async () => {
+ const { result } = renderHook(
+ () => {
+ const res = useUpsertActivity();
+ const setIsActivityInCreateMode = useSetRecoilState(
+ isActivityInCreateModeState(),
+ );
+
+ return { ...res, setIsActivityInCreateMode };
+ },
+ {
+ wrapper: getWrapper(0),
+ },
+ );
+
+ act(() => {
+ result.current.setIsActivityInCreateMode(true);
+ });
+
+ await act(async () => {
+ await result.current.upsertActivity({
+ activity,
+ input: {},
+ });
+ });
+
+ expect(useCreateActivityInDBMock).toHaveBeenCalledTimes(1);
+ });
+
+ it('creates an activity on objects page', async () => {
+ const { result } = renderHook(
+ () => {
+ const res = useUpsertActivity();
+ const setIsActivityInCreateMode = useSetRecoilState(
+ isActivityInCreateModeState(),
+ );
+ const setObjectShowPageTargetableObject = useSetRecoilState(
+ objectShowPageTargetableObjectState,
+ );
+ const setCurrentCompletedTaskQueryVariables = useSetRecoilState(
+ currentCompletedTaskQueryVariablesState,
+ );
+ const setCurrentIncompleteTaskQueryVariables = useSetRecoilState(
+ currentIncompleteTaskQueryVariablesState,
+ );
+
+ const setCurrentNotesQueryVariables = useSetRecoilState(
+ currentNotesQueryVariablesState,
+ );
+
+ return {
+ ...res,
+ setIsActivityInCreateMode,
+ setObjectShowPageTargetableObject,
+ setCurrentCompletedTaskQueryVariables,
+ setCurrentIncompleteTaskQueryVariables,
+ setCurrentNotesQueryVariables,
+ };
+ },
+ {
+ wrapper: getWrapper(1),
+ },
+ );
+
+ act(() => {
+ result.current.setIsActivityInCreateMode(true);
+ result.current.setObjectShowPageTargetableObject({
+ id: '123',
+ targetObjectNameSingular: 'people',
+ });
+ result.current.setCurrentCompletedTaskQueryVariables({});
+ result.current.setCurrentIncompleteTaskQueryVariables({});
+ result.current.setCurrentNotesQueryVariables({});
+ });
+
+ await act(async () => {
+ await result.current.upsertActivity({
+ activity,
+ input: {},
+ });
+ });
+
+ expect(useCreateActivityInDBMock).toHaveBeenCalledTimes(2);
+ });
+});