Skip to content

Improve frontend test coverage above 90% and add missing test files#3923

Merged
kasya merged 7 commits intoOWASP:mainfrom
anurag2787:fix/frontend-unit-test
Feb 14, 2026
Merged

Improve frontend test coverage above 90% and add missing test files#3923
kasya merged 7 commits intoOWASP:mainfrom
anurag2787:fix/frontend-unit-test

Conversation

@anurag2787
Copy link
Contributor

@anurag2787 anurag2787 commented Feb 12, 2026

Proposed change

his PR improves frontend unit test coverage for files that were previously below the 90% line coverage threshold.

Resolves #3881

Checklist

  • Required: I followed the contributing workflow
  • Required: I verified that my code works as intended and resolves the issue as described
  • Required: I ran make check-test locally: all warnings addressed, tests passed
  • I used AI for code, documentation, tests, or communication related to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Summary by CodeRabbit

  • Tests

    • Added comprehensive test coverage for edge cases across multiple components and pages, including null/undefined value handling, fallback behavior, and keyboard navigation.
    • Extended test suite for form inputs, pagination, carousel, and accessibility scenarios.
  • Bug Fixes

    • Improved fallback handling for missing data in component rendering.

Walkthrough

Adds extensive unit tests covering null/undefined/fallback cases, keyboard interactions, navigation and accessibility; introduces small source edits to Pagination, Milestones, SecondaryCard, and proxy exports; and updates footer tests to exercise empty-href fallback and a mutable env mock.

Changes

Cohort / File(s) Summary
Component Tests - Core UI
frontend/__tests__/unit/components/Footer.test.tsx, frontend/__tests__/unit/components/HealthMetrics.test.tsx, frontend/__tests__/unit/components/IssuesTable.test.tsx, frontend/__tests__/unit/components/LabelList.test.tsx, frontend/__tests__/unit/components/LogoCarousel.test.tsx, frontend/__tests__/unit/components/MetricsCard.test.tsx, frontend/__tests__/unit/components/MultiSearch.test.tsx, frontend/__tests__/unit/components/NavDropDown.test.tsx, frontend/__tests__/unit/components/Pagination.test.tsx, frontend/__tests__/unit/components/ScrollToTop.test.tsx, frontend/__tests__/unit/components/SingleModuleCard.test.tsx
Added many tests for null/undefined fallbacks, keyboard navigation, router push mocking, empty-href fallback in Footer, throttling scroll behavior, assignee fallbacks, and related edge cases.
Form & Skeleton Tests
frontend/__tests__/unit/components/forms/shared/FormDateInput.test.tsx, frontend/__tests__/unit/components/skeletons/sharedSkeletons.test.tsx
Introduced comprehensive tests for FormDateInput (validation, min/max, onValueChange) and extensive shared skeleton rendering/structure tests.
Page-Level Tests
frontend/__tests__/unit/pages/ChapterDetails.test.tsx, frontend/__tests__/unit/pages/Chapters.test.tsx, frontend/__tests__/unit/pages/Committees.test.tsx, frontend/__tests__/unit/pages/Contribute.test.tsx, frontend/__tests__/unit/pages/Home.test.tsx, frontend/__tests__/unit/pages/MenteeProfilePage.test.tsx, frontend/__tests__/unit/pages/ModuleDetails.test.tsx, frontend/__tests__/unit/pages/ModuleDetailsPage.test.tsx, frontend/__tests__/unit/pages/Organization.test.tsx, frontend/__tests__/unit/pages/OrganizationDetails.test.tsx, frontend/__tests__/unit/pages/ProgramDetails.test.tsx, frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx, frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx, frontend/__tests__/unit/pages/Projects.test.tsx, frontend/__tests__/unit/pages/RepositoryDetails.test.tsx, frontend/__tests__/unit/pages/SnapshotDetails.test.tsx
Added many page tests exercising null/undefined fallbacks, admin vs non-admin flows, status update/mutation paths, filtering edge cases, and resilient rendering for missing fields.
Utilities & Mock Data
frontend/__tests__/unit/utils/proxy.test.ts, frontend/__tests__/mockData/mockSnapshotData.ts
Added test asserting a new named export config from proxy module; added id fields to two mock release entries.
Source Code - Component Logic
frontend/src/components/Milestones.tsx, frontend/src/components/Pagination.tsx, frontend/src/components/SecondaryCard.tsx
Milestones: removed fallback to empty string when building repository path (uses repositoryName directly). Pagination: removed duplicate-guard when generating intermediate pages and always appends last page (may introduce duplicates). SecondaryCard: removed default empty-object for props parameter (no props default).
Configuration Export (src)
frontend/src/utils/proxy.ts
Exposed a public named export config alongside the default export (added/adjusted for testing).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • arkid15r
  • kasya
🚥 Pre-merge checks | ✅ 4 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Two production code changes are concerning: removal of default props in SecondaryCard.tsx and unconditional pageNumber pushes in Pagination.tsx may introduce bugs. The Pagination.tsx change introduces duplicates and changes fallback behavior in Milestones.tsx; these modifications appear to be unrelated to test coverage objectives. Review the production code changes in SecondaryCard.tsx and Pagination.tsx against PR objectives. The PageNumbers deduplication removal and default props deletion should be justified or reverted to maintain test-focused scope.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (44 files):

⚔️ .github/workflows/run-ci-cd.yaml (content)
⚔️ .github/workflows/run-fuzz-tests.yaml (content)
⚔️ .github/workflows/update-nest-test-images.yaml (content)
⚔️ .pre-commit-config.yaml (content)
⚔️ README.md (content)
⚔️ backend/apps/github/models/__init__.py (content)
⚔️ backend/apps/owasp/admin/entity_member.py (content)
⚔️ backend/poetry.lock (content)
⚔️ backend/pyproject.toml (content)
⚔️ docker/semgrep/Dockerfile (content)
⚔️ docs/poetry.lock (content)
⚔️ frontend/__tests__/mockData/mockSnapshotData.ts (content)
⚔️ frontend/__tests__/unit/components/Footer.test.tsx (content)
⚔️ frontend/__tests__/unit/components/HealthMetrics.test.tsx (content)
⚔️ frontend/__tests__/unit/components/IssuesTable.test.tsx (content)
⚔️ frontend/__tests__/unit/components/LogoCarousel.test.tsx (content)
⚔️ frontend/__tests__/unit/components/MetricsCard.test.tsx (content)
⚔️ frontend/__tests__/unit/components/MultiSearch.test.tsx (content)
⚔️ frontend/__tests__/unit/components/NavDropDown.test.tsx (content)
⚔️ frontend/__tests__/unit/components/Pagination.test.tsx (content)
⚔️ frontend/__tests__/unit/components/ScrollToTop.test.tsx (content)
⚔️ frontend/__tests__/unit/components/SingleModuleCard.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/ChapterDetails.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/Chapters.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/Committees.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/Contribute.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/Home.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/MenteeProfilePage.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/ModuleDetails.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/ModuleDetailsPage.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/Organization.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/OrganizationDetails.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/ProgramDetails.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/Projects.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/RepositoryDetails.test.tsx (content)
⚔️ frontend/__tests__/unit/pages/SnapshotDetails.test.tsx (content)
⚔️ frontend/__tests__/unit/utils/proxy.test.ts (content)
⚔️ frontend/package.json (content)
⚔️ frontend/pnpm-lock.yaml (content)
⚔️ frontend/src/components/Milestones.tsx (content)
⚔️ frontend/src/components/Pagination.tsx (content)
⚔️ frontend/src/components/SecondaryCard.tsx (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly summarizes the main change—improving frontend test coverage to above 90% and adding missing test files—which aligns with the changeset's extensive test additions across numerous components and pages.
Description check ✅ Passed The PR description is related to the changeset; it references improving unit test coverage for files below the 90% threshold and links to issue #3881, which directly matches the test improvements across components and pages in the changeset.
Linked Issues check ✅ Passed The PR successfully implements the primary objective from issue #3881 by adding comprehensive test coverage for 32+ frontend files (components, pages, and utilities) that were below the 90% branch coverage threshold.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch fix/frontend-unit-test
  • Post resolved changes as copyable diffs 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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

10 issues found across 33 files

Confidence score: 4/5

  • Most issues are test-quality gaps (misleading assertions or missing coverage) in frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx, which raises mild uncertainty but doesn’t indicate runtime regressions.
  • Several tests claim to cover error or fallback behavior without asserting it (e.g., ProgramDetailsMentorship.test.tsx, ChapterDetails.test.tsx, Organization.test.tsx, MenteeProfilePage.test.tsx), so the safety risk is low and localized to test fidelity.
  • This PR looks safe to merge, but tightening the assertions would better align tests with intended behavior and reduce false confidence.
  • Pay close attention to frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx, frontend/__tests__/unit/pages/ChapterDetails.test.tsx, frontend/__tests__/unit/pages/Organization.test.tsx, frontend/__tests__/unit/pages/MenteeProfilePage.test.tsx, frontend/__tests__/unit/pages/SnapshotDetails.test.tsx, frontend/__tests__/unit/components/SecondaryCard.test.tsx, frontend/__tests__/unit/components/MultiSearch.test.tsx - misleading or weak test assertions.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="frontend/__tests__/unit/pages/ChapterDetails.test.tsx">

<violation number="1" location="frontend/__tests__/unit/pages/ChapterDetails.test.tsx:169">
P2: Weak assertion: this test only verifies the component doesn't crash, but doesn't assert that the null `suggestedLocation` is actually handled (i.e., that `'Test City, Test Country'` is absent from the DOM). Add a negative assertion to verify the expected behavior, e.g.:
```ts
expect(screen.queryByText('Test City, Test Country')).not.toBeInTheDocument()

The same issue applies to the region: null and combined tests below — they should similarly assert that 'Test Region' (and/or 'Test City, Test Country') is not rendered.

P2: This test is redundant: the mock release data already has no `id` field, so `id: undefined` changes nothing. To meaningfully test the fallback key path, add an `id` field to the base mock release data, then override it to `undefined` here. Otherwise this test gives a false sense of added coverage. P2: Calling a React component directly as a function (`SecondaryCard()`) is an anti-pattern — it bypasses React's reconciliation, lifecycle, and hook infrastructure. This test also duplicates the existing "renders successfully with no props at all" test (line 28) which already renders `` with no props and asserts on `.mb-8`.

If the goal is to cover the = {} default parameter in the component signature, consider removing that default from the component instead (React always passes an object for props via JSX), which would eliminate the untested branch without needing this workaround.

P2: Test name says "shows error toast when trying to update with invalid status" but the mock button sends `'PUBLISHED'` (a valid status), and the only assertion is `expect(mockUpdateProgram).toHaveBeenCalled()`. No error toast is verified and no invalid status is tested. This is essentially a duplicate of the "successfully updates status" test above. P2: Test claims to verify graceful error handling but only asserts the mutation was called — no verification of error toast, error display, or `handleAppError` invocation. This doesn't confirm the component handles the error at all. P2: Misleading test: the name says "shows error toast when updateStatus is called with invalid status" but the test body never asserts any toast or passes an invalid status. The final assertion is just `expect(publishButton).toBeInTheDocument()`, which verifies nothing related to error toasts. This test inflates coverage metrics without validating the claimed behavior. Consider removing this test since the `capturedSetStatus('INVALID_STATUS')` test below already properly covers this path. P2: Misleading test: claims to verify `handleAppError` is called on mutation failure, but only asserts the mutation was invoked. The comment says "we can't easily mock it" — but `handleAppError` is a named export from `app/global-error` and is straightforwardly mockable with `jest.mock`. Either mock and assert `handleAppError` or rename the test to reflect what it actually verifies. P2: Test claims to verify null `avatarUrl` fallback behavior but has no assertion on the avatar element itself. It only checks that the card renders text. Consider adding an assertion that actually validates the fallback — e.g., query the `` element and assert its `src` attribute, or verify a placeholder/default avatar is shown. P2: This test does not actually exercise the empty-selection branch it claims to cover. Opening the dropdown without clicking an item never calls `onSelectionChange`, so the `if (key)` guard in the component is never reached. To actually test that branch, you'd need to invoke `onSelectionChange` with an empty `Set` — for example, by modifying the mock to support deselection or by directly simulating the callback with `new Set([])`. P2: Test title claims to test both Enter and Space key handling, but only Enter is actually tested. Either rename the test to only mention Enter, or add a Space key assertion to match the title and cover the Space branch in `handleSuggestionKeyDown`. ```

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/__tests__/unit/components/Footer.test.tsx (1)

369-377: ⚠️ Potential issue | 🟡 Minor

Test name doesn't match what's being asserted.

The test is named "applies correct section content classes for collapsed/expanded states" but only verifies aria-expanded='true' after a click — it doesn't assert any CSS classes for the collapsed or expanded content. Either rename the test to reflect what it actually checks, or add assertions for the section content's class changes.

🤖 Fix all issues with AI agents
In `@frontend/__tests__/unit/components/Footer.test.tsx`:
- Around line 150-156: The loop over regularLinks embeds the fallback logic in
the assertion (expect(...).toHaveAttribute('href', link.href || '/')), which
reduces clarity; instead compute an explicit expectedHref before asserting so
the test checks a concrete value. Update the test loop that references
regularLinks and link.href to set a local expectedHref (e.g., const expectedHref
= link.href === '' ? '/' : link.href) then assert
expect(linkElement).toHaveAttribute('href', expectedHref); keep the existing
target assertion and use the same screen.getByRole('link', { name: link.text })
lookup.

In `@frontend/__tests__/unit/components/MultiSearch.test.tsx`:
- Around line 588-605: The test title says "Enter/Space" but only triggers
Enter; either add a Space key assertion or rename the test to mention only
Enter. To add Space: after focusing suggestionBtn (from MultiSearchBar test),
send a second keyboard event via user.keyboard('{Space}') (or in a separate it
block) and assert mockPush was called with '/chapters/test-chapter' (resetting
or checking call counts as needed). If Space isn't supported, simply rename the
test string for the it(...) that references suggestionBtn and
user.keyboard('{Enter}') to remove "Space". Use the existing identifiers
suggestionBtn, user.keyboard, mockPush, MultiSearchBar and defaultProps to
locate and modify the test.

In `@frontend/__tests__/unit/pages/MenteeProfilePage.test.tsx`:
- Around line 527-545: The test never triggers the onSelectionChange
empty-selection branch; update the test's Select mock to expose a way to invoke
onSelectionChange (e.g., render a hidden/test-only button or element inside the
mock that calls props.onSelectionChange(new Set()) or new Set(['']) when
clicked), then in the test render MenteeProfilePage, locate and click that
trigger to simulate an empty selection, and assert that the displayed issues
remain unchanged; target the mocked Select component and the onSelectionChange
handler in the MenteeProfilePage test so the empty Set branch is exercised.

In `@frontend/__tests__/unit/pages/ModuleDetails.test.tsx`:
- Around line 121-167: Update the three test case names in
ModuleDetails.test.tsx so they accurately reflect that the mocked fields are set
to null (not undefined): change "renders module without admins (uses undefined
fallback)" to "renders module without admins (uses null fallback)", change
"renders module without domains (uses undefined fallback)" to "renders module
without domains (uses null fallback)", and change "renders module without tags
(uses undefined fallback)" to "renders module without tags (uses null
fallback)"; these are the it(...) blocks that set admins: null, domains: null,
and tags: null via mockUseQuery using mockModuleData.

In `@frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx`:
- Around line 427-453: The test claims to assert that handleAppError is called
when the mutation fails but never mocks or spies on handleAppError; either mock
handleAppError from the app/global-error module and assert it was called with
the rejection (e.g., jest.mock('app/global-error', () => ({ handleAppError:
jest.fn() })) and expect(handleAppError).toHaveBeenCalledWith(mockError)), or
change the test name/expectations to only assert the observable behavior (that
mockUpdateProgram was called and failed). Locate the failing flow around
capturedSetStatus, mockUpdateProgram and the test block in
ProgramDetailsMentorship.test.tsx to apply the mock or rename the test
accordingly.

In `@frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx`:
- Around line 158-182: The mock used in the test for ProjectHealthMetricsDetails
omits the createdAt field and the assertion doesn't verify null-handling; update
the test to include a createdAt value on
mockProjectsDashboardMetricsDetailsData.project.healthMetricsList[0] (matching
the GraphQL schema) or explicitly set it to null only if you intend to test the
fallback, then assert the component's fallback rendering (for example check for
the fallback text like "N/A" or the empty label used by
MetricsCard/HealthMetrics) instead of just expecting 'Stars'; if this
null-createdAt scenario is unrealistic for production, consider removing the
defensive test and focusing on more realistic edge cases.
🧹 Nitpick comments (26)
frontend/__tests__/unit/components/HealthMetrics.test.tsx (1)

122-128: Empty array test may not reflect real usage.

Based on learnings, the parent DetailsCard component guards rendering of HealthMetrics with a healthMetricsData.length > 0 check, so data={[]} shouldn't occur in practice. This test isn't harmful, but be aware it's testing a path that's unreachable in the actual app. Based on learnings from PR #1633: the DetailsCard component ensures HealthMetrics is only rendered when data has at least one element.

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

98-102: Avoid calling React components directly as functions in tests.

SecondaryCard() bypasses React's reconciler (hooks, context, error boundaries won't work correctly). The default parameter = {} only triggers when the argument is literally undefined, which never happens through normal JSX usage — <SecondaryCard /> always passes {}. You're covering a code path that's unreachable in production.

If the sole goal is to hit the = {} default for coverage metrics, consider adding a /* istanbul ignore next */ or equivalent hint on the default parameter instead. Alternatively, if you still want a test, prefer JSX:

Suggested change
-    it('renders correctly when called directly as a function (default props)', () => {
-      // This covers the default parameter `= {}` in the component signature
-      const { container } = render(SecondaryCard())
-      expect(container.querySelector('.mb-8')).toBeInTheDocument()
-    })

The existing test on line 24–27 (renders successfully with no props at all) already validates this behavior via JSX.

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

326-367: New edge-case tests look good overall.

The three added tests appropriately cover contributor name fallback, missing pathname segments, and empty enum values. A few minor observations:

  1. Line 353-357: The "handles pathname without program key gracefully" test only asserts the component doesn't crash. Consider also verifying the generated module link href to confirm the path construction degrades as expected (e.g., doesn't produce a malformed URL like //modules/test-module). This would make the test more meaningful since the PR objective includes path construction changes.

  2. Line 366: toBeGreaterThan(0) is a loose assertion. If the component renders multiple "Unknown" texts unexpectedly, this wouldn't catch it. A precise count (toHaveLength(1)) would be more robust, assuming only experienceLevel was emptied.

Neither of these is blocking — the tests are functional and achieve coverage goals.

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

138-140: Clear mockPush in beforeEach to prevent cross-test leakage.

Only LabelList is cleared here, but mockPush is module-scoped and only manually cleared in one test (line 414). If any future or reordered test triggers router.push, assertions on mockPush in the "Click Handlers" block could produce false positives or negatives.

Proposed fix
   beforeEach(() => {
+    mockPush.mockClear()
     jest.mocked(LabelList).mockClear()
   })
frontend/__tests__/unit/pages/RepositoryDetails.test.tsx (1)

262-323: Consider strengthening assertions for null-data tests.

The three tests for null issues, recentMilestones, and releases only assert that 'Test Repo' renders, which effectively just verifies the component doesn't crash. They don't confirm that the corresponding UI sections are correctly hidden or empty. For example, you could additionally assert that milestone titles or issue titles from the default mock data are not present:

     await waitFor(() => {
       expect(screen.getByText('Test Repo')).toBeInTheDocument()
     })
+    // Verify the section that depends on null data is absent
+    expect(screen.queryByText('Recent Milestones')).not.toBeInTheDocument()

This would make the tests more meaningful by verifying graceful degradation rather than just no-crash behavior.

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

91-119: setTimeout spy counts may be fragile due to React internals also calling setTimeout under fake timers.

When jest.useFakeTimers() is active, React's scheduler can fall back to setTimeout, which would inflate your spy's call count and cause flaky failures. Instead of asserting exact toHaveBeenCalledTimes, consider filtering by the throttle delay argument:

♻️ Suggested improvement
-    expect(setTimeoutSpy).toHaveBeenCalledTimes(1)
+    const throttleCalls = () =>
+      setTimeoutSpy.mock.calls.filter(([, delay]) => delay === 100).length
+
+    expect(throttleCalls()).toBe(1)

     // Immediate second scroll event - should NOT set a new timeout due to throttling
     globalThis.dispatchEvent(new Event('scroll'))

-    expect(setTimeoutSpy).toHaveBeenCalledTimes(1) // Still 1
+    expect(throttleCalls()).toBe(1) // Still 1

     // Fast forward time to clear the timeout
     act(() => {
       jest.advanceTimersByTime(100)
     })

     // Third scroll event - should set a new timeout now
     globalThis.dispatchEvent(new Event('scroll'))
-    expect(setTimeoutSpy).toHaveBeenCalledTimes(2)
+    expect(throttleCalls()).toBe(2)
frontend/__tests__/unit/pages/Home.test.tsx (2)

343-380: Test name promises icon verification but only asserts text presence.

The test is named "renders project icons correctly for different types" but only checks that each project's name text appears. It never asserts which icon (FaCode, FaBook, FaTag, FaFileCode) is rendered for each type. This likely still achieves the coverage goal (exercising all getProjectIcon branches), but the test name overpromises.

Consider either:

  • Renaming to something like "renders projects for all type variants", or
  • Adding assertions on the rendered icon elements (e.g., via data-testid or SVG role/title attributes) to truly verify icon correctness.

382-413: Null-stats assertions are weak — consider verifying the rendered stat values.

Line 410–411 only checks that the "Active Projects" header text exists, which would pass even without the null-stat mock data (the header renders regardless). This doesn't confirm the component handles null stats gracefully vs. crashing or showing wrong values.

Consider asserting what the component actually renders for null stats (e.g., "0+", "0", or absence of stat values) to make the test meaningful beyond just "it didn't throw."

frontend/__tests__/unit/pages/SnapshotDetails.test.tsx (3)

218-264: Tests exercise fallback paths but don't assert fallback behavior.

The "without level" and "without summary" tests only verify the component doesn't crash (by checking the project name renders). They don't assert the effect of the missing field — e.g., that no level badge is displayed or that no summary text appears. This means a regression where undefined renders as literal text "undefined" would go undetected.

Consider adding negative assertions:

     await waitFor(() => {
       expect(screen.getByText('OWASP Nest')).toBeInTheDocument()
     })
+    expect(screen.queryByText('INCUBATOR')).not.toBeInTheDocument()

Similarly for the summary test, assert that the summary text from the mock data is absent.


266-312: Same observation: chapter fallback tests lack negative assertions.

The "without relatedUrls" and "without summary" chapter tests only confirm the chapter name renders. Adding assertions that verify the absent data isn't displayed (e.g., queryByText for the original summary text) would strengthen these tests.


192-402: Consider a helper to reduce repetitive mock setup.

All seven new tests repeat the same boilerplate: spread mockSnapshotDetailsData.snapshot, override one entity array, and zero out the other two. A small helper would reduce duplication and make tests easier to maintain:

Example helper
function mockSnapshotWith(overrides: Partial<typeof mockSnapshotDetailsData.snapshot>) {
  ;(useQuery as unknown as jest.Mock).mockReturnValue({
    data: {
      snapshot: {
        ...mockSnapshotDetailsData.snapshot,
        newChapters: [],
        newProjects: [],
        newReleases: [],
        ...overrides,
      },
    },
    error: null,
    loading: false,
  })
}

Then each test becomes:

mockSnapshotWith({ newProjects: [projectWithNullKey] })
frontend/__tests__/unit/components/skeletons/sharedSkeletons.test.tsx (2)

142-153: Remove stale reasoning comments and the redundant { exact: false } option.

The block of comments (lines 145–152) reads like working notes rather than test documentation. Also, { exact: false } is the default behavior of toHaveClass, so it's redundant.

✏️ Suggested cleanup
     it('renders without rounded class when false', () => {
       render(<SectionHeader titleSkeletonWidth="w-20" rounded={false} />)
       const skeletons = screen.getAllByTestId('skeleton')
-      // The first skeleton has 'w-5' in base class, check it doesn't have 'rounded' explicitly added by the prop logic
-      // Actually code is: `h-5 w-5 ${rounded ? 'rounded' : ''}`.
-      // If rounded is false, it's empty string.
-      // But note that skeletons might have rounded-lg or similar from base styles if Skeleton component had them,
-      // but here we are mocking Skeleton.
-      // We check for exact class match or presence.
-      expect(skeletons[0]).not.toHaveClass('rounded', { exact: false })
-      // Wait, 'rounded' might be a partial match if we aren't careful, but here expecting class list to NOT contain the specific class 'rounded'.
+      expect(skeletons[0]).not.toHaveClass('rounded')
     })

28-39: Hardcoded skeleton counts are brittle — consider a comment or constant to ease future maintenance.

Tests like expect(skeletons).toHaveLength(17) (line 57) and toHaveLength(54) (line 130) embed the formula 2 + N * 5 and 2 * (2 + 5 * 5) respectively. The inline comments explaining the math are helpful, but if the underlying component's structure changes (e.g., an extra skeleton is added to ItemCardSkeleton), multiple tests will fail with opaque count mismatches.

This is a known trade-off with structural skeleton tests and fine to keep as-is for now, but worth noting.

Also applies to: 42-58, 117-131

frontend/__tests__/unit/components/LogoCarousel.test.tsx (4)

446-494: Coverage edge-case tests look reasonable overall.

The tests exercise the key-generation fallback chain (idurlname'sponsor') and the name || 'Sponsor' text fallback path, which is appropriate for branch coverage. Since React keys aren't observable in the DOM, the count-based assertions are the practical approach here.

One small observation: the first three tests (lines 447–481) are nearly identical in their assertions — they all just verify that 4 sponsor-link elements are rendered. The real value they add is triggering different branches in the keyBase ternary, but a reader can't tell from the assertions alone whether the correct branch was hit. Consider adding a brief inline comment in each test (e.g., // Exercises: sponsor.url || …) to make it immediately clear why each variant exists, since the assertions themselves don't distinguish the cases.


349-356: Duplicate test: "renders image alt text correctly" is identical to "renders sponsor names correctly in alt attributes" (lines 341–347).

Both tests render the same sponsors and assert the same alt attributes on the same elements. One of these can be removed without losing any coverage.


233-275: Test description "duplicates innerHTML on mount" is misleading.

The component doesn't use innerHTML or ref-based DOM cloning — it renders the sponsor list twice via JSX (keySuffix 'a' and 'b'). The test name and the describe block title ("State Changes and Internal Logic") suggest implementation details that don't match the actual component. Consider renaming to something like "renders each sponsor twice and updates on rerender".


438-438: Assertion expects 202 links for 100 sponsors — verify this is intentional.

100 sponsors × 2 (duplicate lists) = 200 sponsor links + 2 footer links = 202. The math checks out, but this relies on the footer links also matching data-testid="sponsor-link" due to the next/link mock. This coupling is fragile — if the footer links ever change or the mock is refined, this test breaks for the wrong reason.

frontend/__tests__/unit/components/forms/shared/FormDateInput.test.tsx (2)

4-10: No-op mock — remove or justify.

This mock re-exports the actual module unchanged, so it has no effect. It appears to be leftover boilerplate. Removing it reduces noise.

Suggested fix
-// Mock styles from imported component
-jest.mock('components/forms/shared/FormDateInput', () => {
-  const originalModule = jest.requireActual('components/forms/shared/FormDateInput')
-  return {
-    ...originalModule,
-  }
-})
-
 // Mock `@heroui/react` Input component

144-159: Export COMMON_INPUT_CLASS_NAMES and use it in the test to avoid brittleness.

The hardcoded class-name values duplicate the COMMON_INPUT_CLASS_NAMES constant. If those values are updated, this test fails even though the component still works correctly.

First, export the constant from FormDateInput.tsx, then import and use it in the test:

Suggested fix

In frontend/src/components/forms/shared/FormDateInput.tsx:

-const COMMON_INPUT_CLASS_NAMES = {
+export const COMMON_INPUT_CLASS_NAMES = {

In frontend/__tests__/unit/components/forms/shared/FormDateInput.test.tsx:

 import { render, screen, fireEvent } from '@testing-library/react'
-import { FormDateInput } from 'components/forms/shared/FormDateInput'
+import { FormDateInput, COMMON_INPUT_CLASS_NAMES } from 'components/forms/shared/FormDateInput'
 
 // Mock styles from imported component
 jest.mock('components/forms/shared/FormDateInput', () => {
@@ -143,14 +143,7 @@ describe('FormDateInput Component', () => {
     it('passes common class names to Input', () => {
       render(<FormDateInput {...defaultProps} />)
 
       const input = screen.getByLabelText('Test Date')
       const classNames = JSON.parse(input.getAttribute('data-class-names') || '{}')
 
-      expect(classNames).toEqual(
-        expect.objectContaining({
-          base: 'w-full min-w-0',
-          label: 'text-sm font-semibold text-gray-600 dark:text-gray-300',
-          input: 'text-gray-800 dark:text-gray-200',
-          inputWrapper: 'bg-gray-50 dark:bg-gray-800',
-          helperWrapper: 'min-w-0 max-w-full w-full',
-          errorMessage: 'break-words whitespace-normal max-w-full w-full',
-        })
-      )
+      expect(classNames).toEqual(COMMON_INPUT_CLASS_NAMES)
frontend/__tests__/unit/pages/Contribute.test.tsx (1)

247-315: Consider consolidating undefined and null variant tests.

The four new tests follow a near-identical pattern, differing only in whether objectID/summary is undefined vs null. While both paths are worth exercising, you could use test.each to reduce duplication.

Not a blocker — fine to keep as-is if you prefer explicit test names.

♻️ Example using test.each
-  test('renders issue card without objectID (uses url fallback)', async () => {
-    ...
-  })
-
-  test('renders issue card with null objectID (uses url fallback)', async () => {
-    ...
-  })
+  test.each([undefined, null])('renders issue card when objectID is %s (uses url fallback)', async (objectID) => {
+    const issue = {
+      ...mockContributeData.issues[0],
+      objectID,
+      url: 'https://github.com/test/issue/1',
+    }
+    ;(fetchAlgoliaData as jest.Mock).mockResolvedValue({
+      hits: [issue],
+      totalPages: 1,
+    })
+
+    render(<ContributePage />)
+
+    await waitFor(() => {
+      expect(screen.getByText('Contribution 1')).toBeInTheDocument()
+    })
+  })
frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx (4)

188-203: Misleading test name — this doesn't test invalid status or error toast.

This test clicks the "Publish" button (which sends a valid 'PUBLISHED' status via the mock) and only asserts the mutation was called. It doesn't pass an invalid status or verify any error toast. It's effectively a duplicate of the test at Line 165 ("successfully updates status from Draft to Published").

The actual invalid-status test is at Line 328 using capturedSetStatus('INVALID_STATUS'). Consider removing this test to avoid confusion.


281-302: Test is a no-op — the comments themselves acknowledge it can't test what it claims.

Lines 294–301 explicitly state the limitation. The test ends up only asserting the publish button exists — it doesn't exercise invalid status or toast logic. The actual meaningful test for this scenario lives at Line 328. This test should be removed.


205-219: Duplicate of "renders program details correctly for a non-admin" (Line 123).

Both tests set a non-admin user and assert the actions button is absent. The test name claims "shows permission denied toast" but no toast assertion is made. The real permission-denied-toast test is at Line 358 using capturedSetStatus. Consider removing this to reduce noise.


11-11: Type annotation doesn't reflect async nature of the captured callback.

capturedSetStatus is typed as (status: string) => void, but Line 404 awaits it, implying the real callback is async. Consider typing it as (status: string) => void | Promise<void> for accuracy.

Proposed fix
-let capturedSetStatus: ((status: string) => void) | null = null
+let capturedSetStatus: ((status: string) => void | Promise<void>) | null = null
frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx (1)

211-258: Assertions only verify section headers, not how null values are rendered.

This test is useful as a smoke test (component doesn't crash with all-null counts), but the assertions at Lines 255-256 only check for static section headers that would appear regardless of the data. Consider adding at least one assertion on the rendered fallback for a null metric (e.g., checking that 0, N/A, or an empty cell appears where a count would normally be) to confirm the null-handling logic is actually correct.

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

105-116: Redundant clearAllMocks in both beforeEach and afterEach.

clearAllMocks() in beforeEach already guarantees a clean state before each test. The duplicate call in afterEach (line 114) is unnecessary. Consider keeping only the beforeEach cleanup (with restoreAllMocks remaining in afterEach if needed).

♻️ Suggested simplification
   beforeEach(() => {
     jest.clearAllMocks()
     mockEnv = {
       ENVIRONMENT: 'production',
       RELEASE_VERSION: '1.2.3',
     }
   })

   afterEach(() => {
-    jest.clearAllMocks()
     jest.restoreAllMocks()
   })

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 15 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="frontend/__tests__/unit/pages/ModuleDetails.test.tsx">

<violation number="1" location="frontend/__tests__/unit/pages/ModuleDetails.test.tsx:121">
P3: The renamed test description is less accurate than the original. The component applies `admins ?? undefined`, so the fallback value is `undefined`, not `null`. The test provides `null` as the *input*, but the *fallback* used is `undefined`. Consider reverting to the original description or using something like `'renders module without admins (input is null, falls back to undefined)'`.

(Based on your team's feedback about maintaining consistent and accurate naming.) [FEEDBACK_USED]</violation>

<violation number="2" location="frontend/__tests__/unit/pages/ModuleDetails.test.tsx:137">
P3: Same inaccuracy: the component uses `domains ?? undefined`, so the fallback is `undefined`. Consider reverting to the original description.

(Based on your team's feedback about maintaining consistent and accurate naming.) [FEEDBACK_USED]</violation>

<violation number="3" location="frontend/__tests__/unit/pages/ModuleDetails.test.tsx:153">
P3: Same inaccuracy: the component uses `tags ?? undefined`, so the fallback is `undefined`. Consider reverting to the original description.

(Based on your team's feedback about maintaining consistent and accurate naming.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

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: 2

🤖 Fix all issues with AI agents
In `@frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx`:
- Around line 195-210: The test "shows error toast when trying to update with
invalid status" is misleading and duplicates the valid-status test; update it to
exercise the invalid-status path: render ProgramDetailsPage, open the actions
menu, and instead of clicking the 'Publish' menuitem, trigger the invalid status
flow by invoking capturedSetStatus('INVALID_STATUS') (or simulate the UI action
that leads to capturedSetStatus with 'INVALID_STATUS'); then assert that
mockUpdateProgram is either not called and that addToast was invoked with an
error message (or called with the correct error toast arguments). Use the
existing identifiers mockUpdateProgram, addToast, capturedSetStatus and
ProgramDetailsPage to locate and modify the test.
- Around line 212-226: The test titled "shows permission denied toast when
non-admin tries to update status" is a duplicate that only checks the absence of
the actions button (same as the non-admin render test) and never asserts on
addToast; remove this test block (the test(...) that mocks useSession with
non-admin and renders ProgramDetailsPage) or replace it by implementing the
proper permission-denied flow: mock addToast, simulate the status update
attempt, and assert addToast was called with the permission-denied message
(refer to useSession, render, ProgramDetailsPage, and addToast for locating and
fixing the test).
🧹 Nitpick comments (7)
frontend/__tests__/unit/pages/MenteeProfilePage.test.tsx (1)

453-497: Consider consolidating the null and undefined labels tests with it.each.

The two tests (null labels at Line 453, undefined labels at Line 474) are structurally identical — only the labels value and the title string differ. A parameterized test would reduce duplication.

♻️ Optional consolidation
-  it('handles issues with null labels using fallback', () => {
-    const dataWithNullLabels = {
-      ...mockMenteeData,
-      getMenteeModuleIssues: [
-        {
-          id: 'issue1',
-          number: 101,
-          title: 'Issue with null labels',
-          state: 'open',
-          url: 'http://example.com/issue1',
-          labels: null,
-        },
-      ],
-    }
-    mockUseQuery.mockReturnValue({ data: dataWithNullLabels, loading: false, error: undefined })
-    render(<MenteeProfilePage />)
-
-    // Should render without crashing
-    expect(screen.getAllByText('Issue with null labels').length).toBeGreaterThan(0)
-  })
-
-  it('handles issues with undefined labels using fallback', () => {
-    const dataWithUndefinedLabels = {
-      ...mockMenteeData,
-      getMenteeModuleIssues: [
-        {
-          id: 'issue1',
-          number: 101,
-          title: 'Issue with undefined labels',
-          state: 'open',
-          url: 'http://example.com/issue1',
-          labels: undefined,
-        },
-      ],
-    }
-    mockUseQuery.mockReturnValue({
-      data: dataWithUndefinedLabels,
-      loading: false,
-      error: undefined,
-    })
-    render(<MenteeProfilePage />)
-
-    // Should render without crashing
-    expect(screen.getAllByText('Issue with undefined labels').length).toBeGreaterThan(0)
-  })
+  it.each([
+    ['null', null],
+    ['undefined', undefined],
+  ])('handles issues with %s labels using fallback', (_desc, labels) => {
+    const data = {
+      ...mockMenteeData,
+      getMenteeModuleIssues: [
+        {
+          id: 'issue1',
+          number: 101,
+          title: `Issue with ${_desc} labels`,
+          state: 'open',
+          url: 'http://example.com/issue1',
+          labels,
+        },
+      ],
+    }
+    mockUseQuery.mockReturnValue({ data, loading: false, error: undefined })
+    render(<MenteeProfilePage />)
+    expect(screen.getAllByText(`Issue with ${_desc} labels`).length).toBeGreaterThan(0)
+  })
frontend/__tests__/unit/components/LogoCarousel.test.tsx (2)

446-493: Test assertions don't verify the stated behavior.

All three key-generation tests (lines 447–481) share the same assertion (toHaveLength(4)) regardless of which fallback path is taken. They confirm the component renders without crashing, which is sufficient for branch-coverage metrics, but the test names ("uses url for key generation…", "uses name for key generation…") promise more than the assertions deliver.

If the goal is purely coverage, this is fine. If you also want to guard against regressions in key logic, consider asserting on the actual key prop (e.g., via a data-key attribute or snapshot) so the tests fail when fallback order changes.


448-454: Repeated undefined as unknown as string casts — extract a helper.

The triple type-cast pattern to create a sponsor with a missing id is duplicated three times. A small factory function would reduce noise and make the intent clearer.

♻️ Suggested helper
+const makeSponsor = (overrides: Partial<Sponsor> = {}): Sponsor => ({
+  id: undefined as unknown as string,
+  name: '',
+  imageUrl: '',
+  url: '',
+  sponsorType: 'Gold',
+  ...overrides,
+})
+
 describe('Coverage Edge Cases', () => {
   it('uses url for key generation when id is missing', () => {
-    const sponsor: Sponsor = {
-      id: undefined as unknown as string,
-      name: 'Name',
-      imageUrl: 'https://img.com',
-      url: 'https://url.com',
-      sponsorType: 'Gold',
-    }
+    const sponsor = makeSponsor({ name: 'Name', imageUrl: 'https://img.com', url: 'https://url.com' })
     render(<MovingLogos sponsors={[sponsor]} />)
     expect(screen.getAllByTestId('sponsor-link')).toHaveLength(4)
   })

Also applies to: 460-466, 472-478

frontend/__tests__/unit/pages/Organization.test.tsx (2)

58-61: Redundant mock setup — already done in beforeEach.

Lines 34–37 in beforeEach set the exact same fetchAlgoliaData mock (same hits, same totalPages: 2). This duplicate can be removed without affecting the test.

Suggested diff
   test('handles page change correctly', async () => {
     window.scrollTo = jest.fn()
-      ; (fetchAlgoliaData as jest.Mock).mockResolvedValue({
-        hits: mockOrganizationData.hits,
-        totalPages: 2,
-      })
     render(<Organization />)

34-39: Nit: inconsistent indentation on semicolon-prefixed lines.

Lines 34 and 39 (and similarly 125, 161) use ; (…) for ASI protection but with varying indentation — some are flush, others are indented an extra level. Consider aligning them consistently for readability.

frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx (2)

327-330: Silent skip if capturedSetStatus is null — tests will falsely pass.

The if (capturedSetStatus) guard (used here and at Lines 358, 388, 428) silently skips the core action when the mock fails to capture the callback. If this ever becomes null due to a refactor, the tests pass without exercising any meaningful logic.

Replace with an explicit assertion or throw:

Proposed fix (apply at all four call sites)
-    if (capturedSetStatus) {
-      capturedSetStatus('INVALID_STATUS')
-    }
+    expect(capturedSetStatus).not.toBeNull()
+    capturedSetStatus!('INVALID_STATUS')

460-473: Most "default value" checks are only comments, not assertions.

Lines 467–472 describe expected defaults for key, name, description, status, startedAt, and endedAt but never assert them. The test only verifies the component doesn't crash and checks one value (Mentees Limit0). Either add assertions for the commented expectations or remove the misleading comments.

coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 12, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 8 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="frontend/src/components/UserCard.tsx">

<violation number="1">
P1: Accessibility regression: removing `role="img"` and `aria-label="Default profile picture"` makes the fallback avatar invisible to screen readers. These are not test-only attributes — they provide essential accessibility semantics. The PR goal is to improve test coverage, not modify production accessibility behavior.

(Based on your team's feedback about not mixing unrelated changes into a PR.) [FEEDBACK_USED]</violation>
</file>

<file name="frontend/__tests__/unit/pages/Organization.test.tsx">

<violation number="1" location="frontend/__tests__/unit/pages/Organization.test.tsx:167">
P2: Removing the `getByRole('img', { name: 'Default profile picture' })` assertion weakens this test. The test is named "renders organization card with null avatarUrl **using fallback**" but no longer verifies the fallback is actually rendered. Confirming the avatar image is NOT shown doesn't prove a fallback IS shown — both could be absent (a bug). Consider restoring the positive assertion or replacing it with an equivalent check for the fallback element.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/__tests__/unit/components/Footer.test.tsx (1)

295-344: ⚠️ Potential issue | 🟠 Major

Two conflicting environment-mock strategies — one of them may silently not work.

The file uses two different techniques to control ENVIRONMENT / RELEASE_VERSION:

  1. Mutable mockEnv + getters (lines 76-88, used at line 457): mutates mockEnv.ENVIRONMENT and relies on the getter to pick up the change.
  2. Direct assignment to envModule (lines 309-310, 315-316, 329-330): writes envModule.ENVIRONMENT = 'staging' on the object returned by jest.requireMock.

If Jest preserves the accessor descriptors from the mock factory, the direct assignments in approach 2 are silently ignored in non-strict mode (no setter exists), meaning these tests pass only because mockEnv still holds the beforeEach default. Conversely, if Jest flattens them to data properties, the getter-based approach in the "Edge Cases" block won't work.

Recommend unifying on a single strategy — the mockEnv mutation approach is simpler and self-consistent:

Suggested refactor — use mockEnv everywhere
   describe('Version Link Behavior', () => {
-    let originalEnvironment: string
-    let originalReleaseVersion: string
-    let envModule: typeof import('utils/env.client')
-
-    beforeEach(() => {
-      jest.clearAllMocks()
-      envModule = jest.requireMock<typeof import('utils/env.client')>('utils/env.client')
-      originalEnvironment = envModule.ENVIRONMENT
-      originalReleaseVersion = envModule.RELEASE_VERSION
-    })
-
-    afterEach(() => {
-      if (envModule) {
-        envModule.ENVIRONMENT = originalEnvironment
-        envModule.RELEASE_VERSION = originalReleaseVersion
-      }
-    })
-
     test('renders version as commit link in staging environment', () => {
-      envModule.ENVIRONMENT = 'staging'
-      envModule.RELEASE_VERSION = '24.2.10-12c25c5'
+      mockEnv.ENVIRONMENT = 'staging'
+      mockEnv.RELEASE_VERSION = '24.2.10-12c25c5'

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

     test('renders version as release tag link in production environment', () => {
-      envModule.ENVIRONMENT = 'production'
-      envModule.RELEASE_VERSION = '1.2.3'
+      mockEnv.ENVIRONMENT = 'production'
+      mockEnv.RELEASE_VERSION = '1.2.3'

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

The outer beforeEach (lines 105-111) already resets mockEnv to production defaults, so the dedicated setup/teardown in this block becomes unnecessary.

🤖 Fix all issues with AI agents
In `@frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx`:
- Around line 305-333: The test "calls addToast with error when setStatus is
called with invalid status" is a duplicate of the earlier test "shows error
toast when trying to update with invalid status" (both call
capturedSetStatus('INVALID_STATUS') and assert the same addToast payload);
remove one of these duplicate tests (either delete the test whose name starts
with "calls addToast with error when setStatus is called with invalid status" or
the earlier one) so only a single test asserting that
capturedSetStatus('INVALID_STATUS') triggers addToast with the expected payload
remains, keeping references to capturedSetStatus, addToast, and
ProgramDetailsPage intact.
- Around line 203-205: The tests currently skip body when the mock callback
variable capturedSetStatus is null due to the `if (capturedSetStatus)` guard,
which can hide failures; replace each guard in ProgramDetailsMentorship.test.tsx
(all occurrences referencing capturedSetStatus) with an explicit assertion that
the callback was captured (e.g., assert or expect that capturedSetStatus is
defined/not null) and then invoke capturedSetStatus('INVALID_STATUS'); this
ensures a missing callback fails the test instead of silently passing.
- Around line 281-303: The test ProgramDetailsMentorship.test.tsx at the
described diff is misnamed and duplicates the 404 case: update or remove the
test so it matches its intent — either delete the entire test block if
redundant, or repurpose it to assert permission-denied behavior by making
getProgram return a valid program (instead of null) and keeping useSession
mocked with a non-admin user; then assert the permission-denied toast or UI
(rather than "Program Not Found"). Locate the test function (the one starting
with test('shows permission denied when non-admin program is null' ...) and
modify the useQuery mock (getProgram/getProgramModules), useSession mock, and
expectations against ProgramDetailsPage accordingly, or remove the test if
redundant with the existing 404 test.
🧹 Nitpick comments (2)
frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx (2)

191-216: Missing assertion for null score rendering.

The mock sets score: null but there's no assertion verifying how the component renders a null score (e.g., does it show "0", "N/A", or nothing?). The compliance flag assertions are good — they confirm null is treated as not-compliant. Add a similar assertion for the score fallback to make this test meaningful for all three null fields it introduces.


218-265: Test sets ~20 fields to null but only asserts two section headers exist.

This test exercises null paths for coverage but doesn't verify the component's actual behavior with null values — it only confirms the page doesn't crash. Consider asserting on at least a few representative fallback outputs (e.g., chart series containing 0 instead of actual counts, or specific fallback text) to give the test real regression-catching value.

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

🤖 Fix all issues with AI agents
In `@frontend/__tests__/unit/components/Footer.test.tsx`:
- Around line 75-88: Add a test that exercises the non-production branch by
mutating the existing mockEnv before rendering Footer: set mockEnv.ENVIRONMENT
to a non-'production' value and set mockEnv.RELEASE_VERSION to a commit-hash,
render the Footer component, and assert that the output contains the commit-hash
link (the same behavior previously covered by the "Version Link Behavior"
tests). Use the current getter-based jest.mock for utils/env.client (mockEnv,
ENVIRONMENT, RELEASE_VERSION) so the mock picks up the mutated values, and
ensure you reset mockEnv back to defaults after the test to avoid leaking state.
🧹 Nitpick comments (3)
frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx (3)

310-347: Tests at Lines 310 and 349 largely duplicate those at Lines 172 and 219.

  • Lines 172 vs 310: Both render as admin and trigger publish. Line 310 additionally asserts the success toast, which is valuable — but the mutation assertion is identical. Consider merging the addToast assertion into the test at Line 172 instead of having a separate test.
  • Lines 219 vs 349: Both test mutation failure → handleAppError. The only difference is invocation method (UI click vs capturedSetStatus). Unless you specifically intend to test both paths, one can be removed.

Also applies to: 349-373


375-411: Weak assertions — null-field fallback behavior is mostly untested.

Lines 401–409 comment on expected fallback values for key, name, description, status, startedAt, and endedAt, but only menteesLimit is actually asserted. The test proves it doesn't crash, but doesn't verify that fallback values render correctly. Consider adding assertions for at least the commented expectations (e.g., checking for empty strings or specific fallback text in the rendered output).


238-278: Null-field fallback tests only assert the title renders — they don't verify the fallback itself.

All three tests (null admins, null domains, null tags) only check that 'Test Program' is still rendered. They confirm the component doesn't crash, but they don't assert that the admins/domains/tags section is absent or shows a fallback value. Strengthening the assertions would make these tests more meaningful.

coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 12, 2026
@anurag2787 anurag2787 marked this pull request as ready for review February 12, 2026 18:39
@anurag2787
Copy link
Contributor Author

Hi @kasya the pr is ready for review
Thanks!

@sonarqubecloud
Copy link

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

🤖 Fix all issues with AI agents
In `@frontend/__tests__/unit/pages/ChapterDetails.test.tsx`:
- Around line 175-192: The test "handles missing region gracefully" sets
chapter.region to null but never asserts the region text is absent; update that
test in ChapterDetails.test.tsx (the test block named 'handles missing region
gracefully') to also assert that 'Test Region' (the region string from
mockChapterDetailsData) is not rendered—use screen.queryByText('Test Region')
and expect it toBeNull() or expect(...).not.toBeInTheDocument() after rendering
ChapterDetailsPage with the useQuery mock.
🧹 Nitpick comments (9)
frontend/__tests__/unit/pages/Committees.test.tsx (1)

147-163: Consider asserting the actual fallback behavior, not just non-crash.

The test name claims it "uses empty string fallback," but it only verifies that the committee name renders. It doesn't confirm the summary is actually absent or empty. Adding an assertion like expect(screen.queryByText('This is a summary of Committee 1.')).not.toBeInTheDocument() would make the test actually validate the fallback path rather than just proving the page doesn't crash.

Suggested assertion addition
     await waitFor(() => {
       expect(screen.getByText('Committee 1')).toBeInTheDocument()
     })
+    expect(screen.queryByText('This is a summary of Committee 1.')).not.toBeInTheDocument()
   })
frontend/__tests__/unit/components/Footer.test.tsx (1)

285-300: Redundant manual reset of mockEnv at end of test.

The beforeEach at lines 106–109 already resets mockEnv to production defaults before every test. The manual reset at lines 298–299 is unnecessary and adds maintenance burden.

Suggested cleanup
       expect(versionLink).toHaveAttribute('rel', 'noopener noreferrer')
-
-      mockEnv.ENVIRONMENT = 'production'
-      mockEnv.RELEASE_VERSION = '1.2.3'
     })
frontend/__tests__/unit/components/ScrollToTop.test.tsx (1)

91-115: Well-structured throttle test — minor fragility note on setTimeout spy counting.

The test logic correctly validates the throttle mechanism: first scroll schedules a timeout, immediate second scroll is suppressed, and after advancing timers past the 100ms window a third scroll schedules a new timeout. The act() wrapping around jest.advanceTimersByTime is correctly placed where the state update (setIsVisible) fires.

One minor note: jest.spyOn(globalThis, 'setTimeout') captures all setTimeout calls, including any from React internals or the fake timer infrastructure. The toHaveBeenCalledTimes assertions could become fragile across React version upgrades. A more resilient approach would be to track the spy's call count at a baseline before each scroll dispatch:

♻️ Optional: make assertions resilient to unrelated setTimeout calls
     render(<ScrollToTop />)
 
+    const baselineCalls = setTimeoutSpy.mock.calls.length
+
     Object.defineProperty(globalThis, 'scrollY', { value: 400, writable: true })
     globalThis.dispatchEvent(new Event('scroll'))
 
-    expect(setTimeoutSpy).toHaveBeenCalledTimes(1)
+    expect(setTimeoutSpy.mock.calls.length).toBe(baselineCalls + 1)
 
     globalThis.dispatchEvent(new Event('scroll'))
 
-    expect(setTimeoutSpy).toHaveBeenCalledTimes(1) // Still 1
+    expect(setTimeoutSpy.mock.calls.length).toBe(baselineCalls + 1) // Still same
 
     act(() => {
       jest.advanceTimersByTime(100)
     })
 
     globalThis.dispatchEvent(new Event('scroll'))
-    expect(setTimeoutSpy).toHaveBeenCalledTimes(2)
+    expect(setTimeoutSpy.mock.calls.length).toBe(baselineCalls + 2)
frontend/__tests__/unit/components/skeletons/sharedSkeletons.test.tsx (3)

34-44: Magic skeleton counts are fragile.

The assertions on exact skeleton counts (17, 54) are tightly coupled to the internal DOM structure of SectionSkeleton and TwoColumnSection. Any future tweak to skeleton markup (e.g., adding or removing a decorative skeleton) will silently break these tests even though the component is still correct. Consider asserting a minimum count or a structural property (e.g., number of ItemCardSkeleton wrappers) instead.


124-128: { exact: false } is the default for toHaveClass — the option is unnecessary here.

toHaveClass('rounded') already performs a subset check by default. The explicit { exact: false } adds no value and may confuse future readers into thinking the default is exact: true.

Suggested simplification
-      expect(skeletons[0]).not.toHaveClass('rounded', { exact: false })
+      expect(skeletons[0]).not.toHaveClass('rounded')

137-157: Consider adding a test for default/empty optional props on CardSection.

Currently only the "all-options-provided" path is tested. A test rendering <CardSection><div /></CardSection> with no className, minHeight, or colSpan would cover the || '' fallback branches in the source and help reach the 90 % branch-coverage target the PR is aiming for.

Example additional test
it('renders with default props (no className, minHeight, or colSpan)', () => {
  const { container } = render(
    <CardSection>
      <div>Default</div>
    </CardSection>
  )
  // Base classes are always present
  expect(container.firstChild).toHaveClass('mb-8')
  // Optional classes are absent
  expect(container.firstChild).not.toHaveClass('min-h-10')
  expect(container.firstChild).not.toHaveClass('col-span-2')
})
frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx (3)

158-186: Assertions don't verify the null-createdAt fallback behavior.

The test name says "handles null createdAt gracefully" but the assertions only confirm general UI elements (Stars, Forks, Issues, charts) render — the same elements that render in the happy-path test. There's no assertion on the actual fallback output (e.g., empty chart labels or "N/A" text) that would prove the null-createdAt path is exercised correctly. Consider asserting on the specific fallback value produced when createdAt is null.


188-213: Consider asserting the fallback for score: null.

The test sets score: null but never asserts what the UI renders for a null score. The compliance flag assertions are good (verifying "Not Compliant" when null), but adding an assertion for the score fallback (e.g., checking that no score number appears, or that a default/fallback is shown) would make the test name accurate for all three null fields.


215-262: Extensive null setup with minimal assertions — this is effectively a smoke test.

The mock carefully nullifies ~20 fields across healthMetricsList and healthMetricsLatest, but the assertions only check that "Days Metrics" and "Stars" headings render. This confirms the component doesn't crash, but doesn't verify any fallback/default values (e.g., that charts receive 0 for null counts, or that bar chart days/requirements default to 0). If the goal is branch coverage, the assertions should target the specific fallback branches being exercised.

@codecov
Copy link

codecov bot commented Feb 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.00%. Comparing base (dbc57be) to head (6cd8453).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3923      +/-   ##
==========================================
- Coverage   95.39%   94.00%   -1.40%     
==========================================
  Files         463      512      +49     
  Lines       14540    15814    +1274     
  Branches     2061     2131      +70     
==========================================
+ Hits        13871    14866     +995     
- Misses        328      676     +348     
+ Partials      341      272      -69     
Flag Coverage Δ
backend 92.88% <ø> (-2.80%) ⬇️
frontend 97.42% <100.00%> (+2.77%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
frontend/src/components/Milestones.tsx 100.00% <ø> (+6.25%) ⬆️
frontend/src/components/Pagination.tsx 100.00% <100.00%> (+8.82%) ⬆️
frontend/src/components/SecondaryCard.tsx 100.00% <ø> (ø)

... and 77 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update dbc57be...6cd8453. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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 these are awesome! Thanks!
I cleaned up comments a little bit but the rest looks great👍🏼

@kasya kasya added this pull request to the merge queue Feb 14, 2026
Merged via the queue into OWASP:main with commit b8cc42f Feb 14, 2026
60 of 61 checks passed
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.

Improve frontend test coverage (<90%)

2 participants

Comments