Skip to content

Commit

Permalink
refactor and add tests for list admin
Browse files Browse the repository at this point in the history
  • Loading branch information
mgunnerud committed Dec 15, 2023
1 parent d91445d commit 81f2f9e
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 13 deletions.
4 changes: 3 additions & 1 deletion frontend/packages/shared/src/api/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ import { buildQueryParams } from 'app-shared/utils/urlUtils';
import { componentSchemaUrl, expressionSchemaUrl, layoutSchemaUrl, newsListUrl, numberFormatSchemaUrl, orgsListUrl } from '../cdn-paths';
import type { JsonSchema } from 'app-shared/types/JsonSchema';
import type { PolicyAction, Policy, PolicySubject } from '@altinn/policy-editor';
import type { PartyList, PartyListResourceLink, Resource, ResourceListItem, ResourceVersionStatus, Validation } from 'app-shared/types/ResourceAdm';
import type { BrregOrganizationResult, BrregUnderOrganizationResult, PartyList, PartyListResourceLink, Resource, ResourceListItem, ResourceVersionStatus, Validation } from 'app-shared/types/ResourceAdm';
import type { AppConfig } from 'app-shared/types/AppConfig';
import type { Commit } from 'app-shared/types/Commit';
import type { ApplicationMetadata } from 'app-shared/types/ApplicationMetadata';
Expand Down Expand Up @@ -126,6 +126,8 @@ export const getAltinn2LinkServices = (org: string, environment: string) => get<
export const getPartyLists = (org: string, environment: string) => get<PartyList[]>(partyListsPath(org, environment));
export const getPartyList = (org: string, listId: string, environment: string) => get<PartyList>(partyListPath(org, listId, environment));
export const getResourcePartyLists = (org: string, resourceId: string, environment: string) => get<PartyListResourceLink[]>(resourcePartyListsPath(org, resourceId, environment));
export const getEnheter = (url: string) => get<BrregOrganizationResult>(url);
export const getUnderenheter = (url: string) => get<BrregUnderOrganizationResult>(url);

// ProcessEditor
export const getBpmnFile = (org: string, app: string) => get(processEditorPath(org, app));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { render as rtlRender, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { act } from 'react-dom/test-utils';
import { textMock } from '../../../testing/mocks/i18nMock';
import { queriesMock } from 'app-shared/mocks/queriesMock';
import { PartyListDetail, PartyListDetailProps } from './PartyListDetail';
import { ServicesContextProps, ServicesContextProvider } from 'app-shared/contexts/ServicesContext';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';

const mockedNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedNavigate,
}));

const testOrg = 'ttd';
const testEnv = 'tt02';
const testListeIdentifier = 'listid';

const defaultProps = {
org: testOrg,
env: testEnv,
list: {
env: testEnv,
identifier: testListeIdentifier,
name: 'Test-liste',
description: 'Dette er en beskrivelse',
members: [
{
orgNr: '123456789',
orgName: '',
isUnderenhet: false,
},
],
},
backUrl: '/listadmin',
};

const user = userEvent.setup();

describe('PartyListDetail', () => {
it('should show special name if name is not found', () => {
render();
expect(screen.getByText('<navn ikke funnet>')).toBeInTheDocument();
});

it('should show message when list is empty', () => {
render({ list: { ...defaultProps.list, members: [] } });
expect(screen.getByText('Listen inneholder ingen enheter')).toBeInTheDocument();
});

it('should call service to remove member', async () => {
const removePartyListMemberMock = jest.fn();
render({}, { removePartyListMember: removePartyListMemberMock });

const removeButton = screen.getByText('Fjern fra liste');
await act(() => user.click(removeButton));

expect(removePartyListMemberMock).toHaveBeenCalled();
});

it('should call service to add member if member is added back', async () => {
const addPartyListMemberMock = jest.fn();
render({}, { addPartyListMember: addPartyListMemberMock });

const removeButton = screen.getByText('Fjern fra liste');
await act(() => user.click(removeButton));

const reAddButton = screen.getByText('Angre fjern');
await act(() => user.click(reAddButton));

expect(addPartyListMemberMock).toHaveBeenCalled();
});

it('should call service to update name', async () => {
const updatePartyListMock = jest.fn();
render({}, { updatePartyList: updatePartyListMock });

const nameField = screen.getByLabelText('Listenavn');
await act(() => user.type(nameField, ' endret'));
await act(() => nameField.blur());

expect(updatePartyListMock).toHaveBeenCalledWith(testOrg, testListeIdentifier, testEnv, [
{ op: 'replace', path: '/name', value: 'Test-liste endret' },
]);
});

it('should call service to update description', async () => {
const updatePartyListMock = jest.fn();
render({}, { updatePartyList: updatePartyListMock });

const descriptionField = screen.getByLabelText('Beskrivelse');
await act(() => user.type(descriptionField, ' endret'));
await act(() => descriptionField.blur());

expect(updatePartyListMock).toHaveBeenCalledWith(testOrg, testListeIdentifier, testEnv, [
{ op: 'replace', path: '/description', value: 'Dette er en beskrivelse endret' },
]);
});

it('should call service to remove description', async () => {
const updatePartyListMock = jest.fn();
render({}, { updatePartyList: updatePartyListMock });

const descriptionField = screen.getByLabelText('Beskrivelse');
await act(() => user.clear(descriptionField));
await act(() => descriptionField.blur());

expect(updatePartyListMock).toHaveBeenCalledWith(testOrg, testListeIdentifier, testEnv, [
{ op: 'remove', path: '/description' },
]);
});

it('should navigate back after list is deleted', async () => {
const addPartyListMemberMock = jest.fn();
render({}, { addPartyListMember: addPartyListMemberMock });

const deleteListButton = screen.getByText('Slett liste');
await act(() => user.click(deleteListButton));

const confirmDeleteButton = screen.getAllByText('Slett liste');
await act(() => user.click(confirmDeleteButton[0]));

expect(mockedNavigate).toHaveBeenCalledWith('/listadmin');
});

it('should close modal on cancel delete', async () => {
const addPartyListMemberMock = jest.fn();
render({}, { addPartyListMember: addPartyListMemberMock });

const deleteListButton = screen.getByText('Slett liste');
await act(() => user.click(deleteListButton));

const cancelDeleteButton = screen.getByText('Avbryt');
await act(() => user.click(cancelDeleteButton));

expect(screen.queryByText('Bekreft sletting av liste')).not.toBeInTheDocument();
});
});

const render = (
props: Partial<PartyListDetailProps> = {},
queries: Partial<ServicesContextProps> = {},
) => {
const allQueries: ServicesContextProps = {
...queriesMock,
...queries,
};

return rtlRender(
<MemoryRouter>
<ServicesContextProvider {...allQueries} client={createQueryClientMock()}>
<PartyListDetail {...defaultProps} {...props} />
</ServicesContextProvider>
</MemoryRouter>,
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { createReplacePatch } from '../../utils/jsonPatchUtils/jsonPatchUtils';
import { useDeletePartyListMutation } from 'resourceadm/hooks/mutations/useDeletePartyListMutation';
import { PartyListSearch } from './PartyListSearch';

interface PartyListDetailProps {
export interface PartyListDetailProps {
org: string;
env: string;
list: PartyList;
Expand Down Expand Up @@ -91,21 +91,24 @@ export const PartyListDetail = ({
});
};

const closeModal = (): void => {
deleteWarningModalRef.current?.close();
};

return (
<div className={classes.partyListDetailWrapper}>
<Modal ref={deleteWarningModalRef} onClose={() => deleteWarningModalRef.current?.close()}>
<Modal ref={deleteWarningModalRef} onClose={closeModal}>
<Modal.Header>Bekreft sletting av liste</Modal.Header>
<Modal.Content>Vil du slette denne listen?</Modal.Content>
<Modal.Footer>
<Button color='danger' onClick={() => handleDelete()}>
Slett liste
</Button>
<Button variant='tertiary' onClick={() => deleteWarningModalRef.current?.close()}>
<Button variant='tertiary' onClick={closeModal}>
Avbryt
</Button>
</Modal.Footer>
</Modal>

<div>
<DigdirLink to={backUrl} as={Link}>
Tilbake
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { render as rtlRender, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { act } from 'react-dom/test-utils';
import { textMock } from '../../../testing/mocks/i18nMock';
import { queriesMock } from 'app-shared/mocks/queriesMock';
import { PartyListSearch } from './PartyListSearch';
import { ServicesContextProps, ServicesContextProvider } from 'app-shared/contexts/ServicesContext';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';

const enhetSearchResult = {
_embedded: {
enheter: [{ organisasjonsnummer: '123456789', navn: 'Digdir' }],
},
};

const underenhetSearchResult = {
_embedded: {
underenheter: [{ organisasjonsnummer: '987654321', navn: 'Under Digdir' }],
},
};

const handleAddMemberMock = jest.fn();

const defaultProps = {
existingMembers: [
{
orgNr: '123456789',
orgName: '',
isUnderenhet: false,
},
],
handleAddMember: handleAddMemberMock,
};

const user = userEvent.setup();

describe('PartyListSearch', () => {
it('should call handleAddMember when enhet is selected', async () => {
render();

const searchField = screen.getByTestId('enhet-search');
await act(() => user.type(searchField, 'Digdir'));

await waitFor(() => screen.findByText('987654321 - Under Digdir'));
const searchResultsButton = screen.getByText('987654321 - Under Digdir');
await act(() => user.click(searchResultsButton));

expect(handleAddMemberMock).toHaveBeenCalled();
});
});

const render = () => {
const allQueries: ServicesContextProps = {
...queriesMock,
getEnheter: jest.fn().mockImplementation(() => Promise.resolve(enhetSearchResult)),
getUnderenheter: jest.fn().mockImplementation(() => Promise.resolve(underenhetSearchResult)),
};

return rtlRender(
<MemoryRouter>
<ServicesContextProvider {...allQueries} client={createQueryClientMock()}>
<PartyListSearch {...defaultProps} />
</ServicesContextProvider>
</MemoryRouter>,
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,10 @@ export const PartyListSearch = ({
return (
<div>
<Textfield
data-testid='enhet-search'
value={searchText}
placeholder='søk etter enhet'
onChange={(event) => {
setSearchText(event.target.value);
}}
onChange={(event) => setSearchText(event.target.value)}
/>
{(isLoadingEnheterSearch || isLoadingUnderenheterSearch) && debouncedSearchText && (
<Spinner size='xlarge' variant='interaction' title='Laster...' />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useQuery, UseQueryResult } from '@tanstack/react-query';
import { useServicesContext } from 'app-shared/contexts/ServicesContext';
import { QueryKey } from 'app-shared/types/QueryKey';
import {
BrregOrganizationResult,
Expand All @@ -16,19 +17,23 @@ const getQueryUrl = (enhetType: string, search: string) => {
export const useEnhetsregisterOrganizationQuery = (
navn: string,
): UseQueryResult<BrregOrganizationResult, AxiosError> => {
const { getEnheter } = useServicesContext();

return useQuery<BrregOrganizationResult, AxiosError>({
queryKey: [QueryKey.EnhetsregisterOrgenhetSearch, navn],
queryFn: () => get(getQueryUrl('enheter', navn)),
queryFn: () => getEnheter(getQueryUrl('enheter', navn)),
enabled: !!navn,
});
};

export const useEnhetsregisterUnderOrganizationQuery = (
navn: string,
): UseQueryResult<BrregUnderOrganizationResult, AxiosError> => {
const { getUnderenheter } = useServicesContext();

return useQuery<BrregUnderOrganizationResult, AxiosError>({
queryKey: [QueryKey.EnhetsregisterUnderenhetSearch, navn],
queryFn: () => get(getQueryUrl('underenheter', navn)),
queryFn: () => getUnderenheter(getQueryUrl('underenheter', navn)),
enabled: !!navn,
});
};
7 changes: 4 additions & 3 deletions frontend/resourceadm/utils/jsonPatchUtils/jsonPatchUtils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
export interface JsonPatch {
op: 'replace' | 'add' | 'remove';
path: string;
value: string | number;
value?: string | number;
}

export const createReplacePatch = <T>(diff: T): JsonPatch[] => {
return Object.keys(diff).map((key) => {
const isRemove = !diff[key];
return {
op: 'replace',
op: isRemove ? 'remove' : 'replace',
path: `/${key}`,
value: diff[key],
...(!isRemove && { value: diff[key] }),
};
});
};

0 comments on commit 81f2f9e

Please sign in to comment.