From 0ef1736188b4b39ac789503f5b7e8e769e17a8f5 Mon Sep 17 00:00:00 2001 From: gitstart-twenty <140154534+gitstart-twenty@users.noreply.github.com> Date: Sat, 13 Jan 2024 10:05:50 +0100 Subject: [PATCH] Add tests for `modules/object-record/record-board` (#3421) * Add tests for `modules/object-record/record-board` Co-authored-by: v1b3m Co-authored-by: RubensRafael * Revert unwanted changes Co-authored-by: v1b3m Co-authored-by: RubensRafael --------- Co-authored-by: gitstart-twenty Co-authored-by: v1b3m Co-authored-by: RubensRafael --- .../hooks/__tests__/useRecordBoard.test.tsx | 88 ++++++++++++ .../__tests__/useCreateOpportunity.test.tsx | 78 +++++++++++ ...ntRecordBoardCardSelectedInternal.test.tsx | 56 ++++++++ ...eSelectedRecordBoardCardsInternal.test.tsx | 123 +++++++++++++++++ ...cordBoardActionBarEntriesInternal.test.tsx | 54 ++++++++ .../useRecordBoardCardFieldsInternal.test.tsx | 110 +++++++++++++++ .../useRecordBoardColumnsInternal.test.tsx | 128 ++++++++++++++++++ ...rdBoardContextMenuEntriesInternal.test.tsx | 55 ++++++++ ...etRecordBoardCardSelectedInternal.test.tsx | 58 ++++++++ ...UpdateCompanyBoardColumnsInternal.test.tsx | 113 ++++++++++++++++ 10 files changed, 863 insertions(+) create mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/__tests__/useRecordBoard.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useCreateOpportunity.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useCurrentRecordBoardCardSelectedInternal.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useDeleteSelectedRecordBoardCardsInternal.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardActionBarEntriesInternal.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardCardFieldsInternal.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardColumnsInternal.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardContextMenuEntriesInternal.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useSetRecordBoardCardSelectedInternal.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useUpdateCompanyBoardColumnsInternal.test.tsx diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/__tests__/useRecordBoard.test.tsx b/packages/twenty-front/src/modules/object-record/record-board/hooks/__tests__/useRecordBoard.test.tsx new file mode 100644 index 000000000..3ab88feef --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/__tests__/useRecordBoard.test.tsx @@ -0,0 +1,88 @@ +import { MockedProvider } from '@apollo/client/testing'; +import { act, renderHook, waitFor } from '@testing-library/react'; +import { RecoilRoot, useRecoilValue } from 'recoil'; + +import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates'; +import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard'; + +const Wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); + +const recordBoardScopeId = 'recordBoardScopeId'; + +const renderHookConfig = { + wrapper: Wrapper, +}; + +const useRecordBoardHook = () => { + const recordBoard = useRecordBoard({ recordBoardScopeId }); + const { isBoardLoadedState, boardColumnsState, onFieldsChangeState } = + useRecordBoardScopedStates({ + recordBoardScopeId: recordBoardScopeId, + }); + const isBoardLoaded = useRecoilValue(isBoardLoadedState); + const boardColumns = useRecoilValue(boardColumnsState); + const onFieldsChange = useRecoilValue(onFieldsChangeState); + + return { + recordBoard, + isBoardLoaded, + boardColumns, + onFieldsChange, + }; +}; + +describe('useRecordBoard', () => { + it('should set isBoardLoadedState', async () => { + const { result } = renderHook(() => useRecordBoardHook(), renderHookConfig); + + act(() => { + result.current.recordBoard.setIsBoardLoaded(true); + }); + + await waitFor(() => { + expect(result.current.isBoardLoaded).toBe(true); + }); + }); + + it('should set boardColumnsState', async () => { + const columns = [ + { + id: '1', + title: '1', + position: 1, + }, + { + id: '1', + title: '1', + position: 1, + }, + ]; + const { result } = renderHook(() => useRecordBoardHook(), renderHookConfig); + + act(() => { + result.current.recordBoard.setBoardColumns(columns); + }); + + await waitFor(() => { + expect(result.current.boardColumns).toEqual(columns); + }); + }); + + it('should set setOnFieldsChange', async () => { + const onFieldsChangeFunction = () => {}; + const onFieldsChange = jest.fn(() => onFieldsChangeFunction); + const { result } = renderHook(() => useRecordBoardHook(), renderHookConfig); + + act(() => { + result.current.recordBoard.setOnFieldsChange(onFieldsChange); + }); + + await waitFor(() => { + expect(result.current.onFieldsChange).toEqual(onFieldsChangeFunction); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useCreateOpportunity.test.tsx b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useCreateOpportunity.test.tsx new file mode 100644 index 000000000..b136e44f1 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useCreateOpportunity.test.tsx @@ -0,0 +1,78 @@ +import { MockedProvider } from '@apollo/client/testing'; +import { act, renderHook } from '@testing-library/react'; +import gql from 'graphql-tag'; +import { RecoilRoot, useRecoilValue } from 'recoil'; + +import { useCreateOpportunity } from '@/object-record/record-board/hooks/internal/useCreateOpportunity'; +import { recordBoardCardIdsByColumnIdFamilyState } from '@/object-record/record-board/states/recordBoardCardIdsByColumnIdFamilyState'; + +const mockedUuid = 'mocked-uuid'; +jest.mock('uuid', () => ({ + v4: () => mockedUuid, +})); + +jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({ + useMapFieldMetadataToGraphQLQuery: () => () => '\n', +})); + +const mocks = [ + { + request: { + query: gql` + mutation CreateOneOpportunity($input: OpportunityCreateInput!) { + createOpportunity(data: $input) { + id + } + } + `, + variables: { + input: { + id: mockedUuid, + pipelineStepId: 'pipelineStepId', + companyId: 'New Opportunity', + }, + }, + }, + result: jest.fn(() => ({ + data: { createOpportunity: { id: '' } }, + })), + }, +]; + +const Wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); + +const renderHookConfig = { + wrapper: Wrapper, +}; + +describe('useCreateOpportunity', () => { + it('should create opportunity successfully', () => { + const companyIdname = 'New Opportunity'; + const opportunityPipelineStepId = 'pipelineStepId'; + + const { result } = renderHook( + () => ({ + createOpportunity: useCreateOpportunity(), + recordBoardCardIdsByColumnId: useRecoilValue( + recordBoardCardIdsByColumnIdFamilyState(opportunityPipelineStepId), + ), + }), + renderHookConfig, + ); + + act(() => { + result.current.createOpportunity( + companyIdname, + opportunityPipelineStepId, + ); + }); + + expect(result.current.recordBoardCardIdsByColumnId).toStrictEqual([ + mockedUuid, + ]); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useCurrentRecordBoardCardSelectedInternal.test.tsx b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useCurrentRecordBoardCardSelectedInternal.test.tsx new file mode 100644 index 000000000..be0655577 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useCurrentRecordBoardCardSelectedInternal.test.tsx @@ -0,0 +1,56 @@ +import { act, renderHook } from '@testing-library/react'; +import { RecoilRoot, useRecoilValue } from 'recoil'; + +import { BoardCardIdContext } from '@/object-record/record-board/contexts/BoardCardIdContext'; +import { useCurrentRecordBoardCardSelectedInternal } from '@/object-record/record-board/hooks/internal/useCurrentRecordBoardCardSelectedInternal'; +import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates'; +import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope'; +import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState'; + +const scopeId = 'scopeId'; +const boardCardId = 'boardCardId'; + +const Wrapper = ({ children }: { children: React.ReactNode }) => ( + + + {children} + + +); + +describe('useCurrentRecordBoardCardSelectedInternal', () => { + it('should update the data when selecting and deselecting the cardId', () => { + const { result } = renderHook( + () => ({ + currentCardSelect: useCurrentRecordBoardCardSelectedInternal(), + activeCardIdsState: useRecoilValue( + useRecordBoardScopedStates().activeCardIdsState, + ), + actionBarOpenState: useRecoilValue(actionBarOpenState), + }), + { + wrapper: Wrapper, + }, + ); + + expect(result.current.activeCardIdsState).toStrictEqual([]); + expect(result.current.actionBarOpenState).toBe(false); + expect(result.current.currentCardSelect.isCurrentCardSelected).toBe(false); + + act(() => { + result.current.currentCardSelect.setCurrentCardSelected(true); + }); + + expect(result.current.activeCardIdsState).toStrictEqual([boardCardId]); + expect(result.current.actionBarOpenState).toBe(true); + expect(result.current.currentCardSelect.isCurrentCardSelected).toBe(true); + + act(() => { + result.current.currentCardSelect.setCurrentCardSelected(false); + }); + + expect(result.current.activeCardIdsState).toStrictEqual([]); + expect(result.current.actionBarOpenState).toBe(false); + expect(result.current.currentCardSelect.isCurrentCardSelected).toBe(false); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useDeleteSelectedRecordBoardCardsInternal.test.tsx b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useDeleteSelectedRecordBoardCardsInternal.test.tsx new file mode 100644 index 000000000..cc8114ada --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useDeleteSelectedRecordBoardCardsInternal.test.tsx @@ -0,0 +1,123 @@ +import { MockedProvider } from '@apollo/client/testing'; +import { act, renderHook, waitFor } from '@testing-library/react'; +import gql from 'graphql-tag'; +import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil'; + +import { BoardCardIdContext } from '@/object-record/record-board/contexts/BoardCardIdContext'; +import { useCreateOpportunity } from '@/object-record/record-board/hooks/internal/useCreateOpportunity'; +import { useCurrentRecordBoardCardSelectedInternal } from '@/object-record/record-board/hooks/internal/useCurrentRecordBoardCardSelectedInternal'; +import { useDeleteSelectedRecordBoardCardsInternal } from '@/object-record/record-board/hooks/internal/useDeleteSelectedRecordBoardCardsInternal'; +import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates'; +import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope'; +import { recordBoardCardIdsByColumnIdFamilyState } from '@/object-record/record-board/states/recordBoardCardIdsByColumnIdFamilyState'; + +jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({ + useMapFieldMetadataToGraphQLQuery: jest.fn().mockReturnValue(() => '\n'), +})); + +const mockedUuid = 'mocked-uuid'; +jest.mock('uuid', () => ({ v4: () => mockedUuid })); + +const mocks = [ + { + request: { + query: gql` + mutation DeleteManyOpportunities($filter: OpportunityFilterInput!) { + deleteOpportunities(filter: $filter) { + id + } + } + `, + variables: { filter: { id: { in: [mockedUuid] } } }, + }, + result: jest.fn(() => ({ + data: { deleteOpportunities: { id: '' } }, + })), + }, + { + request: { + query: gql` + mutation CreateOneOpportunity($input: OpportunityCreateInput!) { + createOpportunity(data: $input) { + id + } + } + `, + variables: { + input: { + id: mockedUuid, + pipelineStepId: 'pipelineStepId', + companyId: 'New Opportunity', + }, + }, + }, + result: jest.fn(() => ({ + data: { createOpportunity: { id: '' } }, + })), + }, +]; + +const scopeId = 'scopeId'; + +const Wrapper = ({ children }: { children: React.ReactNode }) => ( + + + + {children} + + + +); + +describe('useDeleteSelectedRecordBoardCardsInternal', () => { + it('should run apollo mutation and update recoil state when delete selected cards', async () => { + const companyIdname = 'New Opportunity'; + const opportunityPipelineStepId = 'pipelineStepId'; + + const { result } = renderHook( + () => ({ + createOpportunity: useCreateOpportunity(), + deleteSelectedCards: useDeleteSelectedRecordBoardCardsInternal(), + setBoardColumns: useSetRecoilState( + useRecordBoardScopedStates({ + recordBoardScopeId: scopeId, + }).boardColumnsState, + ), + recordBoardCardIdsByColumnId: useRecoilValue( + recordBoardCardIdsByColumnIdFamilyState(opportunityPipelineStepId), + ), + currentSelect: useCurrentRecordBoardCardSelectedInternal(), + }), + { + wrapper: Wrapper, + }, + ); + + act(() => { + result.current.currentSelect.setCurrentCardSelected(true); + result.current.setBoardColumns([ + { + id: opportunityPipelineStepId, + title: '1', + position: 1, + }, + ]); + result.current.createOpportunity( + companyIdname, + opportunityPipelineStepId, + ); + }); + + expect(result.current.recordBoardCardIdsByColumnId).toStrictEqual([ + mockedUuid, + ]); + await act(async () => { + await result.current.deleteSelectedCards(); + }); + + await waitFor(() => { + expect(result.current.recordBoardCardIdsByColumnId).toStrictEqual([]); + expect(mocks[0].result).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardActionBarEntriesInternal.test.tsx b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardActionBarEntriesInternal.test.tsx new file mode 100644 index 000000000..8608e91bc --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardActionBarEntriesInternal.test.tsx @@ -0,0 +1,54 @@ +import { MockedProvider } from '@apollo/client/testing'; +import { act, renderHook, waitFor } from '@testing-library/react'; +import { RecoilRoot, useRecoilValue } from 'recoil'; + +import { useDeleteSelectedRecordBoardCardsInternal } from '@/object-record/record-board/hooks/internal/useDeleteSelectedRecordBoardCardsInternal'; +import { useRecordBoardActionBarEntriesInternal } from '@/object-record/record-board/hooks/internal/useRecordBoardActionBarEntriesInternal'; +import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope'; +import { IconTrash } from '@/ui/display/icon'; +import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState'; + +const scopeId = 'scopeId'; +const Wrapper = ({ children }: { children: React.ReactNode }) => ( + + + {children} + + +); + +const renderHookConfig = { + wrapper: Wrapper, +}; + +describe('useRecordBoardActionBarEntriesInternal', () => { + it('should update actionBarEntries', async () => { + const { result } = renderHook(() => { + const deleteSelectedBoardCards = + useDeleteSelectedRecordBoardCardsInternal(); + const newActionBarEntry = { + label: 'Delete', + Icon: IconTrash, + accent: 'danger', + onClick: deleteSelectedBoardCards, + }; + return { + setActionBarEntries: useRecordBoardActionBarEntriesInternal(), + actionBarEntries: useRecoilValue(actionBarEntriesState), + newActionBarEntry, + }; + }, renderHookConfig); + + expect(result.current.actionBarEntries).toStrictEqual([]); + + act(() => { + result.current.setActionBarEntries.setActionBarEntries(); + }); + + await waitFor(() => { + expect(JSON.stringify(result.current.actionBarEntries)).toBe( + JSON.stringify([result.current.newActionBarEntry]), + ); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardCardFieldsInternal.test.tsx b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardCardFieldsInternal.test.tsx new file mode 100644 index 000000000..3a7b5645e --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardCardFieldsInternal.test.tsx @@ -0,0 +1,110 @@ +import { act } from 'react-dom/test-utils'; +import { renderHook } from '@testing-library/react'; +import { RecoilRoot, useRecoilState, useRecoilValue } from 'recoil'; + +import { FieldType } from '@/object-record/field/types/FieldType'; +import { useRecordBoardCardFieldsInternal } from '@/object-record/record-board/hooks/internal/useRecordBoardCardFieldsInternal'; +import { onFieldsChangeScopedState } from '@/object-record/record-board/states/onFieldsChangeScopedState'; +import { recordBoardCardFieldsScopedState } from '@/object-record/record-board/states/recordBoardCardFieldsScopedState'; +import { savedRecordBoardCardFieldsScopedState } from '@/object-record/record-board/states/savedRecordBoardCardFieldsScopedState'; + +const recordBoardScopeId = 'recordBoardScopeId'; + +const renderHookConfig = { + wrapper: RecoilRoot, +}; + +describe('useRecordBoardCardFieldsInternal', () => { + it('should toggle field visibility', async () => { + const { result } = renderHook(() => { + const [cardFieldsList, setCardFieldsList] = useRecoilState( + recordBoardCardFieldsScopedState({ scopeId: recordBoardScopeId }), + ); + return { + boardCardFields: useRecordBoardCardFieldsInternal({ + recordBoardScopeId, + }), + cardFieldsList, + setCardFieldsList, + }; + }, renderHookConfig); + + const field = { + position: 0, + fieldMetadataId: 'id', + label: 'label', + iconName: 'icon', + type: 'TEXT' as FieldType, + metadata: { + fieldName: 'fieldName', + }, + isVisible: true, + }; + + act(() => { + result.current.setCardFieldsList([field]); + }); + + expect(result.current.cardFieldsList[0].isVisible).toBe(true); + + act(() => { + result.current.boardCardFields.handleFieldVisibilityChange(field); + }); + + expect(result.current.cardFieldsList[0].isVisible).toBe(false); + + act(() => { + result.current.boardCardFields.handleFieldVisibilityChange({ + ...field, + isVisible: false, + }); + }); + + expect(result.current.cardFieldsList[0].isVisible).toBe(true); + }); + + it('should call the onFieldsChange callback and update board card states', async () => { + const { result } = renderHook(() => { + const [onFieldsChange, setOnFieldsChange] = useRecoilState( + onFieldsChangeScopedState({ scopeId: recordBoardScopeId }), + ); + return { + boardCardFieldsHook: useRecordBoardCardFieldsInternal({ + recordBoardScopeId, + }), + boardCardFieldsList: useRecoilValue( + recordBoardCardFieldsScopedState({ scopeId: recordBoardScopeId }), + ), + savedBoardCardFieldsList: useRecoilValue( + savedRecordBoardCardFieldsScopedState({ + scopeId: recordBoardScopeId, + }), + ), + onFieldsChange, + setOnFieldsChange, + }; + }, renderHookConfig); + + const field = { + position: 0, + fieldMetadataId: 'id', + label: 'label', + iconName: 'icon', + type: 'TEXT' as FieldType, + metadata: { + fieldName: 'fieldName', + }, + isVisible: true, + }; + const onChangeFunction = jest.fn(); + + await act(async () => { + result.current.setOnFieldsChange(() => onChangeFunction); + result.current.boardCardFieldsHook.handleFieldsReorder([field]); + }); + + expect(onChangeFunction).toHaveBeenCalledWith([field]); + expect(result.current.savedBoardCardFieldsList).toStrictEqual([field]); + expect(result.current.boardCardFieldsList).toStrictEqual([field]); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardColumnsInternal.test.tsx b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardColumnsInternal.test.tsx new file mode 100644 index 000000000..21f8a974c --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardColumnsInternal.test.tsx @@ -0,0 +1,128 @@ +import { MockedProvider } from '@apollo/client/testing'; +import { act, renderHook, waitFor } from '@testing-library/react'; +import gql from 'graphql-tag'; +import { RecoilRoot, useRecoilState, useSetRecoilState } from 'recoil'; + +import { useBoardColumnsInternal } from '@/object-record/record-board/hooks/internal/useRecordBoardColumnsInternal'; +import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates'; +import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope'; +import { BoardColumnDefinition } from '@/object-record/record-board/types/BoardColumnDefinition'; + +jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({ + useMapFieldMetadataToGraphQLQuery: jest.fn().mockReturnValue(() => '\n'), +})); + +const mocks = [ + { + request: { + query: gql` + mutation UpdateOnePipelineStep( + $idToUpdate: ID! + $input: PipelineStepUpdateInput! + ) { + updatePipelineStep(id: $idToUpdate, data: $input) { + id + } + } + `, + variables: { idToUpdate: '1', input: { position: 0 } }, + }, + result: jest.fn(() => ({ + data: { updatePipelineStep: { id: '' } }, + })), + }, +]; + +const scopeId = 'scopeId'; +const Wrapper = ({ children }: { children: React.ReactNode }) => ( + + + {children} + + +); + +const renderHookConfig = { + wrapper: Wrapper, +}; + +describe('useBoardColumnsInternal', () => { + it('should update boardColumns state when moving to left and right', async () => { + const { result } = renderHook(() => { + const [boardColumnsList, setBoardColumnsList] = useRecoilState( + useRecordBoardScopedStates().boardColumnsState, + ); + return { + boardColumns: useBoardColumnsInternal(), + boardColumnsList, + setBoardColumnsList, + }; + }, renderHookConfig); + const columns: BoardColumnDefinition[] = [ + { + id: '1', + title: '1', + position: 0, + }, + { + id: '2', + title: '2', + position: 1, + }, + ]; + act(() => { + result.current.setBoardColumnsList(columns); + }); + + act(() => { + result.current.boardColumns.handleMoveBoardColumn('right', columns[0]); + }); + + await waitFor(() => { + expect(result.current.boardColumnsList).toStrictEqual([ + { id: '2', title: '2', position: 0, index: 0 }, + { id: '1', title: '1', position: 1, index: 1 }, + ]); + }); + + act(() => { + result.current.boardColumns.handleMoveBoardColumn('left', columns[0]); + }); + + await waitFor(() => { + expect(result.current.boardColumnsList).toStrictEqual([ + { id: '1', title: '1', position: 0, index: 0 }, + { id: '2', title: '2', position: 1, index: 1 }, + ]); + }); + }); + + it('should call apollo mutation after persistBoardColumns', async () => { + const { result } = renderHook(() => { + return { + boardColumns: useBoardColumnsInternal(), + setBoardColumnsList: useSetRecoilState( + useRecordBoardScopedStates().boardColumnsState, + ), + }; + }, renderHookConfig); + const columns: BoardColumnDefinition[] = [ + { + id: '1', + title: '1', + position: 0, + }, + ]; + act(() => { + result.current.setBoardColumnsList(columns); + }); + + act(() => { + result.current.boardColumns.persistBoardColumns(); + }); + + await waitFor(() => { + expect(mocks[0].result).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardContextMenuEntriesInternal.test.tsx b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardContextMenuEntriesInternal.test.tsx new file mode 100644 index 000000000..44e4bb9bb --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useRecordBoardContextMenuEntriesInternal.test.tsx @@ -0,0 +1,55 @@ +import { MockedProvider } from '@apollo/client/testing'; +import { act, renderHook, waitFor } from '@testing-library/react'; +import { RecoilRoot, useRecoilValue } from 'recoil'; + +import { useDeleteSelectedRecordBoardCardsInternal } from '@/object-record/record-board/hooks/internal/useDeleteSelectedRecordBoardCardsInternal'; +import { useRecordBoardContextMenuEntriesInternal } from '@/object-record/record-board/hooks/internal/useRecordBoardContextMenuEntriesInternal'; +import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope'; +import { IconTrash } from '@/ui/display/icon'; +import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState'; + +const scopeId = 'scopeId'; +const Wrapper = ({ children }: { children: React.ReactNode }) => ( + + + {children} + + +); + +describe('useRecordBoardContextMenuEntriesInternal', () => { + it('should update contextEntries', async () => { + const { result } = renderHook( + () => { + const deleteSelectedBoardCards = + useDeleteSelectedRecordBoardCardsInternal(); + const newContextEntry = { + label: 'Delete', + Icon: IconTrash, + accent: 'danger', + onClick: deleteSelectedBoardCards, + }; + return { + setContextEntries: useRecordBoardContextMenuEntriesInternal(), + contextEntries: useRecoilValue(contextMenuEntriesState), + newContextEntry, + }; + }, + { + wrapper: Wrapper, + }, + ); + + expect(result.current.contextEntries).toStrictEqual([]); + + act(() => { + result.current.setContextEntries.setContextMenuEntries(); + }); + + await waitFor(() => { + expect(JSON.stringify(result.current.contextEntries)).toBe( + JSON.stringify([result.current.newContextEntry]), + ); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useSetRecordBoardCardSelectedInternal.test.tsx b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useSetRecordBoardCardSelectedInternal.test.tsx new file mode 100644 index 000000000..d2f65b74d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useSetRecordBoardCardSelectedInternal.test.tsx @@ -0,0 +1,58 @@ +import { act, renderHook } from '@testing-library/react'; +import { RecoilRoot, useRecoilValue } from 'recoil'; + +import { useSetRecordBoardCardSelectedInternal } from '@/object-record/record-board/hooks/internal/useSetRecordBoardCardSelectedInternal'; +import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope'; +import { isRecordBoardCardSelectedFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedFamilyState'; + +const scopeId = 'scopeId'; +const boardCardId = 'boardCardId'; + +const Wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); + +const recordBoardScopeId = 'recordBoardScopeId'; + +describe('useSetRecordBoardCardSelectedInternal', () => { + it('should update the data when selecting and deselecting the cardId', async () => { + const { result } = renderHook( + () => { + return { + cardSelect: useSetRecordBoardCardSelectedInternal({ + recordBoardScopeId, + }), + isSelected: useRecoilValue( + isRecordBoardCardSelectedFamilyState(boardCardId), + ), + }; + }, + { + wrapper: Wrapper, + }, + ); + + expect(result.current.isSelected).toBe(false); + + act(() => { + result.current.cardSelect.setCardSelected(boardCardId, true); + }); + + expect(result.current.isSelected).toBe(true); + + act(() => { + result.current.cardSelect.setCardSelected(boardCardId, false); + }); + + expect(result.current.isSelected).toBe(false); + + act(() => { + result.current.cardSelect.setCardSelected(boardCardId, true); + result.current.cardSelect.unselectAllActiveCards(); + }); + + expect(result.current.isSelected).toBe(false); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useUpdateCompanyBoardColumnsInternal.test.tsx b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useUpdateCompanyBoardColumnsInternal.test.tsx new file mode 100644 index 000000000..fe794f193 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/__tests__/useUpdateCompanyBoardColumnsInternal.test.tsx @@ -0,0 +1,113 @@ +import { act, renderHook } from '@testing-library/react'; +import { RecoilRoot, useRecoilValue } from 'recoil'; + +import { CompanyForBoard } from '@/companies/types/CompanyProgress'; +import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates'; +import { useUpdateCompanyBoardColumnsInternal } from '@/object-record/record-board/hooks/internal/useUpdateCompanyBoardColumnsInternal'; +import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope'; +import { recordBoardCardIdsByColumnIdFamilyState } from '@/object-record/record-board/states/recordBoardCardIdsByColumnIdFamilyState'; +import { currentPipelineStepsState } from '@/pipeline/states/currentPipelineStepsState'; +import { Opportunity } from '@/pipeline/types/Opportunity'; +import { PipelineStep } from '@/pipeline/types/PipelineStep'; + +const scopeId = 'scopeId'; +const Wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); + +describe('useUpdateCompanyBoardColumnsInternal', () => { + it('should update recoil state after updateCompanyBoardColumns call ', async () => { + const { result } = renderHook( + () => { + return { + updateCompanyBoardColumns: useUpdateCompanyBoardColumnsInternal(), + currentPipeline: useRecoilValue(currentPipelineStepsState), + boardColumns: useRecoilValue( + useRecordBoardScopedStates().boardColumnsState, + ), + savedBoardColumns: useRecoilValue( + useRecordBoardScopedStates().savedBoardColumnsState, + ), + idsByColumnId: useRecoilValue( + recordBoardCardIdsByColumnIdFamilyState('1'), + ), + }; + }, + { + wrapper: Wrapper, + }, + ); + + const pipelineSteps: PipelineStep[] = [ + { + id: '1', + name: 'Step 1', + color: 'red', + position: 1, + createdAt: '2024-01-12', + updatedAt: '2024-01-12', + }, + { + id: '2', + name: 'Step 2', + color: 'blue', + position: 1, + createdAt: '2024-01-12', + updatedAt: '2024-01-12', + }, + ]; + const opportunity: Opportunity = { + id: '123', + amount: { + amountMicros: 1000000, + currencyCode: 'USD', + }, + closeDate: new Date('2024-02-01'), + probability: 0.75, + pipelineStepId: '1', + pipelineStep: pipelineSteps[0], + pointOfContactId: '456', + pointOfContact: { + id: '456', + name: { + firstName: 'John', + lastName: 'Doe', + }, + avatarUrl: 'https://example.com/avatar.jpg', + }, + }; + + const companyForBoard: CompanyForBoard = { + id: '789', + name: 'Acme Inc.', + domainName: 'acme.com', + }; + + expect(result.current.currentPipeline).toStrictEqual([]); + expect(result.current.savedBoardColumns).toStrictEqual([]); + expect(result.current.boardColumns).toStrictEqual([]); + expect(result.current.idsByColumnId).toStrictEqual([]); + + act(() => { + result.current.updateCompanyBoardColumns( + pipelineSteps, + [opportunity], + [companyForBoard], + ); + }); + + const expectedBoardColumns = [ + { id: '1', title: 'Step 1', colorCode: 'red', position: 1 }, + { id: '2', title: 'Step 2', colorCode: 'blue', position: 1 }, + ]; + + expect(result.current.currentPipeline).toStrictEqual(pipelineSteps); + expect(result.current.savedBoardColumns).toStrictEqual( + expectedBoardColumns, + ); + expect(result.current.boardColumns).toStrictEqual(expectedBoardColumns); + expect(result.current.idsByColumnId).toStrictEqual([opportunity.id]); + }); +});