From b1a2eb53188c9f1a8d146bc9feb743d5d21d769a Mon Sep 17 00:00:00 2001 From: IK Date: Mon, 21 Sep 2026 07:44:07 +0700 Subject: [PATCH] test: cover key mobile route contracts --- apps/web/tests/ux-route-contract.spec.ts | 25 ++++++++++++++++++++++++ docs/ROADMAP.md | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 apps/web/tests/ux-route-contract.spec.ts diff --git a/apps/web/tests/ux-route-contract.spec.ts b/apps/web/tests/ux-route-contract.spec.ts new file mode 100644 index 0000000..bdd76be --- /dev/null +++ b/apps/web/tests/ux-route-contract.spec.ts @@ -0,0 +1,25 @@ +import { expect, test } from "@playwright/test"; + +const routes = [ + { path: "/", heading: /Выбирай место/, action: ".spot-card" }, + { path: "/spots/vyunok-6331x6332", heading: /^Точка /, action: "[data-plan-save]" }, + { path: "/waterbodies/vyunok", heading: /Вьюнок/, action: ".catalog-results" }, + { path: "/plan", heading: /Мой план/, action: "[data-plan-share]" }, + { path: "/tackle", heading: /Снасти и приманки/, action: "[aria-label=\"Фильтры каталога снастей\"]" }, +] as const; + +test("key routes keep their first answer and action visible on mobile", async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + for (const route of routes) { + await page.goto(route.path); + await expect(page.locator("main h1")).toHaveText(route.heading); + await expect(page.locator(route.action).first()).toBeVisible(); + const state = await page.evaluate(() => ({ + clientWidth: document.documentElement.clientWidth, + scrollWidth: document.documentElement.scrollWidth, + main: Boolean(document.querySelector("main")), + })); + expect(state.main, `${route.path} has no main landmark`).toBe(true); + expect(state.scrollWidth, `${route.path} overflows at 390px`).toBeLessThanOrEqual(state.clientWidth); + } +}); diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 988066b..1ff7e83 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -97,7 +97,7 @@ - [ ] **U03 · Evidence/trust card.** Общий evidence-контракт используется на activity-карточках, detail точки, водоёма и карточках снастей: freshness с текстовым `Свежо`/`Устарело`, явный период расчёта, completeness, confidence/статус, source badges и доступные доменные поля; при выборке меньше 3 явно показано отдельное состояние `Недостаточно данных`, ограниченный источник не смешивается с неполными полями, а явно переданные provenance-конфликты видны текстом. Targeted E2E и API-тест проверяют публичные пути, период, 48-часовой порог и конфликт источников. - [ ] **U04 · List/map и progressive disclosure.** List остаётся честным базовым режимом: фильтры, сортировка, URL-состояние и evidence-карточки уже работают без имитации координатной карты. Следующий шаг — единый list/map-контракт после подтверждения геометрии; на mobile карта должна открываться отдельным действием. Вторичные raw/provenance-поля не исчезают и раскрываются по запросу. - [ ] **U05 · Mobile-first и сохранённый план рыбалки.** `/plan` поддерживает список до 5 локальных вариантов, удаление, очистку, переход к точке, print/PDF и восстановление из shareable URL; кнопка «Поделиться планом» использует native share или clipboard fallback. Detail-кнопка сохраняет данные с `aria-pressed` и восстанавливается после reload. Print/mobile-контракт проверяет 320/390 px, лимит импорта и отсутствие горизонтального overflow; карточка плана теперь сравнивает подтверждённые метод/проводку и явно показывает риск по выборке/уверенности. -- [ ] **U06 · Контентная и визуальная иерархия.** Для detail точки действие «Что взять» выделено отдельным заголовком, provenance и качество собраны в общем паспорте данных, а подтверждённые метод/проводка видны рядом с последними уловами; статусы дополнительно передаются текстом, малая выборка не маркируется как готовая рекомендация, а дублирующие catches/players/confidence убраны из вторичной колонки activity-карточки. Осталось провести ручной review на 5 ключевых маршрутах. +- [ ] **U06 · Контентная и визуальная иерархия.** Для detail точки действие «Что взять» выделено отдельным заголовком, provenance и качество собраны в общем паспорте данных, а подтверждённые метод/проводка видны рядом с последними уловами; статусы дополнительно передаются текстом, малая выборка не маркируется как готовая рекомендация, а дублирующие catches/players/confidence убраны из вторичной колонки activity-карточки. Mobile route-contract проверяет первый ответ и действие на home, spot, waterbody, plan и tackle; остаётся ручной review визуальных деталей. - [ ] **U07 · UX-приёмка и измерения.** Playwright-контракты уже покрывают query journey, сквозной task journey, evidence states, saved plan, share/print, accessibility и visual matrix; unit-контракт добавил детерминированные stale- и blocked-подписи, а insufficient-data теперь отделён от incomplete в публичном паспорте. `/status` явно сообщает, что при ограничении источника опубликованные данные сохранены. Осталось сохранить reference screenshots и получить production Lighthouse/CLS/INP/time-to-first-useful-answer после пилота. - [ ] **Q01 · Документы источников — реестр готов, нужны первичные подтверждения.** Создан единый production-gate с атрибуцией, общим лимитом 30 минут, хранением и процедурой отзыва для RF4DB, RF4-STAT, RF4MAP, RF4 Posts и официального RF4. До открытой публикации приложить устойчивые ссылки/копии первичных разрешений, контакты, даты и отдельно подтвердить право на изображения; пустое поле блокирует соответствующий источник.