diff --git a/packages/common/src/state/actions/createdOneDamage.ts b/packages/common/src/state/actions/createdOneDamage.ts new file mode 100644 index 000000000..5e8c32014 --- /dev/null +++ b/packages/common/src/state/actions/createdOneDamage.ts @@ -0,0 +1,57 @@ +import { Damage } from '@monkvision/types'; +import { MonkAction, MonkActionType } from './monkAction'; +import { MonkState } from '../state'; + +/** + * The payload of a MonkCreatedOneDamagePayload. + */ +export interface MonkCreatedOneDamagePayload { + /** + * The damage created. + */ + damage: Damage; +} + +/** + * Action dispatched when a vehicle have been updated. + */ +export interface MonkCreatedOneDamageAction extends MonkAction { + /** + * The type of the action : `MonkActionType.CREATED_ONE_DAMAGE`. + */ + type: MonkActionType.CREATED_ONE_DAMAGE; + /** + * The payload of the action containing the fetched entities. + */ + payload: MonkCreatedOneDamagePayload; +} + +/** + * Matcher function that matches a CreatedOneDamage while also inferring its type using TypeScript's type predicate + * feature. + */ +export function isCreatedOneDamageAction(action: MonkAction): action is MonkCreatedOneDamageAction { + return action.type === MonkActionType.CREATED_ONE_DAMAGE; +} + +/** + * Reducer function for a createdOneDamage action. + */ +export function createdOneDamage(state: MonkState, action: MonkCreatedOneDamageAction): MonkState { + const { damages, inspections, parts } = state; + const { payload } = action; + + const inspection = inspections.find((value) => value.id === payload.damage.inspectionId); + if (inspection) { + inspection.damages.push(action.payload.damage.id); + } + const partsRelated = action.payload.damage.parts + .map((part) => parts.find((value) => value.type === part)?.id) + .filter((v) => v !== undefined) as string[]; + damages.push({ ...action.payload.damage, parts: partsRelated }); + return { + ...state, + damages: [...damages], + inspections: [...inspections], + }; +} diff --git a/packages/common/src/state/actions/createdOnePricing.ts b/packages/common/src/state/actions/createdOnePricing.ts index 6e584a723..f9885e732 100644 --- a/packages/common/src/state/actions/createdOnePricing.ts +++ b/packages/common/src/state/actions/createdOnePricing.ts @@ -13,7 +13,7 @@ export interface MonkCreatedOnePricingPayload { } /** - * Action dispatched when a vehicle have been updated. + * Action dispatched when a pricing have been updated. */ export interface MonkCreatedOnePricingAction extends MonkAction { /** diff --git a/packages/common/src/state/actions/deletedOneDamage.ts b/packages/common/src/state/actions/deletedOneDamage.ts new file mode 100644 index 000000000..e7d460120 --- /dev/null +++ b/packages/common/src/state/actions/deletedOneDamage.ts @@ -0,0 +1,57 @@ +import { MonkAction, MonkActionType } from './monkAction'; +import { MonkState } from '../state'; + +/** + * The payload of a MonkDeletedOneDamagePayload. + */ +export interface MonkDeletedOneDamagePayload { + /** + * The ID of the inspection to which the damage was deleted. + */ + inspectionId: string; + /** + * The damage ID deleted. + */ + damageId: string; +} + +/** + * Action dispatched when a vehicle have been updated. + */ +export interface MonkDeletedOneDamageAction extends MonkAction { + /** + * The type of the action : `MonkActionType.DELETED_ONE_DAMAGE`. + */ + type: MonkActionType.DELETED_ONE_DAMAGE; + /** + * The payload of the action containing the fetched entities. + */ + payload: MonkDeletedOneDamagePayload; +} + +/** + * Matcher function that matches a DeletedOneDamage while also inferring its type using TypeScript's type predicate + * feature. + */ +export function isDeletedOneDamageAction(action: MonkAction): action is MonkDeletedOneDamageAction { + return action.type === MonkActionType.DELETED_ONE_DAMAGE; +} + +/** + * Reducer function for a deletedOneDamage action. + */ +export function deletedOneDamage(state: MonkState, action: MonkDeletedOneDamageAction): MonkState { + const { damages, inspections } = state; + const { payload } = action; + + const inspection = inspections.find((value) => value.id === payload.inspectionId); + if (inspection) { + inspection.damages = inspection.damages?.filter((damageId) => damageId !== payload.damageId); + } + const newDamages = damages.filter((damage) => damage.id !== payload.damageId); + return { + ...state, + damages: [...newDamages], + inspections: [...inspections], + }; +} diff --git a/packages/common/src/state/actions/index.ts b/packages/common/src/state/actions/index.ts index 496420e68..3d76a0011 100644 --- a/packages/common/src/state/actions/index.ts +++ b/packages/common/src/state/actions/index.ts @@ -8,3 +8,5 @@ export * from './createdOnePricing'; export * from './deletedOnePricing'; export * from './updatedOnePricing'; export * from './updatedOneInspectionAdditionalData'; +export * from './createdOneDamage'; +export * from './deletedOneDamage'; diff --git a/packages/common/src/state/actions/monkAction.ts b/packages/common/src/state/actions/monkAction.ts index 53ad2625c..4a2917d75 100644 --- a/packages/common/src/state/actions/monkAction.ts +++ b/packages/common/src/state/actions/monkAction.ts @@ -34,6 +34,14 @@ export enum MonkActionType { * A pricing has been deleted. */ DELETED_ONE_PRICING = 'deleted_one_pricing', + /** + * A damage has been uploaded to the API. + */ + CREATED_ONE_DAMAGE = 'created_one_damage', + /** + * A damage has been deleted. + */ + DELETED_ONE_DAMAGE = 'deleted_one_damage', /** * Clear and reset the state. */ diff --git a/packages/common/src/state/actions/updatedOneInspectionAdditionalData.ts b/packages/common/src/state/actions/updatedOneInspectionAdditionalData.ts index a96d30ce4..c1bfa8a27 100644 --- a/packages/common/src/state/actions/updatedOneInspectionAdditionalData.ts +++ b/packages/common/src/state/actions/updatedOneInspectionAdditionalData.ts @@ -7,7 +7,7 @@ import { MonkState } from '../state'; */ export interface MonkUpdatedOneInspectionAdditionalDataPayload { /** - * The ID of the inspection to which the pricing was updated. + * The ID of the inspection to which the additionalData was updated. */ inspectionId: string; /** diff --git a/packages/common/src/state/actions/updatedOnePricing.ts b/packages/common/src/state/actions/updatedOnePricing.ts index 3b6a026c5..ba38a745c 100644 --- a/packages/common/src/state/actions/updatedOnePricing.ts +++ b/packages/common/src/state/actions/updatedOnePricing.ts @@ -7,7 +7,7 @@ import { MonkState } from '../state'; */ export interface MonkUpdatedOnePricingPayload { /** - * The pricing created. + * The pricing updated. */ pricing: PricingV2; } diff --git a/packages/common/src/state/reducer.ts b/packages/common/src/state/reducer.ts index 713b6bd7b..79626683a 100644 --- a/packages/common/src/state/reducer.ts +++ b/packages/common/src/state/reducer.ts @@ -18,6 +18,10 @@ import { updatedOnePricing, updatedOneInspectionAdditionalData, updatedVehicle, + createdOneDamage, + isCreatedOneDamageAction, + deletedOneDamage, + isDeletedOneDamageAction, } from './actions'; import { MonkState } from './state'; @@ -52,5 +56,11 @@ export function monkReducer(state: MonkState, action: MonkAction): MonkState { if (isUpdatedVehicleAction(action)) { return updatedVehicle(state, action); } + if (isCreatedOneDamageAction(action)) { + return createdOneDamage(state, action); + } + if (isDeletedOneDamageAction(action)) { + return deletedOneDamage(state, action); + } return state; } diff --git a/packages/common/test/state/actions/createdOneDamage.test.ts b/packages/common/test/state/actions/createdOneDamage.test.ts new file mode 100644 index 000000000..fef914ec6 --- /dev/null +++ b/packages/common/test/state/actions/createdOneDamage.test.ts @@ -0,0 +1,65 @@ +import { + createEmptyMonkState, + MonkActionType, + createdOneDamage, + isCreatedOneDamageAction, + MonkCreatedOneDamageAction, +} from '../../../src'; +import { Inspection, MonkEntityType, VehiclePart, DamageType, Part } from '@monkvision/types'; + +const action: MonkCreatedOneDamageAction = { + type: MonkActionType.CREATED_ONE_DAMAGE, + payload: { + damage: { + entityType: MonkEntityType.DAMAGE, + id: 'test-id', + inspectionId: 'inspections-test', + parts: [VehiclePart.ROOF], + relatedImages: [], + type: DamageType.SCRATCH, + }, + }, +}; + +describe('CreatedOneDamage action handlers', () => { + describe('Action matcher', () => { + it('should return true if the action has the proper type', () => { + expect(isCreatedOneDamageAction({ type: MonkActionType.CREATED_ONE_DAMAGE })).toBe(true); + }); + + it('should return false if the action does not have the proper type', () => { + expect(isCreatedOneDamageAction({ type: MonkActionType.RESET_STATE })).toBe(false); + }); + }); + + describe('Action handler', () => { + it('should return a new state', () => { + const state = createEmptyMonkState(); + expect(Object.is(createdOneDamage(state, action), state)).toBe(false); + }); + + it('should create damage in the state', () => { + const state = createEmptyMonkState(); + const part = { + id: 'part-id', + type: VehiclePart.ROOF, + }; + state.inspections.push({ + id: 'inspections-test', + damages: [] as string[], + } as Inspection); + state.parts.push(part as Part); + const newState = createdOneDamage(state, action); + const inspectionDamage = newState.inspections.find( + (ins) => ins.id === action.payload.damage.inspectionId, + )?.damages; + + expect(inspectionDamage?.length).toBe(1); + expect(inspectionDamage).toContainEqual(action.payload.damage.id); + expect(newState.damages).toContainEqual({ + ...action.payload.damage, + parts: [part.id], + }); + }); + }); +}); diff --git a/packages/common/test/state/actions/deletedOneDamage.test.ts b/packages/common/test/state/actions/deletedOneDamage.test.ts new file mode 100644 index 000000000..4622774d5 --- /dev/null +++ b/packages/common/test/state/actions/deletedOneDamage.test.ts @@ -0,0 +1,53 @@ +import { + createEmptyMonkState, + MonkActionType, + isDeletedOneDamageAction, + deletedOneDamage, + MonkDeletedOneDamageAction, +} from '../../../src'; +import { Inspection } from '@monkvision/types'; + +const action: MonkDeletedOneDamageAction = { + type: MonkActionType.DELETED_ONE_DAMAGE, + payload: { + inspectionId: 'inspections-test', + damageId: 'pricing-id-test', + }, +}; + +describe('DeletedOneDamage action handlers', () => { + describe('Action matcher', () => { + it('should return true if the action has the proper type', () => { + expect(isDeletedOneDamageAction({ type: MonkActionType.DELETED_ONE_DAMAGE })).toBe(true); + }); + + it('should return false if the action does not have the proper type', () => { + expect(isDeletedOneDamageAction({ type: MonkActionType.RESET_STATE })).toBe(false); + }); + }); + + describe('Action handler', () => { + it('should return a new state', () => { + const state = createEmptyMonkState(); + expect(Object.is(deletedOneDamage(state, action), state)).toBe(false); + }); + + it('should delete damage in the state', () => { + const state = createEmptyMonkState(); + state.inspections.push({ + id: 'inspections-test', + damages: [action.payload.damageId] as string[], + } as Inspection); + const newState = deletedOneDamage(state, action); + const inspectionDamage = newState.inspections.find( + (ins) => ins.id === action.payload.inspectionId, + )?.damages; + + expect(inspectionDamage?.length).toBe(0); + expect(inspectionDamage).not.toContainEqual(action.payload.damageId); + expect( + newState.damages.find((damage) => damage.id === action.payload.damageId), + ).toBeUndefined(); + }); + }); +}); diff --git a/packages/common/test/state/reducer.test.ts b/packages/common/test/state/reducer.test.ts index 57c804fc8..dea33eb7b 100644 --- a/packages/common/test/state/reducer.test.ts +++ b/packages/common/test/state/reducer.test.ts @@ -6,6 +6,8 @@ jest.mock('../../src/state/actions', () => ({ isCreatedOnePricingAction: jest.fn(() => false), isDeletedOnePricingAction: jest.fn(() => false), isUpdatedOnePricingAction: jest.fn(() => false), + isCreatedOneDamageAction: jest.fn(() => false), + isDeletedOneDamageAction: jest.fn(() => false), isUpdatedOneInspectionAdditionalDataAction: jest.fn(() => false), isUpdatedVehicleAction: jest.fn(() => false), createdOneImage: jest.fn(() => null), @@ -15,6 +17,8 @@ jest.mock('../../src/state/actions', () => ({ createdOnePricing: jest.fn(() => null), deletedOnePricing: jest.fn(() => null), updatedOnePricing: jest.fn(() => null), + createdOneDamage: jest.fn(() => null), + deletedOneDamage: jest.fn(() => null), updatedOneInspectionAdditionalData: jest.fn(() => null), updatedVehicle: jest.fn(() => null), })); @@ -25,6 +29,8 @@ import { createdOnePricing, deletedOnePricing, updatedOnePricing, + createdOneDamage, + deletedOneDamage, updatedOneInspectionAdditionalData, updatedVehicle, isCreatedOneImageAction, @@ -34,6 +40,8 @@ import { isCreatedOnePricingAction, isDeletedOnePricingAction, isUpdatedOnePricingAction, + isCreatedOneDamageAction, + isDeletedOneDamageAction, isUpdatedOneInspectionAdditionalDataAction, isUpdatedVehicleAction, MonkAction, @@ -51,6 +59,8 @@ const actions = [ { matcher: isCreatedOnePricingAction, handler: createdOnePricing }, { matcher: isDeletedOnePricingAction, handler: deletedOnePricing }, { matcher: isUpdatedOnePricingAction, handler: updatedOnePricing }, + { matcher: isCreatedOneDamageAction, handler: createdOneDamage }, + { matcher: isDeletedOneDamageAction, handler: deletedOneDamage }, { matcher: isUpdatedOneInspectionAdditionalDataAction, handler: updatedOneInspectionAdditionalData, diff --git a/packages/network/README.md b/packages/network/README.md index 82b9c6b35..ba61a6886 100644 --- a/packages/network/README.md +++ b/packages/network/README.md @@ -165,7 +165,7 @@ import { MonkApi } from '@monkvision/network'; MonkApi.deletePricing(options, apiConfig, dispatch); ``` -Update a pricing of an inspection. +Delete a pricing of an inspection. | Parameter | Type | Description | Required | |-----------|----------------------|-----------------------------|----------| @@ -184,6 +184,31 @@ Update the additional data of an inspection. |-----------|-----------------------------|-----------------------------|----------| | options | UpdateAdditionalDataOptions | The options of the request. | ✔️ | +### createDamage +```typescript +import { MonkApi } from '@monkvision/network'; + +MonkApi.createDamage(options, apiConfig, dispatch); +``` + +Create a new damage of an inspection. + +| Parameter | Type | Description | Required | +|-----------|---------------------|-----------------------------|----------| +| options | CreateDamageOptions | The options of the request. | ✔️ | + +### deleteDamage +```typescript +import { MonkApi } from '@monkvision/network'; + +MonkApi.deleteDamage(options, apiConfig, dispatch); +``` + +Delete a damage of an inspection. + +| Parameter | Type | Description | Required | +|-----------|---------------------|-----------------------------|----------| +| options | DeleteDamageOptions | The options of the request. | ✔️ | # React Tools In order to simply integrate the Monk Api requests into your React app, you can make use of the `useMonkApi` hook. This diff --git a/packages/network/src/api/api.ts b/packages/network/src/api/api.ts index e24edca7a..0ae7b7fdf 100644 --- a/packages/network/src/api/api.ts +++ b/packages/network/src/api/api.ts @@ -4,6 +4,7 @@ import { startInspectionTasks, updateTaskStatus } from './task'; import { getLiveConfig } from './liveConfigs'; import { updateInspectionVehicle } from './vehicle'; import { createPricing, deletePricing, updatePricing } from './pricing'; +import { createDamage, deleteDamage } from './damage'; /** * Object regrouping the different API requests available to communicate with the API using the `@monkvision/network` @@ -21,4 +22,6 @@ export const MonkApi = { createPricing, deletePricing, updatePricing, + createDamage, + deleteDamage, }; diff --git a/packages/network/src/api/damage/index.ts b/packages/network/src/api/damage/index.ts new file mode 100644 index 000000000..c3dff2f3f --- /dev/null +++ b/packages/network/src/api/damage/index.ts @@ -0,0 +1 @@ +export * from './requests'; diff --git a/packages/network/src/api/damage/mappers.ts b/packages/network/src/api/damage/mappers.ts new file mode 100644 index 000000000..69d44f093 --- /dev/null +++ b/packages/network/src/api/damage/mappers.ts @@ -0,0 +1,12 @@ +import { DamageType, VehiclePart } from '@monkvision/types'; +import { ApiDamagePost } from '../models'; + +export function mapApiDamagePostRequest( + damageType: DamageType, + vehiclePart: VehiclePart, +): ApiDamagePost { + return { + damage_type: damageType, + part_type: vehiclePart, + }; +} diff --git a/packages/network/src/api/damage/requests.ts b/packages/network/src/api/damage/requests.ts new file mode 100644 index 000000000..24281252d --- /dev/null +++ b/packages/network/src/api/damage/requests.ts @@ -0,0 +1,116 @@ +import { + MonkActionType, + MonkCreatedOneDamageAction, + MonkDeletedOneDamageAction, +} from '@monkvision/common'; +import { DamageType, MonkEntityType, VehiclePart } from '@monkvision/types'; +import ky from 'ky'; +import { Dispatch } from 'react'; +import { getDefaultOptions, MonkApiConfig } from '../config'; +import { ApiIdColumn } from '../models'; +import { MonkApiResponse } from '../types'; +import { mapApiDamagePostRequest } from './mappers'; + +/** + * Options passed to the `createDamage` API request. + */ +export interface CreateDamageOptions { + /** + * The ID of the inspection to update via the API. + */ + id: string; + /** + * Damage type used for the update operation. + */ + damageType: DamageType; + /** + * Vehicle part used for the update operation. + */ + vehiclePart: VehiclePart; +} + +/** + * Create a new damage with the given options. See the `CreateDamageOptions` interface for more details. + * + * @param options The options of the inspection. + * @param config The API config. + * @param [dispatch] Optional MonkState dispatch function that you can pass if you want this request to handle React + * state management for you. + * @see CreateDamageOptions + */ + +export async function createDamage( + options: CreateDamageOptions, + config: MonkApiConfig, + dispatch?: Dispatch, +): Promise { + const kyOptions = getDefaultOptions(config); + const response = await ky.post(`inspections/${options.id}/damages`, { + ...kyOptions, + json: mapApiDamagePostRequest(options.damageType, options.vehiclePart), + }); + const body = await response.json(); + dispatch?.({ + type: MonkActionType.CREATED_ONE_DAMAGE, + payload: { + damage: { + entityType: MonkEntityType.DAMAGE, + id: body.id, + inspectionId: options.id, + parts: [options.vehiclePart], + relatedImages: [], + type: options.damageType, + }, + }, + }); + return { + id: body.id, + response, + body, + }; +} + +/** + * Options passed to the `deleteDamage` API request. + */ +export interface DeleteDamageOptions { + /** + * The ID of the inspection to update via the API. + */ + id: string; + /** + * Damage ID that will be deleted. + */ + damageId: string; +} + +/** + * Delete a damage with the given options. See the `DeleteDamageOptions` interface for more details. + * + * @param options The options of the inspection. + * @param config The API config. + * @param [dispatch] Optional MonkState dispatch function that you can pass if you want this request to handle React + * state management for you. + * @see DeleteDamageOptions + */ + +export async function deleteDamage( + options: DeleteDamageOptions, + config: MonkApiConfig, + dispatch?: Dispatch, +): Promise { + const kyOptions = getDefaultOptions(config); + const response = await ky.delete(`inspections/${options.id}/damages/${options.damageId}`, { + ...kyOptions, + }); + const body = await response.json(); + dispatch?.({ + type: MonkActionType.DELETED_ONE_DAMAGE, + payload: { inspectionId: options.id, damageId: body.id }, + }); + return { + id: body.id, + response, + body, + }; +} diff --git a/packages/network/src/api/models/damage.ts b/packages/network/src/api/models/damage.ts index 61ab2d52c..04e91b729 100644 --- a/packages/network/src/api/models/damage.ts +++ b/packages/network/src/api/models/damage.ts @@ -1,3 +1,4 @@ +import { DamageType, VehiclePart } from '@monkvision/types'; import type { ApiRelatedImages } from './image'; import type { ApiPartIds } from './part'; @@ -12,3 +13,8 @@ export interface ApiDamage { export type ApiDamages = ApiDamage[]; export type ApiDamageIds = string[]; + +export interface ApiDamagePost { + damage_type: DamageType; + part_type: VehiclePart; +} diff --git a/packages/network/src/api/models/pricingV2.ts b/packages/network/src/api/models/pricingV2.ts index b7fb248b1..f680eb4bd 100644 --- a/packages/network/src/api/models/pricingV2.ts +++ b/packages/network/src/api/models/pricingV2.ts @@ -32,7 +32,7 @@ export interface ApiPricingV2 { total_price?: number; } -export interface ApiPricingPost { +export interface ApiPricingPostPatch { pricing: number; related_item_type: PricingV2RelatedItemType; part_type: VehiclePart | undefined; diff --git a/packages/network/src/api/pricing/mappers.ts b/packages/network/src/api/pricing/mappers.ts index f2a48ce7e..44dbc78f2 100644 --- a/packages/network/src/api/pricing/mappers.ts +++ b/packages/network/src/api/pricing/mappers.ts @@ -5,7 +5,7 @@ import { RepairOperationType, VehiclePart, } from '@monkvision/types'; -import { ApiPricingPost, ApiPricingV2Details } from '../models'; +import { ApiPricingPostPatch, ApiPricingV2Details } from '../models'; import { PricingOptions } from './types'; export function mapApiPricingPost(inspectionId: string, response: ApiPricingV2Details): PricingV2 { @@ -21,7 +21,7 @@ export function mapApiPricingPost(inspectionId: string, response: ApiPricingV2De }; } -export function mapApiPricingPostRequest(options: PricingOptions): ApiPricingPost { +export function mapApiPricingPostRequest(options: PricingOptions): ApiPricingPostPatch { return { pricing: options.pricing >= 0 ? options.pricing : 0, related_item_type: options.type, diff --git a/packages/network/src/api/react.ts b/packages/network/src/api/react.ts index a8bc474a3..5f90167c2 100644 --- a/packages/network/src/api/react.ts +++ b/packages/network/src/api/react.ts @@ -138,5 +138,19 @@ export function useMonkApi(config: MonkApiConfig) { * @see UpdatePricingOptions */ updatePricing: reactify(MonkApi.updatePricing, config, dispatch, handleError), + /** + * Create a new damage with the given options. See the `CreateDamageOptions` interface for more details. + * + * @param options The options of the inspection. + * @see CreateDamageOptions + */ + createDamage: reactify(MonkApi.createDamage, config, dispatch, handleError), + /** + * Delete a damage with the given options. See the `DeleteDamageOptions` interface for more details. + * + * @param options The options of the inspection. + * @see DeleteDamageOptions + */ + deleteDamage: reactify(MonkApi.deleteDamage, config, dispatch, handleError), }; } diff --git a/packages/network/test/api/damage/mappers.test.ts b/packages/network/test/api/damage/mappers.test.ts new file mode 100644 index 000000000..807d54ad3 --- /dev/null +++ b/packages/network/test/api/damage/mappers.test.ts @@ -0,0 +1,25 @@ +import { DamageType, VehiclePart } from '@monkvision/types'; +import { mapApiDamagePostRequest } from '../../../src/api/damage/mappers'; + +function createApiDamagePost() { + return { + damageType: DamageType.RUSTINESS, + vehiclePart: VehiclePart.ROCKER_PANEL, + }; +} + +describe('Damage API Mappers', () => { + describe('ApiDamagePost mapper', () => { + it('should properly map the ApiDamagePost object', () => { + const apiDamagePostData = createApiDamagePost(); + const result = mapApiDamagePostRequest( + apiDamagePostData.damageType, + apiDamagePostData.vehiclePart, + ); + expect(result).toEqual({ + damage_type: apiDamagePostData.damageType, + part_type: apiDamagePostData.vehiclePart, + }); + }); + }); +}); diff --git a/packages/network/test/api/damage/requests.test.ts b/packages/network/test/api/damage/requests.test.ts new file mode 100644 index 000000000..0a1ac9592 --- /dev/null +++ b/packages/network/test/api/damage/requests.test.ts @@ -0,0 +1,110 @@ +import { DamageType, MonkEntityType, VehiclePart } from '@monkvision/types'; +import { deleteDamage, CreateDamageOptions, createDamage } from '../../../src/api/damage'; +import { MonkActionType } from '@monkvision/common'; + +function createDamageMock(): CreateDamageOptions { + return { + id: 'test-id', + vehiclePart: VehiclePart.ROOF, + damageType: DamageType.SCRATCH, + }; +} + +jest.mock('../../../src/api/config', () => ({ + getDefaultOptions: jest.fn(() => ({ prefixUrl: 'getDefaultOptionsTest' })), +})); +jest.mock('../../../src/api/damage/mappers', () => ({ + mapApiDamagePost: jest.fn(() => ({ test: 'hello' })), + mapApiDamagePostRequest: jest.fn(() => createDamageMock()), +})); +jest.mock('ky', () => ({ + post: jest.fn(() => Promise.resolve({ json: jest.fn(() => Promise.resolve({ id: 'test-id' })) })), + delete: jest.fn(() => + Promise.resolve({ json: jest.fn(() => Promise.resolve({ id: 'delete-test-fake-id' })) }), + ), + patch: jest.fn(() => + Promise.resolve({ json: jest.fn(() => Promise.resolve({ id: 'patch-test-fake-id' })) }), + ), +})); + +import ky from 'ky'; +import { getDefaultOptions } from '../../../src/api/config'; +import { mapApiDamagePostRequest } from '../../../src/api/damage/mappers'; + +const apiConfig = { + apiDomain: 'apiDomain', + authToken: 'authToken', + thumbnailDomain: 'thumbnailDomain', +}; + +describe('Damage requests', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('createDamage request', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('should make the proper API call and map the resulting response', async () => { + const dispatch = jest.fn(); + const damage = createDamageMock(); + const result = await createDamage(damage, apiConfig, dispatch); + const response = await (ky.post as jest.Mock).mock.results[0].value; + const body = await response.json(); + + const apiDamage = (mapApiDamagePostRequest as jest.Mock).mock.results[0].value; + expect(mapApiDamagePostRequest).toHaveBeenCalledWith(damage.damageType, damage.vehiclePart); + expect(getDefaultOptions).toHaveBeenCalledWith(apiConfig); + const kyOptions = getDefaultOptions(apiConfig); + expect(ky.post).toHaveBeenCalledWith(`inspections/${damage.id}/damages`, { + ...kyOptions, + json: apiDamage, + }); + expect(dispatch).toHaveBeenCalledWith({ + type: MonkActionType.CREATED_ONE_DAMAGE, + payload: { + damage: { + entityType: MonkEntityType.DAMAGE, + id: body.id, + inspectionId: damage.id, + parts: [damage.vehiclePart], + relatedImages: [], + type: damage.damageType, + }, + }, + }); + expect(result).toEqual({ + id: body.id, + response, + body, + }); + }); + }); + + describe('deleteDamage request', () => { + it('should make the proper API call and map the resulting response', async () => { + const id = 'test-inspection-id'; + const damageId = 'test-damage-id'; + const dispatch = jest.fn(); + const result = await deleteDamage({ id, damageId }, apiConfig, dispatch); + const response = await (ky.delete as jest.Mock).mock.results[0].value; + const body = await response.json(); + + expect(getDefaultOptions).toHaveBeenCalledWith(apiConfig); + const kyOptions = getDefaultOptions(apiConfig); + expect(ky.delete).toHaveBeenCalledWith(`inspections/${id}/damages/${damageId}`, { + ...kyOptions, + }); + expect(dispatch).toHaveBeenCalledWith({ + type: MonkActionType.DELETED_ONE_DAMAGE, + payload: { inspectionId: id, damageId: body.id }, + }); + expect(result).toEqual({ + id: body.id, + response, + body, + }); + }); + }); +}); diff --git a/packages/network/test/api/react.test.ts b/packages/network/test/api/react.test.ts index 064b76a86..8cbc7356a 100644 --- a/packages/network/test/api/react.test.ts +++ b/packages/network/test/api/react.test.ts @@ -5,6 +5,8 @@ jest.mock('../../src/api/api', () => ({ createPricing: jest.fn(() => Promise.resolve({ test: 'createPricing' })), deletePricing: jest.fn(() => Promise.resolve({ test: 'deletePricing' })), updatePricing: jest.fn(() => Promise.resolve({ test: 'updatePricing' })), + createDamage: jest.fn(() => Promise.resolve({ test: 'createDamage' })), + deleteDamage: jest.fn(() => Promise.resolve({ test: 'deleteDamage' })), }, })); @@ -77,6 +79,23 @@ describe('Monk API React utilities', () => { requestResultMock = await requestMock.mock.results[0].value; expect(resultMock).toBe(requestResultMock); + dispatchMock.mockClear(); + + param = 'test-createDamage'; + resultMock = await (result.current.createDamage as any)(param); + requestMock = MonkApi.createDamage as jest.Mock; + expect(requestMock).toHaveBeenCalledWith(param, config, dispatchMock); + requestResultMock = await requestMock.mock.results[0].value; + expect(resultMock).toBe(requestResultMock); + + dispatchMock.mockClear(); + + param = 'test-deleteDamage'; + resultMock = await (result.current.deleteDamage as any)(param); + requestMock = MonkApi.deleteDamage as jest.Mock; + expect(requestMock).toHaveBeenCalledWith(param, config, dispatchMock); + requestResultMock = await requestMock.mock.results[0].value; + expect(resultMock).toBe(requestResultMock); unmount(); });