Skip to content

Commit c983392

Browse files
Piyushrathoreearkid15rkasyacoderabbitai[bot]
authored
Add test : RecentRelease component unit test (#1946)
* Add test : RecentRelease component unit test * fix:update recentRelease test for latest recentrelease component * updated the timestamp harcoding issue * Update RecentRelease.test.tsx * Update RecentRelease.test.tsx * update the suggested changes by coderabbit * update the change assisted by code rabbit and change test according to it * Update frontend/__tests__/unit/components/RecentRelease.test.tsx as per coderabbitai this commit fixes the timestamp data Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update code --------- Co-authored-by: Arkadii Yakovets <[email protected]> Co-authored-by: Kate Golovanova <[email protected]> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Arkadii Yakovets <[email protected]>
1 parent 4dcf9c8 commit c983392

File tree

4 files changed

+402
-12
lines changed

4 files changed

+402
-12
lines changed

frontend/__tests__/unit/components/ItemCardList.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ const mockRelease: Release = {
178178
publishedAt: 1640995200000,
179179
repositoryName: 'test-repo',
180180
tagName: 'v1.0.0',
181-
url: 'https://github.com/test-org/test-repo/releases/tag/v1.0.0',
182181
}
183182

184183
describe('ItemCardList Component', () => {
Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
import { render, screen, fireEvent, act } from '@testing-library/react'
2+
import type { ReactElement, ReactNode } from 'react'
3+
import type { Release } from 'types/release'
4+
import RecentReleases from 'components/RecentReleases'
5+
6+
// Define proper types for mock components
7+
interface MockComponentProps {
8+
children?: ReactNode
9+
[key: string]: unknown
10+
}
11+
12+
interface MockImageProps {
13+
alt?: string
14+
src?: string
15+
[key: string]: unknown
16+
}
17+
18+
// Mock framer-motion to prevent LazyMotion issues
19+
jest.mock('framer-motion', () => ({
20+
motion: {
21+
div: ({ children, ...props }: MockComponentProps): ReactElement => (
22+
<div {...props}>{children}</div>
23+
),
24+
span: ({ children, ...props }: MockComponentProps): ReactElement => (
25+
<span {...props}>{children}</span>
26+
),
27+
},
28+
AnimatePresence: ({ children }: { children: ReactNode }): ReactNode => children,
29+
useAnimation: () => ({
30+
start: jest.fn(),
31+
set: jest.fn(),
32+
}),
33+
LazyMotion: ({ children }: { children: ReactNode }): ReactNode => children,
34+
domAnimation: jest.fn(),
35+
}))
36+
37+
// Mock HeroUI components
38+
jest.mock('@heroui/tooltip', () => ({
39+
Tooltip: ({
40+
children,
41+
closeDelay: _closeDelay,
42+
delay: _delay,
43+
placement: _placement,
44+
showArrow: _showArrow,
45+
id: _id,
46+
content: _content,
47+
...props
48+
}: MockComponentProps): ReactElement => <div {...props}>{children}</div>,
49+
}))
50+
51+
const mockRouterPush = jest.fn()
52+
53+
jest.mock('next/navigation', () => ({
54+
useRouter: () => ({
55+
push: mockRouterPush,
56+
}),
57+
}))
58+
59+
jest.mock('next/image', () => ({
60+
__esModule: true,
61+
default: (props: MockImageProps): ReactElement => {
62+
// eslint-disable-next-line @next/next/no-img-element
63+
return <img {...props} alt={props.alt || ''} />
64+
},
65+
}))
66+
67+
const now = Date.now()
68+
const mockReleases: Release[] = [
69+
{
70+
name: 'v1.0 The First Release',
71+
publishedAt: now,
72+
repositoryName: 'our-awesome-project',
73+
organizationName: 'our-org',
74+
tagName: 'v1.0',
75+
isPreRelease: false,
76+
author: {
77+
login: 'testuser',
78+
name: 'Test User',
79+
avatarUrl: 'https://example.com/avatar.png',
80+
key: 'testuser',
81+
contributionsCount: 0,
82+
createdAt: 0,
83+
followersCount: 0,
84+
followingCount: 0,
85+
publicRepositoriesCount: 0,
86+
url: 'https://example.com/user/testuser',
87+
},
88+
},
89+
{
90+
name: 'v2.0 The Second Release',
91+
publishedAt: now,
92+
repositoryName: 'another-cool-project',
93+
organizationName: 'our-org',
94+
tagName: 'v2.0',
95+
isPreRelease: false,
96+
author: {
97+
login: 'jane-doe',
98+
name: 'Jane Doe',
99+
avatarUrl: 'https://example.com/avatar2.png',
100+
key: 'jane-doe',
101+
contributionsCount: 0,
102+
createdAt: 0,
103+
followersCount: 0,
104+
followingCount: 0,
105+
publicRepositoriesCount: 0,
106+
url: 'https://example.com/user/jane-doe',
107+
},
108+
},
109+
]
110+
111+
describe('RecentReleases Component', () => {
112+
beforeEach(() => {
113+
mockRouterPush.mockClear()
114+
})
115+
116+
it('should display a message when there is no data', () => {
117+
act(() => {
118+
render(<RecentReleases data={[]} />)
119+
})
120+
expect(screen.getByText('No recent releases.')).toBeInTheDocument()
121+
})
122+
123+
it('should render release details and links correctly with data', () => {
124+
act(() => {
125+
render(<RecentReleases data={mockReleases} />)
126+
})
127+
128+
const releaseLink = screen.getByRole('link', { name: /v1.0 The First Release/i })
129+
const repoNameElement = screen.getByText(/another-cool-project/i)
130+
const authorLink = screen.getByRole('link', { name: /Test User/i })
131+
132+
expect(releaseLink).toBeInTheDocument()
133+
expect(repoNameElement).toBeInTheDocument()
134+
expect(authorLink).toBeInTheDocument()
135+
136+
expect(releaseLink).toHaveAttribute(
137+
'href',
138+
'https://github.com/our-org/our-awesome-project/releases/tag/v1.0'
139+
)
140+
expect(releaseLink).toHaveAttribute('target', '_blank')
141+
expect(authorLink).toHaveAttribute('href', '/members/testuser')
142+
})
143+
144+
it('should navigate when the repository name is clicked', () => {
145+
act(() => {
146+
render(<RecentReleases data={mockReleases} />)
147+
})
148+
149+
const repoNameElement = screen.getByText(/our-awesome-project/i)
150+
act(() => {
151+
fireEvent.click(repoNameElement)
152+
})
153+
154+
expect(mockRouterPush).toHaveBeenCalledTimes(1)
155+
expect(mockRouterPush).toHaveBeenCalledWith(
156+
'/organizations/our-org/repositories/our-awesome-project'
157+
)
158+
})
159+
160+
it('should not render avatars if showAvatar is false', () => {
161+
act(() => {
162+
render(<RecentReleases data={mockReleases} showAvatar={false} />)
163+
})
164+
expect(screen.queryByRole('link', { name: /Test User/i })).not.toBeInTheDocument()
165+
expect(screen.queryByRole('link', { name: /Jane Doe/i })).not.toBeInTheDocument()
166+
})
167+
168+
it('should apply single-column class when showSingleColumn is true', () => {
169+
let container: HTMLElement
170+
act(() => {
171+
const result = render(<RecentReleases data={mockReleases} showSingleColumn={true} />)
172+
container = result.container
173+
})
174+
const gridContainer = container.querySelector('.grid')
175+
176+
expect(gridContainer).toHaveClass('grid-cols-1')
177+
expect(gridContainer).not.toHaveClass('md:grid-cols-2')
178+
})
179+
180+
it('should apply multi-column classes by default', () => {
181+
let container: HTMLElement
182+
act(() => {
183+
const result = render(<RecentReleases data={mockReleases} />)
184+
container = result.container
185+
})
186+
const gridContainer = container.querySelector('.grid')
187+
188+
expect(gridContainer).not.toHaveClass('grid-cols-1')
189+
expect(gridContainer).toHaveClass('md:grid-cols-2', 'lg:grid-cols-3')
190+
})
191+
192+
// New test cases for comprehensive coverage
193+
194+
it('should handle releases with missing author name', () => {
195+
const releasesWithMissingAuthor = [
196+
{
197+
...mockReleases[0],
198+
author: {
199+
...mockReleases[0].author,
200+
name: '',
201+
},
202+
},
203+
]
204+
205+
act(() => {
206+
render(<RecentReleases data={releasesWithMissingAuthor} />)
207+
})
208+
209+
// Should still render the release name
210+
expect(screen.getByText('v1.0 The First Release')).toBeInTheDocument()
211+
// Should handle missing author gracefully
212+
expect(screen.getByAltText('testuser')).toBeInTheDocument()
213+
})
214+
215+
it('should handle releases with missing repository information', () => {
216+
const releasesWithMissingRepo = [
217+
{
218+
...mockReleases[0],
219+
repositoryName: undefined,
220+
organizationName: undefined,
221+
},
222+
]
223+
224+
act(() => {
225+
render(<RecentReleases data={releasesWithMissingRepo} />)
226+
})
227+
228+
// Should still render the release name
229+
expect(screen.getByText('v1.0 The First Release')).toBeInTheDocument()
230+
// Should handle missing repo info gracefully - check for button element
231+
const repoButton = screen.getByRole('button')
232+
expect(repoButton).toBeInTheDocument()
233+
})
234+
235+
it('should handle releases with missing URLs', () => {
236+
const releasesWithMissingUrls = [
237+
{
238+
...mockReleases[0],
239+
url: undefined,
240+
},
241+
]
242+
243+
act(() => {
244+
render(<RecentReleases data={releasesWithMissingUrls} />)
245+
})
246+
247+
const releaseLink = screen.getByRole('link', { name: /v1.0 The First Release/i })
248+
expect(releaseLink).toHaveAttribute(
249+
'href',
250+
'https://github.com/our-org/our-awesome-project/releases/tag/v1.0'
251+
)
252+
})
253+
254+
it('should render with default props when not provided', () => {
255+
let container: HTMLElement
256+
act(() => {
257+
const result = render(<RecentReleases data={mockReleases} />)
258+
container = result.container
259+
})
260+
// Should show avatars by default
261+
expect(screen.getByRole('link', { name: /Test User/i })).toBeInTheDocument()
262+
const gridContainer = container.querySelector('.grid')
263+
expect(gridContainer).toHaveClass('md:grid-cols-2', 'lg:grid-cols-3')
264+
})
265+
266+
it('should handle null/undefined data gracefully', () => {
267+
const { unmount } = render(<RecentReleases data={[]} />)
268+
expect(screen.getByText('No recent releases.')).toBeInTheDocument()
269+
unmount()
270+
271+
render(<RecentReleases data={[]} />)
272+
expect(screen.getByText('No recent releases.')).toBeInTheDocument()
273+
})
274+
275+
it('should have proper accessibility attributes', () => {
276+
act(() => {
277+
render(<RecentReleases data={mockReleases} />)
278+
})
279+
280+
// Check for proper alt text on images
281+
const authorImage = screen.getByAltText('Test User')
282+
expect(authorImage).toBeInTheDocument()
283+
284+
// Check for proper link roles
285+
const releaseLink = screen.getByRole('link', { name: /v1.0 The First Release/i })
286+
expect(releaseLink).toBeInTheDocument()
287+
288+
// Check for proper button roles
289+
const repoButton = screen.getByText(/our-awesome-project/i)
290+
expect(repoButton).toBeInTheDocument()
291+
})
292+
293+
it('should handle multiple releases correctly', () => {
294+
act(() => {
295+
render(<RecentReleases data={mockReleases} />)
296+
})
297+
298+
// Should render both releases
299+
expect(screen.getByText('v1.0 The First Release')).toBeInTheDocument()
300+
expect(screen.getByText('v2.0 The Second Release')).toBeInTheDocument()
301+
302+
// Should render both repository names
303+
expect(screen.getByText('our-awesome-project')).toBeInTheDocument()
304+
expect(screen.getByText('another-cool-project')).toBeInTheDocument()
305+
})
306+
307+
it('should handle repository click with missing organization name', () => {
308+
const releasesWithMissingOrg = [
309+
{
310+
...mockReleases[0],
311+
organizationName: undefined,
312+
},
313+
]
314+
315+
act(() => {
316+
render(<RecentReleases data={releasesWithMissingOrg} />)
317+
})
318+
319+
const repoButton = screen.getByRole('button')
320+
expect(repoButton).toBeDisabled()
321+
322+
act(() => {
323+
fireEvent.click(repoButton)
324+
})
325+
326+
// Should not navigate when organization name is missing
327+
expect(mockRouterPush).not.toHaveBeenCalled()
328+
})
329+
330+
it('should disable repository button if repository name is missing', () => {
331+
const releasesWithMissingRepoName = [
332+
{
333+
...mockReleases[0],
334+
repositoryName: undefined,
335+
},
336+
]
337+
338+
act(() => {
339+
render(<RecentReleases data={releasesWithMissingRepoName} />)
340+
})
341+
342+
const repoButton = screen.getByRole('button')
343+
expect(repoButton).toBeDisabled()
344+
345+
act(() => {
346+
fireEvent.click(repoButton)
347+
})
348+
349+
// Should not navigate when repository name is missing
350+
expect(mockRouterPush).not.toHaveBeenCalled()
351+
})
352+
353+
it('should render with proper CSS classes for styling', () => {
354+
let container: HTMLElement
355+
act(() => {
356+
const result = render(<RecentReleases data={mockReleases} />)
357+
container = result.container
358+
})
359+
360+
// Check for main card structure - look for the card wrapper
361+
const cardElement = container.querySelector(
362+
'.mb-4.w-full.rounded-lg.bg-gray-200.p-4.dark\\:bg-gray-700'
363+
)
364+
expect(cardElement).toBeInTheDocument()
365+
366+
// Check for proper grid layout
367+
const gridElement = container.querySelector('.grid')
368+
expect(gridElement).toBeInTheDocument()
369+
370+
// Check for proper text styling - look for the title
371+
const titleElement = container.querySelector('.text-2xl.font-semibold')
372+
expect(titleElement).toBeInTheDocument()
373+
})
374+
375+
it('should handle releases with very long names gracefully', () => {
376+
const releasesWithLongNames = [
377+
{
378+
...mockReleases[0],
379+
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',
380+
},
381+
]
382+
383+
act(() => {
384+
render(<RecentReleases data={releasesWithLongNames} />)
385+
})
386+
387+
// Should still render the long name
388+
expect(screen.getByText(/This is a very long release name/)).toBeInTheDocument()
389+
})
390+
})

0 commit comments

Comments
 (0)