diff --git a/frontend/__tests__/unit/components/ItemCardList.test.tsx b/frontend/__tests__/unit/components/ItemCardList.test.tsx
index 5eafffd374..852a6e576d 100644
--- a/frontend/__tests__/unit/components/ItemCardList.test.tsx
+++ b/frontend/__tests__/unit/components/ItemCardList.test.tsx
@@ -178,7 +178,6 @@ const mockRelease: Release = {
publishedAt: 1640995200000,
repositoryName: 'test-repo',
tagName: 'v1.0.0',
- url: 'https://github.com/test-org/test-repo/releases/tag/v1.0.0',
}
describe('ItemCardList Component', () => {
diff --git a/frontend/__tests__/unit/components/RecentRelease.test.tsx b/frontend/__tests__/unit/components/RecentRelease.test.tsx
new file mode 100644
index 0000000000..cbed308e81
--- /dev/null
+++ b/frontend/__tests__/unit/components/RecentRelease.test.tsx
@@ -0,0 +1,390 @@
+import { render, screen, fireEvent, act } from '@testing-library/react'
+import type { ReactElement, ReactNode } from 'react'
+import type { Release } from 'types/release'
+import RecentReleases from 'components/RecentReleases'
+
+// Define proper types for mock components
+interface MockComponentProps {
+ children?: ReactNode
+ [key: string]: unknown
+}
+
+interface MockImageProps {
+ alt?: string
+ src?: string
+ [key: string]: unknown
+}
+
+// Mock framer-motion to prevent LazyMotion issues
+jest.mock('framer-motion', () => ({
+ motion: {
+ div: ({ children, ...props }: MockComponentProps): ReactElement => (
+
{children}
+ ),
+ span: ({ children, ...props }: MockComponentProps): ReactElement => (
+ {children}
+ ),
+ },
+ AnimatePresence: ({ children }: { children: ReactNode }): ReactNode => children,
+ useAnimation: () => ({
+ start: jest.fn(),
+ set: jest.fn(),
+ }),
+ LazyMotion: ({ children }: { children: ReactNode }): ReactNode => children,
+ domAnimation: jest.fn(),
+}))
+
+// Mock HeroUI components
+jest.mock('@heroui/tooltip', () => ({
+ Tooltip: ({
+ children,
+ closeDelay: _closeDelay,
+ delay: _delay,
+ placement: _placement,
+ showArrow: _showArrow,
+ id: _id,
+ content: _content,
+ ...props
+ }: MockComponentProps): ReactElement => {children}
,
+}))
+
+const mockRouterPush = jest.fn()
+
+jest.mock('next/navigation', () => ({
+ useRouter: () => ({
+ push: mockRouterPush,
+ }),
+}))
+
+jest.mock('next/image', () => ({
+ __esModule: true,
+ default: (props: MockImageProps): ReactElement => {
+ // eslint-disable-next-line @next/next/no-img-element
+ return
+ },
+}))
+
+const now = Date.now()
+const mockReleases: Release[] = [
+ {
+ name: 'v1.0 The First Release',
+ publishedAt: now,
+ repositoryName: 'our-awesome-project',
+ organizationName: 'our-org',
+ tagName: 'v1.0',
+ isPreRelease: false,
+ author: {
+ login: 'testuser',
+ name: 'Test User',
+ avatarUrl: 'https://example.com/avatar.png',
+ key: 'testuser',
+ contributionsCount: 0,
+ createdAt: 0,
+ followersCount: 0,
+ followingCount: 0,
+ publicRepositoriesCount: 0,
+ url: 'https://example.com/user/testuser',
+ },
+ },
+ {
+ name: 'v2.0 The Second Release',
+ publishedAt: now,
+ repositoryName: 'another-cool-project',
+ organizationName: 'our-org',
+ tagName: 'v2.0',
+ isPreRelease: false,
+ author: {
+ login: 'jane-doe',
+ name: 'Jane Doe',
+ avatarUrl: 'https://example.com/avatar2.png',
+ key: 'jane-doe',
+ contributionsCount: 0,
+ createdAt: 0,
+ followersCount: 0,
+ followingCount: 0,
+ publicRepositoriesCount: 0,
+ url: 'https://example.com/user/jane-doe',
+ },
+ },
+]
+
+describe('RecentReleases Component', () => {
+ beforeEach(() => {
+ mockRouterPush.mockClear()
+ })
+
+ it('should display a message when there is no data', () => {
+ act(() => {
+ render()
+ })
+ expect(screen.getByText('No recent releases.')).toBeInTheDocument()
+ })
+
+ it('should render release details and links correctly with data', () => {
+ act(() => {
+ render()
+ })
+
+ const releaseLink = screen.getByRole('link', { name: /v1.0 The First Release/i })
+ const repoNameElement = screen.getByText(/another-cool-project/i)
+ const authorLink = screen.getByRole('link', { name: /Test User/i })
+
+ expect(releaseLink).toBeInTheDocument()
+ expect(repoNameElement).toBeInTheDocument()
+ expect(authorLink).toBeInTheDocument()
+
+ expect(releaseLink).toHaveAttribute(
+ 'href',
+ 'https://github.com/our-org/our-awesome-project/releases/tag/v1.0'
+ )
+ expect(releaseLink).toHaveAttribute('target', '_blank')
+ expect(authorLink).toHaveAttribute('href', '/members/testuser')
+ })
+
+ it('should navigate when the repository name is clicked', () => {
+ act(() => {
+ render()
+ })
+
+ const repoNameElement = screen.getByText(/our-awesome-project/i)
+ act(() => {
+ fireEvent.click(repoNameElement)
+ })
+
+ expect(mockRouterPush).toHaveBeenCalledTimes(1)
+ expect(mockRouterPush).toHaveBeenCalledWith(
+ '/organizations/our-org/repositories/our-awesome-project'
+ )
+ })
+
+ it('should not render avatars if showAvatar is false', () => {
+ act(() => {
+ render()
+ })
+ expect(screen.queryByRole('link', { name: /Test User/i })).not.toBeInTheDocument()
+ expect(screen.queryByRole('link', { name: /Jane Doe/i })).not.toBeInTheDocument()
+ })
+
+ it('should apply single-column class when showSingleColumn is true', () => {
+ let container: HTMLElement
+ act(() => {
+ const result = render()
+ container = result.container
+ })
+ const gridContainer = container.querySelector('.grid')
+
+ expect(gridContainer).toHaveClass('grid-cols-1')
+ expect(gridContainer).not.toHaveClass('md:grid-cols-2')
+ })
+
+ it('should apply multi-column classes by default', () => {
+ let container: HTMLElement
+ act(() => {
+ const result = render()
+ container = result.container
+ })
+ const gridContainer = container.querySelector('.grid')
+
+ expect(gridContainer).not.toHaveClass('grid-cols-1')
+ expect(gridContainer).toHaveClass('md:grid-cols-2', 'lg:grid-cols-3')
+ })
+
+ // New test cases for comprehensive coverage
+
+ it('should handle releases with missing author name', () => {
+ const releasesWithMissingAuthor = [
+ {
+ ...mockReleases[0],
+ author: {
+ ...mockReleases[0].author,
+ name: '',
+ },
+ },
+ ]
+
+ act(() => {
+ render()
+ })
+
+ // Should still render the release name
+ expect(screen.getByText('v1.0 The First Release')).toBeInTheDocument()
+ // Should handle missing author gracefully
+ expect(screen.getByAltText('testuser')).toBeInTheDocument()
+ })
+
+ it('should handle releases with missing repository information', () => {
+ const releasesWithMissingRepo = [
+ {
+ ...mockReleases[0],
+ repositoryName: undefined,
+ organizationName: undefined,
+ },
+ ]
+
+ act(() => {
+ render()
+ })
+
+ // Should still render the release name
+ expect(screen.getByText('v1.0 The First Release')).toBeInTheDocument()
+ // Should handle missing repo info gracefully - check for button element
+ const repoButton = screen.getByRole('button')
+ expect(repoButton).toBeInTheDocument()
+ })
+
+ it('should handle releases with missing URLs', () => {
+ const releasesWithMissingUrls = [
+ {
+ ...mockReleases[0],
+ url: undefined,
+ },
+ ]
+
+ act(() => {
+ render()
+ })
+
+ const releaseLink = screen.getByRole('link', { name: /v1.0 The First Release/i })
+ expect(releaseLink).toHaveAttribute(
+ 'href',
+ 'https://github.com/our-org/our-awesome-project/releases/tag/v1.0'
+ )
+ })
+
+ it('should render with default props when not provided', () => {
+ let container: HTMLElement
+ act(() => {
+ const result = render()
+ container = result.container
+ })
+ // Should show avatars by default
+ expect(screen.getByRole('link', { name: /Test User/i })).toBeInTheDocument()
+ const gridContainer = container.querySelector('.grid')
+ expect(gridContainer).toHaveClass('md:grid-cols-2', 'lg:grid-cols-3')
+ })
+
+ it('should handle null/undefined data gracefully', () => {
+ const { unmount } = render()
+ expect(screen.getByText('No recent releases.')).toBeInTheDocument()
+ unmount()
+
+ render()
+ expect(screen.getByText('No recent releases.')).toBeInTheDocument()
+ })
+
+ it('should have proper accessibility attributes', () => {
+ act(() => {
+ render()
+ })
+
+ // Check for proper alt text on images
+ const authorImage = screen.getByAltText('Test User')
+ expect(authorImage).toBeInTheDocument()
+
+ // Check for proper link roles
+ const releaseLink = screen.getByRole('link', { name: /v1.0 The First Release/i })
+ expect(releaseLink).toBeInTheDocument()
+
+ // Check for proper button roles
+ const repoButton = screen.getByText(/our-awesome-project/i)
+ expect(repoButton).toBeInTheDocument()
+ })
+
+ it('should handle multiple releases correctly', () => {
+ act(() => {
+ render()
+ })
+
+ // Should render both releases
+ expect(screen.getByText('v1.0 The First Release')).toBeInTheDocument()
+ expect(screen.getByText('v2.0 The Second Release')).toBeInTheDocument()
+
+ // Should render both repository names
+ expect(screen.getByText('our-awesome-project')).toBeInTheDocument()
+ expect(screen.getByText('another-cool-project')).toBeInTheDocument()
+ })
+
+ it('should handle repository click with missing organization name', () => {
+ const releasesWithMissingOrg = [
+ {
+ ...mockReleases[0],
+ organizationName: undefined,
+ },
+ ]
+
+ act(() => {
+ render()
+ })
+
+ const repoButton = screen.getByRole('button')
+ expect(repoButton).toBeDisabled()
+
+ act(() => {
+ fireEvent.click(repoButton)
+ })
+
+ // Should not navigate when organization name is missing
+ expect(mockRouterPush).not.toHaveBeenCalled()
+ })
+
+ it('should disable repository button if repository name is missing', () => {
+ const releasesWithMissingRepoName = [
+ {
+ ...mockReleases[0],
+ repositoryName: undefined,
+ },
+ ]
+
+ act(() => {
+ render()
+ })
+
+ const repoButton = screen.getByRole('button')
+ expect(repoButton).toBeDisabled()
+
+ act(() => {
+ fireEvent.click(repoButton)
+ })
+
+ // Should not navigate when repository name is missing
+ expect(mockRouterPush).not.toHaveBeenCalled()
+ })
+
+ it('should render with proper CSS classes for styling', () => {
+ let container: HTMLElement
+ act(() => {
+ const result = render()
+ container = result.container
+ })
+
+ // Check for main card structure - look for the card wrapper
+ const cardElement = container.querySelector(
+ '.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')
+ expect(gridElement).toBeInTheDocument()
+
+ // Check for proper text styling - look for the title
+ const titleElement = container.querySelector('.text-2xl.font-semibold')
+ expect(titleElement).toBeInTheDocument()
+ })
+
+ it('should handle releases with very long names gracefully', () => {
+ const releasesWithLongNames = [
+ {
+ ...mockReleases[0],
+ name: 'This is a very long release name that should be truncated properly in the UI to prevent layout issues and maintain consistent styling across different screen sizes',
+ },
+ ]
+
+ act(() => {
+ render()
+ })
+
+ // Should still render the long name
+ expect(screen.getByText(/This is a very long release name/)).toBeInTheDocument()
+ })
+})
diff --git a/frontend/src/components/RecentReleases.tsx b/frontend/src/components/RecentReleases.tsx
index 5ea73c37fb..c8aadef0f1 100644
--- a/frontend/src/components/RecentReleases.tsx
+++ b/frontend/src/components/RecentReleases.tsx
@@ -44,7 +44,7 @@ const RecentReleases: React.FC = ({
{showAvatar && item?.author && (
= ({
>
@@ -71,7 +71,7 @@ const RecentReleases: React.FC = ({
target="_blank"
rel="noopener noreferrer"
>
-
+
@@ -84,11 +84,13 @@ const RecentReleases: React.FC = ({
diff --git a/frontend/src/types/release.ts b/frontend/src/types/release.ts
index 296faa5e86..2e422ec81d 100644
--- a/frontend/src/types/release.ts
+++ b/frontend/src/types/release.ts
@@ -10,5 +10,4 @@ export type Release = {
repository?: RepositoryDetails
repositoryName: string
tagName: string
- url: string
}