Clear stale parent-view filters on front-component cross-object navigation (#21869)
This commit is contained in:
+79
@@ -1,7 +1,11 @@
|
||||
import { i18n } from '@lingui/core';
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { getDefaultStore } from 'jotai';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { contextStoreRecordShowParentViewComponentState } from '@/context-store/states/contextStoreRecordShowParentViewComponentState';
|
||||
import { useFrontComponentExecutionContext } from '@/front-components/hooks/useFrontComponentExecutionContext';
|
||||
|
||||
const mockNavigateApp = jest.fn();
|
||||
@@ -109,10 +113,24 @@ const renderUseFrontComponentExecutionContext = (
|
||||
const FRONT_COMPONENT_ID = 'fc-test-id';
|
||||
const COMMAND_MENU_ITEM_ID = 'cmd-item-1';
|
||||
|
||||
const parentViewAtom =
|
||||
contextStoreRecordShowParentViewComponentState.atomFamily({
|
||||
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
|
||||
});
|
||||
|
||||
const createParentView = (parentViewObjectNameSingular: string) => ({
|
||||
parentViewComponentId: 'parent-view-component-id',
|
||||
parentViewObjectNameSingular,
|
||||
parentViewFilterGroups: [],
|
||||
parentViewFilters: [],
|
||||
parentViewSorts: [],
|
||||
});
|
||||
|
||||
describe('useFrontComponentExecutionContext', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockCurrentUser = { id: 'user-123' };
|
||||
getDefaultStore().set(parentViewAtom, undefined);
|
||||
});
|
||||
|
||||
describe('executionContext', () => {
|
||||
@@ -207,6 +225,67 @@ describe('useFrontComponentExecutionContext', () => {
|
||||
{ replace: true },
|
||||
);
|
||||
});
|
||||
|
||||
it('should clear stale parent-view state when navigating to a record of a different object', async () => {
|
||||
const store = getDefaultStore();
|
||||
store.set(parentViewAtom, createParentView('company'));
|
||||
|
||||
const { result } = renderUseFrontComponentExecutionContext({
|
||||
frontComponentId: FRONT_COMPONENT_ID,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.frontComponentHostCommunicationApi.navigate(
|
||||
AppPath.RecordShowPage,
|
||||
{ objectNameSingular: 'person', objectRecordId: 'record-1' },
|
||||
);
|
||||
});
|
||||
|
||||
expect(store.get(parentViewAtom)).toBeUndefined();
|
||||
expect(mockNavigateApp).toHaveBeenCalledWith(
|
||||
AppPath.RecordShowPage,
|
||||
{ objectNameSingular: 'person', objectRecordId: 'record-1' },
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it('should keep parent-view state when navigating to a record of the same object', async () => {
|
||||
const store = getDefaultStore();
|
||||
const parentView = createParentView('company');
|
||||
store.set(parentViewAtom, parentView);
|
||||
|
||||
const { result } = renderUseFrontComponentExecutionContext({
|
||||
frontComponentId: FRONT_COMPONENT_ID,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.frontComponentHostCommunicationApi.navigate(
|
||||
AppPath.RecordShowPage,
|
||||
{ objectNameSingular: 'company', objectRecordId: 'record-2' },
|
||||
);
|
||||
});
|
||||
|
||||
expect(store.get(parentViewAtom)).toEqual(parentView);
|
||||
});
|
||||
|
||||
it('should keep parent-view state when navigating to a non-record page', async () => {
|
||||
const store = getDefaultStore();
|
||||
const parentView = createParentView('company');
|
||||
store.set(parentViewAtom, parentView);
|
||||
|
||||
const { result } = renderUseFrontComponentExecutionContext({
|
||||
frontComponentId: FRONT_COMPONENT_ID,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.frontComponentHostCommunicationApi.navigate(
|
||||
AppPath.SettingsCatchAll,
|
||||
);
|
||||
});
|
||||
|
||||
expect(store.get(parentViewAtom)).toEqual(parentView);
|
||||
});
|
||||
});
|
||||
|
||||
describe('openSidePanelPage', () => {
|
||||
|
||||
+26
-1
@@ -6,12 +6,14 @@ import {
|
||||
type FrontComponentExecutionContext,
|
||||
type FrontComponentHostCommunicationApi,
|
||||
} from 'twenty-front-component-renderer';
|
||||
import { type AppPath, type EnqueueSnackbarParams } from 'twenty-shared/types';
|
||||
import { AppPath, type EnqueueSnackbarParams } from 'twenty-shared/types';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { useCommandMenuConfirmationModal } from '@/command-menu-item/confirmation-modal/hooks/useCommandMenuConfirmationModal';
|
||||
import { useUnmountCommand } from '@/command-menu-item/engine-command/hooks/useUnmountEngineCommand';
|
||||
import { commandMenuItemProgressFamilyState } from '@/command-menu-item/states/commandMenuItemProgressFamilyState';
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { contextStoreRecordShowParentViewComponentState } from '@/context-store/states/contextStoreRecordShowParentViewComponentState';
|
||||
import { useRequestApplicationTokenRefresh } from '@/front-components/hooks/useRequestApplicationTokenRefresh';
|
||||
import { useNavigateSidePanel } from '@/side-panel/hooks/useNavigateSidePanel';
|
||||
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
|
||||
@@ -19,6 +21,7 @@ import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
|
||||
import { useStore } from 'jotai';
|
||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/icon';
|
||||
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
|
||||
@@ -44,6 +47,7 @@ export const useFrontComponentExecutionContext = ({
|
||||
} => {
|
||||
const currentUser = useAtomStateValue(currentUserState);
|
||||
const navigateApp = useNavigateApp();
|
||||
const store = useStore();
|
||||
const { requestAccessTokenRefresh } = useRequestApplicationTokenRefresh({
|
||||
frontComponentId,
|
||||
});
|
||||
@@ -74,6 +78,27 @@ export const useFrontComponentExecutionContext = ({
|
||||
queryParams,
|
||||
options,
|
||||
) => {
|
||||
if (to === AppPath.RecordShowPage) {
|
||||
const targetObjectNameSingular = (
|
||||
params as { objectNameSingular?: string | null } | undefined
|
||||
)?.objectNameSingular;
|
||||
|
||||
const parentViewAtom =
|
||||
contextStoreRecordShowParentViewComponentState.atomFamily({
|
||||
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
|
||||
});
|
||||
|
||||
const parentView = store.get(parentViewAtom);
|
||||
|
||||
if (
|
||||
isDefined(parentView) &&
|
||||
isDefined(targetObjectNameSingular) &&
|
||||
parentView.parentViewObjectNameSingular !== targetObjectNameSingular
|
||||
) {
|
||||
store.set(parentViewAtom, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
navigateApp(
|
||||
to as AppPath,
|
||||
params as Parameters<typeof navigateApp>[1],
|
||||
|
||||
Reference in New Issue
Block a user