Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Wrap the new FormDesignerNavigation with AppContext #14884

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 0 additions & 27 deletions frontend/app-development/router/routes.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { textMock } from '@studio/testing/mocks/i18nMock';
import type { QueryClient } from '@tanstack/react-query';
import { LayoutContext } from 'app-development/contexts/LayoutContext/LayoutContext';
import { SubApp as UiEditorLatest } from '@altinn/ux-editor/SubApp';
import { FeatureFlag, shouldDisplayFeature } from 'app-shared/utils/featureToggleUtils';

// Mocks:
jest.mock('@altinn/ux-editor-v3/SubApp', () => ({
Expand All @@ -34,15 +33,6 @@ jest.mock('@altinn/ux-editor/SubApp', () => ({
},
}));

jest.mock('@altinn/ux-editor/containers/FormDesignNavigation', () => ({
FormDesignerNavigation: () => <div>Form Designer Navigation</div>,
}));

jest.mock('app-shared/utils/featureToggleUtils', () => ({
...jest.requireActual('app-shared/utils/featureToggleUtils'),
shouldDisplayFeature: jest.fn(),
}));

const renderWithProviders = (
ui: React.ReactElement,
queryClient: QueryClient,
Expand Down Expand Up @@ -134,23 +124,6 @@ describe('routes', () => {
expect(screen.getByText(textMock('ux_editor.loading_page'))).toBeInTheDocument();
});

it('renders FormDesignerNavigation when task navigation is enabled and no layout set is selected', () => {
(shouldDisplayFeature as jest.Mock).mockImplementation(
(feature) => feature === FeatureFlag.TaskNavigation,
);
const queryClient = createQueryClientMock();
queryClient.setQueryData([QueryKey.AppVersion, org, app], {
frontendVersion: '4.0.0',
backendVersion: '7.0.0',
});

renderWithProviders(<UiEditor />, queryClient, {
setSelectedLayoutSetName: jest.fn(),
selectedFormLayoutSetName: undefined,
});
expect(screen.getByText('Form Designer Navigation')).toBeInTheDocument();
});

const renderUiEditor = (queryClient: QueryClient = createQueryClientMock()) =>
renderSubapp(RoutePaths.UIEditor, queryClient);
});
Expand Down
10 changes: 1 addition & 9 deletions frontend/app-development/router/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ import { useAppVersionQuery } from 'app-shared/hooks/queries';
import React from 'react';
import { usePreviewContext } from '../contexts/PreviewContext';
import { useLayoutContext } from '../contexts/LayoutContext';
import { StudioPageSpinner, useLocalStorage } from '@studio/components';
import { StudioPageSpinner } from '@studio/components';
import { useTranslation } from 'react-i18next';
import { AppContentLibrary } from 'app-development/features/appContentLibrary';
import { FormDesignerNavigation } from '@altinn/ux-editor/containers/FormDesignNavigation';
import { FeatureFlag, shouldDisplayFeature } from 'app-shared/utils/featureToggleUtils';

interface IRouteProps {
headerTextKey?: string;
Expand Down Expand Up @@ -47,8 +45,6 @@ export const UiEditor = () => {
const { data: version, isPending: fetchingVersionIsPending } = useAppVersionQuery(org, app);
const { shouldReloadPreview, previewHasLoaded } = usePreviewContext();
const { setSelectedLayoutSetName } = useLayoutContext();
const [selectedFormLayoutSetName] = useLocalStorage<string>('layoutSet/' + app);
const isTaskNavigationEnabled = shouldDisplayFeature(FeatureFlag.TaskNavigation);

if (fetchingVersionIsPending) {
return <StudioPageSpinner spinnerTitle={t('ux_editor.loading_page')} />;
Expand All @@ -57,10 +53,6 @@ export const UiEditor = () => {
if (!version) return null;

const renderUiEditorContent = () => {
if (isTaskNavigationEnabled && !selectedFormLayoutSetName) {
return <FormDesignerNavigation />;
}

const handleLayoutSetNameChange = (layoutSetName: string) => {
setSelectedLayoutSetName(layoutSetName);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import type { QueryClient } from '@tanstack/react-query';
import { useSelectedFormLayoutSetName } from './useSelectedFormLayoutSetName';
import { type LayoutSets } from 'app-shared/types/api/LayoutSetsResponse';
import { typedLocalStorage } from '@studio/pure-functions';
import { FeatureFlag } from 'app-shared/utils/featureToggleUtils';

// Test data:
export const layoutSet1NameMock = 'test-layout-set';
Expand Down Expand Up @@ -51,7 +53,10 @@ const wrapper = ({
};

describe('useSelectedFormLayoutSetName', () => {
afterEach(jest.clearAllMocks);
afterEach(() => {
typedLocalStorage.removeItem('featureFlags');
jest.clearAllMocks();
});

it('should return empty string when there are no layout sets', async () => {
const { result } = renderHook(() => useSelectedFormLayoutSetName(undefined), { wrapper });
Expand All @@ -69,6 +74,18 @@ describe('useSelectedFormLayoutSetName', () => {
expect(result.current.selectedFormLayoutSetName).toEqual(layoutSetsMock.sets[0].id);
});

it('should return undefined when selected layout does not exist and taskNavigation feature flag is enabled', async () => {
const client = createQueryClientMock();
typedLocalStorage.setItem('featureFlags', [FeatureFlag.TaskNavigation]);

const { result } = renderHook(() => useSelectedFormLayoutSetName(layoutSetsMock), {
wrapper: ({ children }) => {
return wrapper({ children, client });
},
});
expect(result.current.selectedFormLayoutSetName).toEqual(undefined);
});

it('should return selected layout set when selected does exist', async () => {
const client = createQueryClientMock();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
import { useLocalStorage } from '@studio/components/src/hooks/useLocalStorage';
import { type LayoutSets } from 'app-shared/types/api/LayoutSetsResponse';
import { FeatureFlag, shouldDisplayFeature } from 'app-shared/utils/featureToggleUtils';

export type UseSelectedFormLayoutSetNameResult = {
selectedFormLayoutSetName: string;
setSelectedFormLayoutSetName: (layoutName: string) => void;
removeSelectedFormLayoutSetName: () => void;
};

export const useSelectedFormLayoutSetName = (
layoutSets: LayoutSets,
): UseSelectedFormLayoutSetNameResult => {
const { app } = useStudioEnvironmentParams();
const isTaskNavigationEnabled = shouldDisplayFeature(FeatureFlag.TaskNavigation);

const defaultLayoutSet = layoutSets?.sets[0]?.id ?? '';
const defaultLayoutSet = isTaskNavigationEnabled ? undefined : (layoutSets?.sets[0]?.id ?? '');

const [selectedFormLayoutSetName, setSelectedFormLayoutSetName] = useLocalStorage<string>(
'layoutSet/' + app,
);
const [selectedFormLayoutSetName, setSelectedFormLayoutSetName, removeSelectedFormLayoutSetName] =
useLocalStorage<string>('layoutSet/' + app);

const layoutSetExists = layoutSets?.sets.some((set) => set.id === selectedFormLayoutSetName);

return {
selectedFormLayoutSetName: layoutSetExists ? selectedFormLayoutSetName : defaultLayoutSet,
setSelectedFormLayoutSetName,
removeSelectedFormLayoutSetName,
};
};
10 changes: 8 additions & 2 deletions frontend/packages/ux-editor/src/AppContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface AppContextProps {
previewIframeRef: MutableRefObject<HTMLIFrameElement>;
selectedFormLayoutSetName: string;
setSelectedFormLayoutSetName: (selectedFormLayoutSetName: string) => void;
removeSelectedFormLayoutSetName: () => void;
selectedFormLayoutName: string;
setSelectedFormLayoutName: (selectedFormLayoutName: string) => void;
updateLayoutsForPreview: (layoutSetName: string, resetQueries?: boolean) => Promise<void>;
Expand Down Expand Up @@ -48,8 +49,11 @@ export const AppContextProvider = ({
const { org, app } = useStudioEnvironmentParams();
const { data: layoutSets, isPending: pendingLayoutsets } = useLayoutSetsQuery(org, app);

const { selectedFormLayoutSetName, setSelectedFormLayoutSetName } =
useSelectedFormLayoutSetName(layoutSets);
const {
selectedFormLayoutSetName,
setSelectedFormLayoutSetName,
removeSelectedFormLayoutSetName,
} = useSelectedFormLayoutSetName(layoutSets);

const { selectedFormLayoutName, setSelectedFormLayoutName } =
useSelectedFormLayoutName(selectedFormLayoutSetName);
Expand Down Expand Up @@ -102,6 +106,7 @@ export const AppContextProvider = ({
previewIframeRef,
selectedFormLayoutSetName,
setSelectedFormLayoutSetName,
removeSelectedFormLayoutSetName,
selectedFormLayoutName,
setSelectedFormLayoutName,
updateLayoutsForPreview,
Expand All @@ -117,6 +122,7 @@ export const AppContextProvider = ({
setSelectedFormLayoutSetName,
selectedFormLayoutName,
setSelectedFormLayoutName,
removeSelectedFormLayoutSetName,
updateLayoutsForPreview,
updateLayoutSetsForPreview,
updateLayoutSettingsForPreview,
Expand Down
55 changes: 48 additions & 7 deletions frontend/packages/ux-editor/src/SubApp.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,72 @@ import type { ReactNode } from 'react';
import React from 'react';
import { SubApp } from './SubApp';
import { render, screen, within } from '@testing-library/react';
import { appContextMock } from './testing/appContextMock';
import { FeatureFlag, shouldDisplayFeature } from 'app-shared/utils/featureToggleUtils';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { ServicesContextProvider } from 'app-shared/contexts/ServicesContext';
import { queriesMock } from 'app-shared/mocks/queriesMock';
import type { QueryClient } from '@tanstack/react-query';
import { QueryKey } from 'app-shared/types/QueryKey';
import { app, org } from '@studio/testing/testids';

const providerTestId = 'provider';
const appTestId = 'app';
const formNavigationTestId = 'formNavigation';
jest.mock('./AppContext', () => ({
AppContextProvider: ({ children }: { children: ReactNode }) => {
return <div data-testid={providerTestId}>{children}</div>;
},
}));
jest.mock('./hooks', () => ({
useAppContext: () => {
return {};
},
}));
jest.mock('./containers/FormDesignNavigation', () => ({
FormDesignerNavigation: () => {
return <div data-testid={formNavigationTestId}>Form Designer Navigation</div>;
},
}));
jest.mock('./App', () => ({
App: () => {
return <div data-testid={appTestId}>App</div>;
},
}));

jest.mock('app-shared/utils/featureToggleUtils', () => ({
...jest.requireActual('app-shared/utils/featureToggleUtils'),
shouldDisplayFeature: jest.fn(),
}));

describe('SubApp', () => {
it('Renders the app within the AppContext provider', () => {
render(
<SubApp
shouldReloadPreview={false}
previewHasLoaded={jest.fn()}
onLayoutSetNameChange={jest.fn()}
/>,
);
renderWithProviders();
const provider = screen.getByTestId(providerTestId);
expect(provider).toBeInTheDocument();
expect(within(provider).getByTestId(appTestId)).toBeInTheDocument();
});

it('renders FormDesignerNavigation when task navigation is enabled and no layout set is selected', () => {
(shouldDisplayFeature as jest.Mock).mockImplementation(
(feature) => feature === FeatureFlag.TaskNavigation,
);
const queryClient = createQueryClientMock();
queryClient.setQueryData([QueryKey.AppVersion, org, app], {
frontendVersion: '4.0.0',
backendVersion: '7.0.0',
});
renderWithProviders(queryClient);
const provider = screen.getByTestId(providerTestId);
expect(provider).toBeInTheDocument();
expect(within(provider).getByTestId(formNavigationTestId)).toBeInTheDocument();
});
});

const renderWithProviders = (queryClient?: QueryClient) => {
return render(
<ServicesContextProvider {...queriesMock} client={queryClient}>
<SubApp {...appContextMock} />
</ServicesContextProvider>,
);
};
16 changes: 15 additions & 1 deletion frontend/packages/ux-editor/src/SubApp.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import React from 'react';
import { App } from './App';
import './styles/index.css';
import { AppContextProvider } from './AppContext';
import { App as FormDesigner } from './App';
import { FeatureFlag, shouldDisplayFeature } from 'app-shared/utils/featureToggleUtils';
import { FormDesignerNavigation } from './containers/FormDesignNavigation';
import { useAppContext } from './hooks';

type SubAppProps = {
shouldReloadPreview: boolean;
previewHasLoaded: () => void;
onLayoutSetNameChange: (layoutSetName: string) => void;
};

const App = () => {
const isTaskNavigationEnabled = shouldDisplayFeature(FeatureFlag.TaskNavigation);
const { selectedFormLayoutSetName } = useAppContext();

return isTaskNavigationEnabled && !selectedFormLayoutSetName ? (
<FormDesignerNavigation />
) : (
<FormDesigner />
);
};

export const SubApp = (props: SubAppProps) => {
return (
<AppContextProvider {...props}>
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/ux-editor/src/testing/appContextMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const appContextMock: AppContextProps = {
previewIframeRef: previewIframeRefMock,
selectedFormLayoutSetName: layoutSet1NameMock,
setSelectedFormLayoutSetName: jest.fn(),
removeSelectedFormLayoutSetName: jest.fn(),
selectedFormLayoutName: layout1NameMock,
setSelectedFormLayoutName: jest.fn(),
updateLayoutSetsForPreview: jest.fn(),
Expand Down