/** * M-series (mobile shell) — Fase 5.1 * * The app shell must switch between desktop (sidebar) and mobile (top bar + * bottom tab bar + drawer) based on viewport. All four tests log in as Borja * (member of the seed collective) so the collective-aware chrome is hydrated. */ import { test, expect, devices } from '@playwright/test'; import { USERS } from '../fixtures/users.js'; import { loginAs } from '../fixtures/login.js'; const MOBILE = devices['iPhone 13'].viewport; // 390 × 844 const DESKTOP = { width: 1280, height: 800 }; test.describe('Mobile shell — layout switches at md breakpoint', () => { test('M-01: at mobile viewport the desktop sidebar is not visible', async ({ page }) => { await page.setViewportSize(MOBILE); await loginAs(page, USERS.borja); await page.goto('/lists'); await expect(page.getByTestId('desktop-sidebar')).toBeHidden({ timeout: 10_000 }); }); test('M-02: at mobile viewport the bottom tab bar is visible with 4 nav items', async ({ page }) => { await page.setViewportSize(MOBILE); await loginAs(page, USERS.borja); await page.goto('/lists'); const tabBar = page.getByTestId('bottom-tab-bar'); await expect(tabBar).toBeVisible({ timeout: 10_000 }); await expect(tabBar.getByRole('link', { name: /lists|listas/i })).toBeVisible(); await expect(tabBar.getByRole('link', { name: /tasks|tareas/i })).toBeVisible(); await expect(tabBar.getByRole('link', { name: /notes|notas/i })).toBeVisible(); await expect(tabBar.getByRole('link', { name: /search|buscar/i })).toBeVisible(); // The active route (/lists) must have aria-current="page" await expect( tabBar.getByRole('link', { name: /lists|listas/i }) ).toHaveAttribute('aria-current', 'page'); }); test('M-03: tapping the hamburger opens the drawer with the collective switcher', async ({ page }) => { await page.setViewportSize(MOBILE); await loginAs(page, USERS.borja); await page.goto('/lists'); await page.getByTestId('mobile-hamburger').click(); const drawer = page.getByTestId('mobile-drawer'); await expect(drawer).toBeVisible({ timeout: 5_000 }); await expect(drawer.getByText(/Casa García-López/)).toBeVisible(); }); test('M-04: at desktop viewport the sidebar is visible and the bottom bar is hidden', async ({ page }) => { await page.setViewportSize(DESKTOP); await loginAs(page, USERS.borja); await page.goto('/lists'); await expect(page.getByTestId('desktop-sidebar')).toBeVisible({ timeout: 10_000 }); await expect(page.getByTestId('bottom-tab-bar')).toBeHidden(); }); test('M-08: on a detail route the MobileTopBar is hidden but BottomTabBar stays', async ({ page }) => { await page.setViewportSize(MOBILE); await loginAs(page, USERS.borja); await page.goto('/lists/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'); // The global hamburger lives inside MobileTopBar — absence == hidden. await expect(page.getByTestId('mobile-hamburger')).toHaveCount(0, { timeout: 5_000 }); // The detail view still shows a back link + bottom tab bar. await expect(page.getByRole('link', { name: /^back$/i }).first()).toBeVisible(); await expect(page.getByTestId('bottom-tab-bar')).toBeVisible(); }); });