generated from graasp/graasp-repo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add mutations and hooks for membership request (#899)
* feat: add mutations and hooks for membership request * refactor: invalidate keys on mutate * refactor: add enabled for get requests * refactor: allow enabled option on invitation hook * feat: add enroll mutation * feat: import enroll * fix: enroll * test: add tests * refactor: update translations
- Loading branch information
Showing
22 changed files
with
677 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { DiscriminatedItem } from '@graasp/sdk'; | ||
|
||
import { verifyAuthentication } from '../../api/axios.js'; | ||
import { PartialQueryConfigForApi } from '../../types.js'; | ||
import { buildEnroll } from './routes.js'; | ||
|
||
export const enroll = async ( | ||
{ itemId }: { itemId: DiscriminatedItem['id'] }, | ||
{ API_HOST, axios }: PartialQueryConfigForApi, | ||
) => | ||
verifyAuthentication(() => { | ||
return axios.post<void>(`${API_HOST}/${buildEnroll(itemId)}`); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { FolderItemFactory, HttpMethod } from '@graasp/sdk'; | ||
import { SUCCESS_MESSAGES } from '@graasp/translations'; | ||
|
||
import { act } from '@testing-library/react'; | ||
import { StatusCodes } from 'http-status-codes'; | ||
import nock from 'nock'; | ||
import { afterEach, describe, expect, it, vi } from 'vitest'; | ||
|
||
import { OK_RESPONSE, UNAUTHORIZED_RESPONSE } from '../../../test/constants.js'; | ||
import { | ||
mockMutation, | ||
setUpTest, | ||
waitForMutation, | ||
} from '../../../test/utils.js'; | ||
import { itemKeys } from '../../keys.js'; | ||
import { buildEnroll } from './routes.js'; | ||
import { enrollRoutine } from './routines.js'; | ||
|
||
const mockedNotifier = vi.fn(); | ||
const { wrapper, queryClient, mutations } = setUpTest({ | ||
notifier: mockedNotifier, | ||
}); | ||
|
||
const item = FolderItemFactory(); | ||
const itemId = item.id; | ||
const key = itemKeys.single(itemId).content; | ||
const membershipKey = itemKeys.single(itemId).memberships; | ||
|
||
describe('useEnroll', () => { | ||
const mutation = mutations.useEnroll; | ||
const route = `/${buildEnroll(itemId)}`; | ||
|
||
afterEach(() => { | ||
nock.cleanAll(); | ||
queryClient.clear(); | ||
}); | ||
|
||
it('Enroll', async () => { | ||
// set data in cache | ||
queryClient.setQueryData(key, item); | ||
queryClient.setQueryData(membershipKey, [{}]); | ||
|
||
const endpoints = [ | ||
{ | ||
response: OK_RESPONSE, | ||
method: HttpMethod.Post, | ||
route, | ||
}, | ||
]; | ||
|
||
const mockedMutation = await mockMutation({ | ||
endpoints, | ||
mutation, | ||
wrapper, | ||
}); | ||
|
||
await act(async () => { | ||
mockedMutation.mutate({ itemId }); | ||
await waitForMutation(); | ||
}); | ||
|
||
expect(mockedNotifier).toHaveBeenCalledWith({ | ||
type: enrollRoutine.SUCCESS, | ||
payload: { message: SUCCESS_MESSAGES.ENROLL }, | ||
}); | ||
|
||
expect(queryClient.getQueryState(key)?.isInvalidated).toBeTruthy(); | ||
expect( | ||
queryClient.getQueryState(membershipKey)?.isInvalidated, | ||
).toBeTruthy(); | ||
}); | ||
|
||
it('Unauthorized to enroll', async () => { | ||
// set data in cache | ||
queryClient.setQueryData(key, item); | ||
queryClient.setQueryData(membershipKey, [{}]); | ||
|
||
const endpoints = [ | ||
{ | ||
response: UNAUTHORIZED_RESPONSE, | ||
statusCode: StatusCodes.UNAUTHORIZED, | ||
method: HttpMethod.Patch, | ||
route, | ||
}, | ||
]; | ||
|
||
const mockedMutation = await mockMutation({ | ||
endpoints, | ||
mutation, | ||
wrapper, | ||
}); | ||
|
||
await act(async () => { | ||
mockedMutation.mutate({ itemId }); | ||
await waitForMutation(); | ||
}); | ||
|
||
const state = queryClient.getQueryState(key); | ||
expect(state?.isInvalidated).toBeTruthy(); | ||
|
||
expect(mockedNotifier).toHaveBeenCalledWith({ | ||
type: enrollRoutine.FAILURE, | ||
payload: expect.anything(), | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { UUID } from '@graasp/sdk'; | ||
import { SUCCESS_MESSAGES } from '@graasp/translations'; | ||
|
||
import { useMutation, useQueryClient } from '@tanstack/react-query'; | ||
|
||
import { itemKeys } from '../../keys.js'; | ||
import { QueryClientConfig } from '../../types.js'; | ||
import { enroll } from './api.js'; | ||
import { enrollRoutine } from './routines.js'; | ||
|
||
export const useEnroll = (queryConfig: QueryClientConfig) => () => { | ||
const { notifier } = queryConfig; | ||
|
||
const queryClient = useQueryClient(); | ||
return useMutation( | ||
(payload: { itemId: UUID }) => enroll(payload, queryConfig), | ||
{ | ||
onSuccess: () => { | ||
notifier?.({ | ||
type: enrollRoutine.SUCCESS, | ||
payload: { message: SUCCESS_MESSAGES.ENROLL }, | ||
}); | ||
}, | ||
onError: (error: Error, _args, _context) => { | ||
notifier?.({ | ||
type: enrollRoutine.FAILURE, | ||
payload: { error }, | ||
}); | ||
}, | ||
onSettled: (_data, _error, { itemId }) => { | ||
// on success, enroll should have given membership to the user | ||
// invalidate full item because of packed | ||
queryClient.invalidateQueries(itemKeys.single(itemId).content); | ||
}, | ||
}, | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { DiscriminatedItem } from '@graasp/sdk'; | ||
|
||
import { ITEMS_ROUTE } from '../routes.js'; | ||
|
||
export const buildEnroll = (itemId: DiscriminatedItem['id']) => | ||
`${ITEMS_ROUTE}/${itemId}/enroll`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import createRoutine from '../../routines/utils.js'; | ||
|
||
export const enrollRoutine = createRoutine('ENROLL'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { | ||
CompleteMembershipRequest, | ||
Member, | ||
MembershipRequestStatus, | ||
UUID, | ||
} from '@graasp/sdk'; | ||
|
||
import { PartialQueryConfigForApi } from '../../types.js'; | ||
import { | ||
buildDeleteMembershipRequestRoute, | ||
buildGetOwnMembershipRequestRoute, | ||
buildRequestMembershipRoute, | ||
} from './routes.js'; | ||
|
||
export const requestMembership = async ( | ||
{ id }: { id: UUID }, | ||
{ API_HOST, axios }: PartialQueryConfigForApi, | ||
) => | ||
axios | ||
.post(`${API_HOST}/${buildRequestMembershipRoute(id)}`) | ||
.then(({ data }) => data); | ||
|
||
export const getOwnMembershipRequest = async ( | ||
{ id }: { id: UUID }, | ||
{ API_HOST, axios }: PartialQueryConfigForApi, | ||
) => | ||
axios | ||
.get<{ | ||
status: MembershipRequestStatus; | ||
}>(`${API_HOST}/${buildGetOwnMembershipRequestRoute(id)}`) | ||
.then(({ data }) => data); | ||
|
||
export const getMembershipRequests = async ( | ||
{ id }: { id: UUID }, | ||
{ API_HOST, axios }: PartialQueryConfigForApi, | ||
) => | ||
axios | ||
.get< | ||
CompleteMembershipRequest[] | ||
>(`${API_HOST}/${buildRequestMembershipRoute(id)}`) | ||
.then(({ data }) => data); | ||
|
||
export const deleteMembershipRequest = async ( | ||
{ itemId, memberId }: { itemId: UUID; memberId: Member['id'] }, | ||
{ API_HOST, axios }: PartialQueryConfigForApi, | ||
) => | ||
axios | ||
.delete( | ||
`${API_HOST}/${buildDeleteMembershipRequestRoute({ itemId, memberId })}`, | ||
) | ||
.then(({ data }) => data); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { | ||
CompleteMembershipRequest, | ||
MemberFactory, | ||
MembershipRequestStatus, | ||
PackedFolderItemFactory, | ||
} from '@graasp/sdk'; | ||
|
||
import nock from 'nock'; | ||
import { afterEach, describe, expect, it } from 'vitest'; | ||
|
||
import { mockHook, setUpTest } from '../../../test/utils.js'; | ||
import { membershipRequestsKeys } from './keys.js'; | ||
import { | ||
buildGetOwnMembershipRequestRoute, | ||
buildRequestMembershipRoute, | ||
} from './routes.js'; | ||
|
||
const { hooks, wrapper, queryClient } = setUpTest(); | ||
|
||
const item = PackedFolderItemFactory(); | ||
const itemId = item.id; | ||
|
||
describe('Action Hooks', () => { | ||
afterEach(() => { | ||
nock.cleanAll(); | ||
queryClient.clear(); | ||
}); | ||
|
||
describe('useOwnMembershipRequest', () => { | ||
const hook = () => hooks.useOwnMembershipRequest(itemId); | ||
const route = `/${buildGetOwnMembershipRequestRoute(itemId)}`; | ||
|
||
it(`Return own membership request`, async () => { | ||
const response = { | ||
status: MembershipRequestStatus.Approved, | ||
}; | ||
const endpoints = [ | ||
{ | ||
route, | ||
response, | ||
}, | ||
]; | ||
const { data } = await mockHook({ | ||
endpoints, | ||
hook, | ||
wrapper, | ||
}); | ||
|
||
expect(data).toMatchObject(response); | ||
|
||
// verify cache keys | ||
expect( | ||
queryClient.getQueryData(membershipRequestsKeys.own(itemId)), | ||
).toMatchObject(response); | ||
}); | ||
}); | ||
|
||
describe('useMembershipRequests', () => { | ||
const hook = () => hooks.useMembershipRequests(itemId); | ||
const route = `/${buildRequestMembershipRoute(itemId)}`; | ||
|
||
it(`Return own membership request`, async () => { | ||
const response: CompleteMembershipRequest[] = [ | ||
{ | ||
item, | ||
createdAt: new Date().toISOString(), | ||
member: MemberFactory(), | ||
}, | ||
]; | ||
const endpoints = [ | ||
{ | ||
route, | ||
response, | ||
}, | ||
]; | ||
const { data } = await mockHook({ | ||
endpoints, | ||
hook, | ||
wrapper, | ||
}); | ||
|
||
expect(data).toMatchObject(response); | ||
|
||
// verify cache keys | ||
expect( | ||
queryClient.getQueryData(membershipRequestsKeys.single(itemId)), | ||
).toMatchObject(response); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.