diff --git a/app/layout.tsx b/app/layout.tsx index 6ae4ca5..f3b78ea 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from 'next'; import { fraunces, publicSans } from '$shared/lib'; +import { Footer } from '$widgets/Footer'; import './globals.css'; export const metadata: Metadata = { @@ -13,7 +14,10 @@ export const metadata: Metadata = { export default function RootLayout({ children }: { children: React.ReactNode }) { return ( -
{children} + + {children} + + ); } diff --git a/src/widgets/Footer/index.ts b/src/widgets/Footer/index.ts new file mode 100644 index 0000000..8094bd1 --- /dev/null +++ b/src/widgets/Footer/index.ts @@ -0,0 +1 @@ +export { Footer } from './ui/Footer/Footer'; diff --git a/src/widgets/Footer/ui/Footer/Footer.test.tsx b/src/widgets/Footer/ui/Footer/Footer.test.tsx new file mode 100644 index 0000000..b7afc0f --- /dev/null +++ b/src/widgets/Footer/ui/Footer/Footer.test.tsx @@ -0,0 +1,47 @@ +import { render, screen } from '@testing-library/react'; +import { Footer } from './Footer'; + +describe('Footer', () => { + describe('structure', () => { + it('renders a footer element', () => { + const { container } = render(); + expect(container.querySelector('footer')).toBeInTheDocument(); + }); + + it('has brutal-border-top separator', () => { + const { container } = render(); + expect(container.querySelector('footer')).toHaveClass('brutal-border-top'); + }); + }); + + describe('email link', () => { + it('renders the contact email as a mailto link', () => { + render(); + const link = screen.getByRole('link', { name: /hello@allmy\.work/i }); + expect(link).toHaveAttribute('href', 'mailto:hello@allmy.work'); + }); + }); + + describe('CV download', () => { + it('renders a CV download link', () => { + render(); + expect(screen.getByRole('link', { name: /download cv/i })).toBeInTheDocument(); + }); + + it('CV link points to the cv file', () => { + render(); + expect(screen.getByRole('link', { name: /download cv/i })).toHaveAttribute('href', '/cv.pdf'); + }); + + it('CV link has download attribute', () => { + render(); + expect(screen.getByRole('link', { name: /download cv/i })).toHaveAttribute('download'); + }); + + it('CV link has button styling', () => { + render(); + const link = screen.getByRole('link', { name: /download cv/i }); + expect(link).toHaveClass('brutal-border', 'uppercase'); + }); + }); +}); diff --git a/src/widgets/Footer/ui/Footer/Footer.tsx b/src/widgets/Footer/ui/Footer/Footer.tsx new file mode 100644 index 0000000..d823cfd --- /dev/null +++ b/src/widgets/Footer/ui/Footer/Footer.tsx @@ -0,0 +1,23 @@ +import { CONTACT_LINKS } from '$shared/lib'; + +/** + * Site-wide footer with contact email and CV download link. + */ +export function Footer() { + return ( + + ); +} diff --git a/src/widgets/index.ts b/src/widgets/index.ts index 95e14a9..71ffc2e 100644 --- a/src/widgets/index.ts +++ b/src/widgets/index.ts @@ -1 +1,2 @@ +export * from './Footer'; export * from './Navigation';