Skip to content

Commit e8c1f0d

Browse files
author
Gabriel Tira
committed
Implemented deleteImage API, state and tests
Reset state objects containing the deleted image Updated objects to be deleted in State Updated API export and added more tests Added docs
1 parent ce18560 commit e8c1f0d

File tree

13 files changed

+369
-29
lines changed

13 files changed

+369
-29
lines changed

configs/test-utils/src/__mocks__/ky.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ export = {
2828
get: createMockKyRequestFn(),
2929
post: createMockKyRequestFn(),
3030
patch: createMockKyRequestFn(),
31+
delete: createMockKyRequestFn(),
3132
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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, damages, parts, renderedOutputs, views } = 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 deletedImage = images.find((image) => image.id === payload.imageId);
49+
const newImages = images.filter((image) => image.id !== payload.imageId);
50+
const newDamages = damages.map((damage) => ({
51+
...damage,
52+
relatedImages: damage.relatedImages.filter((imageId) => imageId !== payload.imageId),
53+
}));
54+
const newParts = parts.map((part) => ({
55+
...part,
56+
relatedImages: part.relatedImages.filter((imageId) => imageId !== payload.imageId),
57+
}));
58+
const newViews = views.map((view) => ({
59+
...view,
60+
renderedOutputs: view.renderedOutputs.filter(
61+
(outputId) => !deletedImage?.renderedOutputs.includes(outputId),
62+
),
63+
}));
64+
const newRenderedOutputs = renderedOutputs.filter(
65+
(output) => !deletedImage?.renderedOutputs.includes(output.id),
66+
);
67+
68+
return {
69+
...state,
70+
images: [...newImages],
71+
inspections: [...inspections],
72+
damages: [...newDamages],
73+
parts: [...newParts],
74+
renderedOutputs: [...newRenderedOutputs],
75+
views: [...newViews],
76+
};
77+
}

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: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { Damage, Image, Inspection, Part, RenderedOutput, View } 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+
const outputId = 'rendered-output-id-test';
38+
const damageId = 'damage-id-test';
39+
const partId = 'part-id-test';
40+
const viewId = 'view-id-test';
41+
state.inspections.push({
42+
id: action.payload.inspectionId,
43+
images: [action.payload.imageId] as string[],
44+
} as Inspection);
45+
state.images.push({
46+
id: action.payload.imageId,
47+
inspectionId: action.payload.inspectionId,
48+
views: [viewId],
49+
renderedOutputs: [outputId],
50+
} as Image);
51+
state.damages.push({
52+
id: damageId,
53+
relatedImages: [action.payload.imageId],
54+
inspectionId: action.payload.inspectionId,
55+
} as Damage);
56+
state.parts.push({
57+
id: partId,
58+
relatedImages: [action.payload.imageId],
59+
inspectionId: action.payload.inspectionId,
60+
} as Part);
61+
state.renderedOutputs.push({
62+
id: outputId,
63+
baseImageId: action.payload.imageId,
64+
} as RenderedOutput);
65+
state.views.push({
66+
id: viewId,
67+
renderedOutputs: [outputId],
68+
} as View);
69+
const newState = deletedOneImage(state, action);
70+
const inspectionImages = newState.inspections.find(
71+
(ins) => ins.id === action.payload.inspectionId,
72+
)?.images;
73+
74+
expect(inspectionImages?.length).toBe(0);
75+
expect(inspectionImages).not.toContainEqual(action.payload.imageId);
76+
expect(newState.images.length).toBe(0);
77+
expect(newState.damages.length).toBe(1);
78+
expect(newState.parts.length).toBe(1);
79+
expect(newState.views.length).toBe(1);
80+
expect(
81+
newState.damages.find((damage) => damage.relatedImages.includes(action.payload.imageId)),
82+
).toBeUndefined();
83+
expect(
84+
newState.parts.find((part) => part.relatedImages.includes(action.payload.imageId)),
85+
).toBeUndefined();
86+
expect(newState.renderedOutputs.length).toBe(0);
87+
expect(
88+
newState.renderedOutputs.find((output) => output.baseImageId === action.payload.imageId),
89+
).toBeUndefined();
90+
expect(
91+
newState.views.find((view) =>
92+
view.renderedOutputs.find((id) => id === action.payload.imageId),
93+
),
94+
).toBeUndefined();
95+
});
96+
});
97+
});

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 },

0 commit comments

Comments
 (0)