11// src/components/__tests__/Navbar.test.tsx
2- import { render , screen , fireEvent } from '@testing-library/react'
3- import { describe , it , expect , vi , beforeEach } from 'vitest'
2+ import { render , screen , fireEvent , act } from '@testing-library/react'
3+ import { describe , it , expect , vi } from 'vitest'
44import { MemoryRouter } from 'react-router-dom'
55import { ThemeContext } from "../../context/ThemeContext" ;
6+ import { AuthContext } from "../../context/AuthContext" ;
67import Navbar from '../Navbar.tsx'
78
89// Helper to render Navbar with a mock ThemeContext
9- const renderNavbar = ( mode : 'light' | 'dark' = 'light' ) => {
10+ const renderNavbar = (
11+ mode : 'light' | 'dark' = 'light' ,
12+ isAuthenticated = false
13+ ) => {
1014 const toggleTheme = vi . fn ( )
15+ const logout = vi . fn ( ) . mockResolvedValue ( undefined )
1116 render (
1217 < MemoryRouter >
1318 < ThemeContext . Provider value = { { mode, toggleTheme } } >
14- < Navbar />
19+ < AuthContext . Provider
20+ value = { {
21+ user : isAuthenticated ? { id : '1' , username : 'tester' , email : 'tester@example.com' } : null ,
22+ isAuthenticated,
23+ isLoading : false ,
24+ refreshAuth : vi . fn ( ) ,
25+ handleLoginSuccess : vi . fn ( ) ,
26+ logout,
27+ } }
28+ >
29+ < Navbar />
30+ </ AuthContext . Provider >
1531 </ ThemeContext . Provider >
1632 </ MemoryRouter >
1733 )
18- return { toggleTheme }
34+ return { toggleTheme, logout }
1935}
2036
2137describe ( 'Navbar' , ( ) => {
@@ -31,6 +47,14 @@ describe('Navbar', () => {
3147 expect ( screen . getByRole ( 'link' , { name : / ^ t r a c k e r $ / i } ) ) . toBeInTheDocument ( )
3248 expect ( screen . getByRole ( 'link' , { name : / c o n t r i b u t o r s / i } ) ) . toBeInTheDocument ( )
3349 expect ( screen . getByRole ( 'link' , { name : / l o g i n / i } ) ) . toBeInTheDocument ( )
50+ expect ( screen . getByRole ( 'link' , { name : / s i g n u p / i } ) ) . toBeInTheDocument ( )
51+ } )
52+
53+ it ( 'shows logout instead of login and signup when authenticated' , ( ) => {
54+ renderNavbar ( 'light' , true )
55+ expect ( screen . getByRole ( 'button' , { name : / l o g o u t / i } ) ) . toBeInTheDocument ( )
56+ expect ( screen . queryByRole ( 'link' , { name : / l o g i n / i } ) ) . not . toBeInTheDocument ( )
57+ expect ( screen . queryByRole ( 'link' , { name : / s i g n u p / i } ) ) . not . toBeInTheDocument ( )
3458 } )
3559
3660 // --- Theme toggle ---
@@ -51,34 +75,42 @@ describe('Navbar', () => {
5175 // --- Mobile menu ---
5276 it ( 'mobile menu is hidden by default' , ( ) => {
5377 renderNavbar ( )
54- expect ( screen . queryByText ( 'About' ) ) . not . toBeInTheDocument ( )
78+ expect ( screen . getAllByRole ( 'link' , { name : / s i g n u p / i } ) ) . toHaveLength ( 1 )
5579 } )
5680
5781 it ( 'opens mobile menu when hamburger is clicked' , ( ) => {
5882 renderNavbar ( )
59- const hamburger = screen . getAllByRole ( 'button' ) [ 1 ] // second button = hamburger
83+ const hamburger = screen . getByLabelText ( / t o g g l e m e n u / i )
6084 fireEvent . click ( hamburger )
61- expect ( screen . getByText ( 'About' ) ) . toBeInTheDocument ( )
62- expect ( screen . getByText ( 'Contact' ) ) . toBeInTheDocument ( )
85+ expect ( screen . getAllByRole ( 'link' , { name : / l o g i n / i } ) ) . toHaveLength ( 2 )
86+ expect ( screen . getAllByRole ( 'link' , { name : / s i g n u p / i } ) ) . toHaveLength ( 2 )
6387 } )
6488
6589 it ( 'closes mobile menu when a nav link is clicked' , ( ) => {
6690 renderNavbar ( )
67- const hamburger = screen . getAllByRole ( 'button' ) [ 1 ]
91+ const hamburger = screen . getByLabelText ( / t o g g l e m e n u / i )
6892 fireEvent . click ( hamburger ) // open
6993 const homeLinks = screen . getAllByRole ( 'link' , { name : / h o m e / i } )
7094 fireEvent . click ( homeLinks [ homeLinks . length - 1 ] ) // click the mobile one
71- expect ( screen . queryByText ( 'About' ) ) . not . toBeInTheDocument ( ) // closed
95+ expect ( screen . getAllByRole ( 'link' , { name : / s i g n u p / i } ) ) . toHaveLength ( 1 ) // closed
7296 } )
7397
7498 it ( 'calls toggleTheme from the mobile menu button' , ( ) => {
7599 const { toggleTheme } = renderNavbar ( 'dark' )
76- const hamburger = screen . getAllByRole ( 'button' ) [ 1 ]
100+ const hamburger = screen . getByLabelText ( / t o g g l e m e n u / i )
77101 fireEvent . click ( hamburger )
78- fireEvent . click ( screen . getByText ( / l i g h t / i) )
102+ fireEvent . click ( screen . getAllByLabelText ( / t o g g l e t h e m e / i) [ 1 ] )
79103 expect ( toggleTheme ) . toHaveBeenCalledTimes ( 1 )
80104 } )
81105
106+ it ( 'calls logout when the authenticated logout button is clicked' , async ( ) => {
107+ const { logout } = renderNavbar ( 'light' , true )
108+ await act ( async ( ) => {
109+ fireEvent . click ( screen . getByRole ( 'button' , { name : / l o g o u t / i } ) )
110+ } )
111+ expect ( logout ) . toHaveBeenCalledTimes ( 1 )
112+ } )
113+
82114 // --- Returns null when ThemeContext is missing ---
83115 it ( 'renders nothing if ThemeContext is not provided' , ( ) => {
84116 const { container } = render (
0 commit comments