Skip to content

Commit

Permalink
feat: create useUpdateOptionListIdMutation hook (#14181)
Browse files Browse the repository at this point in the history
  • Loading branch information
standeren authored and nkylstad committed Nov 28, 2024
1 parent 4388abd commit 73d5d71
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,32 @@ describe('objectUtils', () => {
expect(ObjectUtils.flattenObjectValues(object)).toEqual(['value1', 'value2', 'value3']);
});
});

describe('sortEntriesInObjectByKeys', () => {
it('Sorts all entries in an object by its keys', () => {
const unsortedObject = { b: 'value1', a: 'value2', c: 'value3' };
const sortedObject = ObjectUtils.sortEntriesInObjectByKeys(unsortedObject);
const sortedObjectKeys = Object.keys(sortedObject);
expect(sortedObjectKeys[0]).toBe('a');
expect(sortedObjectKeys[1]).toBe('b');
expect(sortedObjectKeys[2]).toBe('c');
expect(sortedObject).toEqual(unsortedObject);
});

it('Returns same order if entries in object is already sorted', () => {
const unsortedObject = { a: 'value1', b: 'value2', c: 'value3' };
const sortedObject = ObjectUtils.sortEntriesInObjectByKeys(unsortedObject);
const sortedObjectKeys = Object.keys(sortedObject);
expect(sortedObjectKeys[0]).toBe('a');
expect(sortedObjectKeys[1]).toBe('b');
expect(sortedObjectKeys[2]).toBe('c');
expect(sortedObject).toEqual(unsortedObject);
});

it('Returns empty list if entries to sort is empty', () => {
const emptyObject = {};
const sortedObject = ObjectUtils.sortEntriesInObjectByKeys(emptyObject);
expect(sortedObject).toEqual({});
});
});
});
10 changes: 10 additions & 0 deletions frontend/libs/studio-pure-functions/src/ObjectUtils/ObjectUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,14 @@ export class ObjectUtils {
})
.flat();
};

/**
* Sorts all entries in an object by its keys.
* @param object The object to sort.
* @returns A new object with the entries sorted by key.
*/
static sortEntriesInObjectByKeys = <T extends object>(object: T): T =>
Object.fromEntries(
Object.entries(object).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)),
) as T;
}
2 changes: 2 additions & 0 deletions frontend/packages/shared/src/api/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
addImagePath,
optionListUploadPath,
optionListUpdatePath,
optionListIdUpdatePath,
processEditorPath,
selectedMaskinportenScopesPath,
} from 'app-shared/api/paths';
Expand Down Expand Up @@ -120,6 +121,7 @@ export const updateAppConfig = (org: string, app: string, payload: AppConfig) =>
export const uploadDataModel = (org: string, app: string, form: FormData) => post<void, FormData>(dataModelsUploadPath(org, app), form, { headers: { 'Content-Type': 'multipart/form-data' } });
export const uploadOptionList = (org: string, app: string, payload: FormData) => post<void, FormData>(optionListUploadPath(org, app), payload, { headers: { 'Content-Type': 'multipart/form-data' } });
export const updateOptionList = (org: string, app: string, optionsListId: string, payload: Option[]) => put<Option[]>(optionListUpdatePath(org, app, optionsListId), payload);
export const updateOptionListId = (org: string, app: string, optionsListId: string, newOptionsListId: string) => put<void, string>(optionListIdUpdatePath(org, app, optionsListId), JSON.stringify(newOptionsListId), { headers: { 'Content-Type': 'application/json' } });
export const upsertTextResources = (org: string, app: string, language: string, payload: ITextResourcesObjectFormat) => put<ITextResourcesObjectFormat>(textResourcesPath(org, app, language), payload);

// Resourceadm
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/shared/src/api/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const widgetSettingsPath = (org, app) => `${basePath}/${org}/${app}/app-d
export const optionListsPath = (org, app) => `${basePath}/${org}/${app}/options/option-lists`; // Get
export const optionListIdsPath = (org, app) => `${basePath}/${org}/${app}/app-development/option-list-ids`; // Get
export const optionListUpdatePath = (org, app, optionsListId) => `${basePath}/${org}/${app}/options/${optionsListId}`; // Put
export const optionListIdUpdatePath = (org, app, optionsListId) => `${basePath}/${org}/${app}/options/change-name/${optionsListId}`; // Put
export const optionListUploadPath = (org, app) => `${basePath}/${org}/${app}/options/upload`; // Post
export const ruleConfigPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/rule-config?${s({ layoutSetName })}`; // Get, Post
export const appMetadataModelIdsPath = (org, app, onlyUnReferenced) => `${basePath}/${org}/${app}/app-development/model-ids?${s({ onlyUnReferenced })}`; // Get
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/shared/src/hooks/mutations/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { useAddOptionListMutation } from './useAddOptionListMutation';
export { useUpdateOptionListMutation } from './useUpdateOptionListMutation';
export { useUpdateOptionListIdMutation } from './useUpdateOptionListIdMutation';
export { useUpsertTextResourcesMutation } from './useUpsertTextResourcesMutation';
export { useUpsertTextResourceMutation } from './useUpsertTextResourceMutation';
export { useRepoCommitAndPushMutation } from './useRepoCommitAndPushMutation';
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { app, org } from '@studio/testing/testids';
import { queriesMock } from 'app-shared/mocks/queriesMock';
import { renderHookWithProviders } from 'app-shared/mocks/renderHookWithProviders';
import type { UpdateOptionListIdMutationArgs } from './useUpdateOptionListIdMutation';
import { useUpdateOptionListIdMutation } from './useUpdateOptionListIdMutation';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { QueryKey } from 'app-shared/types/QueryKey';
import type { Option } from 'app-shared/types/Option';
import type { OptionsLists } from 'app-shared/types/api/OptionsLists';

// Test data:
const optionListId: string = 'optionListId';
const newOptionListId: string = 'newOptionListId';
const optionListMock: Option[] = [{ value: 'value', label: 'label' }];
const args: UpdateOptionListIdMutationArgs = { optionListId, newOptionListId };

describe('useUpdateOptionListIdMutation', () => {
test('Calls useUpdateOptionIdList with correct parameters', async () => {
const queryClient = createQueryClientMock();
queryClient.setQueryData([QueryKey.OptionLists, org, app], [{ optionListId: optionListMock }]);
const renderUpdateOptionListMutationResult = renderHookWithProviders(
() => useUpdateOptionListIdMutation(org, app),
{ queryClient },
).result;
await renderUpdateOptionListMutationResult.current.mutateAsync(args);
expect(queriesMock.updateOptionListId).toHaveBeenCalledTimes(1);
expect(queriesMock.updateOptionListId).toHaveBeenCalledWith(
org,
app,
optionListId,
newOptionListId,
);
});

test('Sets the option lists cache with new id in correct alphabetical order', async () => {
const optionListA = 'optionListA';
const optionListB = 'optionListB';
const optionListC = 'optionListC';
const optionListZ = 'optionListZ';
const queryClient = createQueryClientMock();
const oldData: OptionsLists = {
optionListA: optionListMock,
optionListB: optionListMock,
optionListZ: optionListMock,
};
queryClient.setQueryData([QueryKey.OptionLists, org, app], oldData);
const renderUpdateOptionListMutationResult = renderHookWithProviders(
() => useUpdateOptionListIdMutation(org, app),
{ queryClient },
).result;
await renderUpdateOptionListMutationResult.current.mutateAsync({
optionListId: optionListA,
newOptionListId: optionListC,
});
const cacheData = queryClient.getQueryData([QueryKey.OptionLists, org, app]);
const cacheDataKeys = Object.keys(cacheData);
expect(cacheDataKeys[0]).toEqual(optionListB);
expect(cacheDataKeys[1]).toEqual(optionListC);
expect(cacheDataKeys[2]).toEqual(optionListZ);
});

test('Invalidates the optionListIds query cache', async () => {
const queryClient = createQueryClientMock();
const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries');
const oldData: OptionsLists = {
firstOptionList: optionListMock,
optionListId: optionListMock,
lastOptionList: optionListMock,
};
queryClient.setQueryData([QueryKey.OptionLists, org, app], oldData);
const renderUpdateOptionListMutationResult = renderHookWithProviders(
() => useUpdateOptionListIdMutation(org, app),
{ queryClient },
).result;
await renderUpdateOptionListMutationResult.current.mutateAsync(args);
expect(invalidateQueriesSpy).toHaveBeenCalledTimes(1);
expect(invalidateQueriesSpy).toHaveBeenCalledWith({
queryKey: [QueryKey.OptionListIds, org, app],
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { QueryKey } from 'app-shared/types/QueryKey';
import type { OptionsLists } from 'app-shared/types/api/OptionsLists';
import { useQueryClient, useMutation } from '@tanstack/react-query';
import { useServicesContext } from 'app-shared/contexts/ServicesContext';
import { ObjectUtils } from '@studio/pure-functions';

export interface UpdateOptionListIdMutationArgs {
optionListId: string;
newOptionListId: string;
}

export const useUpdateOptionListIdMutation = (org: string, app: string) => {
const queryClient = useQueryClient();
const { updateOptionListId } = useServicesContext();

return useMutation({
mutationFn: async ({ optionListId, newOptionListId }: UpdateOptionListIdMutationArgs) => {
return updateOptionListId(org, app, optionListId, newOptionListId).then(() => ({
optionListId,
newOptionListId,
}));
},
onSuccess: ({ optionListId, newOptionListId }) => {
const oldData: OptionsLists = queryClient.getQueryData([QueryKey.OptionLists, org, app]);
const ascSortedData = changeIdAndSortCacheData(optionListId, newOptionListId, oldData);
queryClient.setQueryData([QueryKey.OptionLists, org, app], ascSortedData);
queryClient.invalidateQueries({ queryKey: [QueryKey.OptionListIds, org, app] });
},
});
};

const changeIdAndSortCacheData = (
oldId: string,
newId: string,
oldData: OptionsLists,
): OptionsLists => {
const newData = { ...oldData };
delete newData[oldId];
newData[newId] = oldData[oldId];
return ObjectUtils.sortEntriesInObjectByKeys(newData);
};
1 change: 1 addition & 0 deletions frontend/packages/shared/src/mocks/queriesMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ export const queriesMock: ServicesContextProps = {
updateAppMetadata: jest.fn().mockImplementation(() => Promise.resolve()),
updateAppConfig: jest.fn().mockImplementation(() => Promise.resolve()),
updateOptionList: jest.fn().mockImplementation(() => Promise.resolve()),
updateOptionListId: jest.fn().mockImplementation(() => Promise.resolve()),
uploadDataModel: jest.fn().mockImplementation(() => Promise.resolve<JsonSchema>({})),
uploadOptionList: jest.fn().mockImplementation(() => Promise.resolve()),
upsertTextResources: jest
Expand Down

0 comments on commit 73d5d71

Please sign in to comment.