Skip to content

Commit

Permalink
feat(settings): make it possible to open settings modal based on quer…
Browse files Browse the repository at this point in the history
…y-params (#13996)

Co-authored-by: William Thorenfeldt <[email protected]>
  • Loading branch information
framitdavid and wrt95 authored Nov 19, 2024
1 parent 69d00c9 commit 439262e
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { renderHookWithProviders } from '../test/mocks';
import {
queryParamKey,
useOpenSettingsModalBasedQueryParam,
} from './useOpenSettingsModalBasedQueryParam';
import { useSearchParams } from 'react-router-dom';
import { useSettingsModalContext } from '../contexts/SettingsModalContext';
import { waitFor } from '@testing-library/react';

jest.mock('../contexts/SettingsModalContext');
jest.mock('react-router-dom', () => ({
useSearchParams: jest.fn(),
}));

describe('useOpenSettingsModalBasedQueryParam', () => {
it('should open "settingsModal" if query params has valid tab id', async () => {
const searchParams = buildSearchParams('about');
setupSearchParamMock(searchParams);

const openSettingsMock = jest.fn();
setupUseSettingsModalContextMock(openSettingsMock);

renderHookWithProviders()(() => useOpenSettingsModalBasedQueryParam());
await waitFor(() => expect(openSettingsMock).toHaveBeenCalledWith('about'));
expect(openSettingsMock).toHaveBeenCalledTimes(1);
});

it('should not open "settingsModal" if query params has an invalid tab id', async () => {
const searchParams = buildSearchParams('doestNotExistTab');
setupSearchParamMock(searchParams);

const openSettingsMock = jest.fn();
setupUseSettingsModalContextMock(openSettingsMock);

renderHookWithProviders()(() => useOpenSettingsModalBasedQueryParam());
await waitFor(() => expect(openSettingsMock).not.toHaveBeenCalledWith('doestNotExistTab'));
expect(openSettingsMock).toHaveBeenCalledTimes(0);
});
});

function setupSearchParamMock(searchParams: URLSearchParams): jest.Mock {
return (useSearchParams as jest.Mock).mockReturnValue([searchParams, jest.fn()]);
}

function buildSearchParams(queryParamValue: string): URLSearchParams {
const searchParams: URLSearchParams = new URLSearchParams();
searchParams.set(queryParamKey, queryParamValue);
return searchParams;
}

function setupUseSettingsModalContextMock(openSettingsMock: typeof jest.fn): jest.Mock {
return (useSettingsModalContext as jest.Mock).mockReturnValue({
settingsRef: {
current: {
openSettings: openSettingsMock,
},
},
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useSettingsModalContext } from '../contexts/SettingsModalContext';
import type { SettingsModalTabId } from '../types/SettingsModalTabId';
import { allSettingsModalTabs } from '../layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/hooks/useSettingsModalMenuTabConfigs';

export const queryParamKey: string = 'openSettingsModalWithTab';

export function useOpenSettingsModalBasedQueryParam(): void {
const [searchParams] = useSearchParams();
const { settingsRef } = useSettingsModalContext();

useEffect((): void => {
const tabToOpen: SettingsModalTabId = searchParams.get(queryParamKey) as SettingsModalTabId;
const shouldOpenModal: boolean = isValidTab(tabToOpen);
if (shouldOpenModal) {
settingsRef.current.openSettings(tabToOpen);
}
}, [searchParams, settingsRef]);
}

function isValidTab(tabId: SettingsModalTabId): boolean {
return allSettingsModalTabs.includes(tabId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ const setupTabId: SettingsModalTabId = 'setup';
const policyTabId: SettingsModalTabId = 'policy';
const accessControlTabId: SettingsModalTabId = 'access_control';

export const allSettingsModalTabs: Array<SettingsModalTabId> = [
aboutTabId,
setupTabId,
policyTabId,
accessControlTabId,
];

export const useSettingsModalMenuTabConfigs =
(): StudioContentMenuButtonTabProps<SettingsModalTabId>[] => {
const { t } = useTranslation();
Expand Down
41 changes: 25 additions & 16 deletions frontend/app-development/layout/PageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { NotFoundPage } from './NotFoundPage';
import { useTranslation } from 'react-i18next';
import { WebSocketSyncWrapper } from '../components';
import { PageHeaderContextProvider } from 'app-development/contexts/PageHeaderContext';
import { useOpenSettingsModalBasedQueryParam } from '../hooks/useOpenSettingsModalBasedQueryParam';
import { type AxiosError } from 'axios';
import { type RepoStatus } from 'app-shared/types/RepoStatus';

/**
* Displays the layout for the app development pages
*/
export const PageLayout = (): React.ReactNode => {
const { t } = useTranslation();

const { pathname } = useLocation();
const match = matchPath({ path: '/:org/:app', caseSensitive: true, end: false }, pathname);
const { org, app } = match.params;
Expand All @@ -41,20 +43,6 @@ export const PageLayout = (): React.ReactNode => {
);
}

const renderPages = () => {
if (repoStatusError?.response?.status === ServerCodes.NotFound) {
return <NotFoundPage />;
}
if (repoStatus?.hasMergeConflict) {
return <MergeConflictWarning />;
}
return (
<WebSocketSyncWrapper>
<Outlet />
</WebSocketSyncWrapper>
);
};

return (
<>
<PageHeaderContextProvider user={user} repoOwnerIsOrg={repoOwnerIsOrg}>
Expand All @@ -63,7 +51,28 @@ export const PageLayout = (): React.ReactNode => {
isRepoError={repoStatusError !== null}
/>
</PageHeaderContextProvider>
{renderPages()}
<Pages repoStatus={repoStatus} repoStatusError={repoStatusError} />
</>
);
};

type PagesToRenderProps = {
repoStatusError: AxiosError;
repoStatus: RepoStatus;
};
const Pages = ({ repoStatusError, repoStatus }: PagesToRenderProps) => {
// Listen to URL-search params and opens settings-modal if params matches.
useOpenSettingsModalBasedQueryParam();

if (repoStatusError?.response?.status === ServerCodes.NotFound) {
return <NotFoundPage />;
}
if (repoStatus?.hasMergeConflict) {
return <MergeConflictWarning />;
}
return (
<WebSocketSyncWrapper>
<Outlet />
</WebSocketSyncWrapper>
);
};

0 comments on commit 439262e

Please sign in to comment.