Initial commit
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { Footer } from './Footer'
|
||||
|
||||
describe('Footer', () => {
|
||||
it('renders footer', () => {
|
||||
render(<Footer />)
|
||||
expect(screen.getByTestId('footer')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('displays current year', () => {
|
||||
render(<Footer />)
|
||||
const currentYear = new Date().getFullYear()
|
||||
expect(screen.getByTestId('footer-text')).toHaveTextContent(currentYear.toString())
|
||||
})
|
||||
|
||||
it('displays copyright text', () => {
|
||||
render(<Footer />)
|
||||
expect(screen.getByTestId('footer-text')).toHaveTextContent('© ')
|
||||
expect(screen.getByTestId('footer-text')).toHaveTextContent('Föräldraledigplannerare')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
export function Footer(): ReactNode {
|
||||
const currentYear = new Date().getFullYear()
|
||||
return (
|
||||
<footer role="contentinfo" data-testid="footer">
|
||||
<p data-testid="footer-text">© {currentYear} Föräldraledigplannerare. All rights reserved.</p>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { Header } from './Header'
|
||||
|
||||
describe('Header', () => {
|
||||
it('renders header with default title', () => {
|
||||
render(<Header />)
|
||||
expect(screen.getByTestId('header-title')).toHaveTextContent('Föräldraledigplannerare')
|
||||
})
|
||||
|
||||
it('renders header with custom title', () => {
|
||||
render(<Header title="Custom Title" />)
|
||||
expect(screen.getByTestId('header-title')).toHaveTextContent('Custom Title')
|
||||
})
|
||||
|
||||
it('renders logout button when onLogout provided', () => {
|
||||
const onLogout = vi.fn()
|
||||
render(<Header onLogout={onLogout} />)
|
||||
const button = screen.getByTestId('logout-button')
|
||||
expect(button).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('calls onLogout when logout button clicked', async () => {
|
||||
const onLogout = vi.fn()
|
||||
render(<Header onLogout={onLogout} />)
|
||||
const button = screen.getByTestId('logout-button')
|
||||
await userEvent.click(button)
|
||||
expect(onLogout).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('renders workspace selector button when onWorkspaceSelect provided', () => {
|
||||
const onWorkspaceSelect = vi.fn()
|
||||
render(<Header onWorkspaceSelect={onWorkspaceSelect} />)
|
||||
expect(screen.getByTestId('workspace-selector')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('calls onWorkspaceSelect when workspace button clicked', async () => {
|
||||
const onWorkspaceSelect = vi.fn()
|
||||
render(<Header onWorkspaceSelect={onWorkspaceSelect} />)
|
||||
const button = screen.getByTestId('workspace-selector')
|
||||
await userEvent.click(button)
|
||||
expect(onWorkspaceSelect).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not render buttons when no handlers provided', () => {
|
||||
render(<Header />)
|
||||
expect(screen.queryByTestId('logout-button')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('workspace-selector')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
interface HeaderProps {
|
||||
title?: string
|
||||
onLogout?: () => void
|
||||
onWorkspaceSelect?: () => void
|
||||
}
|
||||
|
||||
export function Header({ title = 'Föräldraledigplannerare', onLogout, onWorkspaceSelect }: HeaderProps): ReactNode {
|
||||
return (
|
||||
<header role="banner" data-testid="header">
|
||||
<div data-testid="header-title">{title}</div>
|
||||
<nav role="navigation" data-testid="header-nav">
|
||||
{onWorkspaceSelect && (
|
||||
<button
|
||||
data-testid="workspace-selector"
|
||||
onClick={onWorkspaceSelect}
|
||||
aria-label="Select workspace"
|
||||
>
|
||||
Select Workspace
|
||||
</button>
|
||||
)}
|
||||
{onLogout && (
|
||||
<button
|
||||
data-testid="logout-button"
|
||||
onClick={onLogout}
|
||||
aria-label="Log out"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { Header, HeaderProps } from './Header'
|
||||
import { Footer } from './Footer'
|
||||
|
||||
interface LayoutProps {
|
||||
children: ReactNode
|
||||
header?: HeaderProps
|
||||
}
|
||||
|
||||
export function Layout({ children, header }: LayoutProps): ReactNode {
|
||||
return (
|
||||
<div role="main" data-testid="layout">
|
||||
<Header {...(header || {})} />
|
||||
<main data-testid="layout-main">{children}</main>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user