fix(tests): mock useAuth in Layout tests for auth-aware navigation
Layout now renders different nav links based on auth state. Tests were using a real AuthProvider which resolved to no user, causing them to look for "My Runs" and "Admin" links that only appear when logged in. Mock useAuth to test both logged-out and logged-in-as-admin states. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,62 +2,108 @@ import { render, screen } from '@testing-library/react'
|
|||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import { MemoryRouter } from 'react-router-dom'
|
import { MemoryRouter } from 'react-router-dom'
|
||||||
import { Layout } from './Layout'
|
import { Layout } from './Layout'
|
||||||
import { AuthProvider } from '../contexts/AuthContext'
|
|
||||||
|
|
||||||
vi.mock('../hooks/useTheme', () => ({
|
vi.mock('../hooks/useTheme', () => ({
|
||||||
useTheme: () => ({ theme: 'dark' as const, toggle: vi.fn() }),
|
useTheme: () => ({ theme: 'dark' as const, toggle: vi.fn() }),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const mockUseAuth = vi.fn()
|
||||||
|
vi.mock('../contexts/AuthContext', () => ({
|
||||||
|
useAuth: () => mockUseAuth(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const loggedOutAuth = {
|
||||||
|
user: null,
|
||||||
|
session: null,
|
||||||
|
loading: false,
|
||||||
|
isAdmin: false,
|
||||||
|
signInWithEmail: vi.fn(),
|
||||||
|
signUpWithEmail: vi.fn(),
|
||||||
|
signInWithGoogle: vi.fn(),
|
||||||
|
signInWithDiscord: vi.fn(),
|
||||||
|
signOut: vi.fn(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminAuth = {
|
||||||
|
...loggedOutAuth,
|
||||||
|
user: { email: 'admin@example.com' },
|
||||||
|
session: {},
|
||||||
|
isAdmin: true,
|
||||||
|
}
|
||||||
|
|
||||||
function renderLayout(initialPath = '/') {
|
function renderLayout(initialPath = '/') {
|
||||||
return render(
|
return render(
|
||||||
<MemoryRouter initialEntries={[initialPath]}>
|
<MemoryRouter initialEntries={[initialPath]}>
|
||||||
<AuthProvider>
|
<Layout />
|
||||||
<Layout />
|
|
||||||
</AuthProvider>
|
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Layout', () => {
|
describe('Layout', () => {
|
||||||
it('renders all desktop navigation links', () => {
|
describe('when logged out', () => {
|
||||||
renderLayout()
|
beforeEach(() => mockUseAuth.mockReturnValue(loggedOutAuth))
|
||||||
expect(screen.getAllByRole('link', { name: /new run/i })[0]).toBeInTheDocument()
|
|
||||||
expect(screen.getAllByRole('link', { name: /my runs/i })[0]).toBeInTheDocument()
|
it('renders logged-out navigation links', () => {
|
||||||
expect(screen.getAllByRole('link', { name: /genlockes/i })[0]).toBeInTheDocument()
|
renderLayout()
|
||||||
expect(screen.getAllByRole('link', { name: /stats/i })[0]).toBeInTheDocument()
|
expect(screen.getAllByRole('link', { name: /^home$/i })[0]).toBeInTheDocument()
|
||||||
expect(screen.getAllByRole('link', { name: /admin/i })[0]).toBeInTheDocument()
|
expect(screen.getAllByRole('link', { name: /^runs$/i })[0]).toBeInTheDocument()
|
||||||
|
expect(screen.getAllByRole('link', { name: /genlockes/i })[0]).toBeInTheDocument()
|
||||||
|
expect(screen.getAllByRole('link', { name: /stats/i })[0]).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not show authenticated links', () => {
|
||||||
|
renderLayout()
|
||||||
|
expect(screen.queryByRole('link', { name: /new run/i })).not.toBeInTheDocument()
|
||||||
|
expect(screen.queryByRole('link', { name: /my runs/i })).not.toBeInTheDocument()
|
||||||
|
expect(screen.queryByRole('link', { name: /admin/i })).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows sign-in link', () => {
|
||||||
|
renderLayout()
|
||||||
|
expect(screen.getByRole('link', { name: /sign in/i })).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when logged in as admin', () => {
|
||||||
|
beforeEach(() => mockUseAuth.mockReturnValue(adminAuth))
|
||||||
|
|
||||||
|
it('renders authenticated navigation links', () => {
|
||||||
|
renderLayout()
|
||||||
|
expect(screen.getAllByRole('link', { name: /new run/i })[0]).toBeInTheDocument()
|
||||||
|
expect(screen.getAllByRole('link', { name: /my runs/i })[0]).toBeInTheDocument()
|
||||||
|
expect(screen.getAllByRole('link', { name: /genlockes/i })[0]).toBeInTheDocument()
|
||||||
|
expect(screen.getAllByRole('link', { name: /stats/i })[0]).toBeInTheDocument()
|
||||||
|
expect(screen.getAllByRole('link', { name: /admin/i })[0]).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows the mobile dropdown when the hamburger is clicked', async () => {
|
||||||
|
renderLayout()
|
||||||
|
const hamburger = screen.getByRole('button', { name: /toggle menu/i })
|
||||||
|
await userEvent.click(hamburger)
|
||||||
|
expect(screen.getAllByRole('link', { name: /my runs/i }).length).toBeGreaterThan(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the brand logo link', () => {
|
it('renders the brand logo link', () => {
|
||||||
|
mockUseAuth.mockReturnValue(loggedOutAuth)
|
||||||
renderLayout()
|
renderLayout()
|
||||||
expect(screen.getByRole('link', { name: /ant/i })).toBeInTheDocument()
|
expect(screen.getByRole('link', { name: /ant/i })).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the theme toggle button', () => {
|
it('renders the theme toggle button', () => {
|
||||||
|
mockUseAuth.mockReturnValue(loggedOutAuth)
|
||||||
renderLayout()
|
renderLayout()
|
||||||
expect(screen.getAllByRole('button', { name: /switch to light mode/i })[0]).toBeInTheDocument()
|
expect(screen.getAllByRole('button', { name: /switch to light mode/i })[0]).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('initially hides the mobile dropdown menu', () => {
|
it('initially hides the mobile dropdown menu', () => {
|
||||||
|
mockUseAuth.mockReturnValue(loggedOutAuth)
|
||||||
renderLayout()
|
renderLayout()
|
||||||
// Mobile menu items exist in DOM but menu is hidden; the mobile dropdown
|
|
||||||
// only appears inside the sm:hidden block after state toggle.
|
|
||||||
// The hamburger button should be present.
|
|
||||||
expect(screen.getByRole('button', { name: /toggle menu/i })).toBeInTheDocument()
|
expect(screen.getByRole('button', { name: /toggle menu/i })).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows the mobile dropdown when the hamburger is clicked', async () => {
|
|
||||||
renderLayout()
|
|
||||||
const hamburger = screen.getByRole('button', { name: /toggle menu/i })
|
|
||||||
await userEvent.click(hamburger)
|
|
||||||
// After click, the menu open state adds a dropdown with nav links
|
|
||||||
// We can verify the menu is open by checking a class change or that
|
|
||||||
// the nav links appear in the mobile dropdown section.
|
|
||||||
// The mobile dropdown renders navLinks in a div inside sm:hidden
|
|
||||||
expect(screen.getAllByRole('link', { name: /my runs/i }).length).toBeGreaterThan(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders the footer with PokeDB attribution', () => {
|
it('renders the footer with PokeDB attribution', () => {
|
||||||
|
mockUseAuth.mockReturnValue(loggedOutAuth)
|
||||||
renderLayout()
|
renderLayout()
|
||||||
expect(screen.getByRole('link', { name: /pokedb/i })).toBeInTheDocument()
|
expect(screen.getByRole('link', { name: /pokedb/i })).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user