Skip to content

Commit

Permalink
- Added tests for upsert and remove record filters hooks
Browse files Browse the repository at this point in the history
- Created and applied upsert and remove record filter everywhere old upsert and remove are used
- Added RecordFilter context
- Renamed usePrefetchRunQuery to useUpsertRecordsInCacheForPrefetchKey
  • Loading branch information
lucasbordeau committed Jan 15, 2025
1 parent 9ba510e commit 21259b4
Show file tree
Hide file tree
Showing 29 changed files with 592 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery';
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';

export const useDeleteFavoriteFolder = () => {
const { deleteOneRecord } = useDeleteOneRecord({
objectNameSingular: CoreObjectNameSingular.FavoriteFolder,
});

const { upsertRecordsInCache } = usePrefetchRunQuery<Favorite>({
prefetchKey: PrefetchKey.AllFavorites,
});
const { upsertRecordsInCache } =
useUpsertRecordsInCacheForPrefetchKey<Favorite>({
prefetchKey: PrefetchKey.AllFavorites,
});

const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { Favorite } from '@/favorites/types/Favorite';
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery';
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useRecoilValue } from 'recoil';
Expand Down Expand Up @@ -33,7 +33,7 @@ export const usePrefetchedFavoritesData = (): PrefetchedFavoritesData => {
);

const { upsertRecordsInCache: upsertFavorites } =
usePrefetchRunQuery<Favorite>({
useUpsertRecordsInCacheForPrefetchKey<Favorite>({
prefetchKey: PrefetchKey.AllFavorites,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { FavoriteFolder } from '@/favorites/types/FavoriteFolder';
import { usePrefetchRunQuery } from '@/prefetch/hooks/internal/usePrefetchRunQuery';
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { useRecoilValue } from 'recoil';
Expand All @@ -26,7 +26,7 @@ export const usePrefetchedFavoritesFoldersData =
);

const { upsertRecordsInCache: upsertFavoriteFolders } =
usePrefetchRunQuery<FavoriteFolder>({
useUpsertRecordsInCacheForPrefetchKey<FavoriteFolder>({
prefetchKey: PrefetchKey.AllFavoritesFolders,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,28 @@ import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery';
import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery';
import { MultiObjectRecordQueryResult } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
import { useEffect, useMemo } from 'react';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';

export const useCombinedFindManyRecords = ({
operationSignatures,
skip = false,
onDataChange,
}: {
operationSignatures: RecordGqlOperationSignature[];
skip?: boolean;
onDataChange?: (
data: MultiObjectRecordQueryResult | null | undefined,
) => void;
}) => {
const findManyQuery = useGenerateCombinedFindManyRecordsQuery({
operationSignatures,
});

const { data, loading } = useQuery<MultiObjectRecordQueryResult>(
findManyQuery ?? EMPTY_QUERY,
{
const { data, loading, previousData } =
useQuery<MultiObjectRecordQueryResult>(findManyQuery ?? EMPTY_QUERY, {
skip,
},
);
});

const resultWithoutConnection = Object.fromEntries(
Object.entries(data ?? {}).map(([namePlural, objectRecordConnection]) => [
Expand All @@ -33,8 +37,20 @@ export const useCombinedFindManyRecords = ({
]),
);

const isSameDataAsPreviousData = useMemo(
() => isDeeplyEqual(previousData, data),
[previousData, data],
);

useEffect(() => {
if (!isSameDataAsPreviousData) {
onDataChange?.(data);
}
}, [isSameDataAsPreviousData, onDataChange, data]);

return {
result: resultWithoutConnection,
loading,
isSameDataAsPreviousData,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { selectedFilterComponentState } from '@/object-record/object-filter-drop
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { getActorSourceMultiSelectOptions } from '@/object-record/object-filter-dropdown/utils/getActorSourceMultiSelectOptions';
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown';
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
Expand Down Expand Up @@ -61,6 +62,7 @@ export const ObjectFilterDropdownSourceSelect = ({
const { currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView(viewComponentId);

// TODO: this should be removed as it is not consistent across re-renders
const [fieldId] = useState(v4());

const sourceTypes = getActorSourceMultiSelectOptions(
Expand All @@ -73,6 +75,8 @@ export const ObjectFilterDropdownSourceSelect = ({

const { emptyRecordFilter } = useEmptyRecordFilter();

const { removeRecordFilter } = useRemoveRecordFilter();

const handleMultipleItemSelectChange = (
itemToSelect: SelectableItem,
newSelectedValue: boolean,
Expand All @@ -83,8 +87,13 @@ export const ObjectFilterDropdownSourceSelect = ({
(id) => id !== itemToSelect.id,
);

if (!filterDefinitionUsedInDropdown) {
throw new Error('Filter definition used in dropdown should be defined');
}

if (newSelectedItemIds.length === 0) {
emptyRecordFilter();
removeRecordFilter(filterDefinitionUsedInDropdown.fieldMetadataId);
deleteCombinedViewFilter(fieldId);
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { RecordFilterDefinition } from '@/object-record/record-filter/types/RecordFilterDefinition';

import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';

export const availableFilterDefinitionsComponentState = createComponentStateV2<
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react-dom/test-utils';

import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { useRemoveRecordFilter } from '../useRemoveRecordFilter';
import { useUpsertRecordFilter } from '../useUpsertRecordFilter';

const Wrapper = getJestMetadataAndApolloMocksWrapper({
apolloMocks: [],
});

describe('useRemoveRecordFilter', () => {
it('should remove an existing filter', () => {
const { result } = renderHook(
() => {
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);

const { upsertRecordFilter } = useUpsertRecordFilter();
const { removeRecordFilter } = useRemoveRecordFilter();

return {
upsertRecordFilter,
removeRecordFilter,
currentRecordFilters,
};
},
{
wrapper: Wrapper,
},
);

const filter: RecordFilter = {
id: 'filter-1',
fieldMetadataId: 'field-1',
value: 'test-value',
operand: ViewFilterOperand.Contains,
displayValue: 'test-value',
definition: {
type: 'TEXT',
fieldMetadataId: 'field-1',
label: 'Test Field',
iconName: 'IconText',
},
};

// First add a filter
act(() => {
result.current.upsertRecordFilter(filter);
});

expect(result.current.currentRecordFilters).toHaveLength(1);
expect(result.current.currentRecordFilters[0]).toEqual(filter);

// Then remove it
act(() => {
result.current.removeRecordFilter(filter.fieldMetadataId);
});

expect(result.current.currentRecordFilters).toHaveLength(0);
});

it('should not modify filters when trying to remove a non-existent filter', () => {
const { result } = renderHook(
() => {
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);
const { upsertRecordFilter } = useUpsertRecordFilter();
const { removeRecordFilter } = useRemoveRecordFilter();
return {
upsertRecordFilter,
removeRecordFilter,
currentRecordFilters,
};
},
{
wrapper: Wrapper,
},
);

const filter: RecordFilter = {
id: 'filter-1',
fieldMetadataId: 'field-1',
value: 'test-value',
operand: ViewFilterOperand.Contains,
displayValue: 'test-value',
definition: {
type: 'TEXT',
fieldMetadataId: 'field-1',
label: 'Test Field',
iconName: 'IconText',
},
};

// Add a filter
act(() => {
result.current.upsertRecordFilter(filter);
});

expect(result.current.currentRecordFilters).toHaveLength(1);

// Try to remove a non-existent filter
act(() => {
result.current.removeRecordFilter('non-existent-field');
});

// Filter list should remain unchanged
expect(result.current.currentRecordFilters).toHaveLength(1);
expect(result.current.currentRecordFilters[0]).toEqual(filter);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react-dom/test-utils';

import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { useUpsertRecordFilter } from '../useUpsertRecordFilter';

const Wrapper = getJestMetadataAndApolloMocksWrapper({
apolloMocks: [],
});

describe('useUpsertRecordFilter', () => {
it('should add a new filter when fieldMetadataId does not exist', () => {
const { result } = renderHook(
() => {
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);

const { upsertRecordFilter } = useUpsertRecordFilter();

return { upsertRecordFilter, currentRecordFilters };
},
{
wrapper: Wrapper,
},
);

const newFilter: RecordFilter = {
id: 'filter-1',
fieldMetadataId: 'field-1',
value: 'test-value',
operand: ViewFilterOperand.Contains,
displayValue: 'test-value',
definition: {
type: 'TEXT',
fieldMetadataId: 'field-1',
label: 'Test Field',
iconName: 'IconText',
},
};

act(() => {
result.current.upsertRecordFilter(newFilter);
});

expect(result.current.currentRecordFilters).toHaveLength(1);
expect(result.current.currentRecordFilters[0]).toEqual(newFilter);
});

it('should update an existing filter when fieldMetadataId exists', () => {
const { result } = renderHook(
() => {
const currentRecordFilters = useRecoilComponentValueV2(
currentRecordFiltersComponentState,
);

const { upsertRecordFilter } = useUpsertRecordFilter();

return { upsertRecordFilter, currentRecordFilters };
},
{
wrapper: Wrapper,
},
);

const initialFilter: RecordFilter = {
id: 'filter-1',
fieldMetadataId: 'field-1',
value: 'initial-value',
operand: ViewFilterOperand.Contains,
displayValue: 'initial-value',
definition: {
type: 'TEXT',
fieldMetadataId: 'field-1',
label: 'Test Field',
iconName: 'IconText',
},
};

const updatedFilter: RecordFilter = {
id: 'filter-1',
fieldMetadataId: 'field-1',
value: 'updated-value',
operand: ViewFilterOperand.Contains,
displayValue: 'updated-value',
definition: {
type: 'TEXT',
fieldMetadataId: 'field-1',
label: 'Test Field',
iconName: 'IconText',
},
};

act(() => {
result.current.upsertRecordFilter(initialFilter);
});

expect(result.current.currentRecordFilters).toHaveLength(1);
expect(result.current.currentRecordFilters[0]).toEqual(initialFilter);

act(() => {
result.current.upsertRecordFilter(updatedFilter);
});

expect(result.current.currentRecordFilters).toHaveLength(1);
expect(result.current.currentRecordFilters[0]).toEqual(updatedFilter);
});
});
Loading

0 comments on commit 21259b4

Please sign in to comment.