diff --git a/docs/README.md b/docs/README.md index 2349810..cb3b3f5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -51,9 +51,9 @@ только если темы нет в существующем документе; иначе обновляется источник истины. -## Текущий baseline Q6d +## Текущий baseline Q6e -На момент фиксации Q6d: +На момент фиксации Q6e: - production registry содержит 122 инструмента; - `pnpm --dir web check` проходит без ошибок и предупреждений; @@ -61,7 +61,7 @@ - `pnpm --dir web test:rules` — 4 test files / 74 tests; - `pnpm --dir web lint:all` — 0 errors и 0 i18n warnings; остаются 4 unused design tokens от token audit; -- `pnpm verify` проходит; production E2E — 121 passed, 0 skipped. +- `pnpm verify` проходит; production E2E — 161 passed, 0 skipped. Baseline и следующий шаг поддерживаются в `docs/plan-codebase-quality.md`. diff --git a/docs/backlog.md b/docs/backlog.md index 488c706..87db889 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -107,8 +107,8 @@ ## Качество production UI -Q5–Q6d закрывают известные preview-проблемы и baseline special fixtures; -активных `test.fixme` в e2e больше нет. +Q5–Q6e закрывают известные preview-проблемы, special fixtures и registry-wide +smoke; активных `test.fixme` в e2e больше нет. - [x] Генераторы auto-run без кнопки Generate — `web/e2e/generators.spec.ts`. - [x] `resize-png` и `crop-png` используют размеры текущего source через общий @@ -119,6 +119,9 @@ Q5–Q6d закрывают известные preview-проблемы и basel обновляет alert — `web/e2e/i18n.spec.ts`. - [x] Malformed/special PNG fixtures: CRC, truncated, palette и 16-bit проходят через upload без краша — `web/e2e/png-fixtures.spec.ts`. +- [x] Registry-wide smoke покрывает все 122 инструмента по input/result-типу — + `web/e2e/tools-smoke.spec.ts`, `generators.spec.ts`, + `text-and-verdicts.spec.ts`. - [ ] Ошибка декодирования изображения из `createImageBitmap` пока показывается сырым сообщением браузера; нужен `ToolError`-маппер и i18n-ключ. Это продуктовая задача, не Q6d. diff --git a/docs/checklist-manual-testing.md b/docs/checklist-manual-testing.md index 16cf3d3..5722bfa 100644 --- a/docs/checklist-manual-testing.md +++ b/docs/checklist-manual-testing.md @@ -96,10 +96,10 @@ корректный (не «кракозябры»). - [ ] remove-background на сложной полупрозрачности — края не «звенят». -## G. Закрытые Q5/Q6b/Q6c/Q6d-проблемы +## G. Закрытые Q5/Q6b/Q6c/Q6d/Q6e-проблемы -> Q5/Q6b/Q6c/Q6d-проблемы закрыты автоматическими тестами; для ручной проверки -> остаются субъективные сценарии разделов A–F. +> Q5/Q6b/Q6c/Q6d/Q6e-проблемы закрыты автоматическими тестами; для ручной +> проверки остаются субъективные сценарии разделов A–F. - [x] **Генераторы:** auto-run по дефолтам, результат и изменение параметра без кнопки Generate. `web/e2e/generators.spec.ts`. @@ -117,6 +117,9 @@ сигнатуры, quality 10/90 меняет размер файла; `web/e2e/pipeline.spec.ts`. - [x] **Malformed/special PNG:** CRC, truncated, palette и 16-bit fixtures проходят через upload без краша; `web/e2e/png-fixtures.spec.ts`. +- [x] **Registry smoke:** все 122 production tools имеют специализированный + browser smoke по input/result-типу; `web/e2e/tools-smoke.spec.ts`, + `generators.spec.ts`, `text-and-verdicts.spec.ts`. ## Как долго diff --git a/docs/plan-codebase-quality.md b/docs/plan-codebase-quality.md index 28aa59c..263a040 100644 --- a/docs/plan-codebase-quality.md +++ b/docs/plan-codebase-quality.md @@ -1,9 +1,9 @@ # План: линтеры, тесты и качество кодовой базы -> Статус: **draft — Q0–Q5 и Q6a–Q6d готовы к ревью; Q6e — следующий этап**. +> Статус: **draft — Q0–Q5 и Q6a–Q6e готовы к ревью; Q6f — следующий этап**. > > Источник: `docs/backlog.md`, аудит конфигурации `web/`, e2e-тестов и -> инфраструктурных скриптов. Q0–Q5 и Q6a–Q6d выполнены; Q6e–Q8 выполняются +> инфраструктурных скриптов. Q0–Q5 и Q6a–Q6e выполнены; Q6f–Q8 выполняются > отдельными атомарными шагами. > > Цель: сделать качество кодовой базы воспроизводимым, убрать ложную «зелёность» @@ -259,10 +259,26 @@ CRC/truncated отклоняются без падения, ancillary CRC и х - Сырое сообщение браузера при decode-ошибке не локализуется в Q6d; это отдельная задача в backlog. -#### Q6e–Q6f. Оставшиеся направления +#### Q6e. Registry-wide smoke -- smoke всех типов инструментов из registry; -- component-тесты `SchemaToolView`, `SchemaPreview` и schema fields; +**Статус:** выполнен. Smoke расширен до всех 122 production tools из registry: +image→image, image→text, text→image, generator input→image и verdict. FileResult +и malformed/special PNG не дублируются — они уже покрыты Q6b/Q6d. + +- `tools-smoke.spec.ts` расширен image/text-output кейсами, включая все + незакрытые color/geometry инструменты. +- `generators.spec.ts` использует единый data-harness для всех генераторов; + `single-color-png` сохраняет отдельную проверку auto-run/размеров. +- Text-input сценарии используют общий `renderText` helper, включая + `data-uri-to-png`. +- Component-тесты не добавлены: Vitest работает в node без DOM/Testing Library, + а текущий e2e-контракт уже покрывает поведение preview. Выбор test seam или + DOM-окружения перенесён в Q6f. + +#### Q6f. Component coverage, browser matrix и visual gates + +- решить, нужен ли чистый seam для `SchemaFields`/view-model или полноценное + DOM-окружение для `SchemaToolView`/`SchemaPreview`; - browser matrix, mobile viewport, visual snapshots и coverage thresholds. Не применять один одинаковый smoke-сценарий к image-, text-, generator- и diff --git a/docs/testing-strategy.md b/docs/testing-strategy.md index 3058ceb..0c9a3b0 100644 --- a/docs/testing-strategy.md +++ b/docs/testing-strategy.md @@ -62,15 +62,16 @@ browser-контракт: critical CRC и truncated отклоняются, anci ## 5. Helpers и `test.fixme` `web/e2e/helpers/page.ts` владеет общими операциями `openTool`, `uploadImage`, -`resultImage`, `sourceImage`, `verdictStatus`, `textResult`, `errorAlert`, -`emptyState`, `rangeInput`, `downloadResult`, `downloadResultFile` и -`downloadResultBytes`. Специфика должна вызывать эти helpers, а не повторять -raw-селекторы. Инструменты с `result: "files"` проверяются отдельным -ZIP-сценарием: общий image-smoke не применяется к ним. +`renderText`, `resultImage`, `sourceImage`, `verdictStatus`, `textResult`, +`errorAlert`, `emptyState`, `rangeInput`, `downloadResult`, `downloadResultFile` +и `downloadResultBytes`. Специфика должна вызывать эти helpers, а не повторять +raw-селекторы. Harness выбирается по контракту: image — upload + `resultImage`, +text — `renderText` + результат, generator — auto-run без upload, verdict — +`verdictStatus`, files — отдельный ZIP-сценарий. `test.fixme` допускается только для зарегистрированной проблемы в backlog или checklist. В комментарии указывается ссылка на неё. После исправления тест и -ссылка обновляются вместе; в текущем Q6d baseline активных fixme нет. +ссылка обновляются вместе; в текущем Q6e baseline активных fixme нет. ## 6. Проверки diff --git a/web/e2e/generators.spec.ts b/web/e2e/generators.spec.ts index a521034..14f0dfe 100644 --- a/web/e2e/generators.spec.ts +++ b/web/e2e/generators.spec.ts @@ -7,6 +7,29 @@ import { trackErrors, } from "./helpers/page"; +const GENERATOR_IDS = [ + "create-empty-png", + "random-noise-png", + "linear-gradient-png", + "color-spectrum-png", + "random-colors-png", + "draw-grid-png", + "placeholder-png", + "blend-two-png", + "step-colors-png", + "emoji-to-png", + "color-wheel-png", + "complementary-png", + "triadic-png", + "tetradic-png", + "analogous-png", + "monochromatic-png", + "shades-png", + "mix-colors-png", + "sort-colors-png", + "text-to-png", +] as const; + test.describe("generators", () => { test("single-color-png auto-runs and reacts to parameters", async ({ page, @@ -24,10 +47,16 @@ test.describe("generators", () => { expectNoErrors(sink); }); - test("create-empty-png page opens without errors", async ({ page }) => { - const sink = trackErrors(page); - await openTool(page, "create-empty-png"); - await expect(page.getByRole("heading", { level: 1 })).toBeVisible(); - expectNoErrors(sink); - }); + for (const id of GENERATOR_IDS) { + test(`tool ${id} produces a result image`, async ({ page }) => { + const sink = trackErrors(page); + await openTool(page, id); + await expect(resultImage(page)).toBeVisible(); + await expect(page.getByRole("button", { name: "Generate" })).toHaveCount( + 0, + ); + await expectNoErrorAlert(page); + expectNoErrors(sink); + }); + } }); diff --git a/web/e2e/helpers/page.ts b/web/e2e/helpers/page.ts index 4726f1d..ecdd6ad 100644 --- a/web/e2e/helpers/page.ts +++ b/web/e2e/helpers/page.ts @@ -69,6 +69,12 @@ export function rangeInput(page: Page, name: RegExp | string): Locator { return page.getByRole("slider", { name }); } +export async function renderText(page: Page, value: string): Promise { + const input = page.getByRole("textbox", { name: "Text data" }); + await input.fill(value); + await page.getByRole("button", { name: "Render text" }).click(); +} + export async function uploadImage(page: Page, file: SourceFile): Promise { const input = page.locator('input[type="file"]'); await expect(input).toHaveCount(1); diff --git a/web/e2e/text-and-verdicts.spec.ts b/web/e2e/text-and-verdicts.spec.ts index 7c823ce..8a3b6f2 100644 --- a/web/e2e/text-and-verdicts.spec.ts +++ b/web/e2e/text-and-verdicts.spec.ts @@ -10,6 +10,7 @@ import { expectNoErrorAlert, expectNoErrors, openTool, + renderText, resultImage, trackErrors, uploadImage, @@ -30,14 +31,14 @@ for (const [id, input] of [ "rgb-values-to-png", Array.from({ length: 32 }, () => "rgba(255,0,0,255)").join(" "), ], + ["data-uri-to-png", `data:image/png;base64,${tinyBase64}`], ["svg-to-png", svgMarkup], ] as const) { test(`text input → ${id} produces result image`, async ({ page }) => { const sink = trackErrors(page); await openTool(page, id); await expect(async () => { - await page.getByRole("textbox", { name: "Text data" }).fill(input); - await page.getByRole("button", { name: "Render text" }).click(); + await renderText(page, input); await expect(resultImage(page)).toBeVisible(); }).toPass({ timeout: 25_000 }); await expectNoErrorAlert(page); @@ -60,17 +61,13 @@ test("verify-is-png renders different verdicts for valid and invalid input", asy page, }) => { await openTool(page, "verify-is-png"); - const input = page.getByRole("textbox", { name: "Text data" }); - const render = page.getByRole("button", { name: "Render text" }); const verdict = verdictStatus(page); - await input.fill(tinyBase64); - await render.click(); + await renderText(page, tinyBase64); await expect(verdict).toBeVisible(); const validText = await verdict.textContent(); - await input.fill("aGVsbG8="); - await render.click(); + await renderText(page, "aGVsbG8="); await expect(verdict).toBeVisible(); const invalidText = await verdict.textContent(); expect(invalidText).not.toBe(validText); diff --git a/web/e2e/tools-smoke.spec.ts b/web/e2e/tools-smoke.spec.ts index c63720e..ada999c 100644 --- a/web/e2e/tools-smoke.spec.ts +++ b/web/e2e/tools-smoke.spec.ts @@ -20,6 +20,10 @@ const CASES: { id: string; kind: Kind; file?: SourceFile }[] = [ { id: "convert-png-to-webp", kind: "image" }, { id: "png-to-bmp", kind: "image" }, { id: "png-to-base64", kind: "text-out" }, + { id: "png-to-data-uri", kind: "text-out" }, + { id: "png-to-hex", kind: "text-out" }, + { id: "png-to-bytes", kind: "text-out" }, + { id: "png-to-rgb-values", kind: "text-out" }, // ── alpha ────────────────────────────────────────────────── { id: "remove-background-png", kind: "image", file: transparentPng }, { id: "remove-color-from-png", kind: "image", file: transparentPng }, @@ -53,9 +57,25 @@ const CASES: { id: string; kind: Kind; file?: SourceFile }[] = [ { id: "auto-contrast-png", kind: "image" }, { id: "png-to-hsl", kind: "image" }, { id: "png-to-cmyk", kind: "image" }, + { id: "gamma-png", kind: "image" }, + { id: "temperature-png", kind: "image" }, + { id: "custom-palette-png", kind: "image" }, + { id: "adjust-brightness-contrast-png", kind: "image" }, + { id: "change-png-opacity", kind: "image" }, + { id: "change-png-hue", kind: "image" }, + { id: "swap-channels-png", kind: "image" }, + { id: "black-and-white-png", kind: "image" }, + { id: "posterize-png", kind: "image" }, + { id: "png-to-hsv", kind: "image" }, + { id: "png-to-hsi", kind: "image" }, + { id: "png-to-ycbcr", kind: "image" }, + { id: "png-to-lab", kind: "image" }, // ── geometry ─────────────────────────────────────────────── { id: "flip-png", kind: "image" }, { id: "rotate-png", kind: "image" }, + { id: "rotate-free-png", kind: "image" }, + { id: "fit-on-background-png", kind: "image" }, + { id: "add-padding-png", kind: "image" }, { id: "add-border-png", kind: "image" }, { id: "tile-png", kind: "image" }, { id: "change-canvas-size-png", kind: "image" },