Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 4 additions & 19 deletions frontend/__tests__/unit/components/CardDetailsPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -634,16 +634,8 @@ describe('CardDetailsPage', () => {
const userSummary = <div>Custom user summary content</div>
render(<CardDetailsPage {...defaultProps} userSummary={userSummary} />)

expect(screen.getByText('Summary')).toBeInTheDocument()
expect(screen.getByText('Custom user summary content')).toBeInTheDocument()
})

it('renders heatmap section when heatmap prop is provided', () => {
const heatmap = <div data-testid="custom-heatmap">Contribution heatmap</div>
render(<CardDetailsPage {...defaultProps} heatmap={heatmap} />)

expect(screen.getByText('Contribution Heatmap')).toBeInTheDocument()
expect(screen.getByTestId('custom-heatmap')).toBeInTheDocument()
const userSummaryContent = screen.getByText('Custom user summary content')
expect(userSummaryContent).toBeInTheDocument()
})

it('renders health metrics when type is project and health data is available', () => {
Expand Down Expand Up @@ -1272,20 +1264,15 @@ describe('CardDetailsPage', () => {
</ul>
</div>
),
heatmap: (
<div data-testid="complex-heatmap">
<canvas id="contribution-graph" />
<div>Legend</div>
</div>
),
}

render(<CardDetailsPage {...complexProps} />)

expect(screen.getByText('Nested')).toBeInTheDocument()
expect(screen.getByText('Content')).toBeInTheDocument()
expect(screen.getByText('Complex user summary')).toBeInTheDocument()
expect(screen.getByTestId('complex-heatmap')).toBeInTheDocument()
expect(screen.getByText('Item 1')).toBeInTheDocument()
expect(screen.getByText('Item 2')).toBeInTheDocument()
})

it('renders correctly with all optional sections enabled', () => {
Expand All @@ -1294,7 +1281,6 @@ describe('CardDetailsPage', () => {
type: 'project',
summary: 'Project summary text',
userSummary: <div>User summary content</div>,
heatmap: <div data-testid="heatmap">Heatmap content</div>,
socialLinks: ['https://github.com/test', 'https://twitter.com/test'],
entityKey: 'test-entity',
projectName: 'Test Project Name',
Expand All @@ -1312,7 +1298,6 @@ describe('CardDetailsPage', () => {

expect(screen.getByText('Project summary text')).toBeInTheDocument()
expect(screen.getByText('User summary content')).toBeInTheDocument()
expect(screen.getByTestId('heatmap')).toBeInTheDocument()
expect(screen.getByTestId('health-metrics')).toBeInTheDocument()
expect(screen.getByTestId('top-contributors-list')).toBeInTheDocument()
expect(screen.getByTestId('repositories-card')).toBeInTheDocument()
Expand Down
26 changes: 23 additions & 3 deletions frontend/__tests__/unit/pages/UserDetails.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ describe('UserDetailsPage', () => {

expect(screen.getByText('Test User')).toBeInTheDocument()
expect(screen.getByText('Statistics')).toBeInTheDocument()
expect(screen.getByText('Contribution Heatmap')).toBeInTheDocument()
expect(screen.getByText('Test Company')).toBeInTheDocument()
expect(screen.getByText('Test Location')).toBeInTheDocument()
expect(screen.getByText('10 Followers')).toBeInTheDocument()
Expand Down Expand Up @@ -233,13 +232,22 @@ describe('UserDetailsPage', () => {
;(useQuery as jest.Mock).mockReturnValue({
data: mockUserDetailsData,
error: null,
loading: false,
})
;(fetchHeatmapData as jest.Mock).mockResolvedValue({
years: [{ year: '2023' }], // Provide years data to satisfy condition in component
})

render(<UserDetailsPage />)

// Wait for useEffect to process the fetchHeatmapData result
await waitFor(() => {
const heatmapTitle = screen.getByText('Contribution Heatmap')
expect(heatmapTitle).toBeInTheDocument()
const heatmapContainer = screen
.getByAltText('Heatmap Background')
.closest('div.hidden.lg\\:block')
expect(heatmapContainer).toBeInTheDocument()
expect(heatmapContainer).toHaveClass('hidden')
expect(heatmapContainer).toHaveClass('lg:block')
})
})

Expand Down Expand Up @@ -269,6 +277,15 @@ describe('UserDetailsPage', () => {
await waitFor(() => {
const userName = screen.getByText('Test User')
expect(userName).toBeInTheDocument()
const avatar = screen.getByAltText('Test User')
expect(avatar).toHaveClass('rounded-full')
expect(avatar).toHaveClass('h-[200px]')
expect(avatar).toHaveClass('w-[200px]')

// Check for responsive classes
const summaryContainer = avatar.closest('div.flex')
expect(summaryContainer).toHaveClass('flex-col')
expect(summaryContainer).toHaveClass('lg:flex-row')
})
})

Expand Down Expand Up @@ -466,6 +483,9 @@ describe('UserDetailsPage', () => {
render(<UserDetailsPage />)
await waitFor(() => {
expect(screen.getAllByText('N/A').length).toBe(3)
const bioContainer = screen.getByText('@testuser').closest('div')
expect(bioContainer).toHaveClass('text-center')
expect(bioContainer).toHaveClass('lg:text-left')
})
})
test('does not render sponsor block', async () => {
Expand Down
113 changes: 56 additions & 57 deletions frontend/src/app/members/[memberKey]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ const UserDetailsPage: React.FC = () => {
const [data, setData] = useState<HeatmapData>({} as HeatmapData)
const [isLoading, setIsLoading] = useState<boolean>(true)
const [username, setUsername] = useState('')
const [imageLink, setImageLink] = useState('')
const [isPrivateContributor, setIsPrivateContributor] = useState(false)
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const theme = 'blue'

const { data: graphQLData, error: graphQLRequestError } = useQuery(GET_USER_DATA, {
variables: { key: memberKey },
Expand Down Expand Up @@ -75,14 +72,6 @@ const UserDetailsPage: React.FC = () => {
fetchData()
}, [memberKey, user])

useEffect(() => {
if (canvasRef.current && data && data.years && data.years.length > 0) {
drawContributions(canvasRef.current, { data, username, theme })
const imageURL = canvasRef.current.toDataURL()
setImageLink(imageURL)
}
}, [username, data])

const formattedBio = user?.bio?.split(' ').map((word, index) => {
// Regex to match GitHub usernames, but if last character is not a word character or @, it's a punctuation
const mentionMatch = word.match(/^@([\w-]+(?:\.[\w-]+)*)([^\w@])?$/)
Expand Down Expand Up @@ -142,6 +131,7 @@ const UserDetailsPage: React.FC = () => {

const Heatmap = () => {
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const [imgSrc, setImgSrc] = useState('')
const { resolvedTheme } = useTheme()
const isDarkMode = (resolvedTheme ?? 'light') === 'dark'

Expand All @@ -153,69 +143,78 @@ const UserDetailsPage: React.FC = () => {
themeName: isDarkMode ? 'dark' : 'light',
})
const imageURL = canvasRef.current.toDataURL()
setImageLink(imageURL)
setImgSrc(imageURL)
} else {
setImgSrc('')
}
}, [isDarkMode])

return (
<div className="flex flex-col gap-4">
<div className="overflow-hidden rounded-lg bg-white dark:bg-gray-800">
<div className="relative">
<canvas ref={canvasRef} style={{ display: 'none' }} aria-hidden="true"></canvas>
{imageLink ? (
<div className="h-40">
<Image
width={100}
height={100}
src={imageLink}
className="h-full w-full object-cover object-[54%_60%]"
alt="Contribution Heatmap"
/>
</div>
) : (
<div className="relative h-40 items-center justify-center">
<Image
height={100}
width={100}
src={
isDarkMode
? '/img/heatmap-background-dark.png'
: '/img/heatmap-background-light.png'
}
className="heatmap-background-loader h-full w-full border-none object-cover object-[54%_60%]"
alt="Heatmap Background"
/>
<div className="heatmap-loader"></div>
</div>
)}
</div>
<div className="overflow-hidden rounded-lg bg-white dark:bg-gray-800">
<div className="relative">
<canvas ref={canvasRef} style={{ display: 'none' }} aria-hidden="true"></canvas>
{imgSrc ? (
<div className="h-32">
<Image
width={100}
height={100}
src={imgSrc}
className="h-full w-full object-cover object-[54%_60%]"
alt="Contribution Heatmap"
/>
</div>
) : (
<div className="relative h-32 items-center justify-center">
<Image
height={100}
width={100}
src={
isDarkMode
? '/img/heatmap-background-dark.png'
: '/img/heatmap-background-light.png'
}
className="heatmap-background-loader h-full w-full border-none object-cover object-[54%_60%]"
alt="Heatmap Background"
/>
<div className="heatmap-loader"></div>
</div>
)}
</div>
</div>
)
}

const UserSummary = () => (
<div className="mt-4 flex items-center">
<Image
width={64}
height={64}
className="mr-4 h-16 w-16 rounded-full border-2 border-white bg-white object-cover shadow-md dark:border-gray-800 dark:bg-gray-600/60"
src={user?.avatarUrl || '/placeholder.svg'}
alt={user?.name || user?.login || 'User Avatar'}
/>
<div className="w-full">
<Link href={user?.url || '#'} className="text-xl font-bold text-blue-400 hover:underline">
@{user?.login}
</Link>
<p className="text-gray-600 dark:text-gray-400">{formattedBio}</p>
<div className="flex flex-col items-start lg:flex-row">
<div className="mb-4 flex-shrink-0 self-center lg:mr-6 lg:mb-0 lg:self-start">
<Image
width={200}
height={200}
className="h-[200px] w-[200px] rounded-full border-2 border-white bg-white object-cover shadow-md dark:border-gray-800 dark:bg-gray-600/60"
src={user?.avatarUrl || '/placeholder.svg'}
alt={user?.name || user?.login || 'User Avatar'}
/>
</div>
<div className="flex w-full flex-1 flex-col">
<div className="mb-0 text-center lg:mb-4 lg:ml-[26px] lg:text-left">
<Link href={user?.url || '#'} className="text-xl font-bold text-blue-400 hover:underline">
@{user?.login}
</Link>
<p className="text-gray-600 dark:text-gray-400">{formattedBio}</p>
</div>

{!isPrivateContributor && (
<div className="hidden w-full lg:block">
<Heatmap />
</div>
)}
</div>
</div>
)

return (
<DetailsCard
details={userDetails}
heatmap={isPrivateContributor ? undefined : <Heatmap />}
pullRequests={pullRequests}
recentIssues={issues}
recentMilestones={milestones}
Expand Down
17 changes: 1 addition & 16 deletions frontend/src/components/CardDetailsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
faCircleInfo,
faSquarePollVertical,
faChartPie,
faFolderOpen,
faCode,
Expand Down Expand Up @@ -50,7 +49,6 @@ const DetailsCard = ({
entityKey,
geolocationData = null,
healthMetricsData,
heatmap,
isActive = true,
languages,
projectName,
Expand Down Expand Up @@ -116,20 +114,7 @@ const DetailsCard = ({
</SecondaryCard>
)}

{userSummary && (
<SecondaryCard icon={faCircleInfo} title={<AnchorTitle title="Summary" />}>
{userSummary}
</SecondaryCard>
)}

{heatmap && (
<SecondaryCard
icon={faSquarePollVertical}
title={<AnchorTitle title="Contribution Heatmap" />}
>
{heatmap}
</SecondaryCard>
)}
{userSummary && <SecondaryCard>{userSummary}</SecondaryCard>}
<div className="grid grid-cols-1 gap-6 md:grid-cols-7">
<SecondaryCard
icon={faRectangleList}
Expand Down