Skip to content

Added unit tests for components#581

Open
Abhishekmaurya12367 wants to merge 5 commits intoAOSSIE-Org:mainfrom
Abhishekmaurya12367:setup-jest
Open

Added unit tests for components#581
Abhishekmaurya12367 wants to merge 5 commits intoAOSSIE-Org:mainfrom
Abhishekmaurya12367:setup-jest

Conversation

@Abhishekmaurya12367
Copy link

@Abhishekmaurya12367 Abhishekmaurya12367 commented Feb 17, 2026

Unit test setup for this Next.js project using Jest + React Testing Library
jest.config.js, jest.setup.js
Test scripts in package.json
test, test:watch, test:coverage
Component unit tests in src/components/tests/
Tests for Header, Footer, Banner, Card, Container, SectionHeading, CardHome, Prose, XIcon

Why we added it
Catch UI regressions early (links, headings, toggle buttons, render logic breaking after changes).
Improve maintainability by documenting expected component behavior via tests.
Increase confidence in refactors and future PRs with automated checks.

Summary by CodeRabbit

  • New Features

    • Added X icon component for social media links
  • Updates

    • Replaced Twitter icon with X icon in social links across the platform
  • Tests

    • Established Jest testing framework with complete configuration
    • Added 69 unit tests across 9 components—all passing
    • Tests cover rendering, interactions, accessibility, and styling

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

📝 Walkthrough

Walkthrough

Introduces comprehensive Jest testing infrastructure for React components with configuration files, DOM mocking utilities, and nine test suites covering Card, Container, Banner, SectionHeading, CardHome, XIcon, Prose, Header, and Footer components. Also adds a new XIcon component and replaces Twitter icon references with it.

Changes

Cohort / File(s) Summary
Testing Configuration
jest.config.js, jest.setup.js, package.json
Adds Jest configuration with Next.js preset, path aliases, coverage settings; creates setup with DOM matchers and mocks for Next.js Router, Image, and Link components; adds test scripts (test, test:watch, test:coverage) and dependencies (jest, testing-library packages, jsdom).
Component Test Suites
src/components/__tests__/Banner.test.jsx, src/components/__tests__/Card.test.jsx, src/components/__tests__/CardHome.test.jsx, src/components/__tests__/Container.test.jsx, src/components/__tests__/Footer.test.jsx, src/components/__tests__/Header.test.jsx, src/components/__tests__/Prose.test.jsx, src/components/__tests__/SectionHeading.test.jsx, src/components/__tests__/XIcon.test.jsx
Adds 9 test suites covering component rendering, props, interactions, styling, accessibility, dark mode classes, and ref forwarding; includes mocks for FontAwesome icons and Next.js components.
New Icon Component
src/components/XIcon.jsx
Introduces new XIcon component rendering an inline SVG with viewBox "0 0 24 24", aria-hidden, and currentColor fill, accepting optional className prop.
Icon Replacement
src/components/Footer.jsx, src/pages/index.jsx
Replaces Twitter icon (FontAwesome) with XIcon component; updates accessibility labels from "Follow on Twitter" to "Follow on X (Twitter)".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Hops of joy through test suites new,
Jest and mocks make testing true,
Nine components verified with care,
Coverage blooming everywhere!
The X icon hops in to replace,
Twitter's old logo—new taste!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Added unit tests for components' directly and accurately summarizes the primary change—the introduction of comprehensive Jest unit testing infrastructure and test coverage for React components.

✏️ 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

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

@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: 4

🧹 Nitpick comments (7)
src/components/__tests__/Banner.test.jsx (1)

42-56: Simplify the apply link assertion.

The closest('[href]') fallback on Line 54 could match an unrelated ancestor <a> element and produce a false positive. Since the Next.js Link mock renders a standard <a>, you can assert directly on the anchor:

♻️ Suggested simplification
   it('renders apply link with correct href', () => {
-    const { container } = render(<Banner />)
-    const linkText = screen.getByText('Apply to GSoC with AOSSIE')
-    // The Banner uses legacyBehavior, so Link returns Fragment and anchor is in children
-    // Find the anchor tag that contains the text
-    const links = container.querySelectorAll('a')
-    const applyLink = Array.from(links).find(link => 
-      link.textContent.includes('Apply to GSoC with AOSSIE')
-    )
-    expect(applyLink).toBeInTheDocument()
-    // With legacyBehavior, the href might be on a parent or the anchor itself
-    // Check if href exists on the link or its parent
-    const href = applyLink.getAttribute('href') || applyLink.closest('[href]')?.getAttribute('href')
-    expect(href).toBe('/apply')
+    render(<Banner />)
+    const applyLink = screen.getByRole('link', { name: /Apply to GSoC with AOSSIE/i })
+    expect(applyLink).toHaveAttribute('href', '/apply')
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/__tests__/Banner.test.jsx` around lines 42 - 56, The test's
fallback using applyLink.closest('[href]') can match an unrelated ancestor and
yield false positives; update the assertion to verify the href directly on the
anchor element found (the applyLink variable) and remove the closest('[href]')
fallback. Locate the anchor by its text (or use screen.getByRole('link', { name:
/Apply to GSoC with AOSSIE/ })) to ensure you have the actual <a> element
rendered by the mocked Next.js Link, then assert applyLink.getAttribute('href')
=== '/apply'.
src/pages/index.jsx (1)

7-7: Twitter → X rebranding looks good; consider updating the URL too.

The icon and aria-label are updated to reflect the "X" branding, but the href on Line 93 still points to https://twitter.com/aossie_org. While twitter.com redirects to x.com, you may want to update it to https://x.com/aossie_org for consistency. The same applies in src/components/Footer.jsx (Line 49).

Also applies to: 91-95

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/index.jsx` at line 7, Update the external profile links that still
point to twitter.com to use the new X domain: replace instances of
"https://twitter.com/aossie_org" with "https://x.com/aossie_org" (e.g., the
anchor using the XIcon and aria-label "X" in the main page component and the
anchor in Footer.jsx); ensure the hrefs in the link elements that render the
XIcon and the Footer anchor are changed so both components point to the updated
URL.
src/components/__tests__/Header.test.jsx (1)

6-12: Redundant next/image mock — already defined globally in jest.setup.js.

This local mock duplicates the global one in jest.setup.js (lines 28-34). Unless you need Header-specific mock behavior, remove this to reduce noise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/__tests__/Header.test.jsx` around lines 6 - 12, Remove the
redundant local mock of next/image in Header.test.jsx: delete the
jest.mock('next/image', ...) block since the same mock is already provided
globally in jest.setup.js; only reintroduce a local mock here if Header.test.jsx
requires behavior different from the global mock.
jest.setup.js (2)

5-25: pop is not a Next.js router method.

Line 13 mocks pop: jest.fn(), but useRouter in Next.js Pages Router doesn't expose a pop method. This won't cause failures but is misleading. Consider removing it or replacing with replace: jest.fn() which is an actual router method that's missing from this mock.

Fix router mock
       push: jest.fn(),
-      pop: jest.fn(),
+      replace: jest.fn(),
       reload: jest.fn(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jest.setup.js` around lines 5 - 25, The router mock in the jest.mock call
incorrectly includes a non-existent Next.js method `pop`; update the mocked
return object from the `useRouter` function to remove `pop` and add the real
router method `replace: jest.fn()` instead (or simply remove `pop` if you prefer
no replacement). Locate the jest.mock('next/router', ...) block and modify the
object returned by `useRouter()`—specifically replace the `pop: jest.fn()` entry
with `replace: jest.fn()` to align the mock with Next.js's actual router API.

37-45: legacyBehavior branch is identical to the default branch.

Both arms of the conditional produce the exact same element, making the legacyBehavior check a no-op. If you don't need to differentiate behavior in tests, simplify by removing the branch. If you do, the legacy mock should pass href through the child <a> clone rather than wrapping it.

Simplified mock
 jest.mock('next/link', () => {
   const React = require('react')
-  return ({ children, href, className, legacyBehavior, ...props }) => {
-    if (legacyBehavior) {
-      // For legacyBehavior, wrap children in an anchor with href
-      return React.createElement('a', { href, className, ...props }, children)
-    }
+  return ({ children, href, className, legacyBehavior, ...props }) => {
     return React.createElement('a', { href, className, ...props }, children)
   }
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jest.setup.js` around lines 37 - 45, The mock for next/link currently returns
a function with a redundant legacyBehavior branch; either remove the conditional
and always return a single anchor element, or implement the legacyBehavior
behavior correctly: when legacyBehavior is true, clone/wrap the provided
children (instead of creating a new anchor) and pass the href through to the
child anchor. Update the mocked factory (the function returned in
jest.mock('next/link')) to either drop the legacyBehavior check or to inject
href into the child element when legacyBehavior is true so behavior differs from
the default branch.
TESTING_SUMMARY.md (1)

1-129: Consider moving this content into CONTRIBUTING.md or README.md instead of a standalone file.

TESTING_SUMMARY.md reads more like a PR description than permanent documentation. The "Running Tests" section (lines 80-91) is valuable for contributors, but the point-in-time statistics (lines 93-97: "69 tests passing") and the file change log (lines 99-116) will become stale immediately. Consider:

  1. Moving the useful parts ("Running Tests", "Testing Infrastructure" overview) into an existing doc like README or CONTRIBUTING.
  2. Removing transient statistics and file lists that won't be maintained.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TESTING_SUMMARY.md` around lines 1 - 129, Move the permanent, useful docs
(the "Running Tests" section and the "Testing Infrastructure" overview) into an
existing contributor-facing doc (preferably CONTRIBUTING.md or README.md) by
copying the headings "Running Tests" and "Testing Infrastructure" and their
content; remove transient sections "Test Statistics" and "Files
Modified/Created" from the repo docs (delete them from TESTING_SUMMARY.md) and
either remove TESTING_SUMMARY.md or convert it into a temporary PR
description/note; ensure references to scripts in package.json remain accurate
and update CONTRIBUTING.md/README.md to include the test commands and a brief
infra summary while omitting point-in-time counts like "69 tests passing."
src/components/__tests__/Prose.test.jsx (1)

44-64: Tests are coupled to implementation-detail CSS class names.

Asserting exact Tailwind utility classes like prose-code:p-2 and prose-headings:font-extrabold makes these tests brittle — any styling tweak will break them without an actual behavior change. Consider testing only the semantically meaningful aspects (renders children, accepts custom className) and dropping the granular class assertions, or at minimum consolidating them into a single test that checks the class string contains expected substrings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/__tests__/Prose.test.jsx` around lines 44 - 64, The tests in
Prose.test.jsx are tightly coupled to Tailwind utility class names (e.g., the
assertions looking for 'prose-code:p-2', 'prose-code:bg-slate-300',
'prose-headings:font-extrabold', 'prose-headings:mt-0'); update the tests for
the Prose component to avoid brittle, implementation-detail assertions by
removing the specific utility-class expects and instead verify semantic
behavior: ensure Prose renders its children correctly and accepts/merges a
custom className prop (or, if you need to assert styling, replace multiple exact
class checks with a single assertion that the container.className contains
expected substrings like 'prose-code' and 'prose-headings'); update the test
cases around render(<Prose>...</Prose>) and the variable prose =
container.firstChild to reflect these new, less brittle assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/__tests__/CardHome.test.jsx`:
- Around line 24-48: The two tests ("has correct styling classes" and "has dark
mode classes") claim to verify CSS classes but only assert element presence;
update the tests in CardHome.test.jsx to either (A) assert the expected
className values on the elements rendered by <CardHome heading=... content=...
/> — e.g., check the Link's className and the h5/class ".block" contains the
expected class tokens (use toHaveClass or classList checks) — or (B) if class
names are unstable or not intended to be tested, remove these two tests to avoid
redundant existence assertions; locate the tests by their titles and queries for
'a', 'h5', and '.block' and replace the duplicate/empty expects with meaningful
class assertions or delete the test blocks.

In `@src/components/__tests__/Header.test.jsx`:
- Around line 217-222: The test "renders mobile navigation menu items" in
Header.test.jsx is a duplicate and only asserts the "Menu" button; either remove
this test or change it to actually open the Popover and assert the mobile nav
entries appear: render the Header component, trigger the Menu toggle (e.g.,
fireEvent.click or userEvent.click on the "Menu" button), then assert that the
expected mobile items like "About", "Projects", etc. are present in the
Popover.Panel using screen.getByText or await screen.findByText; update the test
name if you keep it to reflect that it checks the Popover panel contents.
- Around line 187-215: The "highlights active navigation item" test in
Header.test.jsx is duplicating href checks and not asserting the active styling;
update the test for the Header component (the "highlights active navigation
item" it block) to assert the active CSS class on the active NavItem instead of
only checking href: set mockRouter.pathname = '/projects', render <Header />,
locate the Projects link (as you already do) and add an expectation that the
link or its NavItem root element has the active class (e.g.,
toHaveClass('text-[`#00843D`]') or whatever className NavItem uses when isActive),
or if you prefer to keep test surface area minimal, remove this entire it block
to avoid duplicating the existing href test.
- Around line 120-154: The test for Header's dark mode toggle currently only
asserts the button exists; update the test in the 'dark mode toggle button calls
toggleMode when clicked' case so it verifies an actual side effect of clicking
toggleButton: either assert that document.documentElement.classList.toggle (or
contains 'dark' class) was called/changed, or assert that the mock
localStorage.setItem was called with the expected theme key/value after await
user.click(toggleButton). Locate the test referencing Header and toggleButton,
ensure matchMedia/localStorage mocks remain or are removed if unused, and
replace the final expect(toggleButton).toBeInTheDocument() with an assertion on
document.documentElement.classList or localStorage.setItem to validate the
toggle behavior.

---

Nitpick comments:
In `@jest.setup.js`:
- Around line 5-25: The router mock in the jest.mock call incorrectly includes a
non-existent Next.js method `pop`; update the mocked return object from the
`useRouter` function to remove `pop` and add the real router method `replace:
jest.fn()` instead (or simply remove `pop` if you prefer no replacement). Locate
the jest.mock('next/router', ...) block and modify the object returned by
`useRouter()`—specifically replace the `pop: jest.fn()` entry with `replace:
jest.fn()` to align the mock with Next.js's actual router API.
- Around line 37-45: The mock for next/link currently returns a function with a
redundant legacyBehavior branch; either remove the conditional and always return
a single anchor element, or implement the legacyBehavior behavior correctly:
when legacyBehavior is true, clone/wrap the provided children (instead of
creating a new anchor) and pass the href through to the child anchor. Update the
mocked factory (the function returned in jest.mock('next/link')) to either drop
the legacyBehavior check or to inject href into the child element when
legacyBehavior is true so behavior differs from the default branch.

In `@src/components/__tests__/Banner.test.jsx`:
- Around line 42-56: The test's fallback using applyLink.closest('[href]') can
match an unrelated ancestor and yield false positives; update the assertion to
verify the href directly on the anchor element found (the applyLink variable)
and remove the closest('[href]') fallback. Locate the anchor by its text (or use
screen.getByRole('link', { name: /Apply to GSoC with AOSSIE/ })) to ensure you
have the actual <a> element rendered by the mocked Next.js Link, then assert
applyLink.getAttribute('href') === '/apply'.

In `@src/components/__tests__/Header.test.jsx`:
- Around line 6-12: Remove the redundant local mock of next/image in
Header.test.jsx: delete the jest.mock('next/image', ...) block since the same
mock is already provided globally in jest.setup.js; only reintroduce a local
mock here if Header.test.jsx requires behavior different from the global mock.

In `@src/components/__tests__/Prose.test.jsx`:
- Around line 44-64: The tests in Prose.test.jsx are tightly coupled to Tailwind
utility class names (e.g., the assertions looking for 'prose-code:p-2',
'prose-code:bg-slate-300', 'prose-headings:font-extrabold',
'prose-headings:mt-0'); update the tests for the Prose component to avoid
brittle, implementation-detail assertions by removing the specific utility-class
expects and instead verify semantic behavior: ensure Prose renders its children
correctly and accepts/merges a custom className prop (or, if you need to assert
styling, replace multiple exact class checks with a single assertion that the
container.className contains expected substrings like 'prose-code' and
'prose-headings'); update the test cases around render(<Prose>...</Prose>) and
the variable prose = container.firstChild to reflect these new, less brittle
assertions.

In `@src/pages/index.jsx`:
- Line 7: Update the external profile links that still point to twitter.com to
use the new X domain: replace instances of "https://twitter.com/aossie_org" with
"https://x.com/aossie_org" (e.g., the anchor using the XIcon and aria-label "X"
in the main page component and the anchor in Footer.jsx); ensure the hrefs in
the link elements that render the XIcon and the Footer anchor are changed so
both components point to the updated URL.

In `@TESTING_SUMMARY.md`:
- Around line 1-129: Move the permanent, useful docs (the "Running Tests"
section and the "Testing Infrastructure" overview) into an existing
contributor-facing doc (preferably CONTRIBUTING.md or README.md) by copying the
headings "Running Tests" and "Testing Infrastructure" and their content; remove
transient sections "Test Statistics" and "Files Modified/Created" from the repo
docs (delete them from TESTING_SUMMARY.md) and either remove TESTING_SUMMARY.md
or convert it into a temporary PR description/note; ensure references to scripts
in package.json remain accurate and update CONTRIBUTING.md/README.md to include
the test commands and a brief infra summary while omitting point-in-time counts
like "69 tests passing."

Comment on lines +24 to +48
it('has correct styling classes', () => {
const { container } = render(
<CardHome heading="Heading" content="Content" />
)
const link = container.querySelector('a')
expect(link).toBeInTheDocument()
// The Link component wraps the content, so classes are on the Link's className
// Since Next.js Link mock passes className through, we check the structure
expect(link).toBeInTheDocument()
// Verify the card structure exists
const h5 = container.querySelector('h5')
expect(h5).toBeInTheDocument()
})

it('has dark mode classes', () => {
const { container } = render(
<CardHome heading="Heading" content="Content" />
)
const link = container.querySelector('a')
expect(link).toBeInTheDocument()
// Verify component structure - classes are applied in the component
const cardContent = container.querySelector('.block')
// The classes are in the component definition, so we verify structure instead
expect(link).toBeInTheDocument()
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Tests "has correct styling classes" and "has dark mode classes" don't assert what they claim.

Both tests have descriptive names implying they verify CSS classes, but neither actually asserts on any class names. They only re-check element existence (already covered by other tests). Line 32 is a duplicate of Line 29, and Lines 45-47 query .block without asserting on the result.

Either add meaningful class assertions or remove these tests to avoid giving false confidence in styling coverage.

♻️ Suggested fix — actually test the classes
   it('has correct styling classes', () => {
     const { container } = render(
       <CardHome heading="Heading" content="Content" />
     )
     const link = container.querySelector('a')
     expect(link).toBeInTheDocument()
-    // The Link component wraps the content, so classes are on the Link's className
-    // Since Next.js Link mock passes className through, we check the structure
-    expect(link).toBeInTheDocument()
-    // Verify the card structure exists
-    const h5 = container.querySelector('h5')
-    expect(h5).toBeInTheDocument()
+    expect(link).toHaveClass('block', 'rounded-lg', 'shadow')
   })
 
   it('has dark mode classes', () => {
     const { container } = render(
       <CardHome heading="Heading" content="Content" />
     )
     const link = container.querySelector('a')
     expect(link).toBeInTheDocument()
-    // Verify component structure - classes are applied in the component
-    const cardContent = container.querySelector('.block')
-    // The classes are in the component definition, so we verify structure instead
-    expect(link).toBeInTheDocument()
+    expect(link).toHaveClass('dark:bg-gray-800', 'dark:border-gray-700')
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/__tests__/CardHome.test.jsx` around lines 24 - 48, The two
tests ("has correct styling classes" and "has dark mode classes") claim to
verify CSS classes but only assert element presence; update the tests in
CardHome.test.jsx to either (A) assert the expected className values on the
elements rendered by <CardHome heading=... content=... /> — e.g., check the
Link's className and the h5/class ".block" contains the expected class tokens
(use toHaveClass or classList checks) — or (B) if class names are unstable or
not intended to be tested, remove these two tests to avoid redundant existence
assertions; locate the tests by their titles and queries for 'a', 'h5', and
'.block' and replace the duplicate/empty expects with meaningful class
assertions or delete the test blocks.

Comment on lines +120 to +154
it('dark mode toggle button calls toggleMode when clicked', async () => {
const user = userEvent.setup()

// Mock localStorage
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
}
global.localStorage = localStorageMock

// Mock matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
})

render(<Header />)
const toggleButton = screen.getByLabelText('Toggle dark mode')

await user.click(toggleButton)

// Check if document.documentElement.classList.toggle was called
// This is tested indirectly through the button click
expect(toggleButton).toBeInTheDocument()
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Dark mode toggle test doesn't actually verify toggle behavior.

The test sets up localStorage and matchMedia mocks (lines 124-144) but the only assertion is expect(toggleButton).toBeInTheDocument() (line 153), which just checks the button exists — already covered by the test on line 98-102. The elaborate mock setup is wasted. Either assert on an observable side effect (e.g., classList change on document.documentElement, or localStorage.setItem being called), or remove this test to avoid false confidence.

Example: actually verify a side effect
     await user.click(toggleButton)
     
-    // Check if document.documentElement.classList.toggle was called
-    // This is tested indirectly through the button click
-    expect(toggleButton).toBeInTheDocument()
+    // Verify dark mode was toggled via observable side effect
+    expect(localStorageMock.setItem).toHaveBeenCalled()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('dark mode toggle button calls toggleMode when clicked', async () => {
const user = userEvent.setup()
// Mock localStorage
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
}
global.localStorage = localStorageMock
// Mock matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
})
render(<Header />)
const toggleButton = screen.getByLabelText('Toggle dark mode')
await user.click(toggleButton)
// Check if document.documentElement.classList.toggle was called
// This is tested indirectly through the button click
expect(toggleButton).toBeInTheDocument()
})
it('dark mode toggle button calls toggleMode when clicked', async () => {
const user = userEvent.setup()
// Mock localStorage
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
}
global.localStorage = localStorageMock
// Mock matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
})
render(<Header />)
const toggleButton = screen.getByLabelText('Toggle dark mode')
await user.click(toggleButton)
// Verify dark mode was toggled via observable side effect
expect(localStorageMock.setItem).toHaveBeenCalled()
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/__tests__/Header.test.jsx` around lines 120 - 154, The test
for Header's dark mode toggle currently only asserts the button exists; update
the test in the 'dark mode toggle button calls toggleMode when clicked' case so
it verifies an actual side effect of clicking toggleButton: either assert that
document.documentElement.classList.toggle (or contains 'dark' class) was
called/changed, or assert that the mock localStorage.setItem was called with the
expected theme key/value after await user.click(toggleButton). Locate the test
referencing Header and toggleButton, ensure matchMedia/localStorage mocks remain
or are removed if unused, and replace the final
expect(toggleButton).toBeInTheDocument() with an assertion on
document.documentElement.classList or localStorage.setItem to validate the
toggle behavior.

Comment on lines +187 to +215
it('highlights active navigation item', () => {
mockRouter.pathname = '/projects'
const { container } = render(<Header />)

// Verify that when pathname is /projects, the NavItem component receives isActive=true
// This is tested indirectly by checking that navigation renders correctly
// The actual styling is applied by clsx based on isActive prop

// Find Projects links in the header
const projectsLinks = screen.getAllByText('Projects')
expect(projectsLinks.length).toBeGreaterThan(0)

// Verify at least one Projects link exists in header
const headerProjectsLink = projectsLinks.find(link =>
link.closest('header') !== null
)
expect(headerProjectsLink).toBeInTheDocument()

// The active state is determined by useRouter().pathname === href
// Since we've set pathname to /projects, the link with href="/projects" should be active
// We verify the link exists and the router logic will handle the active state
const linkElement = headerProjectsLink.closest('a')
expect(linkElement).toBeInTheDocument()
expect(linkElement).toHaveAttribute('href', '/projects')

// Note: The actual className with active state is applied by clsx conditionally
// In a real scenario, this would have 'text-[#00843D]' when active
// For testing purposes, we verify the component structure and router integration
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

"Highlights active navigation item" test doesn't verify active styling.

This test only asserts the link exists with the correct href — the same thing the test at lines 156-185 already verifies. The comments (lines 191-214) explain what it should test but acknowledge it doesn't. Either assert on the active CSS class (e.g., toHaveClass('text-[#00843D]')) or remove this test to avoid duplication and false coverage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/__tests__/Header.test.jsx` around lines 187 - 215, The
"highlights active navigation item" test in Header.test.jsx is duplicating href
checks and not asserting the active styling; update the test for the Header
component (the "highlights active navigation item" it block) to assert the
active CSS class on the active NavItem instead of only checking href: set
mockRouter.pathname = '/projects', render <Header />, locate the Projects link
(as you already do) and add an expectation that the link or its NavItem root
element has the active class (e.g., toHaveClass('text-[`#00843D`]') or whatever
className NavItem uses when isActive), or if you prefer to keep test surface
area minimal, remove this entire it block to avoid duplicating the existing href
test.

Comment on lines +217 to +222
it('renders mobile navigation menu items', () => {
render(<Header />)
// Mobile nav items should be present (they're in the Popover.Panel)
// We can check if the navigation structure exists
expect(screen.getByText('Menu')).toBeInTheDocument()
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

"Renders mobile navigation menu items" test only checks "Menu" button exists.

This duplicates the test on line 92-96. It doesn't verify that mobile nav items (About, Projects, etc.) appear in the Popover panel. Consider asserting on the actual mobile nav content or removing this test.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/__tests__/Header.test.jsx` around lines 217 - 222, The test
"renders mobile navigation menu items" in Header.test.jsx is a duplicate and
only asserts the "Menu" button; either remove this test or change it to actually
open the Popover and assert the mobile nav entries appear: render the Header
component, trigger the Menu toggle (e.g., fireEvent.click or userEvent.click on
the "Menu" button), then assert that the expected mobile items like "About",
"Projects", etc. are present in the Popover.Panel using screen.getByText or
await screen.findByText; update the test name if you keep it to reflect that it
checks the Popover panel contents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments