Files
collective-lists/apps/web/tests/e2e/mobile-shell.test.ts
Oier Bravo Urtasun 4cc5f694d3 feat(fase-5.12): detail-view chrome — hide MobileTopBar, sticky contextual headers
On /lists/[id], /tasks/[id], /notes/[id], /lists/[id]/session the global
MobileTopBar (hamburger + collective + avatar) is hidden on mobile. The
route's own contextual header (back + title + actions) becomes the only
top chrome and is now sticky with glassmorphism, matching the look of
the MobileTopBar it replaces. BottomTabBar stays visible so the user can
still hop between sections.

Detection: a regex over the path — /^\/(lists\/[uuid](\/session)?|
tasks\/[uuid]|notes\/[uuid])\/?$/ — in (app)/+layout.svelte.

Headers touched:
  - apps/web/src/routes/(app)/lists/[id]/+page.svelte (px-4 on mobile,
    px-8 on md+, transparent/no-border on md+ since the masthead
    provides its own spacing)
  - apps/web/src/routes/(app)/tasks/[id]/+page.svelte
  - apps/web/src/routes/(app)/notes/[id]/+page.svelte

Tests
  tests/e2e/mobile-shell.test.ts gains M-08: on /lists/bbbb..., the
  mobile-hamburger element has count 0 (MobileTopBar removed) while the
  detail's back link + BottomTabBar remain visible. All 5 shell tests
  green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 19:02:06 +02:00

69 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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();
});
});