diff --git a/src/factories.tsx b/src/factories.tsx index 1664e10..d0659e3 100644 --- a/src/factories.tsx +++ b/src/factories.tsx @@ -16,7 +16,6 @@ import { DEPARTMENT_SEALS } from './utils/departmentSeals' import { UsersIndex } from './network/useUsers' import { UserShow } from './network/useUser' import { GroupsIndex, GroupShow } from './network/groups' -import { MembershipsIndex } from './network/memberships' export const randomObject = () => { return { [faker.lorem.word()]: faker.lorem.words(3) } @@ -140,59 +139,74 @@ export const buildEmailTemplateIndex = ( } } -export const buildUserIndex = (options?: Partial): UsersIndex => { +const buildUser = (): UserShow => ({ + id: uniqueId(), + email: faker.internet.email(), + role: 'member', +}) + +export const buildUserShow = (options?: Partial): UserShow => { + const user = buildUser() return { - id: uniqueId(), - email: faker.internet.email(), - role: 'member', + ...user, ...options, } } -export const buildUserShow = (options?: Partial): UserShow => { +export const buildUserIndex = (options?: Partial): UsersIndex => { + const user = buildUser() return { - id: uniqueId(), - email: faker.internet.email(), - role: 'member', + ...user, ...options, } } -export const buildGroupIndex = (options?: Partial): GroupsIndex => { +export const buildUserMembershipIndex = (options?: Partial): UsersIndex => { + const user = buildUser() return { - id: uniqueId(), - name: faker.lorem.words(3), - description: faker.lorem.paragraph(), + ...user, ...options, } } -interface Membership { - id: string - userId: string - groupId: string -} +const buildGroup = (): GroupsIndex => ({ + id: uniqueId(), + name: faker.lorem.words(3), + description: faker.lorem.paragraph(), +}) -export const buildMembershipShow = (options?: Partial): Membership => { +export const buildGroupIndex = (options?: Partial): GroupsIndex => { + const group = buildGroup() return { - id: uniqueId(), - groupId: uniqueId(), - userId: uniqueId(), + ...group, ...options, } } export const buildGroupShow = (options?: Partial): GroupShow => { + const group = buildGroup() return { - id: uniqueId(), - name: faker.lorem.words(3), - description: faker.lorem.paragraph(), + ...group, users: [], ...options, } } -export const buildMembershipIndex = (options?: Partial): MembershipsIndex => { +export const buildGroupMembershipIndex = (options?: Partial): GroupsIndex => { + const group = buildGroup() + return { + ...group, + ...options, + } +} + +interface Membership { + id: string + userId: string + groupId: string +} + +export const buildMembershipShow = (options?: Partial): Membership => { return { id: uniqueId(), groupId: uniqueId(), diff --git a/src/network/memberships/__tests__/useCreateMembership.test.tsx b/src/network/memberships/__tests__/useCreateMembership.test.tsx index f83266a..c05cf31 100644 --- a/src/network/memberships/__tests__/useCreateMembership.test.tsx +++ b/src/network/memberships/__tests__/useCreateMembership.test.tsx @@ -6,8 +6,7 @@ import { asMock, buildMembershipShow, userIsSignedIn } from 'src/testHelpers' import { AuthedFetch, useAuthedFetch } from '../../useAuthedFetch' import { useCreateMembership } from '../useCreateMembership' import { randomUUID } from 'crypto' -import { buildUseGroupQueryKey } from 'src/network/groups' -import { buildUseUserQueryKey } from 'src/network/useUser' +import { buildUseMembershipQueryKey } from '../useMembership' jest.mock('../../useAuthedFetch') @@ -54,7 +53,7 @@ describe('useCreateMembership', () => { expect(result.current.data).toEqual({ membership: { id: 'saved id', userId, groupId } }) }) - it('invalidates the useGroup and useUser queries', async () => { + it('invalidates the useMembership queries', async () => { const client = new QueryClient() const membership = buildMembershipShow() asMock(mockAuthedFetch).mockResolvedValue({ @@ -77,10 +76,7 @@ describe('useCreateMembership', () => { await result.current.mutateAsync({ groupId, userId }) await waitFor(() => expect(result.current.isSuccess).toEqual(true)) expect(client.invalidateQueries).toHaveBeenCalledWith({ - queryKey: [buildUseUserQueryKey(membership.userId)], - }) - expect(client.invalidateQueries).toHaveBeenCalledWith({ - queryKey: [buildUseGroupQueryKey(membership.groupId)], + queryKey: [buildUseMembershipQueryKey(membership.id)], }) }) }) diff --git a/src/network/memberships/__tests__/useDestroyMembership.test.tsx b/src/network/memberships/__tests__/useDestroyMembership.test.tsx index ff823fb..3b3713f 100644 --- a/src/network/memberships/__tests__/useDestroyMembership.test.tsx +++ b/src/network/memberships/__tests__/useDestroyMembership.test.tsx @@ -5,8 +5,9 @@ import { AuthProvider } from 'src/utils/AuthContext' import { asMock, buildMembershipShow, userIsSignedIn } from 'src/testHelpers' import { AuthedFetch, useAuthedFetch } from '../../useAuthedFetch' import { useDestroyMembership } from '../useDestroyMembership' -import { buildUseUserQueryKey } from 'src/network/useUser' +import { buildUseMembershipQueryKey } from 'src/network/memberships' import { buildUseGroupQueryKey } from 'src/network/groups' +import { buildUseUserQueryKey } from 'src/network/useUser' jest.mock('../../useAuthedFetch') @@ -46,7 +47,7 @@ describe('useDestroyMembership', () => { expect(result.current.data).toEqual({ membership: { id: membership.id } }) }) - it('invalidates the useGroup and useUser queries', async () => { + it('invalidates the useMembership, useGroup, and useUser queries', async () => { const client = new QueryClient() const membership = buildMembershipShow() asMock(mockAuthedFetch).mockResolvedValue({ @@ -69,10 +70,13 @@ describe('useDestroyMembership', () => { await result.current.mutateAsync(membership) await waitFor(() => expect(result.current.isSuccess).toEqual(true)) expect(client.invalidateQueries).toHaveBeenCalledWith({ - queryKey: [buildUseUserQueryKey(membership.userId)], + queryKey: [buildUseMembershipQueryKey(membership.id)], }) expect(client.invalidateQueries).toHaveBeenCalledWith({ queryKey: [buildUseGroupQueryKey(membership.groupId)], }) + expect(client.invalidateQueries).toHaveBeenCalledWith({ + queryKey: [buildUseUserQueryKey(membership.userId)], + }) }) }) diff --git a/src/network/memberships/__tests__/useMemberships.test.tsx b/src/network/memberships/__tests__/useGroupMemberships.test.tsx similarity index 69% rename from src/network/memberships/__tests__/useMemberships.test.tsx rename to src/network/memberships/__tests__/useGroupMemberships.test.tsx index 900faa1..8ec1df2 100644 --- a/src/network/memberships/__tests__/useMemberships.test.tsx +++ b/src/network/memberships/__tests__/useGroupMemberships.test.tsx @@ -1,9 +1,9 @@ import React from 'react' import { renderHook, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { useMemberships } from '../useMemberships' +import { useGroupMemberships } from '../useGroupMemberships' import { AuthProvider } from 'src/utils/AuthContext' -import { asMock, buildMembershipIndex, userIsSignedIn } from 'src/testHelpers' +import { asMock, buildGroupMembershipIndex, buildUserShow, userIsSignedIn } from 'src/testHelpers' import { AuthedFetch, useAuthedFetch } from '../../useAuthedFetch' jest.mock('../../useAuthedFetch') @@ -19,10 +19,11 @@ describe('useMemberships', () => { it('queries for memberships', async () => { const client = new QueryClient() - const memberships = [buildMembershipIndex(), buildMembershipIndex()] - asMock(mockAuthedFetch).mockResolvedValue({ statusCode: 200, json: { memberships } }) + const user = buildUserShow() + const groups = [buildGroupMembershipIndex(), buildGroupMembershipIndex()] + asMock(mockAuthedFetch).mockResolvedValue({ statusCode: 200, json: { groups } }) - const { result } = renderHook(() => useMemberships(), { + const { result } = renderHook(() => useGroupMemberships(user.id), { wrapper: ({ children }) => { return ( @@ -34,9 +35,9 @@ describe('useMemberships', () => { await waitFor(() => expect(result.current.isSuccess).toEqual(true)) expect(mockAuthedFetch).toHaveBeenCalledWith({ - path: '/memberships', + path: `/users/${user.id}/memberships`, method: 'GET', }) - expect(result.current.data).toEqual(memberships) + expect(result.current.data).toEqual(groups) }) }) diff --git a/src/network/memberships/__tests__/useMembership.test.tsx b/src/network/memberships/__tests__/useMembership.test.tsx new file mode 100644 index 0000000..a1a4073 --- /dev/null +++ b/src/network/memberships/__tests__/useMembership.test.tsx @@ -0,0 +1,42 @@ +import React from 'react' +import { renderHook, waitFor } from '@testing-library/react' +import { MembershipShow, useMembership } from '../useMembership' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { AuthProvider } from 'src/utils/AuthContext' +import { asMock, buildMembershipShow, userIsSignedIn } from 'src/testHelpers' +import { AuthedFetch, useAuthedFetch } from '../../useAuthedFetch' + +jest.mock('../../useAuthedFetch') + +describe('useMembership', () => { + let mockAuthedFetch: AuthedFetch + + beforeEach(() => { + userIsSignedIn() + mockAuthedFetch = jest.fn() + asMock(useAuthedFetch).mockReturnValue(mockAuthedFetch) + }) + + it('queries for the group with the given id', async () => { + const client = new QueryClient() + const membership: MembershipShow = buildMembershipShow() + asMock(mockAuthedFetch).mockResolvedValue({ statusCode: 200, json: { membership } }) + + const { result } = renderHook(() => useMembership(membership.id), { + wrapper: ({ children }) => { + return ( + + {children} + + ) + }, + }) + + await waitFor(() => expect(result.current.isSuccess).toEqual(true)) + expect(mockAuthedFetch).toHaveBeenCalledWith({ + path: `/membership/${membership.id}`, + method: 'GET', + }) + expect(result.current.data).toEqual(membership) + }) +}) diff --git a/src/network/memberships/__tests__/useUserMemberships.test.tsx b/src/network/memberships/__tests__/useUserMemberships.test.tsx new file mode 100644 index 0000000..616ba3e --- /dev/null +++ b/src/network/memberships/__tests__/useUserMemberships.test.tsx @@ -0,0 +1,48 @@ +import React from 'react' +import { renderHook, waitFor } from '@testing-library/react' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { useUserMemberships } from '../useUserMemberships' +import { AuthProvider } from 'src/utils/AuthContext' +import { + asMock, + buildMembershipShow, + buildUserMembershipIndex, + userIsSignedIn, +} from 'src/testHelpers' +import { AuthedFetch, useAuthedFetch } from '../../useAuthedFetch' + +jest.mock('../../useAuthedFetch') + +describe('useMemberships', () => { + let mockAuthedFetch: AuthedFetch + + beforeEach(() => { + userIsSignedIn() + mockAuthedFetch = jest.fn() + asMock(useAuthedFetch).mockReturnValue(mockAuthedFetch) + }) + + it('queries for user memberships', async () => { + const client = new QueryClient() + const membership = buildMembershipShow() + const users = [buildUserMembershipIndex(), buildUserMembershipIndex()] + asMock(mockAuthedFetch).mockResolvedValue({ statusCode: 200, json: { users } }) + + const { result } = renderHook(() => useUserMemberships(membership.id), { + wrapper: ({ children }) => { + return ( + + {children} + + ) + }, + }) + + await waitFor(() => expect(result.current.isSuccess).toEqual(true)) + expect(mockAuthedFetch).toHaveBeenCalledWith({ + path: `/groups/${membership.id}/memberships`, + method: 'GET', + }) + expect(result.current.data).toEqual(users) + }) +}) diff --git a/src/network/memberships/index.ts b/src/network/memberships/index.ts index 5954e20..2b079ab 100644 --- a/src/network/memberships/index.ts +++ b/src/network/memberships/index.ts @@ -1,3 +1,5 @@ -export * from './useMemberships' +export * from './useMembership' +export * from './useGroupMemberships' +export * from './useUserMemberships' export * from './useCreateMembership' export * from './useDestroyMembership' diff --git a/src/network/memberships/useCreateMembership.ts b/src/network/memberships/useCreateMembership.ts index 1a93c9c..9fbd61c 100644 --- a/src/network/memberships/useCreateMembership.ts +++ b/src/network/memberships/useCreateMembership.ts @@ -1,23 +1,16 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' import { useAuthedFetch } from '../useAuthedFetch' -import { buildUseUserQueryKey } from '../useUser' -import { buildUseGroupQueryKey } from '../groups' +import { buildUseMembershipQueryKey, MembershipShow } from '../memberships' -export interface MembershipShow { - id: string - groupId: string - userId: string -} - -interface CreateGroupSuccessfulResponse { +interface CreateMembershipSuccessfulResponse { membership: MembershipShow } -export interface CreateGroupErrorResponse { - errors: { name: string; description?: string } +export interface CreateMembershipErrorResponse { + errors: { user: string; group?: string } } -type CreateGroupResponse = CreateGroupSuccessfulResponse | CreateGroupErrorResponse +type CreateMembershipResponse = CreateMembershipSuccessfulResponse | CreateMembershipErrorResponse export const useCreateMembership = () => { const client = useQueryClient() @@ -27,8 +20,8 @@ export const useCreateMembership = () => { mutationFn: async (membership: { userId: string groupId: string - }): Promise => { - const result = await authedFetch({ + }): Promise => { + const result = await authedFetch({ body: { membership }, method: 'POST', path: '/memberships', @@ -38,8 +31,9 @@ export const useCreateMembership = () => { onSuccess: (result) => { if ('membership' in result) { const { membership } = result - client.invalidateQueries({ queryKey: [buildUseUserQueryKey(membership.userId)] }) - client.invalidateQueries({ queryKey: [buildUseGroupQueryKey(membership.groupId)] }) + client.invalidateQueries({ + queryKey: [buildUseMembershipQueryKey(membership.id)], + }) } }, }) diff --git a/src/network/memberships/useDestroyMembership.ts b/src/network/memberships/useDestroyMembership.ts index a797293..e5808f6 100644 --- a/src/network/memberships/useDestroyMembership.ts +++ b/src/network/memberships/useDestroyMembership.ts @@ -1,9 +1,10 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' import { useAuthedFetch } from '../useAuthedFetch' -import { MembershipShow } from './useCreateMembership' +import { MembershipShow } from './useMembership' import { useRef } from 'react' -import { buildUseUserQueryKey } from '../useUser' +import { buildUseMembershipQueryKey } from '../memberships' import { buildUseGroupQueryKey } from '../groups' +import { buildUseUserQueryKey } from '../useUser' export const useDestroyMembership = () => { const client = useQueryClient() @@ -21,10 +22,15 @@ export const useDestroyMembership = () => { }, onSuccess: () => { if (membershipRef.current) { - client.invalidateQueries({ queryKey: [buildUseUserQueryKey(membershipRef.current.userId)] }) + client.invalidateQueries({ + queryKey: [buildUseMembershipQueryKey(membershipRef.current.id)], + }) client.invalidateQueries({ queryKey: [buildUseGroupQueryKey(membershipRef.current.groupId)], }) + client.invalidateQueries({ + queryKey: [buildUseUserQueryKey(membershipRef.current.userId)], + }) } }, }) diff --git a/src/network/memberships/useGroupMemberships.ts b/src/network/memberships/useGroupMemberships.ts new file mode 100644 index 0000000..f1c994b --- /dev/null +++ b/src/network/memberships/useGroupMemberships.ts @@ -0,0 +1,20 @@ +import { useQuery } from '@tanstack/react-query' +import { useAuthedFetch } from '../useAuthedFetch' +import { GroupsIndex } from '../groups' + +export const GROUP_MEMBERSHIPS_QUERY_KEY = 'useGroupMemberships' + +export const useGroupMemberships = (id: string) => { + const authedFetch = useAuthedFetch() + + return useQuery({ + queryKey: [GROUP_MEMBERSHIPS_QUERY_KEY], + queryFn: async () => { + const result = await authedFetch<{ groups: GroupsIndex[] }>({ + path: `/users/${id}/memberships`, + method: 'GET', + }) + return result.json!.groups + }, + }) +} diff --git a/src/network/memberships/useMembership.ts b/src/network/memberships/useMembership.ts new file mode 100644 index 0000000..0b76b3a --- /dev/null +++ b/src/network/memberships/useMembership.ts @@ -0,0 +1,25 @@ +import { useQuery } from '@tanstack/react-query' +import { useAuthedFetch } from '../useAuthedFetch' + +export interface MembershipShow { + id: string + groupId: string + userId: string +} + +export const buildUseMembershipQueryKey = (id: string): string => `useMembership('${id}')` + +export const useMembership = (id: string) => { + const authedFetch = useAuthedFetch() + + return useQuery({ + queryKey: [buildUseMembershipQueryKey(id)], + queryFn: async () => { + const result = await authedFetch<{ membership: MembershipShow }>({ + path: `/membership/${id}`, + method: 'GET', + }) + return result.json!.membership + }, + }) +} diff --git a/src/network/memberships/useMemberships.ts b/src/network/memberships/useMemberships.ts deleted file mode 100644 index f054ac0..0000000 --- a/src/network/memberships/useMemberships.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import { useAuthedFetch } from '../useAuthedFetch' - -export interface MembershipsIndex { - id: string - groupId: string - userId: string -} - -export const QUERY_KEY = 'useMemberships' - -export const useMemberships = () => { - const authedFetch = useAuthedFetch() - - return useQuery({ - queryKey: [QUERY_KEY], - queryFn: async () => { - const result = await authedFetch<{ memberships: MembershipsIndex[] }>({ - path: '/memberships', - method: 'GET', - }) - return result.json!.memberships - }, - }) -} diff --git a/src/network/memberships/useUserMemberships.ts b/src/network/memberships/useUserMemberships.ts new file mode 100644 index 0000000..1678b47 --- /dev/null +++ b/src/network/memberships/useUserMemberships.ts @@ -0,0 +1,20 @@ +import { useQuery } from '@tanstack/react-query' +import { useAuthedFetch } from '../useAuthedFetch' +import { UserShow } from '../useUser' + +export const USER_MEMBERSHIPS_QUERY_KEY = 'useUserMemberships' + +export const useUserMemberships = (id: string) => { + const authedFetch = useAuthedFetch() + + return useQuery({ + queryKey: [USER_MEMBERSHIPS_QUERY_KEY], + queryFn: async () => { + const result = await authedFetch<{ users: UserShow[] }>({ + path: `/groups/${id}/memberships`, + method: 'GET', + }) + return result.json!.users + }, + }) +}