Skip to content

Commit 3ab10ff

Browse files
author
Gabriel Tira
committed
Implemented deleteImage API, state and tests
1 parent cee21f5 commit 3ab10ff

File tree

7 files changed

+174
-1
lines changed

7 files changed

+174
-1
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { MonkState } from '../state';
2+
import { MonkAction, MonkActionType } from './monkAction';
3+
4+
export interface MonkDeletedOneImagePayload {
5+
/**
6+
* The ID of the inspection to which the image was deleted.
7+
*/
8+
inspectionId: string;
9+
/**
10+
* The image ID deleted.
11+
*/
12+
imageId: string;
13+
}
14+
15+
/**
16+
* Action dispatched when an image have been deleted.
17+
*/
18+
export interface MonkDeletedOneImageAction extends MonkAction {
19+
/**
20+
* The type of the action : `MonkActionType.DELETED_ONE_IMAGE`.
21+
*/
22+
type: MonkActionType.DELETED_ONE_IMAGE;
23+
/**
24+
* The payload of the action containing the fetched entities.
25+
*/
26+
payload: MonkDeletedOneImagePayload;
27+
}
28+
29+
/**
30+
* Matcher function that matches a DeletedOneImage while also inferring its type using TypeScript's type predicate
31+
* feature.
32+
*/
33+
export function isDeletedOneImageAction(action: MonkAction): action is MonkDeletedOneImageAction {
34+
return action.type === MonkActionType.DELETED_ONE_IMAGE;
35+
}
36+
37+
/**
38+
* Reducer function for a deletedOneImage action.
39+
*/
40+
export function deletedOneImage(state: MonkState, action: MonkDeletedOneImageAction): MonkState {
41+
const { images, inspections } = state;
42+
const { payload } = action;
43+
44+
const inspection = inspections.find((value) => value.id === payload.inspectionId);
45+
if (inspection) {
46+
inspection.images = inspection.images?.filter((imageId) => imageId !== payload.imageId);
47+
}
48+
const newImages = images.filter((image) => image.id !== payload.imageId);
49+
return {
50+
...state,
51+
images: [...newImages],
52+
inspections: [...inspections],
53+
};
54+
}

packages/common/src/state/actions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './monkAction';
22
export * from './resetState';
33
export * from './gotOneInspection';
44
export * from './createdOneImage';
5+
export * from './deletedOneImage';
56
export * from './updatedManyTasks';
67
export * from './updatedVehicle';
78
export * from './createdOnePricing';

packages/common/src/state/actions/monkAction.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export enum MonkActionType {
1414
* An image has been uploaded to the API.
1515
*/
1616
CREATED_ONE_IMAGE = 'created_one_image',
17+
/**
18+
* An image has been deleted.
19+
*/
20+
DELETED_ONE_IMAGE = 'deleted_one_image',
1721
/**
1822
* One or more tasks have been updated.
1923
*/

packages/common/src/state/reducer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
isDeletedOneDamageAction,
2525
isGotOneInspectionPdfAction,
2626
gotOneInspectionPdf,
27+
deletedOneImage,
28+
isDeletedOneImageAction,
2729
} from './actions';
2830
import { MonkState } from './state';
2931

@@ -43,6 +45,9 @@ export function monkReducer(state: MonkState, action: MonkAction): MonkState {
4345
if (isCreatedOneImageAction(action)) {
4446
return createdOneImage(state, action);
4547
}
48+
if (isDeletedOneImageAction(action)) {
49+
return deletedOneImage(state, action);
50+
}
4651
if (isUpdatedManyTasksAction(action)) {
4752
return updatedManyTasks(state, action);
4853
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Image, Inspection } from '@monkvision/types';
2+
import {
3+
createEmptyMonkState,
4+
deletedOneImage,
5+
isDeletedOneImageAction,
6+
MonkActionType,
7+
MonkDeletedOneImageAction,
8+
} from '../../../src';
9+
10+
const action: MonkDeletedOneImageAction = {
11+
type: MonkActionType.DELETED_ONE_IMAGE,
12+
payload: {
13+
inspectionId: 'inspections-test',
14+
imageId: 'image-id-test',
15+
},
16+
};
17+
18+
describe('DeletedOneImage action handlers', () => {
19+
describe('Action matcher', () => {
20+
it('should return true if the action has the proper type', () => {
21+
expect(isDeletedOneImageAction({ type: MonkActionType.DELETED_ONE_IMAGE })).toBe(true);
22+
});
23+
24+
it('should return false if the action does not have the proper type', () => {
25+
expect(isDeletedOneImageAction({ type: MonkActionType.RESET_STATE })).toBe(false);
26+
});
27+
});
28+
29+
describe('Action handler', () => {
30+
it('should return a new state', () => {
31+
const state = createEmptyMonkState();
32+
expect(Object.is(deletedOneImage(state, action), state)).toBe(false);
33+
});
34+
35+
it('should delete image in the state', () => {
36+
const state = createEmptyMonkState();
37+
state.inspections.push({
38+
id: action.payload.inspectionId,
39+
images: [action.payload.imageId] as string[],
40+
} as Inspection);
41+
state.images.push({
42+
id: action.payload.imageId,
43+
inspectionId: action.payload.inspectionId,
44+
} as Image);
45+
const newState = deletedOneImage(state, action);
46+
const inspectionImages = newState.inspections.find(
47+
(ins) => ins.id === action.payload.inspectionId,
48+
)?.images;
49+
50+
expect(inspectionImages?.length).toBe(0);
51+
expect(inspectionImages).not.toContainEqual(action.payload.imageId);
52+
expect(newState.images.length).toBe(0);
53+
expect(newState.images.find((image) => image.id === action.payload.imageId)).toBeUndefined();
54+
});
55+
});
56+
});

packages/common/test/state/reducer.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
jest.mock('../../src/state/actions', () => ({
22
isCreatedOneImageAction: jest.fn(() => false),
3+
isDeletedOneImageAction: jest.fn(() => false),
34
isGotOneInspectionAction: jest.fn(() => false),
45
isResetStateAction: jest.fn(() => false),
56
isUpdatedManyTasksAction: jest.fn(() => false),
@@ -12,6 +13,7 @@ jest.mock('../../src/state/actions', () => ({
1213
isUpdatedVehicleAction: jest.fn(() => false),
1314
isGotOneInspectionPdfAction: jest.fn(() => false),
1415
createdOneImage: jest.fn(() => null),
16+
deletedOneImage: jest.fn(() => null),
1517
gotOneInspection: jest.fn(() => null),
1618
resetState: jest.fn(() => null),
1719
updatedManyTasks: jest.fn(() => null),
@@ -27,6 +29,7 @@ jest.mock('../../src/state/actions', () => ({
2729

2830
import {
2931
createdOneImage,
32+
deletedOneImage,
3033
gotOneInspection,
3134
createdOnePricing,
3235
deletedOnePricing,
@@ -37,6 +40,7 @@ import {
3740
updatedVehicle,
3841
gotOneInspectionPdf,
3942
isCreatedOneImageAction,
43+
isDeletedOneImageAction,
4044
isGotOneInspectionAction,
4145
isResetStateAction,
4246
isUpdatedManyTasksAction,
@@ -59,6 +63,7 @@ const actions = [
5963
{ matcher: isResetStateAction, handler: resetState, noParams: true },
6064
{ matcher: isGotOneInspectionAction, handler: gotOneInspection },
6165
{ matcher: isCreatedOneImageAction, handler: createdOneImage },
66+
{ matcher: isDeletedOneImageAction, handler: deletedOneImage },
6267
{ matcher: isUpdatedManyTasksAction, handler: updatedManyTasks },
6368
{ matcher: isCreatedOnePricingAction, handler: createdOnePricing },
6469
{ matcher: isDeletedOnePricingAction, handler: deletedOnePricing },

packages/network/src/api/image/requests.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
MonkActionType,
66
MonkCreatedOneImageAction,
77
vehiclePartLabels,
8+
MonkDeletedOneImageAction,
89
} from '@monkvision/common';
910
import {
1011
ComplianceOptions,
@@ -22,7 +23,13 @@ import {
2223
import { v4 } from 'uuid';
2324
import { labels, sights } from '@monkvision/sights';
2425
import { getDefaultOptions, MonkApiConfig } from '../config';
25-
import { ApiCenterOnElement, ApiImage, ApiImagePost, ApiImagePostTask } from '../models';
26+
import {
27+
ApiCenterOnElement,
28+
ApiIdColumn,
29+
ApiImage,
30+
ApiImagePost,
31+
ApiImagePostTask,
32+
} from '../models';
2633
import { MonkApiResponse } from '../types';
2734
import { mapApiImage } from './mappers';
2835

@@ -593,3 +600,44 @@ export async function addImage(
593600
throw err;
594601
}
595602
}
603+
604+
interface DeleteImageOptions {
605+
/**
606+
* The ID of the inspection to update via the API.
607+
*/
608+
id: string;
609+
/**
610+
* Image ID that will be deleted.
611+
*/
612+
imageId: string;
613+
}
614+
615+
export async function deleteImage(
616+
options: DeleteImageOptions,
617+
config: MonkApiConfig,
618+
dispatch?: Dispatch<MonkDeletedOneImageAction>,
619+
): Promise<MonkApiResponse> {
620+
const kyOptions = getDefaultOptions(config);
621+
try {
622+
const response = await ky.delete(
623+
`inspections/${options.id}/images/${options.imageId}`,
624+
kyOptions,
625+
);
626+
const body = await response.json<ApiIdColumn>();
627+
dispatch?.({
628+
type: MonkActionType.DELETED_ONE_IMAGE,
629+
payload: {
630+
inspectionId: options.id,
631+
imageId: options.imageId,
632+
},
633+
});
634+
return {
635+
id: body.id,
636+
response,
637+
body,
638+
};
639+
} catch (err) {
640+
console.error(`Failed to delete image: ${(err as Error).message}`);
641+
throw err;
642+
}
643+
}

0 commit comments

Comments
 (0)