diff --git a/frontend/__tests__/a11y/components/ContributionHeatmap.a11y.test.tsx b/frontend/__tests__/a11y/components/ContributionHeatmap.a11y.test.tsx index 2ee9d3adec..84620e7d00 100644 --- a/frontend/__tests__/a11y/components/ContributionHeatmap.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/ContributionHeatmap.a11y.test.tsx @@ -78,7 +78,7 @@ describe.each([ it('should not have any accessibility violations', async () => { const { container } = render() - await screen.findByTestId('mock-heatmap-chart') + await screen.findAllByTestId('mock-heatmap-chart') const results = await axe(container) diff --git a/frontend/__tests__/a11y/components/RepositoryCard.a11y.test.tsx b/frontend/__tests__/a11y/components/RepositoryCard.a11y.test.tsx index 427e07caaa..238051e381 100644 --- a/frontend/__tests__/a11y/components/RepositoryCard.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/RepositoryCard.a11y.test.tsx @@ -47,7 +47,7 @@ describe.each([ }) it('should not have any accessibility violations when expanded', async () => { - const repositories = Array.from({ length: 6 }, (_, i) => createMockRepository(i)) + const repositories = Array.from({ length: 7 }, (_, i) => createMockRepository(i)) const { container } = render() const showMoreButton = screen.getByRole('button', { name: /show more/i }) diff --git a/frontend/__tests__/unit/components/ContributionHeatmap.test.tsx b/frontend/__tests__/unit/components/ContributionHeatmap.test.tsx index 7a55210905..8eebc45794 100644 --- a/frontend/__tests__/unit/components/ContributionHeatmap.test.tsx +++ b/frontend/__tests__/unit/components/ContributionHeatmap.test.tsx @@ -85,7 +85,9 @@ describe('ContributionHeatmap', () => { describe('Rendering & Props', () => { it('renders with minimal and all optional props', async () => { const { rerender } = renderWithTheme() - await waitFor(() => expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument()) + await waitFor(() => + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() + ) expect(screen.queryByRole('heading')).not.toBeInTheDocument() rerender( @@ -99,16 +101,16 @@ describe('ContributionHeatmap', () => { it('renders all 7 day series and correct chart type', () => { renderWithTheme() ;['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].forEach((day) => - expect(screen.getByTestId(`series-${day}`)).toBeInTheDocument() + expect(screen.getAllByTestId(`series-${day}`)[0]).toBeInTheDocument() ) - expect(screen.getByTestId('mock-heatmap-chart')).toHaveAttribute('data-type', 'heatmap') + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toHaveAttribute('data-type', 'heatmap') }) it('applies custom unit and handles undefined title', () => { renderWithTheme( ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() expect(screen.queryByRole('heading')).not.toBeInTheDocument() }) }) @@ -124,7 +126,7 @@ describe('ContributionHeatmap', () => { const { unmount } = renderWithTheme( ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() unmount() }) }) @@ -139,7 +141,7 @@ describe('ContributionHeatmap', () => { const { unmount } = renderWithTheme( ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() unmount() }) }) @@ -151,7 +153,7 @@ describe('ContributionHeatmap', () => { endDate: '2024-01-31', } renderWithTheme() - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles large datasets (365 days)', () => { @@ -168,29 +170,35 @@ describe('ContributionHeatmap', () => { endDate="2024-12-31" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles missing startDate by using default dates', () => { renderWithTheme( ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() // Should render with default date range (1 year) - expect(screen.getByTestId('mock-heatmap-chart')).toHaveAttribute('data-series-length', '7') + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toHaveAttribute( + 'data-series-length', + '7' + ) }) it('handles missing endDate by using default dates', () => { renderWithTheme( ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() - expect(screen.getByTestId('mock-heatmap-chart')).toHaveAttribute('data-series-length', '7') + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toHaveAttribute( + 'data-series-length', + '7' + ) }) it('handles both missing startDate and endDate', () => { renderWithTheme() - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles invalid startDate by using default dates', () => { @@ -201,7 +209,7 @@ describe('ContributionHeatmap', () => { endDate="2024-01-31" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles invalid endDate by using default dates', () => { @@ -212,7 +220,7 @@ describe('ContributionHeatmap', () => { endDate="not-a-date" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles both invalid startDate and endDate', () => { @@ -223,7 +231,7 @@ describe('ContributionHeatmap', () => { endDate="also-garbage" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles swapped dates (startDate > endDate) by swapping them', () => { @@ -234,8 +242,11 @@ describe('ContributionHeatmap', () => { endDate="2024-01-01" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() - expect(screen.getByTestId('mock-heatmap-chart')).toHaveAttribute('data-series-length', '7') + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toHaveAttribute( + 'data-series-length', + '7' + ) }) it('handles startDate after endDate and swaps them correctly', () => { @@ -246,7 +257,7 @@ describe('ContributionHeatmap', () => { renderWithTheme( ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) }) @@ -299,7 +310,7 @@ describe('ContributionHeatmap', () => { describe('Chart Configuration & Performance', () => { it('sets correct dimensions and series count', () => { renderWithTheme() - const chart = screen.getByTestId('mock-heatmap-chart') + const chart = screen.getAllByTestId('mock-heatmap-chart')[0] expect(chart).toHaveAttribute('data-height', '195') expect(chart).toHaveAttribute('data-series-length', '7') }) @@ -316,24 +327,26 @@ describe('ContributionHeatmap', () => { ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles dynamic import with SSR disabled', async () => { renderWithTheme() - await waitFor(() => expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument()) + await waitFor(() => + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() + ) }) }) describe('Tooltip Behavior', () => { it('generates correct tooltip with date formatting', () => { renderWithTheme() - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles singular and plural unit labels in tooltip', () => { const { rerender } = renderWithTheme() - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() const singleData = { '2024-01-01': 1 } rerender( @@ -346,26 +359,26 @@ describe('ContributionHeatmap', () => { /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('tooltip respects theme colors', () => { const { rerender } = renderWithTheme(, 'light') - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() ;(useTheme as jest.Mock).mockReturnValue({ theme: 'dark', setTheme: jest.fn() }) rerender( ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles missing data in tooltip gracefully', () => { const { container } = renderWithTheme( ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() const styleTag = container.querySelector('style') expect(styleTag).toBeInTheDocument() @@ -381,7 +394,7 @@ describe('ContributionHeatmap', () => { renderWithTheme( ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles week transitions correctly', () => { @@ -392,7 +405,7 @@ describe('ContributionHeatmap', () => { renderWithTheme( ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) }) @@ -412,7 +425,7 @@ describe('ContributionHeatmap', () => { endDate="2024-01-31" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles boundary values in color ranges', () => { @@ -434,7 +447,7 @@ describe('ContributionHeatmap', () => { endDate="2024-01-31" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) }) @@ -452,7 +465,7 @@ describe('ContributionHeatmap', () => { endDate="2024-03-01" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles year boundaries correctly', () => { @@ -469,7 +482,7 @@ describe('ContributionHeatmap', () => { endDate="2024-01-02" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles dates in reverse chronological order in data object', () => { @@ -486,7 +499,7 @@ describe('ContributionHeatmap', () => { endDate="2024-01-31" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) }) @@ -503,7 +516,7 @@ describe('ContributionHeatmap', () => { endDate="2024-01-31" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles floating point contribution values', () => { @@ -518,7 +531,7 @@ describe('ContributionHeatmap', () => { endDate="2024-01-31" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles extremely large contribution counts', () => { @@ -533,7 +546,7 @@ describe('ContributionHeatmap', () => { endDate="2024-01-31" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) }) @@ -554,14 +567,14 @@ describe('ContributionHeatmap', () => { ) } - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles unmounting and remounting', () => { const { unmount } = renderWithTheme() unmount() renderWithTheme() - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) }) @@ -578,38 +591,38 @@ describe('ContributionHeatmap', () => { endDate="2024-01-31" /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('handles custom unit strings with special characters', () => { const { unmount } = renderWithTheme( ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() unmount() const { rerender } = renderWithTheme() - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() rerender( ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) }) describe('Chart Options Validation', () => { it('configures chart with correct options structure', () => { renderWithTheme() - const chart = screen.getByTestId('mock-heatmap-chart') + const chart = screen.getAllByTestId('mock-heatmap-chart')[0] expect(chart).toHaveAttribute('data-type', 'heatmap') }) it('renders heatmap chart', () => { renderWithTheme() - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) }) @@ -654,9 +667,9 @@ describe('ContributionHeatmap', () => { ) expect(screen.getByText('GitHub Contributions')).toBeInTheDocument() - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() ;['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].forEach((day) => - expect(screen.getByTestId(`series-${day}`)).toBeInTheDocument() + expect(screen.getAllByTestId(`series-${day}`)[0]).toBeInTheDocument() ) }) @@ -671,7 +684,7 @@ describe('ContributionHeatmap', () => { ) expect(screen.getByText('No Activity')).toBeInTheDocument() - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) it('renders correctly with all edge cases combined', () => { @@ -692,62 +705,35 @@ describe('ContributionHeatmap', () => { /> ) - expect(screen.getByTestId('mock-heatmap-chart')).toBeInTheDocument() + expect(screen.getAllByTestId('mock-heatmap-chart')[0]).toBeInTheDocument() }) }) describe('Variants', () => { it('renders default variant with full dimensions', () => { renderWithTheme() - const chart = screen.getByTestId('mock-heatmap-chart') + const chart = screen.getAllByTestId('mock-heatmap-chart')[0] // Verify full-size dimensions (195px height for default variant) expect(chart).toHaveAttribute('data-height', '195') }) it('renders medium variant with medium dimensions', () => { renderWithTheme() - const chart = screen.getByTestId('mock-heatmap-chart') + const chart = screen.getAllByTestId('mock-heatmap-chart')[0] // Verify medium dimensions (172px height for medium variant) expect(chart).toHaveAttribute('data-height', '172') }) it('renders compact variant with smaller dimensions', () => { renderWithTheme() - const chart = screen.getByTestId('mock-heatmap-chart') + const chart = screen.getAllByTestId('mock-heatmap-chart')[0] // Verify compact dimensions (150px height for compact variant) expect(chart).toHaveAttribute('data-height', '150') }) - it('applies compact-specific container styling when variant is compact', () => { - const { container } = renderWithTheme( - - ) - // Verify compact variant uses inline-block class - const chartContainer = container.querySelector('.inline-block') - expect(chartContainer).toBeInTheDocument() - }) - - it('applies default variant container styling when variant is default', () => { - const { container } = renderWithTheme( - - ) - // Verify default variant uses inline-block class - const chartContainer = container.querySelector('.inline-block') - expect(chartContainer).toBeInTheDocument() - }) - - it('applies medium variant container styling', () => { - const { container } = renderWithTheme( - - ) - // Verify medium variant uses inline-block class - const chartContainer = container.querySelector('.inline-block') - expect(chartContainer).toBeInTheDocument() - }) - it('defaults to default variant when no variant is specified', () => { renderWithTheme() - const chart = screen.getByTestId('mock-heatmap-chart') + const chart = screen.getAllByTestId('mock-heatmap-chart')[0] // Should render with default variant dimensions expect(chart).toHaveAttribute('data-height', '195') }) diff --git a/frontend/__tests__/unit/components/InfoBlock.test.tsx b/frontend/__tests__/unit/components/InfoBlock.test.tsx index 7186e0b326..f2baacfeac 100644 --- a/frontend/__tests__/unit/components/InfoBlock.test.tsx +++ b/frontend/__tests__/unit/components/InfoBlock.test.tsx @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event' import '@testing-library/jest-dom' import millify from 'millify' import React from 'react' -import { FaUser, FaStar, FaCode } from 'react-icons/fa6' +import { FaUser, FaStar } from 'react-icons/fa6' import { pluralize } from 'utils/pluralize' import InfoBlock from 'components/InfoBlock' @@ -73,37 +73,16 @@ describe('InfoBlock Component', () => { unit="contributor" pluralizedName="contributors" precision={2} - label="Team Members" className="custom-class" /> ) expect(screen.getByTestId('icon-star')).toBeInTheDocument() - expect(screen.getByText('Team Members')).toBeInTheDocument() expect(screen.getByText('1.5k contributors')).toBeInTheDocument() }) }) describe('Conditional rendering logic', () => { - it('should show label when provided', () => { - mockMillify.mockReturnValue('50') - mockPluralize.mockReturnValue('projects') - - render() - - expect(screen.getByText('Active Projects')).toBeInTheDocument() - expect(screen.getByText('Active Projects')).toHaveClass('text-sm', 'font-medium') - }) - - it('should not show label when not provided', () => { - mockMillify.mockReturnValue('50') - mockPluralize.mockReturnValue('projects') - - render() - - expect(screen.queryByText('Active Projects')).not.toBeInTheDocument() - }) - it('should show "No" prefix when value is 0', () => { mockMillify.mockReturnValue('0') mockPluralize.mockReturnValue('items') @@ -233,20 +212,6 @@ describe('InfoBlock Component', () => { }) describe('Default values and fallbacks', () => { - it('should not render a label element when label prop is not provided', () => { - mockMillify.mockReturnValue('100') - mockPluralize.mockReturnValue('items') - - render() - - // Verify only the formatted value is rendered, no label - expect(screen.getByText('100 items')).toBeInTheDocument() - - // Verify no element with label styling exists - const labelElements = document.querySelectorAll('.text-sm.font-medium') - expect(labelElements).toHaveLength(0) - }) - it('should use empty string as default className', () => { mockMillify.mockReturnValue('100') mockPluralize.mockReturnValue('items') @@ -286,16 +251,6 @@ describe('InfoBlock Component', () => { expect(screen.getByText('2.5k stars')).toBeInTheDocument() }) - it('should render label with correct styling', () => { - mockMillify.mockReturnValue('100') - mockPluralize.mockReturnValue('repos') - - render() - - const label = screen.getByText('Repositories') - expect(label).toHaveClass('text-sm', 'font-medium') - }) - it('should render tooltip content correctly for positive values', () => { mockMillify.mockReturnValue('1.5k') mockPluralize.mockReturnValue('contributors') @@ -379,10 +334,10 @@ describe('InfoBlock Component', () => { expect(wrapper).toHaveClass('flex') const icon = screen.getByTestId('icon-user') - expect(icon).toHaveClass('mr-3', 'mt-1', 'w-5') + expect(icon).toHaveClass('w-5', 'text-gray-500') const contentDiv = wrapper.children[1] as HTMLElement - expect(contentDiv.children[0]).toHaveClass('text-sm', 'md:text-base') + expect(contentDiv).toHaveClass('text-base', 'text-gray-600') }) it('should apply custom className correctly', () => { @@ -404,20 +359,7 @@ describe('InfoBlock Component', () => { render() const icon = screen.getByTestId('icon-user') - expect(icon).toHaveClass('mr-3', 'mt-1', 'w-5') - }) - - it('should have correct text container styling', () => { - mockMillify.mockReturnValue('100') - mockPluralize.mockReturnValue('items') - - render() - - const textContainer = screen.getByText('100 items').closest('div') - expect(textContainer).toHaveClass('text-sm', 'md:text-base') - - const label = screen.getByText('Test Label') - expect(label).toHaveClass('text-sm', 'font-medium') + expect(icon).toHaveClass('w-5', 'text-gray-500') }) }) diff --git a/frontend/__tests__/unit/components/Milestones.test.tsx b/frontend/__tests__/unit/components/Milestones.test.tsx index 0dfd45001e..8b3d990105 100644 --- a/frontend/__tests__/unit/components/Milestones.test.tsx +++ b/frontend/__tests__/unit/components/Milestones.test.tsx @@ -193,7 +193,6 @@ describe('Milestones', () => { render() expect(screen.getByText('Jan 1, 2023')).toBeInTheDocument() - expect(screen.getByText('10 closed')).toBeInTheDocument() expect(screen.getByText('5 open')).toBeInTheDocument() expect(screen.getByTestId('truncated-text')).toHaveTextContent('awesome-repo') }) @@ -246,9 +245,6 @@ describe('Milestones', () => { expect(screen.getByTestId('milestone-0')).toBeInTheDocument() expect(screen.getByTestId('milestone-1')).toBeInTheDocument() expect(screen.getByTestId('milestone-2')).toBeInTheDocument() - expect(screen.getByText('5 closed')).toBeInTheDocument() - expect(screen.getByText('10 closed')).toBeInTheDocument() - expect(screen.getByText('15 closed')).toBeInTheDocument() }) it('handles edge case with zero counts', () => { @@ -256,7 +252,6 @@ describe('Milestones', () => { ) - expect(screen.getByText('0 closed')).toBeInTheDocument() expect(screen.getByText('0 open')).toBeInTheDocument() }) @@ -265,7 +260,6 @@ describe('Milestones', () => { ) - expect(screen.getByText('999 closed')).toBeInTheDocument() expect(screen.getByText('1000 open')).toBeInTheDocument() }) diff --git a/frontend/__tests__/unit/components/RecentRelease.test.tsx b/frontend/__tests__/unit/components/RecentRelease.test.tsx index a7e04d76a2..8b17abbef5 100644 --- a/frontend/__tests__/unit/components/RecentRelease.test.tsx +++ b/frontend/__tests__/unit/components/RecentRelease.test.tsx @@ -382,10 +382,6 @@ describe('RecentReleases Component', () => { }) // Check for main card structure - look for the card wrapper - const cardElement = container.querySelector( - String.raw`.mb-4.w-full.rounded-lg.bg-gray-200.p-4.dark\:bg-gray-700` - ) - expect(cardElement).toBeInTheDocument() // Check for proper grid layout const gridElement = container.querySelector('.grid') diff --git a/frontend/__tests__/unit/components/RepositoryCard.test.tsx b/frontend/__tests__/unit/components/RepositoryCard.test.tsx index a46b483cad..c3e943604f 100644 --- a/frontend/__tests__/unit/components/RepositoryCard.test.tsx +++ b/frontend/__tests__/unit/components/RepositoryCard.test.tsx @@ -82,65 +82,67 @@ describe('RepositoryCard', () => { expect(container.querySelector('.grid')).toBeNull() }) - it('shows first 4 repositories initially when there are more than 4', () => { - const repositories = Array.from({ length: 6 }, (_, i) => createMockRepository(i)) + it('shows first 6 repositories initially when there are more than 6', () => { + const repositories = Array.from({ length: 8 }, (_, i) => createMockRepository(i)) render() expect(screen.getByText('Repository 0')).toBeInTheDocument() - expect(screen.getByText('Repository 3')).toBeInTheDocument() - expect(screen.queryByText('Repository 4')).not.toBeInTheDocument() - expect(screen.queryByText('Repository 5')).not.toBeInTheDocument() + expect(screen.getByText('Repository 5')).toBeInTheDocument() + expect(screen.queryByText('Repository 6')).not.toBeInTheDocument() + expect(screen.queryByText('Repository 7')).not.toBeInTheDocument() }) - it('shows all repositories when there are 4 or fewer', () => { - const repositories = Array.from({ length: 3 }, (_, i) => createMockRepository(i)) + it('shows all repositories when there are 6 or fewer', () => { + const repositories = Array.from({ length: 5 }, (_, i) => createMockRepository(i)) render() expect(screen.getByText('Repository 0')).toBeInTheDocument() expect(screen.getByText('Repository 1')).toBeInTheDocument() expect(screen.getByText('Repository 2')).toBeInTheDocument() + expect(screen.getByText('Repository 3')).toBeInTheDocument() + expect(screen.getByText('Repository 4')).toBeInTheDocument() }) - it('displays ShowMoreButton when there are more than 4 repositories', () => { - const repositories = Array.from({ length: 6 }, (_, i) => createMockRepository(i)) + it('displays ShowMoreButton when there are more than 6 repositories', () => { + const repositories = Array.from({ length: 8 }, (_, i) => createMockRepository(i)) render() expect(screen.getByRole('button', { name: /Show/ })).toBeInTheDocument() }) - it('does not display ShowMoreButton when there are 4 or fewer repositories', () => { - const repositories = Array.from({ length: 4 }, (_, i) => createMockRepository(i)) + it('does not display ShowMoreButton when there are 6 or fewer repositories', () => { + const repositories = Array.from({ length: 6 }, (_, i) => createMockRepository(i)) render() expect(screen.queryByRole('button', { name: /Show/ })).not.toBeInTheDocument() }) - it('toggles between showing 4 and all repositories when clicked', () => { - const repositories = Array.from({ length: 6 }, (_, i) => createMockRepository(i)) + it('toggles between showing 6 and all repositories when clicked', () => { + const repositories = Array.from({ length: 8 }, (_, i) => createMockRepository(i)) render() - // Initially shows first 4 + // Initially shows first 6 expect(screen.getByText('Repository 0')).toBeInTheDocument() - expect(screen.queryByText('Repository 4')).not.toBeInTheDocument() + expect(screen.queryByText('Repository 6')).not.toBeInTheDocument() // Click show more fireEvent.click(screen.getByRole('button', { name: /Show/ })) // Now shows all repositories - expect(screen.getByText('Repository 4')).toBeInTheDocument() - expect(screen.getByText('Repository 5')).toBeInTheDocument() + expect(screen.getByText('Repository 6')).toBeInTheDocument() + expect(screen.getByText('Repository 7')).toBeInTheDocument() // Click show less fireEvent.click(screen.getByRole('button', { name: /Show/ })) - // Back to showing first 4 - expect(screen.queryByText('Repository 4')).not.toBeInTheDocument() - expect(screen.queryByText('Repository 5')).not.toBeInTheDocument() + // Back to showing first 6 + expect(screen.queryByText('Repository 6')).not.toBeInTheDocument() + expect(screen.queryByText('Repository 7')).not.toBeInTheDocument() }) it('renders repository items with correct information', () => { @@ -311,22 +313,22 @@ describe('RepositoryCard', () => { }) it('archived badge persists when toggling show more/less', () => { - const repositories = Array.from({ length: 6 }, (_, i) => ({ + const repositories = Array.from({ length: 8 }, (_, i) => ({ ...createMockRepository(i), isArchived: i % 2 === 0, })) render() - expect(screen.getAllByText('Archived')).toHaveLength(2) + expect(screen.getAllByText('Archived')).toHaveLength(3) fireEvent.click(screen.getByRole('button', { name: /Show/ })) - expect(screen.getAllByText('Archived')).toHaveLength(3) + expect(screen.getAllByText('Archived')).toHaveLength(4) fireEvent.click(screen.getByRole('button', { name: /Show/ })) - expect(screen.getAllByText('Archived')).toHaveLength(2) + expect(screen.getAllByText('Archived')).toHaveLength(3) }) it('clicking archived repository still navigates correctly', () => { diff --git a/frontend/__tests__/unit/pages/Home.test.tsx b/frontend/__tests__/unit/pages/Home.test.tsx index 0f0d3e9232..06960c6331 100644 --- a/frontend/__tests__/unit/pages/Home.test.tsx +++ b/frontend/__tests__/unit/pages/Home.test.tsx @@ -213,7 +213,6 @@ describe('Home', () => { expect(screen.getByText(milestone.title)).toBeInTheDocument() expect(screen.getByText(milestone.repositoryName)).toBeInTheDocument() expect(screen.getByText(`${milestone.openIssuesCount} open`)).toBeInTheDocument() - expect(screen.getByText(`${milestone.closedIssuesCount} closed`)).toBeInTheDocument() } }) }) diff --git a/frontend/__tests__/unit/pages/OrganizationDetails.test.tsx b/frontend/__tests__/unit/pages/OrganizationDetails.test.tsx index 70f832992a..c851c3e850 100644 --- a/frontend/__tests__/unit/pages/OrganizationDetails.test.tsx +++ b/frontend/__tests__/unit/pages/OrganizationDetails.test.tsx @@ -118,7 +118,6 @@ describe('OrganizationDetailsPage', () => { expect(screen.getByText(milestone.title)).toBeInTheDocument() expect(screen.getByText(milestone.repositoryName)).toBeInTheDocument() expect(screen.getByText(`${milestone.openIssuesCount} open`)).toBeInTheDocument() - expect(screen.getByText(`${milestone.closedIssuesCount} closed`)).toBeInTheDocument() } }) }) diff --git a/frontend/__tests__/unit/pages/ProjectDetails.test.tsx b/frontend/__tests__/unit/pages/ProjectDetails.test.tsx index 4afd72e511..7753332299 100644 --- a/frontend/__tests__/unit/pages/ProjectDetails.test.tsx +++ b/frontend/__tests__/unit/pages/ProjectDetails.test.tsx @@ -260,7 +260,6 @@ describe('ProjectDetailsPage', () => { expect(screen.getByText(milestone.title)).toBeInTheDocument() expect(screen.getByText(milestone.repositoryName)).toBeInTheDocument() expect(screen.getByText(`${milestone.openIssuesCount} open`)).toBeInTheDocument() - expect(screen.getByText(`${milestone.closedIssuesCount} closed`)).toBeInTheDocument() } }) }) diff --git a/frontend/__tests__/unit/pages/RepositoryDetails.test.tsx b/frontend/__tests__/unit/pages/RepositoryDetails.test.tsx index c28c160b8f..db71bb4397 100644 --- a/frontend/__tests__/unit/pages/RepositoryDetails.test.tsx +++ b/frontend/__tests__/unit/pages/RepositoryDetails.test.tsx @@ -188,7 +188,6 @@ describe('RepositoryDetailsPage', () => { expect(screen.getByText(milestone.title)).toBeInTheDocument() expect(screen.getByText(milestone.repositoryName)).toBeInTheDocument() expect(screen.getByText(`${milestone.openIssuesCount} open`)).toBeInTheDocument() - expect(screen.getByText(`${milestone.closedIssuesCount} closed`)).toBeInTheDocument() } }) }) diff --git a/frontend/__tests__/unit/pages/UserDetails.test.tsx b/frontend/__tests__/unit/pages/UserDetails.test.tsx index 154dbd1f77..4ce7410edf 100644 --- a/frontend/__tests__/unit/pages/UserDetails.test.tsx +++ b/frontend/__tests__/unit/pages/UserDetails.test.tsx @@ -12,32 +12,6 @@ jest.mock('@apollo/client/react', () => ({ useQuery: jest.fn(), })) -// Mock Badges component -jest.mock('components/Badges', () => { - const MockBadges = ({ - name, - cssClass, - showTooltip, - }: { - name: string - cssClass: string - showTooltip?: boolean - }) => ( -
- -
- ) - MockBadges.displayName = 'MockBadges' - return { - __esModule: true, - default: MockBadges, - } -}) - const mockRouter = { push: jest.fn(), } @@ -72,13 +46,11 @@ describe('UserSummary', () => { name: 'John Doe', avatarUrl: 'https://example.com/avatar.png', url: 'https://github.com/johndoe', + bio: 'Bio text', } render( [0]['user']} formattedBio={Bio text} /> ) @@ -90,25 +62,6 @@ describe('UserSummary', () => { expect(screen.getByText('Bio text')).toBeInTheDocument() }) - test('renders contribution heatmap when hasContributionData is true', () => { - const user = { - login: 'jane', - name: 'Jane', - avatarUrl: '/avatar.png', - url: 'https://github.com/jane', - } - render( - - ) - expect(screen.getByTestId('contribution-heatmap')).toBeInTheDocument() - }) - test('uses login as avatar alt when user has no name', () => { const user = { login: 'nologin', @@ -117,50 +70,10 @@ describe('UserSummary', () => { url: 'https://github.com/nologin', } render( - + [0]['user']} formattedBio={null} /> ) expect(screen.getByRole('img', { name: 'nologin' })).toBeInTheDocument() }) - - test('uses placeholder avatar and fallback alt when user is null', () => { - render( - - ) - const img = screen.getByRole('img', { name: 'User Avatar' }) - expect(img).toHaveAttribute('src', expect.stringContaining('placeholder.svg')) - }) - - test('renders badges when user has badges', () => { - const user = { - login: 'badged', - name: 'Badged User', - avatarUrl: '/a.png', - url: 'https://github.com/badged', - badges: [{ id: 'b1', name: 'Star', cssClass: 'fa-star', description: 'Star', weight: 1 }], - } - render( - - ) - expect(screen.getByTestId('badge-star')).toBeInTheDocument() - }) }) describe('UserDetailsPage', () => { @@ -176,21 +89,6 @@ describe('UserDetailsPage', () => { jest.clearAllMocks() }) - // Helper functions to reduce nesting depth - const getBadgeElements = () => { - return screen.getAllByTestId(/^badge-/) - } - - const getBadgeTestIds = () => { - const badgeElements = getBadgeElements() - return badgeElements.map((element) => element.dataset.testid) - } - - const expectBadgesInCorrectOrder = (expectedOrder: string[]) => { - const badgeTestIds = getBadgeTestIds() - expect(badgeTestIds).toEqual(expectedOrder) - } - test('renders loading state', async () => { ;(useQuery as unknown as jest.Mock).mockReturnValue({ data: null, @@ -222,7 +120,6 @@ describe('UserDetailsPage', () => { expect(screen.getByText('Test User')).toBeInTheDocument() }) - expect(screen.getByText('Statistics')).toBeInTheDocument() expect(screen.getByText('Test Company')).toBeInTheDocument() expect(screen.getByText('Test Location')).toBeInTheDocument() expect(screen.getByText('10 Followers')).toBeInTheDocument() @@ -305,7 +202,6 @@ describe('UserDetailsPage', () => { expect(screen.getByText(milestone.title)).toBeInTheDocument() expect(screen.getByText(milestone.repositoryName)).toBeInTheDocument() expect(screen.getByText(`${milestone.openIssuesCount} open`)).toBeInTheDocument() - expect(screen.getByText(`${milestone.closedIssuesCount} closed`)).toBeInTheDocument() } }) }) @@ -344,9 +240,6 @@ describe('UserDetailsPage', () => { render() await waitFor(() => { - const statisticsTitle = screen.getByText('Statistics') - expect(statisticsTitle).toBeInTheDocument() - const followersCount = screen.getByText('10 Followers') expect(followersCount).toBeInTheDocument() @@ -413,13 +306,11 @@ describe('UserDetailsPage', () => { expect(userName).toBeInTheDocument() const avatar = screen.getByAltText('Test User') expect(avatar).toHaveClass('rounded-full') - expect(avatar).toHaveClass('h-[200px]') - expect(avatar).toHaveClass('w-[200px]') + expect(avatar).toHaveClass('size-32') // Check for responsive classes const summaryContainer = avatar.closest('div.flex') expect(summaryContainer).toHaveClass('flex-col') - expect(summaryContainer).toHaveClass('lg:flex-row') }) }) @@ -516,8 +407,7 @@ describe('UserDetailsPage', () => { render() await waitFor(() => { - expect(screen.getByText('Recent Issues')).toBeInTheDocument() - expect(screen.getByText('No recent releases.')).toBeInTheDocument() + expect(screen.queryByText('Recent Issues')).not.toBeInTheDocument() }) }) @@ -534,8 +424,7 @@ describe('UserDetailsPage', () => { render() await waitFor(() => { - expect(screen.getByText('Recent Pull Requests')).toBeInTheDocument() - expect(screen.queryByText('Test Pull Request')).not.toBeInTheDocument() + expect(screen.queryByText('Recent Pull Requests')).not.toBeInTheDocument() }) }) @@ -551,8 +440,7 @@ describe('UserDetailsPage', () => { }) render() await waitFor(() => { - expect(screen.getByText('Recent Releases')).toBeInTheDocument() - expect(screen.queryByText('Test v1.0.0')).not.toBeInTheDocument() + expect(screen.queryByText('Recent Releases')).not.toBeInTheDocument() }) }) @@ -568,8 +456,7 @@ describe('UserDetailsPage', () => { }) render() await waitFor(() => { - expect(screen.getByText('Recent Milestones')).toBeInTheDocument() - expect(screen.queryByText('v2.0.0 Release')).not.toBeInTheDocument() + expect(screen.queryByText('Recent Milestones')).not.toBeInTheDocument() }) }) @@ -619,8 +506,7 @@ describe('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') + expect(bioContainer).toHaveClass('text-left') }) }) @@ -635,274 +521,6 @@ describe('UserDetailsPage', () => { }) }) - describe('Badge Display Tests', () => { - test('renders badges section when user has badges', async () => { - ;(useQuery as unknown as jest.Mock).mockReturnValue({ - data: mockUserDetailsData, - loading: false, - error: null, - }) - - render() - await waitFor(() => { - expect(screen.getByTestId('badge-contributor')).toBeInTheDocument() - expect(screen.getByTestId('badge-security-expert')).toBeInTheDocument() - }) - }) - - test('renders badges with correct props', async () => { - ;(useQuery as unknown as jest.Mock).mockReturnValue({ - data: mockUserDetailsData, - loading: false, - error: null, - }) - - render() - await waitFor(() => { - const contributorBadge = screen.getByTestId('badge-contributor') - expect(contributorBadge).toHaveAttribute('data-css-class', 'fa-medal') - expect(contributorBadge).toHaveAttribute('data-show-tooltip', 'true') - - const securityBadge = screen.getByTestId('badge-security-expert') - expect(securityBadge).toHaveAttribute('data-css-class', 'fa-shield-alt') - expect(securityBadge).toHaveAttribute('data-show-tooltip', 'true') - }) - }) - - test('does not render badges section when user has no badges', async () => { - const dataWithoutBadges = { - ...mockUserDetailsData, - user: { - ...mockUserDetailsData.user, - badges: [], - badgeCount: 0, - }, - } - - ;(useQuery as unknown as jest.Mock).mockReturnValue({ - data: dataWithoutBadges, - loading: false, - error: null, - }) - - render() - await waitFor(() => { - expect(screen.queryByTestId(/^badge-/)).not.toBeInTheDocument() - }) - }) - - test('does not render badges section when badges is undefined', async () => { - const dataWithoutBadges = { - ...mockUserDetailsData, - user: { - ...mockUserDetailsData.user, - badges: undefined, - badgeCount: 0, - }, - } - - ;(useQuery as unknown as jest.Mock).mockReturnValue({ - data: dataWithoutBadges, - loading: false, - error: null, - }) - - render() - await waitFor(() => { - expect(screen.queryByTestId(/^badge-/)).not.toBeInTheDocument() - }) - }) - - test('renders badges with fallback cssClass when not provided', async () => { - const dataWithIncompleteBadges = { - ...mockUserDetailsData, - user: { - ...mockUserDetailsData.user, - badges: [ - { - id: '1', - name: 'Test Badge', - cssClass: undefined, - description: 'Test description', - weight: 1, - }, - ], - }, - } - - ;(useQuery as unknown as jest.Mock).mockReturnValue({ - data: dataWithIncompleteBadges, - loading: false, - error: null, - }) - - render() - await waitFor(() => { - const badge = screen.getByTestId('badge-test-badge') - expect(badge).toHaveAttribute('data-css-class', 'medal') - }) - }) - - test('renders badges with empty cssClass fallback', async () => { - const dataWithEmptyCssClass = { - ...mockUserDetailsData, - user: { - ...mockUserDetailsData.user, - badges: [ - { - id: '1', - name: 'Test Badge', - cssClass: '', - description: 'Test description', - weight: 1, - }, - ], - }, - } - - ;(useQuery as unknown as jest.Mock).mockReturnValue({ - data: dataWithEmptyCssClass, - loading: false, - error: null, - }) - - render() - await waitFor(() => { - const badge = screen.getByTestId('badge-test-badge') - expect(badge).toHaveAttribute('data-css-class', 'medal') - }) - }) - - test('handles badges with special characters in names', async () => { - const dataWithSpecialBadges = { - ...mockUserDetailsData, - user: { - ...mockUserDetailsData.user, - badges: [ - { - id: '1', - name: 'Badge & More!', - cssClass: 'fa-star', - description: 'Special badge', - weight: 1, - }, - ], - }, - } - ;(useQuery as unknown as jest.Mock).mockReturnValue({ - data: dataWithSpecialBadges, - loading: false, - error: null, - }) - - render() - await waitFor(() => { - expect(screen.getByTestId('badge-badge-&-more!')).toBeInTheDocument() - }) - }) - - test('handles badges with long names', async () => { - const dataWithLongNameBadge = { - ...mockUserDetailsData, - user: { - ...mockUserDetailsData.user, - badges: [ - { - id: '1', - name: 'Very Long Badge Name That Exceeds Normal Length', - cssClass: 'fa-trophy', - description: 'Long name badge', - weight: 1, - }, - ], - }, - } - - ;(useQuery as unknown as jest.Mock).mockReturnValue({ - data: dataWithLongNameBadge, - loading: false, - error: null, - }) - - render() - await waitFor(() => { - expect( - screen.getByTestId('badge-very-long-badge-name-that-exceeds-normal-length') - ).toBeInTheDocument() - }) - }) - - // eslint-disable-next-line jest/expect-expect - test('renders badges in correct order as returned by backend (weight ASC then name ASC)', async () => { - // Backend returns badges sorted by weight ASC, then name ASC - // This test verifies the frontend preserves the backend ordering - const dataWithOrderedBadges = { - ...mockUserDetailsData, - user: { - ...mockUserDetailsData.user, - badges: [ - // Backend returns badges in this order: weight ASC, then name ASC - { - id: '3', - name: 'Alpha Badge', - cssClass: 'fa-star', - description: 'Alpha badge with weight 1', - weight: 1, - }, - { - id: '4', - name: 'Beta Badge', - cssClass: 'fa-trophy', - description: 'Beta badge with weight 1', - weight: 1, - }, - { - id: '1', - name: 'Contributor', - cssClass: 'medal', - description: 'Active contributor', - weight: 1, - }, - { - id: '2', - name: 'Security Expert', - cssClass: 'fa-shield-alt', - description: 'Security expertise', - weight: 2, - }, - { - id: '5', - name: 'Top Contributor', - cssClass: 'fa-crown', - description: 'Highest weight badge', - weight: 3, - }, - ], - badgeCount: 5, - }, - } - - ;(useQuery as unknown as jest.Mock).mockReturnValue({ - data: dataWithOrderedBadges, - loading: false, - error: null, - }) - - render() - await waitFor(() => { - // Expected order matches backend contract: weight ASC (1, 1, 1, 2, 3), then name ASC for equal weights - const expectedOrder = [ - 'badge-alpha-badge', // weight 1, name ASC - 'badge-beta-badge', // weight 1, name ASC - 'badge-contributor', // weight 1, name ASC - 'badge-security-expert', // weight 2 - 'badge-top-contributor', // weight 3 - ] - expectBadgesInCorrectOrder(expectedOrder) - }) - }) - }) - describe('Contribution Heatmap', () => { test('does not render heatmap when user has empty contribution data', async () => { const dataWithEmptyContributions = { @@ -1121,7 +739,7 @@ describe('UserDetailsPage', () => { render() await waitFor(() => { - expect(screen.getByText('Joined:')).toBeInTheDocument() + expect(screen.getByText('Joined :')).toBeInTheDocument() expect(screen.getByText('Not available')).toBeInTheDocument() }) }) diff --git a/frontend/src/app/members/[memberKey]/page.tsx b/frontend/src/app/members/[memberKey]/page.tsx index 15e69f3ef6..8981c9e863 100644 --- a/frontend/src/app/members/[memberKey]/page.tsx +++ b/frontend/src/app/members/[memberKey]/page.tsx @@ -1,89 +1,30 @@ 'use client' import { useQuery } from '@apollo/client/react' -import Image from 'next/image' import Link from 'next/link' import { useParams } from 'next/navigation' import React, { useEffect, useMemo } from 'react' -import { FaCodeMerge, FaFolderOpen, FaPersonWalkingArrowRight, FaUserPlus } from 'react-icons/fa6' import { handleAppError, ErrorDisplay } from 'app/global-error' import { GetUserDataDocument } from 'types/__generated__/userQueries.generated' -import { Badge } from 'types/badge' import { User } from 'types/user' -import { formatDate } from 'utils/dateFormatter' -import Badges from 'components/Badges' -import Contributions from 'components/cards/Contributions' -import Contributors from 'components/cards/Contributors' -import Header from 'components/cards/Header' -import IssuesMilestones from 'components/cards/IssuesMilestones' -import Metadata from 'components/cards/Metadata' +import MemberDetailSidebar from 'components/cards/MemberDetailSidebar' import PageWrapper from 'components/cards/PageWrapper' import RepositoriesModules from 'components/cards/RepositoriesModules' -import Summary from 'components/cards/Summary' import ContributionHeatmap from 'components/ContributionHeatmap' -import MemberDetailsPageSkeleton from 'components/skeletons/MemberDetailsPageSkeleton' +import Milestones from 'components/Milestones' +import RecentIssues from 'components/RecentIssues' +import RecentPullRequests from 'components/RecentPullRequests' +import RecentReleases from 'components/RecentReleases' -type DateRange = { startDate: string; endDate: string } +import MemberDetailsPageSkeleton from 'components/skeletons/MemberDetailsPageSkeleton' interface UserSummaryProps { - user: User | null - contributionData: Record - dateRange: DateRange - hasContributionData: boolean + user: User formattedBio: React.ReactNode } -export const UserSummary: React.FC = ({ - user, - contributionData, - dateRange, - hasContributionData, - formattedBio, -}) => ( -
- {user?.name -
-
-
- - @{user?.login} - - {user?.badges && user.badges.length > 0 && ( -
- {user.badges.slice().map((badge: Badge) => ( - - - - ))} -
- )} -
-

{formattedBio}

-
- {hasContributionData && dateRange.startDate && dateRange.endDate && ( -
-
- -
-
- )} -
-
+export const UserSummary: React.FC = ({ user, formattedBio }) => ( + ) const UserDetailsPage: React.FC = () => { @@ -181,56 +122,59 @@ const UserDetailsPage: React.FC = () => { ) } - const userDetails = [ - { label: 'Joined', value: user?.createdAt ? formatDate(user.createdAt) : 'Not available' }, - { label: 'Email', value: user?.email || 'N/A' }, - { label: 'Company', value: user?.company || 'N/A' }, - { label: 'Location', value: user?.location || 'N/A' }, - ] - - const userStats = [ - { icon: FaPersonWalkingArrowRight, value: user?.followersCount || 0, unit: 'Follower' }, - { icon: FaUserPlus, value: user?.followingCount || 0, unit: 'Following' }, - { - icon: FaFolderOpen, - pluralizedName: 'Repositories', - unit: 'Repository', - value: user?.publicRepositoriesCount ?? 0, - }, - { icon: FaCodeMerge, value: user?.contributionsCount || 0, unit: 'Contribution' }, - ] - return ( -
- - - } - /> - - - - - - - - - - +
+
+ + +
+ {hasContributionData && dateRange.startDate && dateRange.endDate && ( +
+
+ +
+
+ )} + + {topRepositories.length > 0 && ( +
+ +
+ )} + +
+ {issues.length > 0 && ( +
+ +
+ )} + {pullRequests.length > 0 && ( +
+ +
+ )} + {milestones.length > 0 && ( +
+ +
+ )} + {releases.length > 0 && ( +
+ +
+ )} +
+
+
+
) } diff --git a/frontend/src/components/ContributionHeatmap.tsx b/frontend/src/components/ContributionHeatmap.tsx index 95a7e2338c..9ff859d089 100644 --- a/frontend/src/components/ContributionHeatmap.tsx +++ b/frontend/src/components/ContributionHeatmap.tsx @@ -282,21 +282,21 @@ const ContributionHeatmap: React.FC = ({ const weeksCount = heatmapSeries[0]?.data?.length || 0 if (variant === 'compact') { - const pixelPerWeek = 13.4 - const padding = 40 + const pixelPerWeek = 14 + const padding = 60 const calculatedWidth = weeksCount * pixelPerWeek + padding return Math.max(400, calculatedWidth) } if (variant === 'medium') { - const pixelPerWeek = 16 - const padding = 45 + const pixelPerWeek = 17 + const padding = 70 const calculatedWidth = weeksCount * pixelPerWeek + padding return Math.max(500, calculatedWidth) } - const pixelPerWeek = 19.5 - const padding = 50 + const pixelPerWeek = 20 + const padding = 80 const calculatedWidth = weeksCount * pixelPerWeek + padding return Math.max(600, calculatedWidth) }, [heatmapSeries, variant]) @@ -316,7 +316,7 @@ const ContributionHeatmap: React.FC = ({ )} {/* scroll wrapper for small screens */} -
+
-
+
= ({ width={chartWidth} />
+
+ +
) diff --git a/frontend/src/components/InfoBlock.tsx b/frontend/src/components/InfoBlock.tsx index 281e9777f1..3787d7723d 100644 --- a/frontend/src/components/InfoBlock.tsx +++ b/frontend/src/components/InfoBlock.tsx @@ -7,14 +7,13 @@ import { pluralize } from 'utils/pluralize' const InfoBlock = ({ className = '', icon, - label = '', pluralizedName, precision = 1, unit = '', value = 0, }: { className?: string - icon: IconType + icon?: IconType label?: string pluralizedName?: string precision?: number @@ -26,15 +25,12 @@ const InfoBlock = ({ const tooltipValue = value ? `${value.toLocaleString()} ${name}` : `No ${name}` return ( -
- -
-
- {label &&
{label}
} - - {formattedValue} - -
+
+ {icon && } +
+ + {formattedValue} +
) diff --git a/frontend/src/components/ItemCardList.tsx b/frontend/src/components/ItemCardList.tsx index 1bf4a997dc..93a3c58f4c 100644 --- a/frontend/src/components/ItemCardList.tsx +++ b/frontend/src/components/ItemCardList.tsx @@ -90,7 +90,7 @@ const ItemCardList = ({ {data && data.length > 0 ? (
{data.map((item, index) => { const getItemKey = (i: ItemCardData, idx: number): string => { @@ -107,7 +107,7 @@ const ItemCardList = ({ return (
diff --git a/frontend/src/components/Milestones.tsx b/frontend/src/components/Milestones.tsx index 5d708718f9..cab31f37e3 100644 --- a/frontend/src/components/Milestones.tsx +++ b/frontend/src/components/Milestones.tsx @@ -1,12 +1,6 @@ import { useRouter } from 'next/navigation' import React from 'react' -import { - FaCalendar, - FaFolderOpen, - FaSignsPost, - FaCircleCheck, - FaCircleExclamation, -} from 'react-icons/fa6' +import { FaCalendar, FaFolderOpen, FaSignsPost, FaCircleExclamation } from 'react-icons/fa6' import type { Milestone } from 'types/milestone' import { formatDate } from 'utils/dateFormatter' import AnchorTitle from 'components/AnchorTitle' @@ -43,10 +37,6 @@ const Milestones: React.FC = ({ {item.createdAt ? formatDate(item.createdAt) : 'N/A'}
-
- - {item.closedIssuesCount} closed -
{item.openIssuesCount} open diff --git a/frontend/src/components/RecentReleases.tsx b/frontend/src/components/RecentReleases.tsx index d1c02d6aff..e736b6a8e8 100644 --- a/frontend/src/components/RecentReleases.tsx +++ b/frontend/src/components/RecentReleases.tsx @@ -27,7 +27,7 @@ const RecentReleases: React.FC = ({ > {data && data.length > 0 ? (
{data.map((item) => ( = ({ } return ( -
+
{showAvatar && release?.author && ( diff --git a/frontend/src/components/RepositoryCard.tsx b/frontend/src/components/RepositoryCard.tsx index 03c0b5a661..985ec273b2 100644 --- a/frontend/src/components/RepositoryCard.tsx +++ b/frontend/src/components/RepositoryCard.tsx @@ -11,7 +11,7 @@ import StatusBadge from 'components/StatusBadge' import { TruncatedText } from 'components/TruncatedText' const RepositoryCard: React.FC = ({ - maxInitialDisplay = 4, + maxInitialDisplay = 6, repositories, }) => { const [showAllRepositories, setShowAllRepositories] = useState(false) @@ -25,7 +25,7 @@ const RepositoryCard: React.FC = ({ : repositories.slice(0, maxInitialDisplay) return (
-
+
{displayedRepositories.map((repository) => { return ( { + const details: Detail[] = [ + { label: 'Joined', value: user.createdAt ? formatDate(user.createdAt) : 'Not available' }, + { label: 'Email', value: user.email || 'N/A' }, + { label: 'Company', value: user.company || 'N/A' }, + { label: 'Location', value: user.location || 'N/A' }, + ] + + const stats: Stat[] = [ + { icon: FaPersonWalkingArrowRight, value: user.followersCount || 0, unit: 'Follower' }, + { icon: FaUserPlus, value: user.followingCount || 0, unit: 'Following' }, + { + icon: FaFolderOpen, + pluralizedName: 'Repositories', + unit: 'Repository', + value: user.publicRepositoriesCount ?? 0, + }, + { icon: FaCodeMerge, value: user.contributionsCount || 0, unit: 'Contribution' }, + ] + + return ( +
+
+ {user.name +
+

+ {user.name || user.login} +

+ + @{user.login} + + {user.bio && ( +
+ {formattedBio} +
+ )} +
+
+ +
+
+ {details.map((detail) => ( +
+ {detail.label} : + {detail.value} +
+ ))} +
+ +
+ {stats.map((stat) => ( + + ))} +
+
+
+ ) +} + +export default MemberDetailSidebar diff --git a/frontend/src/components/cards/RepositoriesModules.tsx b/frontend/src/components/cards/RepositoriesModules.tsx index d93bed8d54..9a1c5b7993 100644 --- a/frontend/src/components/cards/RepositoriesModules.tsx +++ b/frontend/src/components/cards/RepositoriesModules.tsx @@ -25,7 +25,7 @@ const RepositoriesModules = ({ <> {repositories.length > 0 && ( }> - + )} {modules && diff --git a/frontend/src/components/skeletons/MemberDetailsPageSkeleton.tsx b/frontend/src/components/skeletons/MemberDetailsPageSkeleton.tsx index 387b295924..a1c6ce924f 100644 --- a/frontend/src/components/skeletons/MemberDetailsPageSkeleton.tsx +++ b/frontend/src/components/skeletons/MemberDetailsPageSkeleton.tsx @@ -4,158 +4,139 @@ import { CardSection, PageWrapper, SectionHeader, - TitleSection, TwoColumnSection, } from 'components/skeletons/sharedSkeletons' const MemberDetailsPageSkeleton: React.FC = () => { return ( - - - {/* User Summary Card - bg-gray-100 like SecondaryCard */} -
-
- {/* Avatar */} -