Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dev": "vite --host",
"build": "vite build",
"lint": "eslint .",
"test": "vitest",
"preview": "vite preview",
"docker:dev": "docker compose --profile dev up --build",
"docker:prod": "docker compose --profile prod up -d --build"
Expand All @@ -32,6 +33,9 @@
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/jasmine": "^5.1.8",
"@types/node": "^22.10.1",
"@types/react": "^18.3.23",
Expand All @@ -48,9 +52,11 @@
"express-session": "^1.18.2",
"globals": "^15.11.0",
"jasmine": "^5.9.0",
"jsdom": "^29.1.1",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"supertest": "^7.1.4",
"vite": "^5.4.10"
"vite": "^5.4.10",
"vitest": "^4.1.6"
}
}
91 changes: 91 additions & 0 deletions src/components/__test__/Navbar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// src/components/__tests__/Navbar.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { MemoryRouter } from 'react-router-dom'
import { ThemeContext } from "../../context/ThemeContext";
import Navbar from '../Navbar.tsx'

// Helper to render Navbar with a mock ThemeContext
const renderNavbar = (mode: 'light' | 'dark' = 'light') => {
const toggleTheme = vi.fn()
render(
<MemoryRouter>
<ThemeContext.Provider value={{ mode, toggleTheme }}>
<Navbar />
</ThemeContext.Provider>
</MemoryRouter>
)
return { toggleTheme }
}

describe('Navbar', () => {
// --- Rendering ---
it('renders the GitHub Tracker logo link', () => {
renderNavbar()
expect(screen.getByText('GitHub Tracker')).toBeInTheDocument()
})

it('renders all desktop nav links', () => {
renderNavbar()
expect(screen.getByRole('link', { name: /home/i })).toBeInTheDocument()
expect(screen.getByRole('link', { name: /^tracker$/i })).toBeInTheDocument()
expect(screen.getByRole('link', { name: /contributors/i })).toBeInTheDocument()
expect(screen.getByRole('link', { name: /login/i })).toBeInTheDocument()
})

// --- Theme toggle ---
it('shows Moon icon in light mode', () => {
renderNavbar('light')
// Lucide renders an <svg> — check the button exists and toggleTheme is wired
const themeBtn = screen.getAllByRole('button')[0]
expect(themeBtn).toBeInTheDocument()
})

it('calls toggleTheme when the theme button is clicked', () => {
const { toggleTheme } = renderNavbar('light')
const themeBtn = screen.getAllByRole('button')[0]
fireEvent.click(themeBtn)
expect(toggleTheme).toHaveBeenCalledTimes(1)
})

// --- Mobile menu ---
it('mobile menu is hidden by default', () => {
renderNavbar()
expect(screen.queryByText('About')).not.toBeInTheDocument()
})

it('opens mobile menu when hamburger is clicked', () => {
renderNavbar()
const hamburger = screen.getAllByRole('button')[1] // second button = hamburger
fireEvent.click(hamburger)
expect(screen.getByText('About')).toBeInTheDocument()
expect(screen.getByText('Contact')).toBeInTheDocument()
})

it('closes mobile menu when a nav link is clicked', () => {
renderNavbar()
const hamburger = screen.getAllByRole('button')[1]
fireEvent.click(hamburger) // open
const homeLinks = screen.getAllByRole('link', { name: /home/i })
fireEvent.click(homeLinks[homeLinks.length - 1]) // click the mobile one
expect(screen.queryByText('About')).not.toBeInTheDocument() // closed
})

it('calls toggleTheme from the mobile menu button', () => {
const { toggleTheme } = renderNavbar('dark')
const hamburger = screen.getAllByRole('button')[1]
fireEvent.click(hamburger)
fireEvent.click(screen.getByText(/light/i))
expect(toggleTheme).toHaveBeenCalledTimes(1)
})

// --- Returns null when ThemeContext is missing ---
it('renders nothing if ThemeContext is not provided', () => {
const { container } = render(
<MemoryRouter>
<Navbar />
</MemoryRouter>
)
expect(container.firstChild).toBeNull()
})
})
1 change: 1 addition & 0 deletions src/setupTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@testing-library/jest-dom'
8 changes: 6 additions & 2 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/setupTests.ts',
},
})
Loading