112 lines
8.7 KiB
Markdown
112 lines
8.7 KiB
Markdown
# Active Context — Strava Frontend
|
||
|
||
## Task State
|
||
|
||
- task_id: TASK-TEST-PUBLIC
|
||
- status: in_progress
|
||
- parent_task: —
|
||
- summary: **Написать Playwright integration тесты для фронтенда в `../integration/tests/`. 3 spec файла: auth-flows ✅, public-pages, auth-protected.**
|
||
- next task: TASK-TEST-PROTECTED → Verify
|
||
|
||
## ✅ CODER SUCCESS REPORT — TASK-TEST-AUTH
|
||
|
||
- `../integration/tests/auth-flows.spec.ts` создан: 4 теста (login success, login failure, signup+auto-login, logout)
|
||
- Self-seed via `POST /api/v0/signup`, Vuestic selectors (`input[type=email]`, `input[type=password]`)
|
||
- Verified: `npx playwright test tests/auth-flows.spec.ts` → **4 passed (19.2s)**
|
||
|
||
## TASK-TEST-PUBLIC: public-pages.spec.ts
|
||
|
||
**Цель:** Playwright spec для auth-сценариев (signup, login success/error, logout, validation).
|
||
|
||
**Файл для создания:** `../integration/tests/auth-flows.spec.ts`
|
||
|
||
**Конвенции (см. SKILL.md):**
|
||
- Self-seed user via `POST /api/v0/signup` in `beforeAll`
|
||
- Selectors: `input[type=email]`, `input[type=password]`, `getByRole('button', { name: 'Вход' })`
|
||
- Vuestic floating labels → select by input type, NOT placeholder
|
||
- Auth mock via `page.evaluate(() => localStorage.setItem(...))`
|
||
- Do NOT call `expect()` inside `page.evaluate()`
|
||
- Base URLs: `FRONTEND` (5173), `BACKEND` (8000)
|
||
- `freshPage()` helper: goto → clear localStorage → goto again
|
||
|
||
**Тесты в spec:**
|
||
1. **Signup**: `POST /api/v0/signup` → returns token, user, profile; navigate to `/auth/signup` → fill form → submit → redirect to `/explore`
|
||
2. **Login success**: pre-seed user via API → UI login → token in localStorage → redirect to `/explore`
|
||
3. **Login failure**: wrong password → error toast appears, stays on `/auth/login`
|
||
4. **Logout**: logged in → open profile dropdown → «Выход» → redirect to login, token cleared
|
||
|
||
**Acceptance Criteria:**
|
||
1. Spec file created at `../integration/tests/auth-flows.spec.ts`
|
||
2. All tests self-contained (self-seed data)
|
||
3. Follows existing `sidebar-auth-switch.spec.ts` patterns
|
||
4. `npx playwright test tests/auth-flows.spec.ts` passes against integration stack
|
||
|
||
## TASK-MCP-SETUP: Playwright MCP configuration
|
||
|
||
**Что сделано:**
|
||
- MCP-конфиг Zoo Code (`mcp_settings.json`): server `web-browser` → `@playwright/mcp@latest`, Node 22 via nvm absolute path, `--headless`
|
||
- Chromium `1234` уже установлен в `~/.cache/ms-playwright/`
|
||
- `.mcp.json` создан в проекте (project-level, для совместимости)
|
||
- `techContext.md` обновлён: секция "UI Verification (MCP Browser)"
|
||
|
||
**Как использовать (workflow):**
|
||
1. `yarn dev` — запустить dev server (порт 5173)
|
||
2. `browser_navigate` → `http://localhost:5173/explore` — проверить публичную страницу
|
||
3. `browser_evaluate` → `() => { localStorage.setItem('token', 'test'); localStorage.setItem('user', JSON.stringify({id:1,name:'Test'})); location.reload(); }` — сымитировать auth
|
||
4. `browser_navigate` → проверить auth-страницы (`/workouts`, `/workouts/:id`)
|
||
5. `browser_snapshot` — проверить DOM-структуру
|
||
6. `browser_take_screenshot` — визуальная проверка
|
||
7. `browser_network_requests` — проверить API-запросы
|
||
|
||
### Баг (контекст)
|
||
|
||
Сайдбар показывает `publicRoutes` (Лента + Маршрут) после логина до F5. Корень: [`NavigationRoutes.ts:70`](src/components/sidebar/NavigationRoutes.ts:70) — `routes` вычисляется один раз при module evaluation и кэшируется. TASK-AUTH-NAV-1 уже добавил реактивный `isAuthenticated` в `useGlobalStore` (инициализация из localStorage + `setAuthenticated` в Login/Logout). Эта задача переключает UI на computed.
|
||
|
||
### TASK-AUTH-NAV-2: реактивные маршруты (3 файла)
|
||
|
||
**1. `src/components/sidebar/NavigationRoutes.ts`**
|
||
|
||
- Сделать named exports: `export const authRoutes: INavigationRoute[]` и `export const publicRoutes: INavigationRoute[]` (сейчас это локальные `const`, не экспортированные).
|
||
- Убрать статическое поле `routes` из default export (или оставить default без `routes`, т.к. оба потребителя перейдут на computed). `INavigationRoute` интерфейс — не трогать.
|
||
- Важно: массивы `authRoutes`/`publicRoutes` остаются теми же по структуре/содержимому — только экспортируются.
|
||
|
||
**2. `src/components/sidebar/AppSidebar.vue`** (Options API, `setup()`)
|
||
|
||
- `import { useGlobalStore } from "../../stores/global-store";` + `import { authRoutes, publicRoutes } from "./NavigationRoutes";` (импорт `navigationRoutes` по default можно оставить/убрать — но все обращения `navigationRoutes.routes` заменить).
|
||
- В `setup()`: `const globalStore = useGlobalStore();`
|
||
- computed: `const routes = computed(() => globalStore.isAuthenticated ? authRoutes : publicRoutes);`
|
||
- В `return` заменить `navigationRoutes` на `routes` (тепловой шаблон уже использует `navigationRoutes.routes` → поправить на `routes.value`? НЕТ — в Options API computed из setup доступно в шаблоне как `routes` без `.value`, т.е. в шаблоне `navigationRoutes.routes` → `routes`).
|
||
- `setActiveExpand`: `navigationRoutes.routes.map(...)` → `routes.value.map(...)` (в setup `.value` нужен).
|
||
- Шаблоны: `v-for="(route, index) in navigationRoutes.routes"` → `in routes`.
|
||
|
||
**3. `src/components/app-layout-navigation/AppLayoutNavigation.vue`** (script setup)
|
||
|
||
- `import { authRoutes, publicRoutes } from "../sidebar/NavigationRoutes";` + `import { useGlobalStore } from "../../stores/global-store";`
|
||
- `const globalStore = useGlobalStore();`
|
||
- `findRouteName` (строка ~64): `traverse(NavigationRoutes.routes)` → `traverse(globalStore.isAuthenticated ? authRoutes : publicRoutes)`.
|
||
- Убедиться, что `items` computed (зависит от `route.matched` и `findRouteName`) остаётся реактивным — т.к. `findRouteName` читает `globalStore.isAuthenticated` внутри `items` computed, реactivity сохранится.
|
||
|
||
### Acceptance Criteria (TASK-AUTH-NAV-2)
|
||
|
||
1. `yarn lint` → 0 ошибок.
|
||
2. `yarn build` → 0 ошибок (vue-tsc --noEmit).
|
||
3. `NavigationRoutes.ts`: `authRoutes` и `publicRoutes` экспортируются; статического `routes` с localStorage-тернарником больше нет.
|
||
4. `AppSidebar.vue`: routes = computed от `globalStore.isAuthenticated`; `setActiveExpand` использует `routes.value`.
|
||
5. `AppLayoutNavigation.vue`: `traverse` использует выбор по `globalStore.isAuthenticated`.
|
||
6. **Ручной сценарий (ключевой)**: без F5 — выход из профиля → `/explore` → логин → сайдбар **сразу** показывает «Мои тренировки» (authRoutes). Аут → сайдбар сразу `publicRoutes`.
|
||
|
||
## ✅ CODER SUCCESS REPORT — TASK-AUTH-NAV-2
|
||
|
||
- `src/components/sidebar/NavigationRoutes.ts`: `export const authRoutes` / `export const publicRoutes`; удалён статический `routes: localStorage.getItem("token") ? ...` из default export (оставлен только `root`).
|
||
- `src/components/sidebar/AppSidebar.vue`: импорт `authRoutes, publicRoutes` + `useGlobalStore`; `computed(() => globalStore.isAuthenticated ? authRoutes : publicRoutes)`; `setActiveExpand` использует `routes.value.map`; шаблон `v-for="(route, index) in routes"`.
|
||
- `src/components/app-layout-navigation/AppLayoutNavigation.vue`: `const globalStore = useGlobalStore()`; `traverse(globalStore.isAuthenticated ? authRoutes : publicRoutes)` внутри `findRouteName` (вызывается в `items` computed → реactivity сохранена).
|
||
- Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc --noEmit 0 ошибок, vite ✓ 7.07s).
|
||
|
||
## История (закрытые задачи)
|
||
|
||
- **TASK-AUTH-NAV-1**: success — `isAuthenticated` + `setAuthenticated` в `useGlobalStore`; Login `setAuthenticated(true)`, Logout `setAuthenticated(false)`. Verified lint/build 0.
|
||
- TASK-SSR-DETAIL: success — SSR /public/workouts/:id под DOM WorkoutItem.vue.
|
||
- TASK-404: success — HTTP 404 для неизвестных страниц.
|
||
- TASK-PUBLIC-ROUTE-FIX: success — workout_public_item в top-level router.
|
||
- TASK-F11/F12/F13/F14: success — SEO + фиксы карты тренировки.
|