Skip to content

Commit

Permalink
Merge pull request #730 from nickgros/SWC-6545b
Browse files Browse the repository at this point in the history
  • Loading branch information
nickgros authored Feb 21, 2024
2 parents c9cd4ce + c7abbf7 commit eee038f
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -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(<ChallengeRegisterButton {...props} />, {
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)
})
})
})
Original file line number Diff line number Diff line change
@@ -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<Challenge>()
const { data: userProfile } = useGetCurrentUserProfile()
const [isRegistered, setIsRegistered] = useState<boolean>(false)
const [hasSubmissionTeam, setHasSubmissionTeam] = useState<boolean>(false)
const [loading, setLoading] = useState<boolean>(true)
const [requestError, setRequestError] = useState<SynapseClientError>()

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 <SpinnerButton showSpinner>Loading...</SpinnerButton>
}

return (
<Box>
{(!isRegistered || !hasSubmissionTeam) && (
{!isMemberOfBothParticipantAndSubmissionTeams && (
<SpinnerButton
disableElevation={true}
variant="contained"
Expand All @@ -118,7 +103,7 @@ const ChallengeRegisterButton = ({
Register for this Challenge
</SpinnerButton>
)}
{isRegistered && hasSubmissionTeam && (
{isMemberOfBothParticipantAndSubmissionTeams && (
<SpinnerButton
disableElevation={true}
variant="outlined"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react'
import React from 'react'
import { Button, ButtonProps } from '@mui/material'
import { Link, LinkProps } from 'react-router-dom'
import { SRC_SIGN_IN_CLASS } from '../../utils/SynapseConstants'
import { useSynapseContext } from '../../utils/context'
import { useSynapseContext } from '../../utils'

/* Allow component to behave as a MUI Button or a React Router Link */
export type LoginAwareButtonProps = ButtonProps &
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useQuery, UseQueryOptions } from 'react-query'
import SynapseClient from '../../synapse-client'
import { SynapseClientError } from '../../utils/SynapseClientError'
import { useSynapseContext } from '../../utils/context'
import { SynapseClientError, useSynapseContext } from '../../utils'
import { ActionRequiredList } from '@sage-bionetworks/synapse-types'

export function useGetEntityActionsRequired(
Expand Down
1 change: 1 addition & 0 deletions packages/synapse-react-client/src/synapse-queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export * from './entity'
export * from './search'
export * from './user'
export * from './oauth'
export * from './team'
export * from './KeyFactory'
export * from './QueryClientUtils'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './useTeam'
export * from './useTeamList'
export * from './useTeamMembers'
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import {
UseQueryOptions,
} from 'react-query'
import SynapseClient from '../../synapse-client'
import { SynapseClientError } from '../../utils/SynapseClientError'
import { useSynapseContext } from '../../utils/context'
import { PassingRecord } from '@sage-bionetworks/synapse-types'
import { QuizResponse } from '@sage-bionetworks/synapse-types'
import { SynapseClientError, useSynapseContext } from '../../utils'
import { PassingRecord, QuizResponse } from '@sage-bionetworks/synapse-types'
import { useGetCurrentUserProfile } from './useUserBundle'
import { USER_BUNDLE_MASK_IS_CERTIFIED } from '../../utils/SynapseConstants'

Expand Down

0 comments on commit eee038f

Please sign in to comment.