Files
EventSnap/e2e/specs/09-mobile/viewport-reflow.spec.ts
fabi bbdfae09a0 chore(e2e): add ESLint + Prettier; fix real findings; dedupe BASE
The Playwright suite had no linter and no formatter — only tsc. Add flat-config ESLint
(typescript-eslint, type-aware) and Prettier (2-space, matching the suite's style).

Rules keep the ones that catch real TEST bugs and drop the noise:
  - no-floating-promises KEPT — an un-awaited request/assertion can let a test end before it runs,
    passing vacuously. It caught one: the SSE reader loop in sse-listener is now explicitly `void`.
  - no-unused-vars KEPT — caught three dead bindings (an unused adminToken fixture arg, an unused
    `api` arg, an unused JPEG_MAGIC import), all removed.
  - no-explicit-any OFF — all test code; `any` is the honest type for an untyped res.json() body or
    a page.evaluate() return.
  - no-empty-pattern OFF — Playwright's dependency-free fixtures are `async ({}, use) => {}`.

Refactor: `const BASE = process.env.E2E_FRONTEND_URL ?? '...'` was redeclared verbatim in 23
files — extracted to helpers/env.ts and imported, so a port/scheme change is one edit not a sweep.

Then `prettier --write`. Verified: eslint clean, tsc clean, prettier clean, desktop suite 210
passed / 1 skipped. (One mobile spec flaked once under retries:0 — a pre-existing cross-test
reflow-timing vector from the flakiness audit, not this change: the each-key edit is stable across
16 isolated runs and a clean full mobile re-run.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 20:45:59 +02:00

72 lines
2.6 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.
/**
* Phase 3 mobile — viewport reflow.
*
* Asserts the layout still works at landscape orientation, a narrow
* "small phone" viewport, and a "phablet" viewport. The bottom nav must
* remain reachable; the FAB stays centered; no horizontal overflow.
*/
import { test, expect } from '../../fixtures/test';
const VIEWPORTS = [
{ name: 'portrait (default Pixel 7)', width: 412, height: 915 },
{ name: 'landscape (Pixel 7 rotated)', width: 915, height: 412 },
{ name: 'narrow small phone', width: 320, height: 568 },
{ name: 'phablet', width: 480, height: 1024 },
];
test.describe('Mobile — viewport reflow', () => {
for (const vp of VIEWPORTS) {
test(`bottom nav remains usable at ${vp.name} (${vp.width}×${vp.height})`, async ({
page,
guest,
signIn,
}) => {
const g = await guest(`Reflow_${vp.width}x${vp.height}`);
await signIn(page, g);
await page.setViewportSize({ width: vp.width, height: vp.height });
await page.goto('/feed');
const nav = page
.locator('nav')
.filter({ has: page.getByRole('link', { name: 'Galerie' }) })
.first();
const fab = page.getByRole('button', { name: 'Hochladen' });
await expect(nav).toBeVisible();
await expect(fab).toBeVisible();
// No horizontal overflow on <html>.
const overflowX = await page.evaluate(() => {
const html = document.documentElement;
return html.scrollWidth - html.clientWidth;
});
expect.soft(overflowX, 'no horizontal overflow').toBeLessThanOrEqual(1);
// FAB is roughly centered: its x-mid should be within 30% of the viewport mid.
const fabBox = await fab.boundingBox();
if (!fabBox) throw new Error('FAB has no bounding box');
const fabMidX = fabBox.x + fabBox.width / 2;
const expectedMid = vp.width / 2;
expect.soft(Math.abs(fabMidX - expectedMid)).toBeLessThanOrEqual(vp.width * 0.3);
});
}
test('rotation portrait → landscape preserves auth + bottom nav', async ({
page,
guest,
signIn,
}) => {
const g = await guest('Rotate');
await signIn(page, g);
await page.goto('/feed');
await expect(page.getByRole('link', { name: 'Galerie' })).toBeVisible();
await page.setViewportSize({ width: 915, height: 412 });
// The same nav should still be visible — no layout shift forces a re-render that loses auth.
await expect(page.getByRole('link', { name: 'Galerie' })).toBeVisible();
const stillAuthed = await page.evaluate(() => !!localStorage.getItem('eventsnap_jwt'));
expect(stillAuthed).toBe(true);
});
});