Initial commit

This commit is contained in:
2026-06-01 16:48:21 +02:00
commit 53d157535a
159 changed files with 13641 additions and 0 deletions
@@ -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')
})
})
+10
View File
@@ -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()
})
})
+35
View File
@@ -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>
)
}
+18
View File
@@ -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>
)
}