From 1dc2fe8a255377fc50ec5300563761e6919fa0b5 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Thu, 28 May 2026 12:15:33 +0200 Subject: [PATCH] fix(header): close navigation dropdown immediately on pathname change --- components/layout/Header.test.tsx | 86 +++++++++++++++++++++++++++++++ components/layout/Header.tsx | 4 ++ 2 files changed, 90 insertions(+) create mode 100644 components/layout/Header.test.tsx diff --git a/components/layout/Header.test.tsx b/components/layout/Header.test.tsx new file mode 100644 index 000000000..b9fa8c64f --- /dev/null +++ b/components/layout/Header.test.tsx @@ -0,0 +1,86 @@ +import * as React from 'react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, fireEvent, act, waitFor } from '@testing-library/react'; +import { Header, NavLink } from './Header'; +import { usePathname } from 'next/navigation'; + +// Mock next/navigation +vi.mock('next/navigation', () => ({ + usePathname: vi.fn(), +})); + +// Mock TransitionLink to render standard anchor tag and forward all props +vi.mock('@/components/ui/TransitionLink', () => ({ + TransitionLink: ({ children, href, onClick, ...props }: any) => ( + + {children} + + ), +})); + +// Mock next/image +vi.mock('next/image', () => ({ + default: ({ src, alt, ...props }: any) => {alt}, +})); + +// Mock LanguageSwitcher +vi.mock('./LanguageSwitcher', () => ({ + LanguageSwitcher: () =>
, +})); + +describe('Header Dropdown Navigation TDD', () => { + const mockNavLinks: NavLink[] = [ + { + label: 'Home', + url: '/', + }, + { + label: 'Leistungen', + url: '/leistungen', + children: [ + { label: 'Kabelbau', url: '/leistungen/kabelbau' }, + { label: 'Bohrtechnik', url: '/leistungen/bohrtechnik' }, + ], + }, + ]; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('closes the dropdown menu immediately after a page navigation occurs', async () => { + // 1. Set initial pathname + let currentPath = '/de'; + vi.mocked(usePathname).mockImplementation(() => currentPath); + + // 2. Render Header + const { rerender } = render(
); + + // 3. Hover over "Leistungen" menu item to open dropdown + const leistungenNavItem = screen.getByText('Leistungen'); + const navItemContainer = leistungenNavItem.closest('.group'); + expect(navItemContainer).toBeTruthy(); + + act(() => { + fireEvent.mouseEnter(navItemContainer!); + }); + + // 4. Assert dropdown is open and children are rendered + expect(screen.getByText('Kabelbau')).toBeTruthy(); + expect(screen.getByText('Bohrtechnik')).toBeTruthy(); + + // 5. Simulate page navigation by changing the path + currentPath = '/de/leistungen/kabelbau'; + + act(() => { + // Trigger pathname hook change and force rerender + rerender(
); + }); + + // 6. Assert dropdown is closed (children are not in the document) + await waitFor(() => { + expect(screen.queryByText('Kabelbau')).toBeNull(); + expect(screen.queryByText('Bohrtechnik')).toBeNull(); + }); + }); +}); diff --git a/components/layout/Header.tsx b/components/layout/Header.tsx index 75140a047..d6009acbb 100644 --- a/components/layout/Header.tsx +++ b/components/layout/Header.tsx @@ -164,6 +164,10 @@ export function Header({ navLinks }: HeaderProps) { function NavItem({ link, currentLocale, pathname, isSolidMode }: { link: NavLink, currentLocale: string, pathname: string, isSolidMode: boolean }) { const [isHovered, setIsHovered] = React.useState(false); + React.useEffect(() => { + setIsHovered(false); + }, [pathname]); + const mappedUrl = link.url.startsWith('/') && !link.url.match(/^\/(en|de)/) ? `/${currentLocale}${link.url}` : link.url;