# Vitest: девять практических сценариев

Проект связывает настоящий тестовый runner и живой интерфейс. `public/model.js` используется и посетителем мастерской, и тестами. `fixed` — исправленная реализация, `broken` — намеренные учебные ошибки. На публичной странице отображаются **записи реальных запусков Vitest**, а не исполняется тестовый runner.

Версии: Node 24.15+ в ветке 24 LTS (при подготовке 24.19.0), Vitest / browser providers / coverage-v8 5.1.0, JSDOM 31.0.0. Требование Node обусловлено в том числе JSDOM. Полное дерево фиксируется `package-lock.json`; проверяйте `engines` при обновлении.

## Windows / PowerShell

Установите Node 24 LTS и распакуйте проект в отдельную папку. Используйте `npm.cmd`, если PowerShell запрещает запуск `npm.ps1` политикой выполнения. Менять системную политику ради проекта не требуется.

```powershell
node --version
npm.cmd --version
npm.cmd ci
npm.cmd test
npm.cmd run capture
npm.cmd run verify
npm.cmd run build-public
npm.cmd run serve
```

Откройте http://127.0.0.1:8525/ . Остановка только своего сервера — Ctrl+C. Не открывайте index.html через file://: JSON и ES-модули требуют HTTP.

Обычный `npm test` использует fixed. Для ручного просмотра намеренно падающей версии:

```powershell
$env:LAB_VARIANT = 'broken'
npm.cmd test
# Ожидается exit code 1: это учебные ошибки.
Remove-Item Env:LAB_VARIANT
npm.cmd test
```

Не оставляйте `LAB_VARIANT=broken` в окружении обычного CI. Скрипт capture сам задаёт вариант дочерним процессам и не меняет окружение родительского терминала.

## Linux / Bash

Установите поддерживаемый Node 24.15+ из принятого в вашей системе источника. Дистрибутивный пакет с именем nodejs может быть старее: сначала проверьте версию. Для npm ci нужны доступ к registry и доверенные CA; не отключайте TLS.

```bash
node --version
npm --version
npm ci
npm test
npm run capture
npm run verify
npm run build-public
npm run serve
```

```bash
LAB_VARIANT=broken npm test   # намеренно exit 1
npm test                    # fixed, exit 0
npm run coverage
npm run watch
```

Watch удобен локально; `vitest run` завершает процесс после одного запуска и подходит CI. Не запускайте публичный watch-сервер на VPS. Для знакомства по SSH используйте туннель к 127.0.0.1:8525.

## Девять сценариев и границы доказательств

1. `total`: цена в копейках, количество, скидка; граничный ввод и лишняя потерянная копейка.
2. `validateEmail`: test.each, допустимые и недопустимые значения. Это учебная проверка формы, не полный RFC-парсер и не подтверждение владения почтой.
3. `loadCustomer`: await, успешный JSON, HTTP 503, неверные данные. Тест поднимает **настоящий** Node HTTP-сервер на свободном loopback-порту и останавливает его afterAll. На странице fetch загружает настоящий статический JSON, ошибка — отсутствующий файл; это не отдельный backend с базой.
4. `saveOrder`: vi.fn проверяет путь и аргументы API, единицы измерения и нормализацию. Живой пример использует явно обозначенный адаптер без сети. Он не доказывает работоспособность внешнего API; реальный HTTP отдельно проверяет сценарий 3.
5. `debounceSearch`: fake timers проверяют последний запрос и cancel; живой UI использует настоящие таймеры. afterEach чистит таймеры и восстанавливает окружение даже при падающем assertion.
6. `mountContactForm`: JSDOM проверяет сообщение, aria-invalid и фокус. В браузере посетитель может сам заполнить форму. Сохранение учебное, никуда не отправляет контакт.
7. `mountDisclosure`: JSDOM preflight проверяет aria-expanded/Escape/focus. **Настоящий Browser Mode вынесен отдельно**; в исходной записи `browser.status=not-run`, пока он фактически не выполнен. JSDOM не доказывает CSS-геометрию, touch или физическое устройство.
8. `summary`: inline snapshot фиксирует небольшой формат заказа; coverage получен настоящим V8. Измерение процента не доказывает полноту требований. В `deliveryFee` намеренно проверен лишь самовывоз.
9. `canPublish`: any/all и непустой набор. Живой UI проверяет учебное условие; он не выполняет CI/деплой. Скрипт capture отдельно проверяет реальные exit 0 и exit 1 тестового runner.

## Что запускают команды

- `npm test`: обычная проверка `tests/scenarios.test.js`, environment JSDOM, по умолчанию fixed. Намеренно failing реализация не делает обычный run красным.
- `npm run test:ui`: 15 предметных проверок интерфейса в JSDOM, включая поздний HTTP-ответ, отмену таймера и восстановление из bfcache. Они не доказывают геометрию настоящего браузера или CSP.
- `npm run capture`: запускает fixed и broken отдельными настоящими процессами Vitest, требует соответственно exit 0 и 1, затем запускает coverage fixed. Нормализует абсолютный путь проекта в сохранённых отчётах и пишет `public/reports.json`. Никаких придуманных assertion/duration/процентов нет.
- `npm run verify`: проверяет SHA-256 исходников, статус зелёного запуска и наличие намеренной ошибки в каждом из девяти сценариев. После изменения входных файлов нужно переснять capture; вручную редактировать зелёные результаты нельзя.
- `npm run coverage`: V8 coverage и HTML в `reports/coverage/`. Пороги учебного проекта — минимальный барьер, не обещание качества продукта. Исходник fixed/broken общий, поэтому таблица ветвей включает учебные альтернативы.
- `npm run build-public`: формирует сценарии и читаемые `.txt` копии исходников. Не запускает браузер и не подменяет capture.
- `npm run serve`: локальный статический Node-сервер public на 127.0.0.1:8525. Выдаёт только файлы внутри public, принимает GET/HEAD.

В артефакте capture фиксируются версии runtime и hashes `package.json`, lock, общего модуля, тестов, конфигурации и capture-скрипта. Повторный запуск на другой платформе может иметь другие длительности. Сравнивать следует свойства результата и ожидаемые статусы, а не точное совпадение времён.

## Browser Mode: ручной preview

```powershell
npm.cmd run browser:preview
```

```bash
npm run browser:preview
```

Конфигурация `vitest.browser.config.js` использует `@vitest/browser-preview`. Она задаёт `BROWSER=none`: новый браузер автоматически не открывается. Откройте в своём браузере полный URL runner, который напечатает Vitest (API порт 8526, адрес loopback). Provider preview выполняет тесты в настоящем браузерном DOM, но симулирует события; для CI используйте Playwright provider.

В Vitest 5 импортируйте `page` и `userEvent` из `vitest/browser`. Старый путь `@vitest/browser/context` для этого проекта не подходит. В обеих browser-конфигурациях установлено `publicDir:false`: тест импортирует общий модуль и CSS из папки `public`, поэтому они должны проходить обработку Vite, а не отдаваться как необработанная статика. Статическую мастерскую по-прежнему запускает отдельная команда `npm run serve`.

В закреплённой версии preview 5.1.0 обработчик `advanceTimers` вызывается и при обычных таймерах. Поэтому тест создаёт собственный `userEvent.setup`: для preview он продвигает время только при `vi.isFakeTimers()`, а для Playwright использует стандартную настройку. Это сохраняет настоящие таймеры и проверки клика, фокуса и Escape; ошибка таймеров не скрывается пропуском assertions. После каждого теста очищаются события пользователя и DOM.

`tests/disclosure.browser.test.js` проверяет раскрытие панели, aria-expanded, фокус, Escape и отсутствие горизонтального переполнения на 1280/390/296. Это целевая проверка компонента, а не автоматическая приёмка всей публикации. Мастерскую и встроенный iframe нужно проверять отдельно. По окончании остановите только этот процесс Ctrl+C.

Отдельный preview-прогон 12 ноября 2026 года прошёл все три проверки. Результат и его границы описаны в [BROWSER-CHECK.md](BROWSER-CHECK.md). Исторический Node/jsdom-отчёт `public/reports.json` сохранён без изменений; его отметка `browser.status=not-run` относится к тому, более раннему запуску.

## Browser Mode в CI / Playwright

Для скачивания браузера нужен интернет. Установка browser runtime отличается от установки npm-пакета. На Linux Playwright также нужны системные библиотеки.

```powershell
npx.cmd playwright install chromium
npm.cmd run browser:ci
```

```bash
npx playwright install --with-deps chromium
npm run browser:ci
```

Команда с `--with-deps` может потребовать права для системных пакетов; запускайте её в подготовленном CI-контейнере или выделенном runner, а не обновляйте чужой production-сервер автоматически. `vitest.browser-ci.config.js` использует headless Chromium и такой же тест компонента. В комплекте исходников эта конфигурация подготовлена отдельно; наличие файла не означает, что браузерный прогон уже выполнялся. Смотрите реальные evidence соответствующего запуска.

## CI и Linux/VPS

Минимальные шаги pipeline:

```bash
npm ci
npm test
npm run coverage
npm run capture
npm run verify
npm run build-public
```

Намеренно broken run находится внутри capture: script ожидает ненулевой exit и завершится ошибкой, если плохая версия неожиданно стала зелёной. Это не команда `|| true`, скрывающая произвольные сбои. После capture проверяется существование свежего JSON-отчёта; старый raw удаляется перед запуском. Для Browser Mode добавьте установку Chromium и отдельный `npm run browser:ci`.

Запускайте CI под отдельным пользователем с ограниченными правами. Тесты — исполняемый код проекта: не запускайте неизвестные pull request с production-секретами. Этот учебный проект не нуждается в токенах, паролях или внешней базе. Логи и HTML coverage храните как CI artifacts, доступные нужной команде.

На публичный сайт выкладывается **только public**, не node_modules, runner API и не весь репозиторий. Статический хост должен отдавать `.js` как JavaScript, `.json` как JSON. Веб-сервером может быть Nginx или Caddy; PHP для этой публичной мастерской не требуется, потому что Node/Vitest выполняются на этапе подготовки отчёта. Готовые файлы можно разместить рядом с PHP-сайтом как статические ресурсы.

```nginx
server {
    listen 80;
    server_name tests.example.com;
    root /srv/vitest-workshop/public;
    index index.html;
    location / { try_files $uri $uri/ =404; }
    location ~ /\. { deny all; }
}
```

Для своего домена настройте HTTPS принятым на сервере способом. `nginx -t` перед reload обязателен; не заменяйте конфигурацию соседних сайтов. Архив `vitest-project.zip` публикуется отдельно; ссылка мастерской ожидает его на один каталог выше public. На чисто локальном `npm run serve` этот внешний ZIP не обслуживается — исходники уже находятся у вас.

## Snapshot и тестовые данные

Не обновляйте snapshot только ради зелёного статуса. Сначала выясните, почему изменился контракт: валюта, поле, сортировка, дата или ошибка реализации. В примере RUB→USD является намеренной ошибкой; новый snapshot с USD скрыл бы её. Все данные синтетические, никаких реальных клиентов в fixtures нет.

После каждого DOM-теста снимаются обработчики и очищается документ. Fake timers не остаются активными для следующего сценария. Настоящий HTTP-сервер закрывается afterAll. Эти детали важны именно для failing run: cleanup должен работать и после исключения assertion, а не только в последней строке зелёного теста.

## Лицензии и исходники

Авторский проект — MIT (`LICENSE`). Полные лицензии установленных npm-зависимостей и Bootstrap находятся в `licenses/` и `THIRD-PARTY-LICENSES.txt`. `node_modules` воспроизводится через npm ci и не требуется в публичной упаковке. `reports/initial.json`, временные raw-отчёты и coverage-файлы с абсолютными локальными путями не следует включать в публичный архив; нормализованные fixed/broken/capture и сводка доступны отдельно.

Официальные источники: https://vitest.dev/guide/ , https://vitest.dev/guide/browser/ , https://vitest.dev/guide/coverage , https://vitest.dev/guide/mocking .
