test(s8-3): i18n stage e2e
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
nns 2026-05-31 20:09:40 +05:00
parent 301bf15924
commit d451e77642
2 changed files with 55 additions and 1 deletions

View file

@ -16,7 +16,7 @@
- [x] **1. P2-7 SignalR real-time** — Hub `/hubs/notifications` с группами per-org. События `SalePosted`/`SupplyPosted`/`LowStock`. JWT через query `?access_token=` (для WebSocket). Дашборд: live-индикатор Wifi, оптимистическое приращение «Выручка сегодня», toast.info на SupplyPosted, toast.error на LowStock. Тесты: SignalRNotificationsTests (multi-tenant 1/1) + `stage-ui-signalr.spec.ts` (1/1 ✓). Nginx `/hubs/` с upgrade-хедерами и 24h read_timeout.
- [x] **2. P2-14 Telegram-бот владельца** — миграция `Phase9a_OwnerTelegramChatId`. `TelegramBotClient` (sendMessage HTML), Disabled-mode когда `Telegram__BotToken` пустой. `OwnerDailySummaryJob.RunAsync` — cron `0 6 * * *` UTC = 09:00 МСК. Сводка: выручка вчера, чеков, средний чек, топ-3 по выручке, low-stock 5. `TelegramBindingController` (status/bind/unbind). UI секция в OrganizationSettings с deep-link + пошаговой инструкцией. Тесты: `TelegramOwnerSummaryTests` (рендер ✓), `stage-ui-telegram.spec.ts` (3/3 ✓ на стейдже).
- [ ] **3. P2-6a Локализация UI (en)** — react-i18next, `ru.json` + `en.json`, language switcher в header. kz TODO. На стейдже smoke — все страницы переключаются.
- [x] **3. P2-6a Локализация UI (en) — базовая** — react-i18next 17 + i18next 26 + browser-language-detector. `ru.json`/`en.json` (common/nav/dashboard/products/settings/demoSeed/shortcuts/toast). `LanguageSwitcher` в sidebar. AppLayout (sidebar) + DashboardPage переведены. kz: TODO (требует человека-переводчика). Тесты: `stage-ui-i18n.spec.ts` (3/3 ✓). Остаток (Products/Counterparties/Enters/...) — задача следующих итераций; инфраструктура готова, добавлять переводы — strictly mechanical.
- [ ] **4. P2-15 MinIO/S3 для uploads**`Minio` SDK, bucket `food-market-uploads`, авто-создание на старте, миграция existing volume. `Storage:Type=Local|Minio` с fallback на Local. Тесты + UI upload картинки.
## Журнал

View file

@ -0,0 +1,54 @@
/**
* Sprint 8 пункт 3 i18n smoke на стейдже. Playwright headless Chromium
* по дефолту использует en-US navigator детектор подхватит en. Поэтому
* для теста "ru" принудительно ставим fm.lang='ru', для "en" 'en'.
*/
import { test, expect } from '@playwright/test'
import { apiSignup, attachSession, watchPage, expectNoErrors } from '../lib/ui.js'
test.describe('i18n', () => {
test.describe.configure({ mode: 'serial' })
test('S8-3.1 ru — sidebar и dashboard на русском', async ({ page }) => {
const sess = await apiSignup('i18n31')
const errs = watchPage(page)
await attachSession(page, sess, '/dashboard')
await page.evaluate(() => localStorage.setItem('fm.lang', 'ru'))
await page.reload()
await page.waitForLoadState('networkidle')
await expect(page.locator('aside').getByRole('link', { name: 'Товары' }).first()).toBeVisible({ timeout: 8_000 })
await expect(page.getByText('Выручка сегодня').first()).toBeVisible()
expectNoErrors(errs, 'ru')
})
test('S8-3.2 en — sidebar и dashboard на английском', async ({ page }) => {
const sess = await apiSignup('i18n32')
const errs = watchPage(page)
await attachSession(page, sess, '/dashboard')
await page.evaluate(() => localStorage.setItem('fm.lang', 'en'))
await page.reload()
await page.waitForLoadState('networkidle')
await expect(page.locator('aside').getByRole('link', { name: 'Products' }).first()).toBeVisible({ timeout: 8_000 })
await expect(page.getByText('Revenue today').first()).toBeVisible()
// На en sidebar НЕ содержит «Товары» (русский)
expect(await page.locator('aside a').filter({ hasText: 'Товары' }).count()).toBe(0)
expectNoErrors(errs, 'en')
})
test('S8-3.3 переключатель в sidebar меняет язык без reload', async ({ page }) => {
const sess = await apiSignup('i18n33')
const errs = watchPage(page)
await attachSession(page, sess, '/dashboard')
await page.evaluate(() => localStorage.setItem('fm.lang', 'ru'))
await page.reload()
await page.waitForLoadState('networkidle')
// Найдём select переключателя — он в sidebar после name/role блока
const langSelect = page.locator('aside select').last()
await expect(langSelect).toBeVisible({ timeout: 8_000 })
await langSelect.selectOption('en')
// Без reload — i18n меняется мгновенно
await expect(page.locator('aside').getByRole('link', { name: 'Products' }).first()).toBeVisible({ timeout: 5_000 })
expectNoErrors(errs, 'live switch')
})
})