docs(sprint7): пункт 3 ✓ + toast screenshot script
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
27ce8dddfc
commit
56dd9fb639
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
- [x] **1. Demo-data seeder для stage** — POST /api/admin/seed-demo + admin-кнопка «Заполнить демо-данными». 50 товаров / 5 групп / 10 контрагентов / 5 приёмок / 30 продаж / 1 опт / 1 списание / 1 перемещение / 1 инвентаризация. Идемпотентно (маркер DEMO-). E2E 5/5 ✓ на стейдже.
|
||||
- [x] **2. ConfirmDialog на destructive actions** — общий `<ConfirmDialog>` + хук `useConfirm()`. Применён к 17 страницам + ProductImageGallery. Esc=cancel, focus-on-Cancel, tone='danger'|'warning'. Org-archive уже использует Modal с confirmation-name (не трогали). 2FA UI ещё не существует в web — пропущено. Скриншот стейджа: `tests/e2e/reports/confirm-dialog-1780119970286.png`.
|
||||
- [ ] **3. Toast-система ошибок** — замена console.error в `src/lib/api.ts`. 4xx/5xx → error toast c message из API; мутации 2xx → success. Top-right, 5s autoclose.
|
||||
- [x] **3. Toast-система ошибок** — собственная `lib/toast.ts` + `<Toaster>`. Axios interceptor: 4xx/5xx → error toast (humanizeError() читает ProblemDetails: errors.X[0] / detail / message / title). 401 — refresh-flow, без toast. Success — глобальный mutation onSuccess (через `meta.successMessage`): useCatalogMutations + 36 мутаций на doc-edit pages. Top-right, autoclose 5s, дедуп, ручное закрытие X. Скриншот: `tests/e2e/reports/toast-error-*.png`.
|
||||
- [ ] **4. Loading skeletons** — на DataTable и edit-pages вместо «Загрузка…» — shimmer-скелет. Reusable `<Skeleton>`.
|
||||
- [ ] **5. Empty states с CTA** — list-страницы при `items.length === 0` показывают центрированный блок с иконкой, текстом и кнопкой «Создать первый …».
|
||||
- [ ] **6. Breadcrumbs** — на edit-страницах Reusable `<Breadcrumbs items={...}>`.
|
||||
|
|
@ -41,3 +41,13 @@
|
|||
- Esc=cancel, фокус на Cancel (Enter не подтверждает). Tone='danger'/'warning'.
|
||||
- Скриншот стейджа: `tests/e2e/reports/confirm-dialog-*.png` — диалог рендерится, Esc-закрытие работает.
|
||||
- Коммит: `17a6da2 feat(web): ConfirmDialog компонент + useConfirm hook`.
|
||||
|
||||
### 2026-05-30 — пункт 3 ✓
|
||||
|
||||
- `lib/toast.ts` + `<Toaster>`: top-right, autoclose 5s, дедуп, ручное закрытие, role=alert.
|
||||
- Axios interceptor (src/lib/api.ts): 4xx/5xx → toast.error c humanizeError (ProblemDetails-aware), 401 — refresh-flow без шума.
|
||||
- Global mutation onSuccess (App.tsx) подтягивает meta.successMessage.
|
||||
- useCatalogMutations: create/update/remove получили дефолтные тексты — автоматически работает на всех list-pages, использующих хук.
|
||||
- Doc-edit pages: 36 мутаций (save/post/unpost/remove) получили meta.
|
||||
- Скриншот: `tests/e2e/reports/toast-error-1780120703501.png` — toast «Не найдено / Not Found» рендерится top-right.
|
||||
- Коммит: `27ce8dd feat(web): toast-система`.
|
||||
|
|
|
|||
45
tests/e2e/scripts/screenshot-toast.ts
Normal file
45
tests/e2e/scripts/screenshot-toast.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Sprint 7 item 3 — визуальная проверка toast'a на стейдже.
|
||||
* Логинимся → провоцируем 404 (несуществующий товар) → ждём toast → скриншот.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { makeClient, login } from '../lib/api.js'
|
||||
|
||||
const BASE = process.env.E2E_ADMIN_URL ?? 'https://test.admin.food-market.kz'
|
||||
const TS = Date.now()
|
||||
const EMAIL = process.env.E2E_EMAIL ?? `toast-shot-${TS}@food-market.local`
|
||||
const PASS = process.env.E2E_PASSWORD ?? 'ToastShot12345!'
|
||||
|
||||
async function ensureSession() {
|
||||
const api = makeClient()
|
||||
const r = await api.post('/api/auth/signup', {
|
||||
email: EMAIL, password: PASS,
|
||||
organizationName: `ToastShot ${TS}`, phone: '+77011190001', plan: 'start',
|
||||
})
|
||||
if (r.status !== 200 && r.status !== 409) throw new Error(`signup ${r.status}: ${JSON.stringify(r.data)}`)
|
||||
return login(EMAIL, PASS)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const sess = await ensureSession()
|
||||
console.log(`[shot] session ok ${sess.email}`)
|
||||
|
||||
const browser = await chromium.launch({ headless: true })
|
||||
const ctx = await browser.newContext({ ignoreHTTPSErrors: true, viewport: { width: 1280, height: 800 } })
|
||||
const page = await ctx.newPage()
|
||||
await page.goto(`${BASE}/`)
|
||||
await page.evaluate(({ token }) => localStorage.setItem('fm.access_token', token), { token: sess.accessToken })
|
||||
|
||||
// Идём на несуществующий продукт — должна сработать 404 с тостом.
|
||||
await page.goto(`${BASE}/catalog/products/00000000-0000-0000-0000-000000000000`, { waitUntil: 'domcontentloaded' })
|
||||
await page.waitForLoadState('networkidle')
|
||||
// Ждём наш toast (role=alert + Не найдено)
|
||||
await page.waitForSelector('[role="alert"]', { timeout: 10000 })
|
||||
await page.waitForTimeout(400)
|
||||
await page.screenshot({ path: `reports/toast-error-${TS}.png`, fullPage: false })
|
||||
console.log(`[shot] saved → reports/toast-error-${TS}.png`)
|
||||
|
||||
await browser.close()
|
||||
}
|
||||
|
||||
main().catch(err => { console.error(err); process.exit(1) })
|
||||
Loading…
Reference in a new issue