/** * Phase 3 mobile — gestures listed in USER_JOURNEYS.md §17 as **planned**. * * These tests are marked `test.fixme` so they appear in the report (as * pending, not failing) until the feature ships. Each test contains the * exact assertion that should pass once the gesture is wired — flip * `test.fixme` to `test` when implementing. * * Why ship the tests now? They document the *contract* — the next person * to wire the gesture has a green/red signal instead of needing to invent * an interaction model. */ import { test, expect } from '../../fixtures/test'; import { uploadRaw } from '../../helpers/upload-client'; import { swipe } from '../../helpers/touch'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; const SAMPLE_JPG = join(process.cwd(), 'fixtures', 'media', 'sample.jpg'); test.describe('Mobile — planned gestures (fixme until shipped)', () => { test.fixme('swipe left in lightbox navigates to next filtered item', async ({ page, guest, signIn }) => { const author = await guest('SwipeAuthor'); // Seed two uploads so there's a "next" to navigate to. for (const cap of ['First', 'Second']) { const res = await uploadRaw(author.jwt, readFileSync(SAMPLE_JPG), { filename: `${cap}.jpg`, contentType: 'image/jpeg', caption: cap, }); if (res.status !== 201) throw new Error(`seed ${cap}: ${res.status}`); } await signIn(page, author); await page.goto('/feed'); // Open the lightbox on the first card. const firstImage = page.locator('article').filter({ hasText: 'First' }).getByRole('button', { name: 'Bild vergrößern' }); await firstImage.click(); const lightbox = page.getByRole('dialog'); await expect(lightbox).toBeVisible(); await expect(lightbox).toContainText('First'); // Swipe left across the media element. const media = lightbox.locator('img, video').first(); const box = await media.boundingBox(); if (!box) throw new Error('lightbox media not visible'); await swipe( page, { x: box.x + box.width * 0.9, y: box.y + box.height / 2 }, { x: box.x + box.width * 0.1, y: box.y + box.height / 2 } ); // Expected: the second upload's caption is now visible. await expect(lightbox).toContainText('Second', { timeout: 2_000 }); }); test.fixme('swipe right in lightbox navigates to previous item', async ({ page }) => { // Same setup as above, but starting from the second item and swiping right. expect(true).toBe(true); void page; }); test.fixme('swipe down on UploadSheet dismisses it', async ({ page, guest, signIn }) => { const g = await guest('SwipeDismiss'); await signIn(page, g); await page.goto('/feed'); await page.getByRole('button', { name: 'Hochladen' }).click(); const sheet = page.getByRole('dialog'); await expect(sheet).toBeVisible(); const box = await sheet.boundingBox(); if (!box) throw new Error('sheet not visible'); await swipe( page, { x: box.x + box.width / 2, y: box.y + 10 }, { x: box.x + box.width / 2, y: box.y + 300 } ); // Expected: the sheet is gone. await expect(sheet).not.toBeVisible({ timeout: 2_000 }); }); test.fixme('pull-to-refresh on /feed triggers a delta fetch', async ({ page, guest, signIn }) => { const g = await guest('PullRefresh'); await signIn(page, g); await page.goto('/feed'); let deltaCalled = false; await page.route('**/api/v1/feed/delta**', async (route) => { deltaCalled = true; await route.continue(); }); const box = await page.locator('body').boundingBox(); if (!box) throw new Error('body not visible'); // Pull down from the top of the viewport. await swipe( page, { x: box.x + box.width / 2, y: 20 }, { x: box.x + box.width / 2, y: 200 } ); await page.waitForTimeout(1_000); expect(deltaCalled).toBe(true); }); test.fixme('long-press on a comment opens a context sheet (copy/delete)', async ({ page }) => { // Per journey §17 row "Long-press on a comment (own) → Bottom sheet → Löschen". expect(true).toBe(true); void page; }); });