diff --git a/web/__tests__/responsive-design.test.tsx b/web/__tests__/responsive-design.test.tsx new file mode 100644 index 0000000..f64f519 --- /dev/null +++ b/web/__tests__/responsive-design.test.tsx @@ -0,0 +1,200 @@ +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; + +// Mock next/navigation +jest.mock('next/navigation', () => ({ + useRouter: () => ({ + push: jest.fn(), + replace: jest.fn(), + }), + usePathname: () => '/dashboard', +})); + +// Mock auth store +jest.mock('@/stores/authStore', () => ({ + useAuthStore: () => ({ + isAuthenticated: true, + user: { firstName: 'Test', lastName: 'User', email: 'test@example.com' }, + logout: jest.fn(), + }), +})); + +// Mock API +jest.mock('@/lib/api', () => ({ + __esModule: true, + default: { + getAllBeneficiaries: jest.fn().mockResolvedValue({ ok: true, data: [] }), + }, +})); + +import { Layout } from '@/components/Layout/Layout'; +import { Header } from '@/components/Layout/Header'; +import { Sidebar } from '@/components/Layout/Sidebar'; + +describe('Responsive Design - Layout Components', () => { + describe('Layout Component', () => { + it('has responsive max-width classes for 4K screens', () => { + const { container } = render( + +
Test Content
+
+ ); + + // Find the main content container + const mainContent = container.querySelector('main > div'); + expect(mainContent).toBeInTheDocument(); + + // Check for responsive max-width classes + const className = mainContent?.className || ''; + expect(className).toContain('max-w-7xl'); + expect(className).toContain('3xl:max-w-8xl'); + expect(className).toContain('4xl:max-w-9xl'); + }); + + it('has responsive padding for large screens', () => { + const { container } = render( + +
Test Content
+
+ ); + + const mainContent = container.querySelector('main > div'); + const className = mainContent?.className || ''; + + // Check for responsive padding classes + expect(className).toContain('px-4'); + expect(className).toContain('lg:px-8'); + expect(className).toContain('xl:px-10'); + expect(className).toContain('3xl:px-12'); + expect(className).toContain('4xl:px-16'); + }); + + it('has responsive sidebar offset', () => { + const { container } = render( + +
Test Content
+
+ ); + + // Find main content area (direct child of root container, contains pl- classes) + // The structure is: div.flex.min-h-screen > aside.sidebar + div.flex.flex-1.flex-col + const rootContainer = container.querySelector('.flex.min-h-screen'); + const contentArea = rootContainer?.querySelector(':scope > div.flex-1'); + const className = contentArea?.className || ''; + + expect(className).toContain('lg:pl-64'); + expect(className).toContain('3xl:pl-72'); + expect(className).toContain('4xl:pl-80'); + }); + }); + + describe('Header Component', () => { + it('has responsive container width for large screens', () => { + const { container } = render(
); + + // Find header container + const headerContainer = container.querySelector('header > div'); + const className = headerContainer?.className || ''; + + expect(className).toContain('max-w-7xl'); + expect(className).toContain('3xl:max-w-8xl'); + expect(className).toContain('4xl:max-w-9xl'); + }); + + it('has responsive padding', () => { + const { container } = render(
); + + const headerContainer = container.querySelector('header > div'); + const className = headerContainer?.className || ''; + + expect(className).toContain('px-4'); + expect(className).toContain('lg:px-8'); + expect(className).toContain('xl:px-10'); + expect(className).toContain('3xl:px-12'); + expect(className).toContain('4xl:px-16'); + }); + + it('hides mobile menu on md screens and above', () => { + render(
); + + // Mobile menu button should have md:hidden class + const mobileMenuButton = screen.getByLabelText('Toggle menu'); + expect(mobileMenuButton).toHaveClass('md:hidden'); + }); + + it('shows desktop navigation on md screens and above', () => { + const { container } = render(
); + + // Desktop nav should have hidden md:flex classes + const desktopNav = container.querySelector('nav.hidden.md\\:flex'); + expect(desktopNav).toBeInTheDocument(); + }); + }); + + describe('Sidebar Component', () => { + it('has responsive width for large screens', () => { + const { container } = render(); + + const sidebar = container.querySelector('aside'); + const className = sidebar?.className || ''; + + // Check responsive width classes + expect(className).toContain('lg:w-64'); + expect(className).toContain('3xl:w-72'); + expect(className).toContain('4xl:w-80'); + }); + + it('is hidden on mobile and tablet, shown on lg+', () => { + const { container } = render(); + + const sidebar = container.querySelector('aside'); + const className = sidebar?.className || ''; + + expect(className).toContain('hidden'); + expect(className).toContain('lg:flex'); + }); + }); +}); + +describe('Responsive Design - Breakpoint Coverage', () => { + it('defines required breakpoints in Tailwind config', async () => { + // This is a conceptual test - in practice, check the compiled CSS + // or use visual regression testing + + const breakpoints = { + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + '2xl': '1536px', + '3xl': '1920px', // Full HD + '4xl': '2560px', // QHD / 4K + }; + + // Verify we're targeting all key breakpoints + expect(Object.keys(breakpoints)).toContain('md'); // Tablet + expect(Object.keys(breakpoints)).toContain('lg'); // Desktop + expect(Object.keys(breakpoints)).toContain('3xl'); // Full HD + expect(Object.keys(breakpoints)).toContain('4xl'); // 4K + }); + + it('covers tablet range (768px-1023px)', () => { + // Tablet breakpoint is md (768px) to lg (1024px) + // Components should have specific behavior in this range + + const mdBreakpoint = 768; + const lgBreakpoint = 1024; + + expect(mdBreakpoint).toBeGreaterThanOrEqual(768); + expect(lgBreakpoint - mdBreakpoint).toBeGreaterThan(0); + }); + + it('covers large screen range (1920px-4K)', () => { + // Large screens from Full HD to 4K + const fullHD = 1920; + const fourK = 2560; + + expect(fullHD).toBeGreaterThanOrEqual(1920); + expect(fourK).toBeGreaterThanOrEqual(2560); + }); +}); diff --git a/web/app/(main)/dashboard/page.tsx b/web/app/(main)/dashboard/page.tsx index 1511b9e..265fbf1 100644 --- a/web/app/(main)/dashboard/page.tsx +++ b/web/app/(main)/dashboard/page.tsx @@ -69,7 +69,7 @@ export default function DashboardPage() { if (isLoading) { return (
- +
); } @@ -86,17 +86,17 @@ export default function DashboardPage() { return (
{/* Header */} -
-

+
+

Welcome{user?.firstName ? `, ${user.firstName}` : ''}

-

+

Monitor your loved ones and manage their health sensors

{/* Summary Cards */} -
+
-
-

Your Loved Ones

+
+

Your Loved Ones

) : ( -
+
{beneficiaries.map((beneficiary) => ( -
+
{/* Logo and brand */}
diff --git a/web/components/Layout/Layout.tsx b/web/components/Layout/Layout.tsx index 0980b9c..ea3ec34 100644 --- a/web/components/Layout/Layout.tsx +++ b/web/components/Layout/Layout.tsx @@ -46,13 +46,14 @@ interface LayoutProps { /** * Max width classes mapping + * Responsive: scales up on larger screens for better use of space */ const maxWidthClasses = { full: '', - '7xl': 'max-w-7xl', - '6xl': 'max-w-6xl', - '5xl': 'max-w-5xl', - '4xl': 'max-w-4xl', + '7xl': 'max-w-7xl 3xl:max-w-8xl 4xl:max-w-9xl', + '6xl': 'max-w-6xl 3xl:max-w-7xl 4xl:max-w-8xl', + '5xl': 'max-w-5xl 3xl:max-w-6xl 4xl:max-w-7xl', + '4xl': 'max-w-4xl 3xl:max-w-5xl 4xl:max-w-6xl', }; /** @@ -106,13 +107,13 @@ export function Layout({ {showSidebar && } {/* Main content area */} -
+
{/* Header */} {showHeader &&
} {/* Page content */}
-
+
{/* Breadcrumbs */} {showBreadcrumbs && (
diff --git a/web/components/Layout/Sidebar.tsx b/web/components/Layout/Sidebar.tsx index b95b5dc..618cfcc 100644 --- a/web/components/Layout/Sidebar.tsx +++ b/web/components/Layout/Sidebar.tsx @@ -77,7 +77,7 @@ export function Sidebar() { : user?.email || 'User'; return ( -