Skip to content

Conversation

@anurag2787
Copy link
Contributor

Resolves #1882

Description

Added Unit Tests for RecentIssues Component
Added comprehensive test suite with 18 test cases covering:

  • Renders successfully with minimal required props
  • Conditional rendering logic
  • Prop-based behavior – different props affect output
  • Event handling – simulate user actions and verify callbacks
  • State changes / internal logic
  • Default values and fallbacks
  • Text and content rendering
  • Handles edge cases and invalid inputs
  • Accessibility roles and labels
  • DOM structure / classNames / styles

Checklist

  • I've read and followed the contributing guidelines.
  • I've run make check-test locally; all checks and tests passed.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 18, 2025

Summary by CodeRabbit

  • New Features
    • None.
  • Refactor
    • Simplified repository navigation link construction in Recent Issues to ensure links appear only when a repository is available and navigate reliably to the correct organization repository page.
  • Tests
    • Added comprehensive unit tests for Recent Issues covering rendering, empty states, avatar visibility, labels, dates, multiple items, accessibility, navigation, long names, and missing data handling to verify default behaviors and stability.

Walkthrough

Adds a comprehensive unit test suite for RecentIssues and removes the explicit empty-string fallback when constructing the repository navigation URL in RecentIssues.tsx.

Changes

Cohort / File(s) Summary
Unit tests for RecentIssues
frontend/__tests__/unit/components/RecentIssues.test.tsx
Adds full test suite validating rendering, empty state, avatar visibility, repository link navigation (router.push), date formatting, labels/title behavior, accessibility (heading), DOM structure/classNames, multiple items, edge cases (missing fields), and default avatar; mocks Next.js router, Tooltip, and Next/image.
RecentIssues navigation URL
frontend/src/components/RecentIssues.tsx
Replaces `/organizations/${item.organizationName}/repositories/${item.repositoryName

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Renders successfully with minimal required props; Text and content rendering; Accessibility roles and labels (#1882)
Conditional rendering logic; Prop-based behavior; Default values and fallbacks (#1882)
Event handling — simulate user actions and verify callbacks (#1882)
Handles edge cases and invalid inputs; DOM structure / classNames / styles (#1882)
State changes / internal logic (#1882) Component appears stateless and the PR does not add tests for internal state transitions; unclear if state-related objectives apply.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Removal of empty-string fallback in repository navigation path (frontend/src/components/RecentIssues.tsx) The linked issue (#1882) requested adding tests; altering the navigation string is a functional change not specified by the issue.

Suggested reviewers

  • arkid15r
  • kasya

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3332d75 and 0699967.

📒 Files selected for processing (1)
  • frontend/__tests__/unit/components/RecentIssues.test.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/tests/unit/components/RecentIssues.test.tsx
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (8)
frontend/src/components/RecentIssues.tsx (2)

35-49: Harden navigation: also guard on organizationName and URL-encode path segments.

If upstream data ever omits organizationName, clicking would navigate to /organizations/undefined/repositories/. Also, encoding segments avoids issues with spaces or special characters in names.

Apply this diff within the current block:

-          {item?.repositoryName && (
+          {item?.repositoryName && item?.organizationName && (
             <div className="flex flex-1 items-center overflow-hidden">
               <FontAwesomeIcon icon={faFolderOpen} className="mr-2 h-5 w-4" />
               <button
                 className="cursor-pointer overflow-hidden text-ellipsis whitespace-nowrap text-gray-600 hover:underline dark:text-gray-400"
-                onClick={() =>
-                  router.push(
-                    `/organizations/${item.organizationName}/repositories/${item.repositoryName}`
-                  )
-                }
+                onClick={() => {
+                  const org = encodeURIComponent(item.organizationName as string)
+                  const repo = encodeURIComponent(item.repositoryName as string)
+                  router.push(`/organizations/${org}/repositories/${repo}`)
+                }}
               >
                 <TruncatedText text={item.repositoryName} />
               </button>
             </div>
           )}

38-47: Prefer Next.js Link over button + router.push for navigation (a11y + UX).

Links are semantically correct for navigation, enable open-in-new-tab, and can prefetch. Consider replacing the button with Link.

Example change inside this block:

-              <button
-                className="cursor-pointer overflow-hidden text-ellipsis whitespace-nowrap text-gray-600 hover:underline dark:text-gray-400"
-                onClick={() =>
-                  router.push(
-                    `/organizations/${item.organizationName}/repositories/${item.repositoryName}`
-                  )
-                }
-              >
-                <TruncatedText text={item.repositoryName} />
-              </button>
+              <a
+                href={`/organizations/${encodeURIComponent(item.organizationName!)}/repositories/${encodeURIComponent(item.repositoryName!)}`}
+                className="cursor-pointer overflow-hidden text-ellipsis whitespace-nowrap text-gray-600 hover:underline dark:text-gray-400"
+              >
+                <TruncatedText text={item.repositoryName} />
+              </a>

If you go this route, you can also use Next.js <Link> for prefetching:

// at top-level imports
import Link from 'next/link'
<Link
  href={`/organizations/${encodeURIComponent(item.organizationName!)}/repositories/${encodeURIComponent(item.repositoryName!)}`}
  className="cursor-pointer overflow-hidden text-ellipsis whitespace-nowrap text-gray-600 hover:underline dark:text-gray-400"
>
  <TruncatedText text={item.repositoryName} />
</Link>
frontend/__tests__/unit/components/RecentIssues.test.tsx (6)

107-113: Prefer role-based queries and userEvent for ergonomic, semantic interactions.

Querying by role improves resilience, and userEvent better simulates user behavior.

Apply this diff:

-  it('renders repositoryName and navigates on click', () => {
-    render(<RecentIssues data={[baseIssue]} />)
-    const repoBtn = screen.getByText('repo')
-    expect(repoBtn).toBeInTheDocument()
-    fireEvent.click(repoBtn)
-    expect(mockPush).toHaveBeenCalledWith('/organizations/org/repositories/repo')
-  })
+  it('renders repositoryName and navigates on click', async () => {
+    render(<RecentIssues data={[baseIssue]} />)
+    const repoBtn = screen.getByRole('button', { name: 'repo' })
+    expect(repoBtn).toBeInTheDocument()
+    await userEvent.click(repoBtn)
+    expect(mockPush).toHaveBeenCalledWith('/organizations/org/repositories/repo')
+  })

Add the import at the top:

import userEvent from '@testing-library/user-event'

115-120: Avoid deleting properties on strongly typed objects.

Using delete on a typed object can fight TS’ structural typing and strictness. Prefer creating a new object without the key.

One approach:

-    const issue = { ...baseIssue }
-    delete issue.repositoryName
-    render(<RecentIssues data={[issue]} />)
+    const { repositoryName: _omit, ...rest } = baseIssue
+    // If Issue requires repositoryName, cast only at callsite to keep the local object well-typed.
+    render(<RecentIssues data={[rest as unknown as Issue]} />)

Alternatively, explicitly opt out for that line only:

const issue: any = { ...baseIssue }
delete issue.repositoryName
render(<RecentIssues data={[issue]} />)

161-165: Test name says “truncates” but the assertion doesn’t verify truncation.

Right now it only checks the text is present. Consider asserting the truncation-related classes on the clickable element.

Apply this diff:

-  it('renders with long repositoryName and truncates', () => {
+  it('renders with long repositoryName and applies truncation classes', () => {
     const issue = { ...baseIssue, repositoryName: 'a'.repeat(100) }
     render(<RecentIssues data={[issue]} />)
-    expect(screen.getByText('a'.repeat(100))).toBeInTheDocument()
+    const btn = screen.getByRole('button', { name: 'a'.repeat(100) })
+    expect(btn).toHaveClass('overflow-hidden')
+    expect(btn).toHaveClass('text-ellipsis')
+    expect(btn).toHaveClass('whitespace-nowrap')
   })

149-152: DOM structure assertion is brittle; prefer targeted queries.

closest('div') + toHaveClass('flex') couples to implementation details. Consider asserting on the known title container or heading role, or using getByRole('heading', { name: /Recent Issues/i }) and checking its container via a data-testid.

Example:

-    expect(screen.getByText('Recent Issues').closest('div')).toHaveClass('flex')
+    const heading = screen.getByRole('heading', { name: /Recent Issues/i })
+    expect(heading).toBeInTheDocument()
+    // If needed, add a data-testid to the title container for a stable assertion.

167-172: Consider adding a test for missing organizationName to avoid broken routes.

The component currently doesn’t guard organizationName in the render condition, so a missing org could navigate to /organizations/undefined/.... Adding a test will prevent regressions.

Suggested test:

it('does not render repository button if organizationName is missing', () => {
  const issue: any = { ...baseIssue, organizationName: undefined }
  render(<RecentIssues data={[issue]} />)
  expect(screen.queryByRole('button', { name: 'repo' })).not.toBeInTheDocument()
})

If you adopt this behavior, update the component guard accordingly as suggested in the component review.


52-56: Minor: clear all mocks after each test for consistency.

You already clear mockPush. Clearing all mocks avoids accidental leakage when more mocks get added.

Example:

afterEach(() => {
  jest.clearAllMocks()
})
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0c4bf1e and 07c3823.

📒 Files selected for processing (2)
  • frontend/__tests__/unit/components/RecentIssues.test.tsx (1 hunks)
  • frontend/src/components/RecentIssues.tsx (1 hunks)
🔇 Additional comments (2)
frontend/src/components/RecentIssues.tsx (1)

41-43: Safe removal of repositoryName fallback — guarded by render condition (LGTM).

Because the button is only rendered when item.repositoryName is truthy, dropping the || '' fallback is safe and makes the URL construction clearer.

frontend/__tests__/unit/components/RecentIssues.test.tsx (1)

1-188: Comprehensive, thoughtful coverage — great job.

The suite meaningfully exercises rendering, conditional paths, interactions, and edge cases. Mocks are minimal and targeted; navigation assertions align with the component behavior.

Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anurag2787 Could you run make check locally and address found issues? Thanks!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (10)
frontend/__tests__/unit/components/RecentIssues.test.tsx (10)

52-56: Reset all mocks between tests to avoid cross-test leakage

Using mockPush.mockClear() only clears that one mock’s calls. Prefer clearing all mocks at the start of each test.

Apply this diff:

 const mockPush = jest.fn()
 beforeEach(() => {
-  ;(useRouter as jest.Mock).mockReturnValue({ push: mockPush })
-  mockPush.mockClear()
+  jest.clearAllMocks()
+  ;(useRouter as jest.Mock).mockReturnValue({ push: mockPush })
 })

107-113: Prefer userEvent for click interactions; additionally assert role for robustness

fireEvent.click works but userEvent better simulates user behavior. Also consider asserting by role ('link' or 'button') rather than raw text to reduce brittleness.

Example refactor:

-import { render, screen, fireEvent } from '@testing-library/react'
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'

@@
-  const repoBtn = screen.getByText('repo')
+  const repoBtn = screen.getByRole(/link|button/, { name: 'repo' })
   expect(repoBtn).toBeInTheDocument()
-  fireEvent.click(repoBtn)
+  const user = userEvent.setup()
+  await user.click(repoBtn)
   expect(mockPush).toHaveBeenCalledWith('/organizations/org/repositories/repo')

Note: mark the test async and create a user instance.


115-120: Also assert no navigation occurs when repositoryName is missing

Strengthen the negative case by verifying push is not called.

Apply this diff:

   delete issue.repositoryName
   render(<RecentIssues data={[issue]} />)
   expect(screen.queryByText('repo')).not.toBeInTheDocument()
+  expect(mockPush).not.toHaveBeenCalled()

122-125: Make the date assertion locale-robust

Hardcoding "Mar" is locale-dependent and brittle. Compute the expected month/year from the test data instead.

Apply this diff:

-  it('renders formatted date', () => {
-    render(<RecentIssues data={[baseIssue]} />)
-    expect(screen.getByText(/Mar \d{1,2}, 2024/)).toBeInTheDocument()
-  })
+  it('renders formatted date', () => {
+    render(<RecentIssues data={[baseIssue]} />)
+    const created = new Date(baseIssue.createdAt * 1000)
+    const expMonth = created.toLocaleString('en-US', { month: 'short' })
+    const expYear = created.getFullYear()
+    expect(
+      screen.getByText(new RegExp(`${expMonth} \\d{1,2}, ${expYear}`))
+    ).toBeInTheDocument()
+  })

127-131: Test name mismatches the assertion; consider renaming or asserting labels

The test named “renders label text” asserts the title. Either rename to “renders title text” or assert that the label “bug” is rendered too.

Apply this diff to rename and add a dedicated labels test:

-  it('renders label text', () => {
+  it('renders title text', () => {
     render(<RecentIssues data={[baseIssue]} />)
     expect(screen.getByText('Issue Title')).toBeInTheDocument()
   })
+
+  it('renders labels', () => {
+    render(<RecentIssues data={[baseIssue]} />)
+    expect(screen.getByText('bug')).toBeInTheDocument()
+  })

149-152: DOM/className assertion is brittle; avoid structural coupling

Asserting closest('div') has class 'flex' tightly couples tests to implementation details. Prefer roles/testids or behavior. Consider removing or switching to a stable testid if the component provides one.


154-160: Strengthen “renders multiple issues” assertions

The current regex + count can pass in unintended scenarios. Assert each title explicitly.

Apply this diff:

   render(<RecentIssues data={issues} />)
   expect(screen.getByText('Second Issue')).toBeInTheDocument()
-  expect(screen.getAllByText(/Issue Title|Second Issue/).length).toBeGreaterThan(1)
+  expect(screen.getByText('Issue Title')).toBeInTheDocument()
+  expect(screen.getByText('Second Issue')).toBeInTheDocument()

161-165: Test name contradicts assertion; clarify intent

The name says “truncates” but the assertion checks full text presence. If truncation is visual via CSS, it’s fine to assert text presence and rename the test for clarity.

Apply this diff:

-  it('renders with long repositoryName and truncates', () => {
+  it('renders long repositoryName text (present in DOM; may be visually truncated via CSS)', () => {
     const issue = { ...baseIssue, repositoryName: 'a'.repeat(100) }
     render(<RecentIssues data={[issue]} />)
     expect(screen.getByText('a'.repeat(100))).toBeInTheDocument()
   })

174-177: “Missing props gracefully” test doesn’t validate behavior

This ensures no crash, which is useful, but consider adding a concrete behavior assertion (e.g., empty state) to increase signal.


184-187: Duplicate coverage with earlier avatar tests

This duplicates the default avatar behavior already implied by the "shows avatar when showAvatar is true" plus the default props path. Consider removing to reduce runtime.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 07c3823 and 3332d75.

📒 Files selected for processing (1)
  • frontend/__tests__/unit/components/RecentIssues.test.tsx (1 hunks)
🔇 Additional comments (6)
frontend/__tests__/unit/components/RecentIssues.test.tsx (6)

7-9: Router mock setup looks good

Clean, focused mock for next/navigation's useRouter.


27-50: Solid Next/Image mock

Good approach to sidestep Next's Image in JSDOM while preserving alt and basic style semantics.


144-147: Accessibility heading check is appropriate

Good to assert by role for the main label.


167-172: Custom organization navigation LGTM

Good coverage for org override and correct path formation.


179-182: Null data empty state LGTM

Covers a valuable edge case.


58-83: No action needed – numeric timestamps in seconds are handled correctly
formatDate multiplies numeric inputs by 1000 (treating them as Unix seconds), and the Issue type defines createdAt/updatedAt as numbers in seconds. The 10-digit timestamps in the tests align with this and will render reliably.

@anurag2787 anurag2787 marked this pull request as draft August 19, 2025 05:32
@anurag2787 anurag2787 marked this pull request as ready for review August 19, 2025 09:59
@sonarqubecloud
Copy link

Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These look good 👌🏼 Thanks!

@kasya kasya added this pull request to the merge queue Aug 20, 2025
Merged via the queue into OWASP:main with commit f265295 Aug 20, 2025
24 checks passed
OUMIMANDAL pushed a commit to OUMIMANDAL/Nest-milestones that referenced this pull request Aug 28, 2025
* Added test for RecentIssues Component

* Updated code format

* update missing title test

---------

Co-authored-by: Kate Golovanova <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add tests for <RecentIssues> component

2 participants