Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9d12e17
Add 'is_archived' field to RepositoryNode GraphQL type
mrkeshav-05 Oct 10, 2025
a563121
Add 'isArchived' field to GET_REPOSITORY_DATA query
mrkeshav-05 Oct 10, 2025
14186d9
Add 'isArchived' field to GET_ORGANIZATION_DATA query
mrkeshav-05 Oct 10, 2025
47ba07c
Add 'isArchived' field to GET_PROJECT_DATA query
mrkeshav-05 Oct 10, 2025
49cf7c0
Add 'isArchived' field to topContributedRepositories in GET_USER_DATA…
mrkeshav-05 Oct 10, 2025
d33519c
Add ArchivedBadge component to display archived status
mrkeshav-05 Oct 10, 2025
9ba83a9
Add ArchivedBadge to DetailsCard and RepositoriesCard for archived st…
mrkeshav-05 Oct 10, 2025
be4ea48
Add isArchived field to DetailsCardProps and RepositoryCardProps for …
mrkeshav-05 Oct 10, 2025
45bd3bb
feat: Add isArchived field to RepositoryNode and UserNode types in ge…
mrkeshav-05 Oct 10, 2025
2003add
feat: Pass isArchived prop to DetailsCard for repository archived status
mrkeshav-05 Oct 10, 2025
8661db5
fix: Remove extra whitespace in className for ArchivedBadge component
mrkeshav-05 Oct 10, 2025
afd27d4
fix: Remove extra whitespace and add aria-label for accessibility in …
mrkeshav-05 Oct 10, 2025
6bc4ab2
test: Add tests for is_archived field in RepositoryNode
mrkeshav-05 Oct 11, 2025
81503cf
test: Add tests for archived badge functionality in ArchiveBadge, Car…
mrkeshav-05 Oct 11, 2025
26dd61d
test: Refactor archived badge tests for consistency and clarity in Re…
mrkeshav-05 Oct 11, 2025
6d4237a
feat: ellipsis design for long repository names and add archived badge
mrkeshav-05 Oct 11, 2025
a455b7b
Apply suggestion from @coderabbitai[bot]
mrkeshav-05 Oct 12, 2025
cb09235
Apply suggestion from @coderabbitai[bot]
mrkeshav-05 Oct 12, 2025
6152c96
Apply suggestion from @coderabbitai[bot]
mrkeshav-05 Oct 12, 2025
13fbbf9
style: swap Inactive and MetricsScoreCircle; match Archived badge
mrkeshav-05 Oct 12, 2025
1119cba
style: remove unnecessary newline in test_is_archived_field_exists
mrkeshav-05 Oct 12, 2025
9783fae
fix: update GetLeaderDataDocument to include missing fields
mrkeshav-05 Oct 12, 2025
99e0b1d
test: add some changes in the files
mrkeshav-05 Oct 12, 2025
a7719eb
test: remove snapshot tests for ArchivedBadge component
mrkeshav-05 Oct 12, 2025
95a7c70
fix: remove company and location fields from GetLeaderDataQuery
mrkeshav-05 Oct 13, 2025
f0c2b8d
Merge branch 'main' into feature/archived-badge
kasya Oct 15, 2025
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
1 change: 1 addition & 0 deletions backend/apps/github/api/internal/nodes/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"created_at",
"description",
"forks_count",
"is_archived",
"key",
"license",
"name",
Expand Down
14 changes: 14 additions & 0 deletions backend/tests/apps/github/api/internal/nodes/repository_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def test_meta_configuration(self):
"created_at",
"description",
"forks_count",
"is_archived",
"issues",
"key",
"languages",
Expand Down Expand Up @@ -202,3 +203,16 @@ def test_url_method(self):

result = RepositoryNode.url(mock_repository)
assert result == "https://github.com/test-org/test-repo"

def test_is_archived_field_exists(self):
"""Test that is_archived field is exposed in the GraphQL schema."""
field_names = {field.name for field in RepositoryNode.__strawberry_definition__.fields}
assert "is_archived" in field_names, (
"is_archived field should be exposed in RepositoryNode"
)

def test_resolve_is_archived(self):
"""Test is_archived field type."""
field = self._get_field_by_name("is_archived")
assert field is not None
assert field.type is bool
205 changes: 205 additions & 0 deletions frontend/__tests__/unit/components/ArchivedBadge.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import { render, screen } from '@testing-library/react'
import ArchivedBadge from 'components/ArchivedBadge'

jest.mock('@fortawesome/react-fontawesome', () => ({
FontAwesomeIcon: ({ className }: { className: string }) => (
<i data-testid="archive-icon" className={className} />
),
}))

describe('ArchivedBadge', () => {
describe('Basic Rendering', () => {
it('renders successfully with default props', () => {
render(<ArchivedBadge />)

expect(screen.getByText('Archived')).toBeInTheDocument()
})

it('displays the archived text', () => {
render(<ArchivedBadge />)

const badge = screen.getByText('Archived')
expect(badge).toBeVisible()
})

it('has the correct tooltip', () => {
render(<ArchivedBadge />)

const badge = screen.getByText('Archived')
expect(badge).toHaveAttribute('title', 'This repository has been archived and is read-only')
})
})

describe('Size Variants', () => {
it('renders with small size', () => {
render(<ArchivedBadge size="sm" />)

const badge = screen.getByText('Archived')
expect(badge).toHaveClass('px-2', 'py-1', 'text-xs')
})

it('renders with medium size (default)', () => {
render(<ArchivedBadge size="md" />)

const badge = screen.getByText('Archived')
expect(badge).toHaveClass('px-3', 'py-1', 'text-sm')
})

it('renders with large size', () => {
render(<ArchivedBadge size="lg" />)

const badge = screen.getByText('Archived')
expect(badge).toHaveClass('px-4', 'py-2', 'text-base')
})

it('defaults to medium size when size prop is not provided', () => {
render(<ArchivedBadge />)

const badge = screen.getByText('Archived')
expect(badge).toHaveClass('px-3', 'py-1', 'text-sm')
})
})

describe('Icon Display', () => {
it('shows icon by default', () => {
render(<ArchivedBadge />)

const icon = screen.getByTestId('archive-icon')
expect(icon).toBeInTheDocument()
})

it('shows icon when showIcon is true', () => {
render(<ArchivedBadge showIcon={true} />)

const icon = screen.getByTestId('archive-icon')
expect(icon).toBeInTheDocument()
})

it('hides icon when showIcon is false', () => {
render(<ArchivedBadge showIcon={false} />)

const icon = screen.queryByTestId('archive-icon')
expect(icon).not.toBeInTheDocument()
})

it('applies correct icon class', () => {
render(<ArchivedBadge />)

const icon = screen.getByTestId('archive-icon')
expect(icon).toHaveClass('h-3', 'w-3')
})
})

describe('Styling', () => {
it('has correct base styling classes', () => {
render(<ArchivedBadge />)

const badge = screen.getByText('Archived')
expect(badge).toHaveClass('inline-flex', 'items-center', 'gap-1.5', 'rounded-full', 'border')
})

it('has correct light mode color classes', () => {
render(<ArchivedBadge />)

const badge = screen.getByText('Archived')
expect(badge).toHaveClass('border-yellow-600', 'bg-yellow-50', 'text-yellow-800')
})

it('has correct dark mode color classes', () => {
render(<ArchivedBadge />)

const badge = screen.getByText('Archived')
expect(badge).toHaveClass(
'dark:border-yellow-500',
'dark:bg-yellow-900/30',
'dark:text-yellow-400'
)
})

it('has font-medium class', () => {
render(<ArchivedBadge />)

const badge = screen.getByText('Archived')
expect(badge).toHaveClass('font-medium')
})
})

describe('Custom ClassName', () => {
it('applies custom className', () => {
render(<ArchivedBadge className="custom-class" />)

const badge = screen.getByText('Archived')
expect(badge).toHaveClass('custom-class')
})

it('preserves default classes with custom className', () => {
render(<ArchivedBadge className="ml-2" />)

const badge = screen.getByText('Archived')
expect(badge).toHaveClass('ml-2', 'inline-flex', 'rounded-full')
})

it('handles empty custom className', () => {
render(<ArchivedBadge className="" />)

const badge = screen.getByText('Archived')
expect(badge).toBeInTheDocument()
})
})

describe('Accessibility', () => {
it('renders as a span element', () => {
render(<ArchivedBadge />)

const badge = screen.getByText('Archived')
expect(badge.tagName).toBe('SPAN')
})

it('has descriptive tooltip for screen readers', () => {
render(<ArchivedBadge />)

const badge = screen.getByText('Archived')
const title = badge.getAttribute('title')
expect(title).toBeTruthy()
expect(title).toContain('archived')
expect(title).toContain('read-only')
})

it('has appropriate semantic structure with icon', () => {
render(<ArchivedBadge />)

const badge = screen.getByText('Archived')
expect(badge).toContainHTML('i')
})
})

describe('Edge Cases', () => {
it('handles all props together', () => {
render(<ArchivedBadge size="lg" showIcon={false} className="extra-margin" />)

const badge = screen.getByText('Archived')
expect(badge).toBeInTheDocument()
expect(badge).toHaveClass('px-4', 'py-2', 'text-base', 'extra-margin')
expect(screen.queryByTestId('archive-icon')).not.toBeInTheDocument()
})

it('renders consistently across multiple instances', () => {
const { rerender } = render(<ArchivedBadge />)

const firstRender = screen.getByText('Archived').className

rerender(<ArchivedBadge />)

const secondRender = screen.getByText('Archived').className
expect(firstRender).toBe(secondRender)
})

it('does not render any interactive elements', () => {
const { container } = render(<ArchivedBadge />)

expect(container.querySelector('button')).not.toBeInTheDocument()
expect(container.querySelector('a')).not.toBeInTheDocument()
expect(container.querySelector('input')).not.toBeInTheDocument()
})
})
})
119 changes: 118 additions & 1 deletion frontend/__tests__/unit/components/CardDetailsPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,8 @@ describe('CardDetailsPage', () => {
render(<CardDetailsPage {...defaultProps} isActive={false} />)

expect(screen.getByText('Inactive')).toBeInTheDocument()
expect(screen.getByText('Inactive')).toHaveClass('bg-red-200', 'text-red-800')
// Updated classes for consistent badge styling
expect(screen.getByText('Inactive')).toHaveClass('bg-red-50', 'text-red-800')
})

it('does not render inactive badge when isActive is true', () => {
Expand Down Expand Up @@ -1395,4 +1396,120 @@ describe('CardDetailsPage', () => {
).toBeInTheDocument()
})
})

describe('Archived Badge Functionality', () => {
it('displays archived badge for archived repository', () => {
const archivedProps = {
...defaultProps,
type: 'repository',
isArchived: true,
}

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

expect(screen.getByText('Archived')).toBeInTheDocument()
})

it('does not display archived badge for non-archived repository', () => {
const activeProps = {
...defaultProps,
type: 'repository',
isArchived: false,
}

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

expect(screen.queryByText('Archived')).not.toBeInTheDocument()
})

it('does not display archived badge when isArchived is undefined', () => {
const undefinedProps = {
...defaultProps,
type: 'repository',
}

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

expect(screen.queryByText('Archived')).not.toBeInTheDocument()
})

it('does not display archived badge for non-repository types', () => {
const projectProps = {
...defaultProps,
type: 'project',
isArchived: true,
}

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

expect(screen.queryByText('Archived')).not.toBeInTheDocument()
})

it('displays archived badge alongside inactive badge', () => {
const bothBadgesProps = {
...defaultProps,
type: 'repository',
isArchived: true,
isActive: false,
}

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

expect(screen.getByText('Archived')).toBeInTheDocument()
expect(screen.getByText('Inactive')).toBeInTheDocument()
})

it('displays archived badge independently of active status', () => {
const archivedAndActiveProps = {
...defaultProps,
type: 'repository',
isArchived: true,
isActive: true,
}

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

expect(screen.getByText('Archived')).toBeInTheDocument()
expect(screen.queryByText('Inactive')).not.toBeInTheDocument()
})

it('archived badge has correct positioning with flex container', () => {
const archivedProps = {
...defaultProps,
type: 'repository',
isArchived: true,
}

const { container } = render(<CardDetailsPage {...archivedProps} />)

// New structure: badges are in a flex container with items-center and gap-3
const badgeContainer = container.querySelector('.flex.items-center.gap-3')
expect(badgeContainer).toBeInTheDocument()
})

it('archived badge renders with medium size', () => {
const archivedProps = {
...defaultProps,
type: 'repository',
isArchived: true,
}

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

const badge = screen.getByText('Archived')
expect(badge).toHaveClass('px-3', 'py-1', 'text-sm')
})

it('handles null isArchived gracefully', () => {
const nullArchivedProps = {
...defaultProps,
type: 'repository',
isArchived: null,
}

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

expect(screen.queryByText('Archived')).not.toBeInTheDocument()
})
})
})
Loading