diff --git a/src/api/chat.ts b/src/api/chat.ts index 583e4eb1..5380be80 100644 --- a/src/api/chat.ts +++ b/src/api/chat.ts @@ -9,6 +9,7 @@ import configureAxios, { verifyAuthentication, } from './axios'; import { + buildClearItemChatRoute, buildDeleteItemChatMessageRoute, buildGetItemChatRoute, buildGetPublicItemChatRoute, @@ -62,3 +63,11 @@ export const deleteItemChatMessage = async ( ) .then(({ data }) => data), ); + +export const clearItemChat = async (id: UUID, { API_HOST }: QueryClientConfig, +) => + verifyAuthentication(() => + axios + .delete(`${API_HOST}/${buildClearItemChatRoute(id)}`) + .then(({ data }) => data), + ); diff --git a/src/api/routes.ts b/src/api/routes.ts index 40043bf4..b29dfb52 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -72,6 +72,8 @@ export const buildDeleteItemChatMessageRoute = ( chatId: UUID, messageId: UUID, ) => `${ITEMS_ROUTE}/${chatId}/chat/${messageId}`; +export const buildClearItemChatRoute = (id: UUID) => + `${ITEMS_ROUTE}/${id}/chat`; export const buildGetMemberBy = (email: string) => `${MEMBERS_ROUTE}/search?email=${email.toLowerCase()}`; @@ -296,6 +298,7 @@ export const API_ROUTES = { buildPostItemChatMessageRoute, buildPatchItemChatMessageRoute, buildDeleteItemChatMessageRoute, + buildClearItemChatRoute, buildRecycleItemRoute, buildRecycleItemsRoute, buildGetPublicItemsWithTag, diff --git a/src/config/keys.ts b/src/config/keys.ts index 0443e14d..db1b35f5 100644 --- a/src/config/keys.ts +++ b/src/config/keys.ts @@ -184,6 +184,7 @@ export const MUTATION_KEYS = { POST_ITEM_CHAT_MESSAGE: 'postChatMessage', PATCH_ITEM_CHAT_MESSAGE: 'patchChatMessage', DELETE_ITEM_CHAT_MESSAGE: 'deleteChatMessage', + CLEAR_ITEM_CHAT: 'clearItemChat', RECYCLE_ITEM: 'recycleItem', RECYCLE_ITEMS: 'recycleItems', RESTORE_ITEMS: 'restoreItems', diff --git a/src/mutations/chat.test.ts b/src/mutations/chat.test.ts index a77c48a0..6990c9c9 100644 --- a/src/mutations/chat.test.ts +++ b/src/mutations/chat.test.ts @@ -4,6 +4,7 @@ import nock from 'nock'; import Cookies from 'js-cookie'; import { StatusCodes } from 'http-status-codes'; import { + buildClearItemChatRoute, buildDeleteItemChatMessageRoute, buildPatchItemChatMessageRoute, buildPostItemChatMessageRoute, @@ -19,6 +20,7 @@ import { import { buildItemChatKey, MUTATION_KEYS } from '../config/keys'; import { REQUEST_METHODS } from '../api/utils'; import { + clearItemChatRoutine, deleteItemChatMessageRoutine, patchItemChatMessageRoutine, postItemChatMessageRoutine, @@ -228,6 +230,65 @@ describe('Chat Mutations', () => { ); }); }); + + describe(MUTATION_KEYS.CLEAR_ITEM_CHAT, () => { + const route = `/${buildClearItemChatRoute(itemId)}`; + const mutation = () => useMutation(MUTATION_KEYS.CLEAR_ITEM_CHAT); + + it(`Clear chat`, async () => { + const endpoints = [ + { route, response: OK_RESPONSE, method: REQUEST_METHODS.DELETE }, + ]; + // set random data in cache + queryClient.setQueryData(chatKey, ITEM_CHAT); + + const mockedMutation = await mockMutation({ + endpoints, + mutation, + wrapper, + }); + + await act(async () => { + await mockedMutation.mutate(chatId); + await waitForMutation(); + }); + + // verify cache keys + expect(queryClient.getQueryState(chatKey)?.isInvalidated).toBeTruthy(); + }); + + it(`Unauthorized`, async () => { + const endpoints = [ + { + route, + response: UNAUTHORIZED_RESPONSE, + method: REQUEST_METHODS.DELETE, + statusCode: StatusCodes.UNAUTHORIZED, + }, + ]; + // set random data in cache + queryClient.setQueryData(chatKey, ITEM_CHAT); + + const mockedMutation = await mockMutation({ + endpoints, + mutation, + wrapper, + }); + + await act(async () => { + await mockedMutation.mutate(chatId); + await waitForMutation(); + }); + + // verify cache keys + expect(queryClient.getQueryState(chatKey)?.isInvalidated).toBeTruthy(); + expect(mockedNotifier).toHaveBeenCalledWith( + expect.objectContaining({ + type: clearItemChatRoutine.FAILURE, + }), + ); + }); + }); }); describe('enableWebsockets = true', () => { @@ -265,5 +326,83 @@ describe('Chat Mutations', () => { expect(queryClient.getQueryState(chatKey)?.isInvalidated).toBeFalsy(); }); }); + + describe(MUTATION_KEYS.PATCH_ITEM_CHAT_MESSAGE, () => { + const route = `/${buildPatchItemChatMessageRoute(itemId, messageId)}`; + const mutation = () => useMutation(MUTATION_KEYS.PATCH_ITEM_CHAT_MESSAGE); + it(`Patch item chat message`, async () => { + const endpoints = [ + { route, response: OK_RESPONSE, method: REQUEST_METHODS.PATCH }, + ]; + // set random data in cache + queryClient.setQueryData(chatKey, ITEM_CHAT); + + const mockedMutation = await mockMutation({ + endpoints, + mutation, + wrapper, + }); + + await act(async () => { + await mockedMutation.mutate({ chatId, body: 'new message content' }); + await waitForMutation(); + }); + + // verify cache keys + expect(queryClient.getQueryState(chatKey)?.isInvalidated).toBeFalsy(); + }); + }); + + describe(MUTATION_KEYS.DELETE_ITEM_CHAT_MESSAGE, () => { + const route = `/${buildDeleteItemChatMessageRoute(itemId, messageId)}`; + const mutation = () => useMutation(MUTATION_KEYS.DELETE_ITEM_CHAT_MESSAGE); + it(`Delete item chat message`, async () => { + const endpoints = [ + { route, response: OK_RESPONSE, method: REQUEST_METHODS.DELETE }, + ]; + // set random data in cache + queryClient.setQueryData(chatKey, ITEM_CHAT); + + const mockedMutation = await mockMutation({ + endpoints, + mutation, + wrapper, + }); + + await act(async () => { + await mockedMutation.mutate({ chatId, body: 'message to remove' }); + await waitForMutation(); + }); + + // verify cache keys + expect(queryClient.getQueryState(chatKey)?.isInvalidated).toBeFalsy(); + }); + }); + + describe(MUTATION_KEYS.CLEAR_ITEM_CHAT, () => { + const route = `/${buildClearItemChatRoute(itemId)}`; + const mutation = () => useMutation(MUTATION_KEYS.CLEAR_ITEM_CHAT); + it(`Clear chat`, async () => { + const endpoints = [ + { route, response: OK_RESPONSE, method: REQUEST_METHODS.DELETE }, + ]; + // set random data in cache + queryClient.setQueryData(chatKey, ITEM_CHAT); + + const mockedMutation = await mockMutation({ + endpoints, + mutation, + wrapper, + }); + + await act(async () => { + await mockedMutation.mutate(itemId); + await waitForMutation(); + }); + + // verify cache keys + expect(queryClient.getQueryState(chatKey)?.isInvalidated).toBeFalsy(); + }); + }); }); }); diff --git a/src/mutations/chat.ts b/src/mutations/chat.ts index f2eecb61..a4281e70 100644 --- a/src/mutations/chat.ts +++ b/src/mutations/chat.ts @@ -2,6 +2,7 @@ import { QueryClient } from 'react-query'; import * as Api from '../api'; import { buildItemChatKey, MUTATION_KEYS } from '../config/keys'; import { + clearItemChatRoutine, deleteItemChatMessageRoutine, patchItemChatMessageRoutine, postItemChatMessageRoutine, @@ -12,6 +13,7 @@ const { POST_ITEM_CHAT_MESSAGE, PATCH_ITEM_CHAT_MESSAGE, DELETE_ITEM_CHAT_MESSAGE, + CLEAR_ITEM_CHAT, } = MUTATION_KEYS; export default (queryClient: QueryClient, queryConfig: QueryClientConfig) => { @@ -65,4 +67,21 @@ export default (queryClient: QueryClient, queryConfig: QueryClientConfig) => { } }, }); + + queryClient.setMutationDefaults(CLEAR_ITEM_CHAT, { + mutationFn: (chatId) => Api.clearItemChat(chatId, queryConfig), + onError: (error) => { + queryConfig.notifier?.({ + type: clearItemChatRoutine.FAILURE, + payload: { error }, + }); + }, + onSettled: (_data, _error, chatId) => { + // invalidate keys only if websockets are disabled + // otherwise the cache is updated automatically + if (!queryConfig.enableWebsocket) { + queryClient.invalidateQueries(buildItemChatKey(chatId)); + } + }, + }); }; diff --git a/src/routines/chat.ts b/src/routines/chat.ts index cb724cd8..ba22c447 100644 --- a/src/routines/chat.ts +++ b/src/routines/chat.ts @@ -12,3 +12,7 @@ export const patchItemChatMessageRoutine = createRoutine( export const deleteItemChatMessageRoutine = createRoutine( 'DELETE_ITEM_CHAT_MESSAGE', ); + +export const clearItemChatRoutine = createRoutine( + 'CLEAR_ITEM_CHAT', +); diff --git a/src/ws/constants.ts b/src/ws/constants.ts index bb2146ea..e093df64 100644 --- a/src/ws/constants.ts +++ b/src/ws/constants.ts @@ -38,6 +38,7 @@ export const OPS = { PUBLISH: 'publish', UPDATE: 'update', DELETE: 'delete', + CLEAR: 'clear', CREATE: 'create', }; diff --git a/src/ws/hooks/chat.ts b/src/ws/hooks/chat.ts index 32a99700..50df3b92 100644 --- a/src/ws/hooks/chat.ts +++ b/src/ws/hooks/chat.ts @@ -67,6 +67,11 @@ export const configureWsChatHooks = ( queryClient.setQueryData(chatKey, mutation); break; } + case OPS.CLEAR: { + const mutation = current.update('messages', () => []); + queryClient.setQueryData(chatKey, mutation); + break; + } default: break; }