diff --git a/src/App.test.tsx b/src/App.test.tsx index ab57263902..922e512e9b 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -43,14 +43,4 @@ describe('App', () => { }); await screen.findByRole('heading', { level: 1, name: 'Ukjent feil' }); }); - - test('should render unknown error when hasOrgsError', async () => { - await renderWithInstanceAndLayout({ - renderer: () => , - queries: { - fetchOrgs: () => Promise.reject(new Error('400 Bad Request')), - }, - }); - await screen.findByRole('heading', { level: 1, name: 'Ukjent feil' }); - }); }); diff --git a/src/components/form/Form.tsx b/src/components/form/Form.tsx index 9a8b1f8d0e..41f4f05c24 100644 --- a/src/components/form/Form.tsx +++ b/src/components/form/Form.tsx @@ -232,7 +232,7 @@ function HandleNavigationFocusComponent() { }, [searchParams, setSearchParams]); React.useEffect(() => { - if (focusNode != null) { + if (focusNode) { navigateTo(focusNode); } }, [navigateTo, focusNode]); diff --git a/src/components/presentation/OrganisationLogo/OrganisationLogo.test.tsx b/src/components/presentation/OrganisationLogo/OrganisationLogo.test.tsx index fbccfa98ce..6c51677b75 100644 --- a/src/components/presentation/OrganisationLogo/OrganisationLogo.test.tsx +++ b/src/components/presentation/OrganisationLogo/OrganisationLogo.test.tsx @@ -6,39 +6,59 @@ import { getApplicationMetadataMock } from 'src/__mocks__/getApplicationMetadata import { OrganisationLogo } from 'src/components/presentation/OrganisationLogo/OrganisationLogo'; import { renderWithInstanceAndLayout } from 'src/test/renderWithProviders'; import type { IApplicationMetadata } from 'src/features/applicationMetadata'; +import type * as queries from 'src/queries/queries'; +import type { KeysStartingWith } from 'src/queries/types'; -const render = async (logo: IApplicationMetadata['logo']) => +const render = async ({ + logo, + queriesOverride, +}: { + logo: IApplicationMetadata['logo']; + queriesOverride?: Partial>; +}) => await renderWithInstanceAndLayout({ renderer: () => , queries: { fetchApplicationMetadata: () => Promise.resolve(getApplicationMetadataMock({ logo })), + ...queriesOverride, }, }); describe('OrganisationLogo', () => { it('Should get img src from organisations when logo.source is set to "org" in applicationMetadata', async () => { - await render({ - source: 'org', - displayAppOwnerNameInHeader: false, - }); + const logo = { source: 'org', displayAppOwnerNameInHeader: true } satisfies IApplicationMetadata['logo']; + await render({ logo }); + expect(screen.getByRole('img')).toHaveAttribute('src', 'https://altinncdn.no/orgs/mockOrg/mockOrg.png'); }); it('Should not display appOwner when "showAppOwner" is set to false', async () => { - await render({ - source: 'org', - displayAppOwnerNameInHeader: false, - }); + const logo = { source: 'org', displayAppOwnerNameInHeader: false } satisfies IApplicationMetadata['logo']; + await render({ logo }); expect(screen.queryByText('Mockdepartementet')).not.toBeInTheDocument(); }); it('Should display appOwner when "showAppOwner" is set to true', async () => { + const logo = { source: 'org', displayAppOwnerNameInHeader: true } satisfies IApplicationMetadata['logo']; + await render({ logo }); + + expect(await screen.findByText('Mockdepartementet')).toBeInTheDocument(); + }); + + test('Should render unknown error when fetchOrgs returns error', async () => { + const logo = { source: 'org' } satisfies IApplicationMetadata['logo']; + await render({ - source: 'org', - displayAppOwnerNameInHeader: true, + logo, + queriesOverride: { + fetchOrgs: async () => { + throw new Error('400 Bad Request'); + }, + }, }); - expect(await screen.findByText('Mockdepartementet')).toBeInTheDocument(); + await screen.findByRole('heading', { level: 1, name: 'Ukjent feil' }); + expect(screen.getByRole('heading', { level: 1, name: 'Ukjent feil' })).toBeInTheDocument(); }); }); diff --git a/src/components/presentation/OrganisationLogo/OrganisationLogo.tsx b/src/components/presentation/OrganisationLogo/OrganisationLogo.tsx index b0f346e899..6a48918757 100644 --- a/src/components/presentation/OrganisationLogo/OrganisationLogo.tsx +++ b/src/components/presentation/OrganisationLogo/OrganisationLogo.tsx @@ -3,16 +3,29 @@ import React from 'react'; import cn from 'classnames'; import classes from 'src/components/presentation/OrganisationLogo/OrganisationLogo.module.css'; -import { useAppLogoAltText, useAppOwner } from 'src/core/texts/appTexts'; -import { useAppLogoSize, useAppLogoUrl, useDisplayAppOwnerNameInHeader } from 'src/hooks/useAppLogo'; +import { DisplayError } from 'src/core/errorHandling/DisplayError'; +import { useAppLogoAltText, useAppOwner, useTextResourceWithFallback } from 'src/core/texts/appTexts'; +import { useApplicationMetadata } from 'src/features/applicationMetadata/ApplicationMetadataProvider'; +import { useOrgs } from 'src/hooks/queries/useOrgs'; +import { useAppLogoSize, useDisplayAppOwnerNameInHeader } from 'src/hooks/useAppLogo'; +import type { IApplicationMetadata } from 'src/features/applicationMetadata'; +import type { IAltinnOrgs } from 'src/types/shared'; export const OrganisationLogo = () => { - const appLogoUrl = useAppLogoUrl(); + const { error: orgsError, data: orgs } = useOrgs(); + const appMetadata = useApplicationMetadata(); + const logoUrlFromTextResource = useTextResourceWithFallback('appLogo.url', undefined); const appLogoAltText = useAppLogoAltText(); const appLogoSize = useAppLogoSize(); const showAppOwner = useDisplayAppOwnerNameInHeader(); const appOwner = useAppOwner(); + const appLogoUrl = getAppLogoUrl({ orgs, appMetadata, logoUrlFromTextResource }); + + if (orgsError) { + return ; + } + return (
{
); }; + +export function getAppLogoUrl({ + orgs, + appMetadata, + logoUrlFromTextResource, +}: { + orgs: IAltinnOrgs | undefined; + appMetadata: IApplicationMetadata; + logoUrlFromTextResource: string | undefined; +}) { + if (!orgs) { + return undefined; + } + + const org = appMetadata?.org; + const useOrgAsSource = (appMetadata.logo?.source ?? 'org') === 'org'; + const logoUrlFromOrgs = useOrgAsSource && org ? orgs[org]?.logo : undefined; + + return logoUrlFromOrgs || logoUrlFromTextResource; +} diff --git a/src/core/texts/appTexts.ts b/src/core/texts/appTexts.ts index 3dbfc68631..01a0d3b06c 100644 --- a/src/core/texts/appTexts.ts +++ b/src/core/texts/appTexts.ts @@ -5,9 +5,9 @@ import { import { useCurrentLanguage } from 'src/features/language/LanguageProvider'; import { useHasTextResources } from 'src/features/language/textResources/TextResourcesProvider'; import { useLanguage } from 'src/features/language/useLanguage'; -import { useHasOrgs, useOrgs } from 'src/features/orgs/OrgsProvider'; +import { useOrgs } from 'src/hooks/queries/useOrgs'; -export function useTextResourceOr(resource: string, fallback: T): string | T { +export function useTextResourceWithFallback(resource: string, fallback?: string | undefined) { const { langAsString } = useLanguage(); const fromResources = langAsString(resource); @@ -20,17 +20,16 @@ export function useTextResourceOr(resource: string export function useHasAppTextsYet() { const hasAppMetadata = useHasApplicationMetadata(); - const hasOrgs = useHasOrgs(); const hasTexts = useHasTextResources(); - return hasAppMetadata && hasOrgs && hasTexts; + return hasAppMetadata && hasTexts; } export function useAppName() { const application = useApplicationMetadata(); - const appName = useTextResourceOr('appName', undefined); - const oldAppName = useTextResourceOr('ServiceName', undefined); + const appName = useTextResourceWithFallback('appName', undefined); + const oldAppName = useTextResourceWithFallback('ServiceName', undefined); const selectedLanguage = useCurrentLanguage(); const appNameFromMetadata = application.title[selectedLanguage] || application.title.nb; @@ -40,26 +39,26 @@ export function useAppName() { export function useAppOwner() { const application = useApplicationMetadata(); const fromMetaData = useOrgName(application.org); - return useTextResourceOr('appOwner', fromMetaData); + return useTextResourceWithFallback('appOwner', fromMetaData); } export function useAppReceiver() { const application = useApplicationMetadata(); const fromMetaData = useOrgName(application.org); - return useTextResourceOr('appReceiver', fromMetaData); + return useTextResourceWithFallback('appReceiver', fromMetaData); } export function useAppLogoAltText() { const application = useApplicationMetadata(); const fromMetaData = useOrgName(application.org); - return useTextResourceOr('appLogo.altText', fromMetaData); + return useTextResourceWithFallback('appLogo.altText', fromMetaData); } function useOrgName(org: string | undefined) { - const orgs = useOrgs(); + const { data: orgs } = useOrgs(); const currentLanguage = useCurrentLanguage(); - if (orgs && typeof org === 'string' && orgs[org]) { + if (orgs && org && org in orgs) { return orgs[org].name[currentLanguage] || orgs[org].name.nb; } diff --git a/src/features/instantiate/containers/InstantiateContainer.tsx b/src/features/instantiate/containers/InstantiateContainer.tsx index 1b2ea30df3..3a75143d91 100644 --- a/src/features/instantiate/containers/InstantiateContainer.tsx +++ b/src/features/instantiate/containers/InstantiateContainer.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import { isAxiosError } from 'axios'; - import { Loader } from 'src/core/loading/Loader'; import { InstantiateValidationError } from 'src/features/instantiate/containers/InstantiateValidationError'; import { MissingRolesError } from 'src/features/instantiate/containers/MissingRolesError'; @@ -10,6 +8,7 @@ import { useInstantiation } from 'src/features/instantiate/InstantiationContext' import { useCurrentParty } from 'src/features/party/PartiesProvider'; import { AltinnAppTheme } from 'src/theme/altinnAppTheme'; import { changeBodyBackground } from 'src/utils/bodyStyling'; +import { isAxiosError } from 'src/utils/isAxiosError'; import { HttpStatusCodes } from 'src/utils/network/networking'; export const InstantiateContainer = () => { diff --git a/src/features/orgs/OrgsProvider.tsx b/src/features/orgs/OrgsProvider.tsx deleted file mode 100644 index 269af2398b..0000000000 --- a/src/features/orgs/OrgsProvider.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useEffect } from 'react'; - -import { useQuery } from '@tanstack/react-query'; - -import { useAppQueries } from 'src/core/contexts/AppQueriesProvider'; -import { delayedContext } from 'src/core/contexts/delayedContext'; -import { createQueryContext } from 'src/core/contexts/queryContext'; -import type { IAltinnOrgs } from 'src/types/shared'; - -const extractOrgsFromServerResponse = (response: { orgs: IAltinnOrgs }): IAltinnOrgs => response.orgs; - -// Also used for prefetching @see appPrefetcher.ts -export function useOrgsQueryDef() { - const { fetchOrgs } = useAppQueries(); - return { - queryKey: ['fetchOrganizations'], - queryFn: fetchOrgs, - }; -} - -const useOrgsQuery = () => { - const utils = useQuery({ - ...useOrgsQueryDef(), - select: extractOrgsFromServerResponse, - }); - - useEffect(() => { - utils.error && window.logError('Fetching organizations failed:\n', utils.error); - }, [utils.error]); - - return utils; -}; - -const { Provider, useCtx, useHasProvider } = delayedContext(() => - createQueryContext({ - name: 'Orgs', - required: true, - query: useOrgsQuery, - }), -); - -export const OrgsProvider = Provider; -export const useOrgs = () => useCtx(); -export const useHasOrgs = () => useHasProvider(); diff --git a/src/features/orgs/index.d.ts b/src/features/orgs/index.d.ts deleted file mode 100644 index 06c9325b4f..0000000000 --- a/src/features/orgs/index.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { IAltinnOrgs } from 'src/types/shared'; - -export interface IOrgsState { - allOrgs: IAltinnOrgs | null; -} - -export interface IFetchOrgsFulfilled { - orgs: IAltinnOrgs; -} diff --git a/src/hooks/queries/useOrgs.ts b/src/hooks/queries/useOrgs.ts new file mode 100644 index 0000000000..06effb31fb --- /dev/null +++ b/src/hooks/queries/useOrgs.ts @@ -0,0 +1,30 @@ +import { useQuery } from '@tanstack/react-query'; + +import { useAppQueries } from 'src/core/contexts/AppQueriesProvider'; +import type { IAltinnOrgs } from 'src/types/shared'; + +// Also used for prefetching @see appPrefetcher.ts +export function useOrgsQueryDef() { + const { fetchOrgs } = useAppQueries(); + + return { + queryKey: ['fetchOrganizations'], + queryFn: fetchOrgs, + }; +} + +export const useOrgs = () => { + const orgsQueryDef = useOrgsQueryDef(); + const result = useQuery({ + ...orgsQueryDef, + select: (response: { orgs: IAltinnOrgs }): IAltinnOrgs => response.orgs, + staleTime: 1000 * 60 * 60 * 24 * 30, // 30 days + // retry: 3, + }); + + if (result.isError) { + window.logError('Fetching organizations failed:\n', result.error); + } + + return result; +}; diff --git a/src/hooks/useAppLogo.ts b/src/hooks/useAppLogo.ts index f979930dbd..122a2ebf01 100644 --- a/src/hooks/useAppLogo.ts +++ b/src/hooks/useAppLogo.ts @@ -1,18 +1,4 @@ -import { useTextResourceOr } from 'src/core/texts/appTexts'; import { useApplicationMetadata } from 'src/features/applicationMetadata/ApplicationMetadataProvider'; -import { useOrgs } from 'src/features/orgs/OrgsProvider'; - -export function useAppLogoUrl() { - const orgs = useOrgs(); - const application = useApplicationMetadata(); - const org = application?.org; - - const useOrgAsSource = (application.logo?.source ?? 'org') === 'org'; - const fromOrg = useOrgAsSource && orgs && org ? orgs[org]?.logo : undefined; - const fromTextResources = useTextResourceOr('appLogo.url', undefined); - - return fromOrg || fromTextResources; -} export function useDisplayAppOwnerNameInHeader() { const application = useApplicationMetadata(); diff --git a/src/index.tsx b/src/index.tsx index 150783faef..8b0b674f19 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -30,7 +30,6 @@ import { InstantiationProvider } from 'src/features/instantiate/InstantiationCon import { LangToolsStoreProvider } from 'src/features/language/LangToolsStore'; import { LanguageProvider } from 'src/features/language/LanguageProvider'; import { TextResourcesProvider } from 'src/features/language/textResources/TextResourcesProvider'; -import { OrgsProvider } from 'src/features/orgs/OrgsProvider'; import { PartyProvider } from 'src/features/party/PartiesProvider'; import { ProfileProvider } from 'src/features/profile/ProfileProvider'; import { AppPrefetcher } from 'src/queries/appPrefetcher'; @@ -41,17 +40,6 @@ import 'react-toastify/dist/ReactToastify.css'; import 'src/index.css'; import '@digdir/designsystemet-theme/brand/altinn/tokens.css'; -const router = createHashRouter([ - { - path: '*', - element: ( - - - - ), - }, -]); - document.addEventListener('DOMContentLoaded', () => { const container = document.getElementById('root'); const root = container && createRoot(container); @@ -77,6 +65,17 @@ document.addEventListener('DOMContentLoaded', () => { ); }); +const router = createHashRouter([ + { + path: '*', + element: ( + + + + ), + }, +]); + function Root() { return ( @@ -85,26 +84,24 @@ function Root() { - - - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/src/layout/Likert/LikertTestUtils.tsx b/src/layout/Likert/LikertTestUtils.tsx index 7c1f7ca04e..a48ace633b 100644 --- a/src/layout/Likert/LikertTestUtils.tsx +++ b/src/layout/Likert/LikertTestUtils.tsx @@ -166,7 +166,7 @@ export const render = async ({ export function ContainerTester(props: { id: string }) { const node = useResolvedNode(props.id); - if (!node || !node.isType('Likert')) { + if (!node?.isType('Likert')) { throw new Error(`Could not resolve node with id ${props.id}, or unexpected node type`); } diff --git a/src/layout/NavigationButtons/NavigationButtonsComponent.tsx b/src/layout/NavigationButtons/NavigationButtonsComponent.tsx index 3c394944a0..432d0483a6 100644 --- a/src/layout/NavigationButtons/NavigationButtonsComponent.tsx +++ b/src/layout/NavigationButtons/NavigationButtonsComponent.tsx @@ -23,8 +23,8 @@ export function NavigationButtonsComponent({ node }: INavigationButtons) { const refPrev = React.useRef(null); const refNext = React.useRef(null); - const nextTextKey = textResourceBindings?.next || 'next'; - const backTextKey = textResourceBindings?.back || 'back'; + const nextTextKey = textResourceBindings?.next ?? 'next'; + const backTextKey = textResourceBindings?.back ?? 'back'; const returnToViewText = summaryItem?.textResourceBindings?.returnToSummaryButtonTitle ?? 'form_filler.back_to_summary'; diff --git a/src/layout/Summary/SummaryComponent.tsx b/src/layout/Summary/SummaryComponent.tsx index 5bf5452735..e9661cdcae 100644 --- a/src/layout/Summary/SummaryComponent.tsx +++ b/src/layout/Summary/SummaryComponent.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type { PropsWithChildren } from 'react'; import { Grid } from '@material-ui/core'; import cn from 'classnames'; @@ -17,28 +18,26 @@ import { SummaryContent } from 'src/layout/Summary/SummaryContent'; import { pageBreakStyles } from 'src/utils/formComponentUtils'; import { useResolvedNode } from 'src/utils/layout/NodesContext'; import type { IGrid } from 'src/layout/common.generated'; -import type { SummaryDisplayProperties } from 'src/layout/Summary/config.generated'; -import type { LayoutNode } from 'src/utils/layout/LayoutNode'; +import type { CompSummaryInternal, SummaryDisplayProperties } from 'src/layout/Summary/config.generated'; +import type { BaseLayoutNode, LayoutNode } from 'src/utils/layout/LayoutNode'; + +type Overrides = { + targetNode?: LayoutNode; + grid?: IGrid; + largeGroup?: boolean; + display?: SummaryDisplayProperties; +}; export interface ISummaryComponent { summaryNode: LayoutNode<'Summary'>; - overrides?: { - targetNode?: LayoutNode; - grid?: IGrid; - largeGroup?: boolean; - display?: SummaryDisplayProperties; - }; + overrides?: Overrides; } function _SummaryComponent({ summaryNode, overrides }: ISummaryComponent, ref: React.Ref) { - const { id, grid } = summaryNode.item; - const display = overrides?.display || summaryNode.item.display; const { langAsString } = useLanguage(); const { currentPageId } = useNavigatePage(); - const summaryItem = summaryNode.item; - const targetNode = useResolvedNode(overrides?.targetNode || summaryNode.item.componentRef || summaryNode.item.id); - const targetItem = targetNode?.item; + const targetNode = useResolvedNode(overrides?.targetNode ?? summaryNode.item.componentRef ?? summaryNode.item.id); const targetView = targetNode?.top.top.myKey; const validations = useUnifiedValidationsForNode(targetNode); @@ -54,34 +53,108 @@ function _SummaryComponent({ summaryNode, overrides }: ISummaryComponent, ref: R navigateTo(targetNode, true); setReturnToView?.(currentPageId); - setNodeOfOrigin?.(id); + setNodeOfOrigin?.(summaryNode.item.id); }; - if (!targetNode || !targetItem || targetNode.isHidden() || targetItem.type === 'Summary') { + if (!targetNode || targetNode.isHidden() || !targetNode.item || targetNode.item.type === 'Summary') { // TODO: Show info to developers if target node is not found? return null; } - const displayGrid = - display && display.useComponentGrid ? overrides?.grid || targetItem?.grid : overrides?.grid || grid; + const display = overrides?.display ?? summaryNode.item.display; - const component = targetNode.def; - const RenderSummary = 'renderSummary' in component ? component.renderSummary.bind(component) : null; + const RenderSummary = 'renderSummary' in targetNode.def ? targetNode.def.renderSummary.bind(targetNode.def) : null; + + return ( + + {RenderSummary && 'renderSummaryBoilerplate' in targetNode.def ? ( + + ) : ( + + )} + {errors.length && targetNode.item.type !== 'Group' && !display?.hideValidationMessages ? ( + + {errors.map(({ message }) => ( + + } + /> + ))} + + {!display?.hideChangeButton && ( + + )} + + + ) : null} + + ); +} + +function SummaryGridWrapper({ + ref, + display, + overrides, + targetNode, + summaryNode, + children, +}: PropsWithChildren<{ + ref: React.Ref; + display: SummaryDisplayProperties | undefined; + overrides: Overrides | undefined; + targetNode: LayoutNode; + summaryNode: BaseLayoutNode; +}>) { + const displayGrid = overrides?.grid || (display?.useComponentGrid ? targetNode.item.grid : summaryNode.item.grid); const shouldShowBorder = - RenderSummary && 'renderSummaryBoilerplate' in component && component?.renderSummaryBoilerplate(); + 'renderSummary' in targetNode.def && + 'renderSummaryBoilerplate' in targetNode.def && + targetNode.def?.renderSummaryBoilerplate(); return ( - {RenderSummary && 'renderSummaryBoilerplate' in component ? ( - - ) : ( - - )} - {errors.length && targetItem.type !== 'Group' && !display?.hideValidationMessages ? ( - - {errors.map(({ message }) => ( - - } - /> - ))} - - {!display?.hideChangeButton && ( - - )} - - - ) : null} + {children} ); diff --git a/src/queries/appPrefetcher.ts b/src/queries/appPrefetcher.ts index 5458de1813..b28a634c1c 100644 --- a/src/queries/appPrefetcher.ts +++ b/src/queries/appPrefetcher.ts @@ -7,9 +7,9 @@ import { useFooterLayoutQueryDef } from 'src/features/footer/FooterLayoutProvide import { useLayoutSetsQueryDef } from 'src/features/form/layoutSets/LayoutSetsProvider'; import { useInstanceDataQueryDef } from 'src/features/instance/InstanceContext'; import { useProcessQueryDef } from 'src/features/instance/ProcessContext'; -import { useOrgsQueryDef } from 'src/features/orgs/OrgsProvider'; import { useCurrentPartyQueryDef, usePartiesQueryDef } from 'src/features/party/PartiesProvider'; import { useProfileQueryDef } from 'src/features/profile/ProfileProvider'; +import { useOrgsQueryDef } from 'src/hooks/queries/useOrgs'; /** * Prefetches requests that require no processed data to determine the url diff --git a/src/setupTests.ts b/src/setupTests.ts index 0f62a50c54..ea0e78185e 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -45,7 +45,8 @@ window.org = 'ttd'; window.app = 'test'; window.logError = (...args) => { - throw new Error(args.join(' ')); + // eslint-disable-next-line no-console + console.log(args.join(' ')); }; window.logWarn = window.logError; window.logInfo = window.logError; diff --git a/src/test/renderWithProviders.tsx b/src/test/renderWithProviders.tsx index 6b8c1feb7c..522f06de30 100644 --- a/src/test/renderWithProviders.tsx +++ b/src/test/renderWithProviders.tsx @@ -37,7 +37,6 @@ import { InstantiationProvider } from 'src/features/instantiate/InstantiationCon import { LangToolsStoreProvider } from 'src/features/language/LangToolsStore'; import { LanguageProvider } from 'src/features/language/LanguageProvider'; import { TextResourcesProvider } from 'src/features/language/textResources/TextResourcesProvider'; -import { OrgsProvider } from 'src/features/orgs/OrgsProvider'; import { PartyProvider } from 'src/features/party/PartiesProvider'; import { ProfileProvider } from 'src/features/profile/ProfileProvider'; import { FormComponentContextProvider } from 'src/layout/FormComponentContext'; @@ -105,9 +104,7 @@ export function queryPromiseMock(_name: T) { export const makeMutationMocks = any>( makeMock: T, -): { - [fn in keyof AppMutations]: ReturnType; -} => ({ +): Record> => ({ doAttachmentAddTag: makeMock('doAttachmentAddTag'), doAttachmentRemove: makeMock('doAttachmentRemove'), doAttachmentRemoveTag: makeMock('doAttachmentRemoveTag'), @@ -241,7 +238,7 @@ export function InstanceRouter({ > - - - - - - - - {children} - - - - - - - + + + + + + + {children} + + + + + + diff --git a/src/utils/layout/NodesContext.tsx b/src/utils/layout/NodesContext.tsx index b73b2a3c3a..30c7bb52f5 100644 --- a/src/utils/layout/NodesContext.tsx +++ b/src/utils/layout/NodesContext.tsx @@ -15,9 +15,8 @@ import { useHiddenLayoutsExpressions } from 'src/features/form/layout/LayoutsCon import { useHiddenPages, useSetHiddenPages } from 'src/features/form/layout/PageNavigationContext'; import { runConditionalRenderingRules } from 'src/utils/conditionalRendering'; import { _private, useExpressionDataSources } from 'src/utils/layout/hierarchy'; -import { BaseLayoutNode } from 'src/utils/layout/LayoutNode'; -import type { LayoutNodeFromObj } from 'src/layout/layout'; -import type { LayoutNode } from 'src/utils/layout/LayoutNode'; +import { BaseLayoutNode, type LayoutNode } from 'src/utils/layout/LayoutNode'; +import type { CompTypes, LayoutNodeFromObj } from 'src/layout/layout'; import type { LayoutPages } from 'src/utils/layout/LayoutPages'; interface NodesContext { @@ -117,19 +116,21 @@ export function useIsHiddenComponent() { * belongs to. If you only provide 'currentValue', and the component is still inside a repeating group, most likely * you'll get the first row item as a result. */ -export function useResolvedNode(selector: string | undefined | T | LayoutNode): LayoutNodeFromObj | undefined { +export function useResolvedNode( + selector: string | undefined | null | T | LayoutNode, +): LayoutNodeFromObj | undefined { const nodes = useNodes(); if (typeof selector === 'object' && selector !== null && selector instanceof BaseLayoutNode) { - return selector as any; + return selector as LayoutNodeFromObj; } if (typeof selector === 'string') { - return nodes?.findById(selector) as any; + return nodes?.findById(selector) as LayoutNodeFromObj | undefined; } - if (typeof selector == 'object' && selector !== null && 'id' in selector && typeof selector.id === 'string') { - return nodes?.findById(selector.id) as any; + if (typeof selector === 'object' && selector !== null && 'id' in selector && typeof selector.id === 'string') { + return nodes?.findById(selector.id) as LayoutNodeFromObj | undefined; } return undefined;