diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useEntitySelectSearch.test.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useEntitySelectSearch.test.tsx
new file mode 100644
index 000000000..f738620bc
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useEntitySelectSearch.test.tsx
@@ -0,0 +1,29 @@
+import { ChangeEvent } from 'react';
+import { act, renderHook } from '@testing-library/react';
+import { RecoilRoot } from 'recoil';
+
+import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/useEntitySelectSearch';
+import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
+
+const scopeId = 'scopeId';
+const Wrapper = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+);
+
+describe('useEntitySelectSearch', () => {
+ it('should update searchFilter after change event', async () => {
+ const { result } = renderHook(() => useEntitySelectSearch(), {
+ wrapper: Wrapper,
+ });
+ const filter = 'value';
+
+ act(() => {
+ result.current.handleSearchFilterChange({
+ currentTarget: { value: filter },
+ } as ChangeEvent);
+ });
+ expect(result.current.searchFilter).toBe(filter);
+ });
+});
diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useLimitPerMetadataItem.test.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useLimitPerMetadataItem.test.tsx
new file mode 100644
index 000000000..bc30a5a0c
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useLimitPerMetadataItem.test.tsx
@@ -0,0 +1,58 @@
+import { renderHook } from '@testing-library/react';
+import { RecoilRoot } from 'recoil';
+
+import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem';
+import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
+
+const scopeId = 'scopeId';
+const Wrapper = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+);
+
+describe('useLimitPerMetadataItem', () => {
+ const objectData: ObjectMetadataItem[] = [
+ {
+ createdAt: 'createdAt',
+ id: 'id',
+ isActive: true,
+ isCustom: true,
+ isSystem: true,
+ labelPlural: 'labelPlural',
+ labelSingular: 'labelSingular',
+ namePlural: 'namePlural',
+ nameSingular: 'nameSingular',
+ updatedAt: 'updatedAt',
+ fields: [],
+ },
+ ];
+
+ it('should return object with nameSingular and default limit', async () => {
+ const { result } = renderHook(
+ () => useLimitPerMetadataItem({ objectMetadataItems: objectData }),
+ {
+ wrapper: Wrapper,
+ },
+ );
+
+ expect(result.current.limitPerMetadataItem).toStrictEqual({
+ limitNameSingular: 60,
+ });
+ });
+
+ it('should return an object with nameSingular and specified limit', async () => {
+ const { result } = renderHook(
+ () =>
+ useLimitPerMetadataItem({ objectMetadataItems: objectData, limit: 30 }),
+ {
+ wrapper: Wrapper,
+ },
+ );
+
+ expect(result.current.limitPerMetadataItem).toStrictEqual({
+ limitNameSingular: 30,
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.test.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.test.tsx
new file mode 100644
index 000000000..e0b646bc7
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.test.tsx
@@ -0,0 +1,93 @@
+import { act, renderHook } from '@testing-library/react';
+import { RecoilRoot, useSetRecoilState } from 'recoil';
+
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
+import { useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
+import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
+
+const scopeId = 'scopeId';
+const Wrapper = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+);
+
+const objectMetadataItemsMock = getObjectMetadataItemsMock();
+
+const opportunityId = 'cb702502-4b1d-488e-9461-df3fb096ebf6';
+const personId = 'ab091fd9-1b81-4dfd-bfdb-564ffee032a2';
+
+describe('useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray', () => {
+ it('should return object formatted from objectMetadataItemsState', async () => {
+ const { result } = renderHook(
+ () => {
+ return {
+ formattedRecord:
+ useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray(
+ {
+ multiObjectRecordsQueryResult: {
+ opportunities: {
+ edges: [
+ {
+ node: {
+ id: opportunityId,
+ pointOfContactId:
+ 'e992bda7-d797-4e12-af04-9b427f42244c',
+ updatedAt: '2023-11-30T11:13:15.308Z',
+ createdAt: '2023-11-30T11:13:15.308Z',
+ },
+ cursor: 'cursor',
+ },
+ ],
+ pageInfo: {},
+ },
+ people: {
+ edges: [
+ {
+ node: {
+ id: personId,
+ updatedAt: '2023-11-30T11:13:15.308Z',
+ createdAt: '2023-11-30T11:13:15.308Z',
+ },
+ cursor: 'cursor',
+ },
+ ],
+ pageInfo: {},
+ },
+ },
+ },
+ ),
+ setObjectMetadata: useSetRecoilState(objectMetadataItemsState),
+ };
+ },
+ {
+ wrapper: Wrapper,
+ },
+ );
+ act(() => {
+ result.current.setObjectMetadata(objectMetadataItemsMock);
+ });
+
+ expect(
+ result.current.formattedRecord.objectRecordForSelectArray.length,
+ ).toBe(2);
+
+ const [opportunityRecordForSelect, personRecordForSelect] =
+ result.current.formattedRecord.objectRecordForSelectArray;
+
+ expect(opportunityRecordForSelect.objectMetadataItem.namePlural).toBe(
+ 'opportunities',
+ );
+ expect(opportunityRecordForSelect.record.id).toBe(opportunityId);
+ expect(opportunityRecordForSelect.recordIdentifier.linkToShowPage).toBe(
+ `/opportunities/${opportunityId}`,
+ );
+
+ expect(personRecordForSelect.objectMetadataItem.namePlural).toBe('people');
+ expect(personRecordForSelect.record.id).toBe(personId);
+ expect(personRecordForSelect.recordIdentifier.linkToShowPage).toBe(
+ `/object/person/${personId}`,
+ );
+ });
+});
diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx
new file mode 100644
index 000000000..280f5017e
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx
@@ -0,0 +1,169 @@
+import { gql } from '@apollo/client';
+import { MockedProvider } from '@apollo/client/testing';
+import { act, renderHook, waitFor } from '@testing-library/react';
+import { RecoilRoot, useSetRecoilState } from 'recoil';
+
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { useMultiObjectSearch } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
+
+const query = gql`
+ query FindManyRecordsMultipleMetadataItems(
+ $filterNameSingular: NameSingularFilterInput
+ $orderByNameSingular: NameSingularOrderByInput
+ $lastCursorNameSingular: String
+ $limitNameSingular: Float = 5
+ ) {
+ namePlural(
+ filter: $filterNameSingular
+ orderBy: $orderByNameSingular
+ first: $limitNameSingular
+ after: $lastCursorNameSingular
+ ) {
+ edges {
+ node {
+ id
+ }
+ cursor
+ }
+ pageInfo {
+ hasNextPage
+ startCursor
+ endCursor
+ }
+ }
+ }
+`;
+const response = {
+ namePlural: {
+ edges: [{ node: { id: 'nodeId' }, cursor: 'cursor' }],
+ pageInfo: { startCursor: '', hasNextPage: '', endCursor: '' },
+ },
+};
+
+const mocks = [
+ {
+ request: {
+ query,
+ variables: {
+ filterNameSingular: { and: [{}, { id: { in: ['1'] } }] },
+ orderByNameSingular: { createdAt: 'DescNullsLast' },
+ limitNameSingular: 60,
+ },
+ },
+ result: jest.fn(() => ({
+ data: response,
+ })),
+ },
+ {
+ request: {
+ query,
+ variables: {
+ filterNameSingular: { id: { in: ['1'] } },
+ orderByNameSingular: { createdAt: 'DescNullsLast' },
+ limitNameSingular: 60,
+ },
+ },
+ result: jest.fn(() => ({
+ data: response,
+ })),
+ },
+ {
+ request: {
+ query,
+ variables: {
+ limitNameSingular: 60,
+ filterNameSingular: { and: [{}, { not: { id: { in: ['1'] } } }] },
+ orderByNameSingular: { createdAt: 'DescNullsLast' },
+ },
+ },
+ result: jest.fn(() => ({
+ data: response,
+ })),
+ },
+];
+
+const Wrapper = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+);
+
+describe('useMultiObjectSearch', () => {
+ it('should return object formatted from objectMetadataItemsState', async () => {
+ const { result } = renderHook(
+ () => ({
+ multiObjects: useMultiObjectSearch({
+ searchFilterValue: '',
+ selectedObjectRecordIds: [
+ {
+ objectNameSingular: 'nameSingular',
+ id: '1',
+ },
+ ],
+ }),
+ setObjectMetadata: useSetRecoilState(objectMetadataItemsState),
+ }),
+ {
+ wrapper: Wrapper,
+ },
+ );
+ const objectData: ObjectMetadataItem[] = [
+ {
+ createdAt: 'createdAt',
+ id: 'id',
+ isActive: true,
+ isCustom: true,
+ isSystem: false,
+ labelPlural: 'labelPlural',
+ labelSingular: 'labelSingular',
+ namePlural: 'namePlural',
+ nameSingular: 'nameSingular',
+ updatedAt: 'updatedAt',
+ fields: [],
+ },
+ ];
+ act(() => {
+ result.current.setObjectMetadata(objectData);
+ });
+ await waitFor(() => {
+ expect(mocks[0].result).toHaveBeenCalled();
+ expect(mocks[1].result).toHaveBeenCalled();
+ expect(mocks[2].result).toHaveBeenCalled();
+ });
+ const expectedData = [
+ {
+ objectMetadataItem: {
+ createdAt: 'createdAt',
+ id: 'id',
+ isActive: true,
+ isCustom: true,
+ isSystem: false,
+ labelPlural: 'labelPlural',
+ labelSingular: 'labelSingular',
+ namePlural: 'namePlural',
+ nameSingular: 'nameSingular',
+ updatedAt: 'updatedAt',
+ fields: [],
+ },
+ record: { id: 'nodeId' },
+ recordIdentifier: {
+ id: 'nodeId',
+ name: '',
+ avatarUrl: '',
+ avatarType: 'rounded',
+ linkToShowPage: '/object/nameSingular/nodeId',
+ },
+ },
+ ];
+ expect(result.current.multiObjects.selectedObjectRecords).toStrictEqual(
+ expectedData,
+ );
+ expect(
+ result.current.multiObjects.filteredSelectedObjectRecords,
+ ).toStrictEqual(expectedData);
+ expect(result.current.multiObjects.objectRecordsToSelect).toStrictEqual(
+ expectedData,
+ );
+ });
+});