Skip to content
Merged
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
13 changes: 13 additions & 0 deletions src/__mocks__/getPartyMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ export const getPartyMock = (): IParty => ({
childParties: undefined,
});

export const ServiceOwnerPartyId = 414234123;
export const getServiceOwnerPartyMock = (): IParty => ({
partyId: ServiceOwnerPartyId,
name: 'Brønnøysundregistrene',
ssn: null,
partyTypeName: PartyType.Organisation,
orgNumber: '974760673',
unitType: 'BEDR',
isDeleted: false,
onlyHierarchyElementWithNoAccess: false,
childParties: undefined,
});

export type PartyWithSubunit = { org: IParty & { childParties: IParty[] }; person: IParty };
export const getPartyWithSubunitMock = (): PartyWithSubunit => ({
org: {
Expand Down
3 changes: 1 addition & 2 deletions src/features/instantiate/containers/InstantiateContainer.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 = () => {
Expand Down
19 changes: 16 additions & 3 deletions src/features/language/LanguageProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface LanguageCtx {
current: string;
profileLoaded: boolean;
updateProfile: (profile: IProfile) => void;
noProfileFound: () => void;
setWithLanguageSelector: (language: string) => void;
}

Expand All @@ -20,6 +21,9 @@ const { Provider, useCtx } = createContext<LanguageCtx>({
updateProfile: () => {
throw new Error('LanguageProvider not initialized');
},
noProfileFound: () => {
throw new Error('LanguageProvider not initialized');
},
setWithLanguageSelector: () => {
throw new Error('LanguageProvider not initialized');
},
Expand All @@ -46,19 +50,28 @@ export const LanguageProvider = ({ children }: PropsWithChildren) => {
localStorage.setItem(localStorageKey, newLanguage);
};

const noProfileFound = () => {
// Just mark it as loaded, so we can continue loading language resources
setProfileLoaded(true);
};

const setWithLanguageSelector = (language: string) => {
setCurrent(language);
localStorage.setItem(`selectedAppLanguage${window.app}${userId}`, language);
};

return <Provider value={{ current, profileLoaded, updateProfile, setWithLanguageSelector }}>{children}</Provider>;
return (
<Provider value={{ current, profileLoaded, updateProfile, setWithLanguageSelector, noProfileFound }}>
{children}
</Provider>
);
};

export const useCurrentLanguage = () => useCtx().current;
export const useIsProfileLanguageLoaded = () => useCtx().profileLoaded;
export const useSetCurrentLanguage = () => {
const { setWithLanguageSelector, updateProfile } = useCtx();
return { setWithLanguageSelector, updateProfile };
const { setWithLanguageSelector, updateProfile, noProfileFound } = useCtx();
return { setWithLanguageSelector, updateProfile, noProfileFound };
};

function getLanguageQueryParam() {
Expand Down
127 changes: 127 additions & 0 deletions src/features/pdf/PDFWrapper.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React from 'react';

import { jest } from '@jest/globals';
import { screen, waitFor } from '@testing-library/react';
import type { AxiosError } from 'axios';

import { getIncomingApplicationMetadataMock } from 'src/__mocks__/getApplicationMetadataMock';
import { getInstanceDataMock } from 'src/__mocks__/getInstanceDataMock';
import { getPartyMock, getServiceOwnerPartyMock } from 'src/__mocks__/getPartyMock';
import { getProcessDataMock } from 'src/__mocks__/getProcessDataMock';
import { getProfileMock } from 'src/__mocks__/getProfileMock';
import { ProcessWrapper } from 'src/components/wrappers/ProcessWrapper';
import { InstanceProvider } from 'src/features/instance/InstanceContext';
import { fetchApplicationMetadata, fetchProcessState } from 'src/queries/queries';
import { InstanceRouter, renderWithoutInstanceAndLayout } from 'src/test/renderWithProviders';
import type { AppQueries } from 'src/queries/types';

const exampleGuid = '75154373-aed4-41f7-95b4-e5b5115c2edc';
const exampleInstanceId = `512345/${exampleGuid}`;

enum RenderAs {
User,
ServiceOwner,
}

const axios400Error: AxiosError = {
isAxiosError: true,
code: 'ERR_BAD_REQUEST',
status: 400,
response: {
status: 400,
statusText: 'Bad Request',
headers: {},
config: null!,
data: undefined,
},
name: 'AxiosError',
message: 'Request failed with status code 400',
toJSON: () => ({}),
};

const buildInstance = () =>
getInstanceDataMock((i) => {
i.org = 'brg';
i.id = exampleInstanceId;
i.lastChanged = '2022-02-05T09:19:32.8858042Z';
});

const render = async (renderAs: RenderAs, queriesOverride?: Partial<AppQueries>) => {
jest.mocked(fetchApplicationMetadata).mockImplementationOnce(async () =>
getIncomingApplicationMetadataMock((m) => {
m.org = 'brg';
m.partyTypesAllowed.person = true;
m.partyTypesAllowed.organisation = true;
}),
);
jest.mocked(fetchProcessState).mockImplementation(async () =>
getProcessDataMock((p) => {
p.processTasks = [p.currentTask!];
}),
);

const party = renderAs === RenderAs.User ? getPartyMock() : getServiceOwnerPartyMock();

return await renderWithoutInstanceAndLayout({
renderer: () => (
<InstanceProvider>
<ProcessWrapper />
</InstanceProvider>
),
router: ({ children }) => (
<InstanceRouter
instanceId={exampleInstanceId}
taskId='Task_1'
initialPage=''
query='pdf=1'
>
{children}
</InstanceRouter>
),
queries: {
fetchOrgs: async () => ({
orgs: {
brg: {
name: {
en: 'Brønnøysund Register Centre',
nb: 'Brønnøysundregistrene',
nn: 'Brønnøysundregistera',
},
logo: 'https://altinncdn.no/orgs/brg/brreg.png',
orgnr: '974760673',
homepage: 'https://www.brreg.no',
environments: ['tt02', 'production'],
},
},
}),
fetchInstanceData: async () => buildInstance(),
fetchFormData: async () => ({}),
fetchLayouts: async () => ({}),
fetchCurrentParty: async () => party,
fetchParties: async () => [party],
fetchUserProfile: async () => {
if (renderAs === RenderAs.User) {
return getProfileMock();
}
throw axios400Error;
},
...queriesOverride,
},
});
};

describe('PDFWrapper', () => {
it.each([RenderAs.User, RenderAs.ServiceOwner])(`should render PDF - %s`, async (renderAs) => {
const result = await render(renderAs);

await waitFor(() => expect(result.container.querySelector('#readyForPrint')).not.toBeNull(), { timeout: 5000 });

if (renderAs === RenderAs.ServiceOwner) {
expect(await screen.queryByText('Avsender:')).toBeNull();
expect(await screen.queryByText('01017512345-Ola Privatperson')).toBeNull();
} else if (renderAs === RenderAs.User) {
expect(await screen.queryByText('Avsender:')).not.toBeNull();
expect(await screen.queryByText('01017512345-Ola Privatperson')).not.toBeNull();
}
});
});
17 changes: 15 additions & 2 deletions src/features/profile/ProfileProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useEffect } from 'react';

import { useQuery } from '@tanstack/react-query';
import type { UseQueryResult } 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 { useSetCurrentLanguage } from 'src/features/language/LanguageProvider';
import { useAllowAnonymousIs } from 'src/features/stateless/getAllowAnonymous';
import { isAxiosError } from 'src/utils/isAxiosError';
import type { IProfile } from 'src/types/shared';

// Also used for prefetching @see appPrefetcher.ts
Expand All @@ -19,15 +21,25 @@ export function useProfileQueryDef(enabled: boolean) {
};
}

const canHandleProfileQueryError = (error: UseQueryResult<IProfile | undefined>['error']) =>
// The backend will return 400 if the logged in user/client is not a user.
// Altinn users have profiles, but organisations, service owners and system users do not, so this is expected
isAxiosError(error) && error.response?.status === 400;

const useProfileQuery = () => {
const enabled = useShouldFetchProfile();
const { updateProfile } = useSetCurrentLanguage();
const { updateProfile, noProfileFound } = useSetCurrentLanguage();

const utils = useQuery(useProfileQueryDef(enabled));

useEffect(() => {
if (canHandleProfileQueryError(utils.error)) {
noProfileFound();
return;
}

utils.error && window.logError('Fetching user profile failed:\n', utils.error);
}, [utils.error]);
}, [noProfileFound, utils.error]);

useEffect(() => {
if (utils.data) {
Expand All @@ -46,6 +58,7 @@ const { Provider, useCtx } = delayedContext(() =>
name: 'Profile',
required: false,
default: undefined,
shouldDisplayError: (error) => !canHandleProfileQueryError(error),
query: useProfileQuery,
}),
);
Expand Down
5 changes: 4 additions & 1 deletion src/test/renderWithProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ interface InstanceRouterProps {
taskId?: string;
instanceId?: string;
alwaysRouteToChildren?: boolean;
query?: string;
}

interface ExtendedRenderOptionsWithInstance extends ExtendedRenderOptions, InstanceRouterProps {}
Expand Down Expand Up @@ -246,11 +247,13 @@ export function InstanceRouter({
taskId = 'Task_1',
initialPage = 'FormLayout',
alwaysRouteToChildren = false,
query,
}: PropsWithChildren<InstanceRouterProps>) {
const path = `/ttd/test/instance/${instanceId}/${taskId}/${initialPage}`;
return (
<MemoryRouter
basename='/ttd/test'
initialEntries={[`/ttd/test/instance/${instanceId}/${taskId}/${initialPage}`]}
initialEntries={[query ? `${path}?${query}` : path]}
>
<Routes>
<Route
Expand Down
Loading