Files
websitedev/proto/src/components/Header.test.tsx
T
Do SikiandClaude Sonnet 5 d6f3dda9e5
CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Waiting to run
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Blocked by required conditions
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Blocked by required conditions
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Waiting to run
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Blocked by required conditions
feat(frontend): Payload Local API + hu/en locale routing (MITHOME-91/114)
Replaces the JSON content system with Payload's Local API across every
frontend page, and introduces symmetric locale-prefixed routing
(/hu/..., /en/...) with per-locale translated slugs — supersedes the
earlier "hu unprefixed" decision (see chat 2026-09-10).

Routing structure:
- src/app/(frontend)/layout.tsx: now a minimal shell (html/body, theme
  script, ThemeProvider) — no longer locale-aware.
- src/app/(frontend)/page.tsx: redirects bare "/" to the default
  locale (/hu).
- src/app/(frontend)/not-found.tsx: explicit 404 for the (frontend)
  group — without it, Next's built-in fallback collided with the
  (payload) group's own root and reproduced the "double html / script
  tag" symptom from MITHOME-87, but only on notFound() paths. Verified
  fixed in both dev and a real production (standalone) server; the
  remaining "script tag" console warning on invalid routes turned out
  to be Turbopack dev-mode-only noise (zero console errors in
  production) — confirmed by building and running .next/standalone
  directly.
- src/app/(frontend)/[locale]/layout.tsx: validates the locale segment
  (generateStaticParams hu/en, notFound() otherwise), fetches Common +
  Home via Payload, renders Header/Footer/staging-banner.
- src/app/(frontend)/[locale]/page.tsx: home, fetches Home global +
  Partners collection.
- src/app/(frontend)/[locale]/[slug]/page.tsx: catch-all for about/
  services/contact/privacy/terms — resolves slug -> PageKey via
  src/lib/i18n.ts's PAGE_SLUGS map (generateStaticParams pre-renders
  all 10 locale×slug combinations), generateMetadata per page.

New lib layer:
- src/lib/i18n.ts: Locale/PageKey types, PAGE_SLUGS (translated slugs
  per locale), localePath()/resolvePageKey()/switchLocalePath()
  helpers (the last one already shaped for MITHOME-115).
- src/lib/payload-content.ts: Local API getters that also unwrap
  Payload's `{ value: string }[]` array-field shape back into plain
  string[] (see src/globals/fields/stringArray.ts) — keeps the page
  JSX consuming the exact shape the old content/types.ts had, so the
  migration is a data-source swap, not a markup rewrite.

Presentational split: page bodies moved to src/components/views/
(HomeView, AboutView, ServicesView, ContactView, LegalPageView — the
last one shared by both legal pages, identical shape) as prop-driven
components; the app-router page.tsx files became thin server-side
fetch + render wrappers. Header/Footer converted from importing
content directly to accepting nav/locale/content props, since they're
'use client' and can't call the Payload Local API themselves —
config/site.ts's navigation arrays became getMainNavigation(locale)/
getFooterNavigation(locale)/getFooterLegalLinks(locale) functions.

Two real, pre-existing bugs fixed along the way (not introduced by
this migration):
- Services and Contact pages' "Webmail belépés" links used the
  primary CTA's href (/kapcsolat) with target="_blank" instead of the
  actual webmail URL (home.hero.cta.secondary.href) — now correct.
- The GDPR checkbox link pointed to "/adatkezelesi-tajekoztato", which
  never matched the real privacy page route under any past URL
  scheme. contact.json's gdpr.label now carries a {privacyHref}
  placeholder that ContactView replaces with the locale-correct path
  — also fixes the adatvedelem page's own <title> tag, which
  previously read "Adatvédelmi Tájékoztató | Szolgáltatás jellemzők:"
  (a copy-paste bug using common.labels.features instead of the site
  name).

Known, accepted limitation: the outer shell layout hardcodes
<html lang="hu"> because it sits above the [locale] segment and can't
read the param — every [locale]/[slug] page's own generateMetadata is
locale-correct, but the initial lang attribute isn't. Documented as a
MITHOME-116 (SEO/hreflang) follow-up rather than restructured now.

Verified end to end in a real browser: /hu matches the
https://stage.mozdit.hu visual baseline (MITHOME-117) exactly; /en
renders with English nav/metadata (content body still Hungarian-only,
as expected — MITHOME-111/113 not done yet); /hu/kapcsolat's GDPR link
resolves to /hu/adatvedelem; dark mode still works; invalid locale
(/fr/about) and invalid slug (/hu/nemletezo-oldal) both 404 correctly;
bare "/" redirects to /hu. build/lint/tsc/test (58 passed) all clean,
including a clean production standalone-server run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 16:15:09 +02:00

171 lines
6.2 KiB
TypeScript
Executable File

import { render, screen, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom'
import userEvent from '@testing-library/user-event'
import Header from './Header'
import { common } from '@/content'
import { getMainNavigation } from '@/config/site'
// Mock Next.js Link component
jest.mock('next/link', () => {
return ({ children, href }: { children: React.ReactNode; href: string }) => (
<a href={href}>{children}</a>
)
})
// MITHOME-91/114: Header lett props-alapú (locale-aware nav/a11y a szülő
// [locale] layoutból jön) — a teszt a valódi getMainNavigation('hu')-t adja
// át, hogy a feliratok/hrefek a tényleges alkalmazás-viselkedést tükrözzék.
const headerProps = {
nav: getMainNavigation('hu'),
homeHref: '/hu',
a11y: common.a11y,
}
describe('Header', () => {
it('should render the company logo', () => {
render(<Header {...headerProps} />)
expect(screen.getByAltText('mozdIT Bt.')).toBeInTheDocument()
})
it('should render all navigation links in desktop menu', () => {
render(<Header {...headerProps} />)
// Desktop menu should contain all links with specific structures
const desktopMenu = document.querySelector('.hidden.md\\:flex')
expect(desktopMenu).toBeInTheDocument()
const navLinks = screen.getAllByText('Kezdőlap')
expect(navLinks.length).toBeGreaterThan(0)
expect(screen.getAllByText('Rólunk')).toHaveLength(2) // Both in desktop and mobile menus
expect(screen.getAllByText('Szolgáltatások')).toHaveLength(2) // Both in desktop and mobile menus
})
it('should render contact button with correct styling', () => {
render(<Header {...headerProps} />)
const contactButtons = screen.getAllByText('Kapcsolat')
expect(contactButtons.length).toBeGreaterThan(0)
// Check if any contact button has the correct styling
const contactButton = contactButtons[0]
expect(contactButton).toBeInTheDocument()
// Check for primary button styling
const contactLink = contactButton.closest('a')
if (contactLink) {
expect(contactLink).toHaveClass('btn', 'btn-primary')
}
})
it('should render hamburger menu button on mobile', () => {
render(<Header {...headerProps} />)
// The hamburger menu button is hidden by default in desktop view
// We can test its presence even if not visible
const hamburgerButton = screen.getByRole('button', { name: new RegExp(common.a11y.openMenu, 'i') })
expect(hamburgerButton).toBeInTheDocument()
})
it('should have proper accessibility attributes', () => {
render(<Header {...headerProps} />)
const hamburgerButton = screen.getByRole('button', { name: new RegExp(common.a11y.openMenu, 'i') })
expect(hamburgerButton).toHaveAttribute('aria-expanded', 'false')
})
it('should render with proper semantic structure', () => {
const { container } = render(<Header {...headerProps} />)
// Should have header element with proper structure
const header = container.firstChild as HTMLElement
expect(header?.tagName).toBe('HEADER')
// Should have a nav element
const nav = container.querySelector('nav')
expect(nav).toBeInTheDocument()
// Should be sticky positioned
expect(header).toHaveClass('sticky', 'top-0')
})
it('should toggle mobile menu when hamburger button is clicked', async () => {
const user = userEvent.setup()
render(<Header {...headerProps} />)
const hamburgerButton = screen.getByRole('button', { name: new RegExp(common.a11y.openMenu, 'i') })
// Initially menu should be closed
expect(hamburgerButton).toHaveAttribute('aria-expanded', 'false')
// Click to open menu
await user.click(hamburgerButton)
expect(hamburgerButton).toHaveAttribute('aria-expanded', 'true')
// Click again to close menu
await user.click(hamburgerButton)
expect(hamburgerButton).toHaveAttribute('aria-expanded', 'false')
})
it('should close mobile menu when navigation link is clicked', async () => {
const user = userEvent.setup()
render(<Header {...headerProps} />)
const hamburgerButton = screen.getByRole('button', { name: new RegExp(common.a11y.openMenu, 'i') })
// Open the mobile menu
await user.click(hamburgerButton)
expect(hamburgerButton).toHaveAttribute('aria-expanded', 'true')
// Find a navigation link in the mobile menu and click it
const mobileNavLinks = screen.getAllByText('Rólunk')
const mobileLink = mobileNavLinks.find(link =>
link.closest('.md\\:hidden') !== null
)
if (mobileLink) {
await user.click(mobileLink)
expect(hamburgerButton).toHaveAttribute('aria-expanded', 'false')
}
})
it('should have correct navigation links with proper hrefs', () => {
render(<Header {...headerProps} />)
// Check for home link
const homeLinks = screen.getAllByText('Kezdőlap')
expect(homeLinks.length).toBeGreaterThan(0)
expect(homeLinks[0].closest('a')).toHaveAttribute('href', '/hu')
// Check for about link
const aboutLinks = screen.getAllByText('Rólunk')
expect(aboutLinks.length).toBeGreaterThan(0)
expect(aboutLinks[0].closest('a')).toHaveAttribute('href', '/hu/rolunk')
// Check for services link
const servicesLinks = screen.getAllByText('Szolgáltatások')
expect(servicesLinks.length).toBeGreaterThan(0)
expect(servicesLinks[0].closest('a')).toHaveAttribute('href', '/hu/szolgaltatasok')
// Check for contact link
const contactLinks = screen.getAllByText('Kapcsolat')
expect(contactLinks.length).toBeGreaterThan(0)
expect(contactLinks[0].closest('a')).toHaveAttribute('href', '/hu/kapcsolat')
})
it('should have proper responsive classes', () => {
const { container } = render(<Header {...headerProps} />)
// Desktop menu should be hidden on mobile
const desktopMenu = container.querySelector('.hidden.md\\:flex')
expect(desktopMenu).toBeInTheDocument()
// Mobile menu button should be hidden on desktop
const mobileMenuButton = container.querySelector('.md\\:hidden button')
expect(mobileMenuButton).toBeInTheDocument()
// Mobile menu should be positioned correctly
const mobileMenu = container.querySelector('.md\\:hidden.overflow-hidden')
expect(mobileMenu).toBeInTheDocument()
})
})