From d3fa8991feb90d143bea73eb861da8492abf310f Mon Sep 17 00:00:00 2001 From: Marcel Raddatz Date: Thu, 2 Apr 2026 13:12:04 +0200 Subject: [PATCH] feat(nav): add MobileTabBar with active state and safe-area padding Fixed vitest resolve conditions to use browser entry for Svelte 5. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/lib/nav/MobileTabBar.svelte | 22 +++++++++++ frontend/src/lib/nav/MobileTabBar.test.ts | 47 +++++++++++++++++++++++ frontend/vite.config.ts | 16 ++++++++ 3 files changed, 85 insertions(+) create mode 100644 frontend/src/lib/nav/MobileTabBar.svelte create mode 100644 frontend/src/lib/nav/MobileTabBar.test.ts create mode 100644 frontend/vite.config.ts diff --git a/frontend/src/lib/nav/MobileTabBar.svelte b/frontend/src/lib/nav/MobileTabBar.svelte new file mode 100644 index 0000000..27f8a39 --- /dev/null +++ b/frontend/src/lib/nav/MobileTabBar.svelte @@ -0,0 +1,22 @@ + + + diff --git a/frontend/src/lib/nav/MobileTabBar.test.ts b/frontend/src/lib/nav/MobileTabBar.test.ts new file mode 100644 index 0000000..a0f5a29 --- /dev/null +++ b/frontend/src/lib/nav/MobileTabBar.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import MobileTabBar from './MobileTabBar.svelte'; + +vi.mock('$app/stores', () => { + const { readable } = require('svelte/store'); + return { + page: readable({ url: new URL('http://localhost/planner') }) + }; +}); + +describe('MobileTabBar', () => { + it('renders 4 nav items', () => { + render(MobileTabBar); + const links = screen.getAllByRole('link'); + expect(links).toHaveLength(4); + }); + + it('renders correct labels', () => { + render(MobileTabBar); + expect(screen.getByText('Planer')).toBeInTheDocument(); + expect(screen.getByText('Rezepte')).toBeInTheDocument(); + expect(screen.getByText('Einkauf')).toBeInTheDocument(); + expect(screen.getByText('Einstellungen')).toBeInTheDocument(); + }); + + it('links have correct hrefs', () => { + render(MobileTabBar); + const links = screen.getAllByRole('link'); + expect(links[0]).toHaveAttribute('href', '/planner'); + expect(links[1]).toHaveAttribute('href', '/recipes'); + expect(links[2]).toHaveAttribute('href', '/shopping'); + expect(links[3]).toHaveAttribute('href', '/settings'); + }); + + it('marks active item with aria-current="page"', () => { + render(MobileTabBar); + const plannerLink = screen.getByRole('link', { name: /planer/i }); + expect(plannerLink).toHaveAttribute('aria-current', 'page'); + }); + + it('non-active items do not have aria-current', () => { + render(MobileTabBar); + const recipesLink = screen.getByRole('link', { name: /rezepte/i }); + expect(recipesLink).not.toHaveAttribute('aria-current'); + }); +}); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..0b805cb --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,16 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vitest/config'; +import tailwindcss from '@tailwindcss/vite'; + +export default defineConfig({ + plugins: [tailwindcss(), sveltekit()], + test: { + include: ['src/**/*.{test,spec}.{js,ts}'], + globals: true, + environment: 'jsdom', + setupFiles: ['src/test-setup.ts'] + }, + resolve: { + conditions: ['browser'] + } +});