diff --git a/packages/synapse-react-client/src/components/ChallengeRegisterButton/ChallengeRegisterButton.test.tsx b/packages/synapse-react-client/src/components/ChallengeRegisterButton/ChallengeRegisterButton.test.tsx
new file mode 100644
index 0000000000..b2bcf5530e
--- /dev/null
+++ b/packages/synapse-react-client/src/components/ChallengeRegisterButton/ChallengeRegisterButton.test.tsx
@@ -0,0 +1,145 @@
+import React from 'react'
+import userEvent from '@testing-library/user-event'
+import { render, screen, waitFor } from '@testing-library/react'
+import ChallengeRegisterButton, {
+ ChallengeRegisterButtonProps,
+} from './ChallengeRegisterButton'
+import { createWrapper } from '../../testutils/TestingLibraryUtils'
+import mockProject from '../../mocks/entity/mockProject'
+import SynapseClient from '../../synapse-client'
+import { mockUserProfileData } from '../../mocks/user/mock_user_profile'
+import {
+ mockChallenge,
+ mockChallengeTeamMember,
+} from '../../mocks/challenge/mockChallenge'
+import { MOCK_TEAM_ID } from '../../mocks/team/mockTeam'
+import { SynapseClientError } from '../../utils'
+
+const mockOnError = jest.fn()
+const mockOnJoinClick = jest.fn()
+const mockOnLeaveClick = jest.fn()
+
+jest
+ .spyOn(SynapseClient, 'getUserProfile')
+ .mockResolvedValue(mockUserProfileData)
+jest.spyOn(SynapseClient, 'getEntityChallenge').mockResolvedValue(mockChallenge)
+const mockGetIsUserMemberOfTeam = jest.spyOn(
+ SynapseClient,
+ 'getIsUserMemberOfTeam',
+)
+const mockGetSubmissionTeams = jest.spyOn(SynapseClient, 'getSubmissionTeams')
+
+function renderComponent() {
+ const props: ChallengeRegisterButtonProps = {
+ projectId: mockProject.id,
+ onError: mockOnError,
+ onJoinClick: mockOnJoinClick,
+ onLeaveClick: mockOnLeaveClick,
+ }
+
+ const user = userEvent.setup()
+ const component = render(, {
+ wrapper: createWrapper(),
+ })
+
+ return { user, component }
+}
+
+describe('ChallengeRegisterButton', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
+
+ it('Prompts to register when not a member of the participant team or any submission teams', async () => {
+ mockGetIsUserMemberOfTeam.mockResolvedValue(null)
+ mockGetSubmissionTeams.mockResolvedValue({
+ results: [],
+ totalNumberOfResults: 0,
+ })
+
+ const { user } = renderComponent()
+
+ const button = await screen.findByRole('button', {
+ name: 'Register for this Challenge',
+ })
+
+ await user.click(button)
+
+ expect(mockOnJoinClick).toHaveBeenCalledTimes(1)
+ expect(mockOnLeaveClick).not.toHaveBeenCalled()
+ })
+
+ it('Prompts to register when not a member of the participant team', async () => {
+ mockGetIsUserMemberOfTeam.mockResolvedValue(null)
+ mockGetSubmissionTeams.mockResolvedValue({
+ results: [String(MOCK_TEAM_ID)],
+ totalNumberOfResults: 1,
+ })
+
+ const { user } = renderComponent()
+
+ const button = await screen.findByRole('button', {
+ name: 'Register for this Challenge',
+ })
+ await user.click(button)
+
+ expect(mockOnJoinClick).toHaveBeenCalledTimes(1)
+ expect(mockOnLeaveClick).not.toHaveBeenCalled()
+ })
+
+ it('Prompts to register when not a member of a submission team', async () => {
+ mockGetIsUserMemberOfTeam.mockResolvedValue(mockChallengeTeamMember)
+ mockGetSubmissionTeams.mockResolvedValue({
+ results: [],
+ totalNumberOfResults: 0,
+ })
+
+ const { user } = renderComponent()
+
+ const button = await screen.findByRole('button', {
+ name: 'Register for this Challenge',
+ })
+
+ await user.click(button)
+
+ expect(mockOnJoinClick).toHaveBeenCalledTimes(1)
+ expect(mockOnLeaveClick).not.toHaveBeenCalled()
+ })
+
+ it('Prompts to leave when on both participant and submission teams', async () => {
+ mockGetIsUserMemberOfTeam.mockResolvedValue(mockChallengeTeamMember)
+ mockGetSubmissionTeams.mockResolvedValue({
+ results: [String(MOCK_TEAM_ID)],
+ totalNumberOfResults: 1,
+ })
+
+ const { user } = renderComponent()
+ const button = await screen.findByRole('button', {
+ name: 'Leave Challenge',
+ })
+
+ await user.click(button)
+
+ expect(mockOnLeaveClick).toHaveBeenCalledTimes(1)
+ expect(mockOnJoinClick).not.toHaveBeenCalled()
+ })
+
+ it('Invokes the callback on error', async () => {
+ const error = new SynapseClientError(
+ 500,
+ 'Simulated error in test',
+ expect.getState().currentTestName!,
+ )
+ mockGetIsUserMemberOfTeam.mockRejectedValue(error)
+ mockGetSubmissionTeams.mockResolvedValue({
+ results: [String(MOCK_TEAM_ID)],
+ totalNumberOfResults: 1,
+ })
+
+ renderComponent()
+
+ await waitFor(() => {
+ expect(mockOnError).toHaveBeenCalledWith(error)
+ })
+ })
+})
diff --git a/packages/synapse-react-client/src/components/ChallengeRegisterButton/ChallengeRegisterButton.tsx b/packages/synapse-react-client/src/components/ChallengeRegisterButton/ChallengeRegisterButton.tsx
index 659b873509..b3ccdf4920 100644
--- a/packages/synapse-react-client/src/components/ChallengeRegisterButton/ChallengeRegisterButton.tsx
+++ b/packages/synapse-react-client/src/components/ChallengeRegisterButton/ChallengeRegisterButton.tsx
@@ -1,102 +1,87 @@
-import React, { useEffect, useState } from 'react'
+import React, { useEffect } from 'react'
import ExitToAppIcon from '@mui/icons-material/ExitToApp'
-import { Box } from '@mui/system'
+import { Box } from '@mui/material'
import SpinnerButton from '../SpinnerButton/SpinnerButton'
-import { useSynapseContext } from '../../utils'
import {
useGetCurrentUserProfile,
useGetEntityChallenge,
+ useGetIsUserMemberOfTeam,
useGetUserSubmissionTeams,
} from '../../synapse-queries'
-import { Challenge, PaginatedIds } from '@sage-bionetworks/synapse-types'
-import { useGetIsUserMemberOfTeam } from '../../synapse-queries/team/useTeamMembers'
-import { SynapseClientError } from '../../utils/SynapseClientError'
+import { SynapseClientError, useSynapseContext } from '../../utils'
export interface ChallengeRegisterButtonProps {
projectId: string
- onChallengeError?: (error: SynapseClientError) => void
+ onError?: (error: SynapseClientError) => void
onJoinClick?: () => void
onLeaveClick?: () => void
}
-const EMPTY_ID = ''
-
const ChallengeRegisterButton = ({
projectId,
- onChallengeError,
+ onError,
onJoinClick,
onLeaveClick,
}: ChallengeRegisterButtonProps) => {
const { accessToken } = useSynapseContext()
- const [challenge, setChallenge] = useState()
- const { data: userProfile } = useGetCurrentUserProfile()
- const [isRegistered, setIsRegistered] = useState(false)
- const [hasSubmissionTeam, setHasSubmissionTeam] = useState(false)
- const [loading, setLoading] = useState(true)
- const [requestError, setRequestError] = useState()
-
- useEffect(() => {
- if (requestError && onChallengeError) onChallengeError(requestError)
- }, [requestError, onChallengeError])
+ const isLoggedIn = !!accessToken
+ const { data: userProfile } = useGetCurrentUserProfile({
+ enabled: isLoggedIn,
+ })
- useGetEntityChallenge(projectId, {
- enabled: !!accessToken && !challenge,
- onSettled: (data, error) => {
- if (data) {
- setChallenge(data)
- }
- if (error) {
- setLoading(false)
- setRequestError(error)
- }
- },
+ const {
+ data: challenge,
+ isLoading: isLoadingChallenge,
+ error: getChallengeError,
+ } = useGetEntityChallenge(projectId, {
+ enabled: isLoggedIn,
})
// Verify that user is a member of the participant team
- useGetIsUserMemberOfTeam(
- challenge?.participantTeamId ?? EMPTY_ID,
- userProfile?.ownerId ?? EMPTY_ID,
+ const {
+ data: teamMembership,
+ isLoading: isLoadingTeamMembership,
+ error: getTeamMembershipError,
+ } = useGetIsUserMemberOfTeam(
+ challenge?.participantTeamId!,
+ userProfile?.ownerId!,
{
enabled: !!challenge && !!userProfile,
- onSettled: (data, error) => {
- if (data === null) {
- // User is not a member of the participant team
- setIsRegistered(false)
- setLoading(false)
- }
- if (data !== null) {
- // User is a member of the participant team, continue
- setIsRegistered(true)
- }
- if (error) {
- // Could not determine if user is a member of the participant team
- setLoading(false)
- setRequestError(error)
- }
- },
},
)
- useGetUserSubmissionTeams(challenge?.id ?? '0', 20, 0, {
- enabled: !!challenge && !!accessToken,
- onSettled: (data: PaginatedIds | undefined, error) => {
- if (data) {
- setHasSubmissionTeam(data.results.length > 0)
- }
- if (error) {
- setRequestError(error)
- }
- setLoading(false)
- },
+ const isRegistered = Boolean(teamMembership)
+
+ const {
+ data: userSubmissionTeams,
+ error: getSubmissionTeamsError,
+ isLoading: isLoadingSubmissionTeams,
+ } = useGetUserSubmissionTeams(challenge?.id!, 20, 0, {
+ enabled: !!challenge && isLoggedIn,
})
- if (loading) {
+ const hasSubmissionTeam =
+ userSubmissionTeams && userSubmissionTeams.results.length > 0
+ const isMemberOfBothParticipantAndSubmissionTeams =
+ isRegistered && hasSubmissionTeam
+
+ const error =
+ getChallengeError || getTeamMembershipError || getSubmissionTeamsError
+
+ useEffect(() => {
+ if (error && onError) onError(error)
+ }, [error, onError])
+
+ const isLoading =
+ isLoadingChallenge || isLoadingTeamMembership || isLoadingSubmissionTeams
+
+ if (isLoading) {
return Loading...
}
return (
- {(!isRegistered || !hasSubmissionTeam) && (
+ {!isMemberOfBothParticipantAndSubmissionTeams && (
)}
- {isRegistered && hasSubmissionTeam && (
+ {isMemberOfBothParticipantAndSubmissionTeams && (