mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
test: for tools - generators, text and verdicts, smoke
This commit is contained in:
+3
-3
@@ -51,9 +51,9 @@
|
|||||||
только если темы нет в существующем документе; иначе обновляется источник
|
только если темы нет в существующем документе; иначе обновляется источник
|
||||||
истины.
|
истины.
|
||||||
|
|
||||||
## Текущий baseline Q6d
|
## Текущий baseline Q6e
|
||||||
|
|
||||||
На момент фиксации Q6d:
|
На момент фиксации Q6e:
|
||||||
|
|
||||||
- production registry содержит 122 инструмента;
|
- production registry содержит 122 инструмента;
|
||||||
- `pnpm --dir web check` проходит без ошибок и предупреждений;
|
- `pnpm --dir web check` проходит без ошибок и предупреждений;
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
- `pnpm --dir web test:rules` — 4 test files / 74 tests;
|
- `pnpm --dir web test:rules` — 4 test files / 74 tests;
|
||||||
- `pnpm --dir web lint:all` — 0 errors и 0 i18n warnings; остаются 4 unused
|
- `pnpm --dir web lint:all` — 0 errors и 0 i18n warnings; остаются 4 unused
|
||||||
design tokens от token audit;
|
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`.
|
Baseline и следующий шаг поддерживаются в `docs/plan-codebase-quality.md`.
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -107,8 +107,8 @@
|
|||||||
|
|
||||||
## Качество production UI
|
## Качество production UI
|
||||||
|
|
||||||
Q5–Q6d закрывают известные preview-проблемы и baseline special fixtures;
|
Q5–Q6e закрывают известные preview-проблемы, special fixtures и registry-wide
|
||||||
активных `test.fixme` в e2e больше нет.
|
smoke; активных `test.fixme` в e2e больше нет.
|
||||||
|
|
||||||
- [x] Генераторы auto-run без кнопки Generate — `web/e2e/generators.spec.ts`.
|
- [x] Генераторы auto-run без кнопки Generate — `web/e2e/generators.spec.ts`.
|
||||||
- [x] `resize-png` и `crop-png` используют размеры текущего source через общий
|
- [x] `resize-png` и `crop-png` используют размеры текущего source через общий
|
||||||
@@ -119,6 +119,9 @@ Q5–Q6d закрывают известные preview-проблемы и basel
|
|||||||
обновляет alert — `web/e2e/i18n.spec.ts`.
|
обновляет alert — `web/e2e/i18n.spec.ts`.
|
||||||
- [x] Malformed/special PNG fixtures: CRC, truncated, palette и 16-bit проходят
|
- [x] Malformed/special PNG fixtures: CRC, truncated, palette и 16-bit проходят
|
||||||
через upload без краша — `web/e2e/png-fixtures.spec.ts`.
|
через 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` пока показывается
|
- [ ] Ошибка декодирования изображения из `createImageBitmap` пока показывается
|
||||||
сырым сообщением браузера; нужен `ToolError`-маппер и i18n-ключ. Это
|
сырым сообщением браузера; нужен `ToolError`-маппер и i18n-ключ. Это
|
||||||
продуктовая задача, не Q6d.
|
продуктовая задача, не Q6d.
|
||||||
|
|||||||
@@ -96,10 +96,10 @@
|
|||||||
корректный (не «кракозябры»).
|
корректный (не «кракозябры»).
|
||||||
- [ ] remove-background на сложной полупрозрачности — края не «звенят».
|
- [ ] remove-background на сложной полупрозрачности — края не «звенят».
|
||||||
|
|
||||||
## G. Закрытые Q5/Q6b/Q6c/Q6d-проблемы
|
## G. Закрытые Q5/Q6b/Q6c/Q6d/Q6e-проблемы
|
||||||
|
|
||||||
> Q5/Q6b/Q6c/Q6d-проблемы закрыты автоматическими тестами; для ручной проверки
|
> Q5/Q6b/Q6c/Q6d/Q6e-проблемы закрыты автоматическими тестами; для ручной
|
||||||
> остаются субъективные сценарии разделов A–F.
|
> проверки остаются субъективные сценарии разделов A–F.
|
||||||
|
|
||||||
- [x] **Генераторы:** auto-run по дефолтам, результат и изменение параметра без
|
- [x] **Генераторы:** auto-run по дефолтам, результат и изменение параметра без
|
||||||
кнопки Generate. `web/e2e/generators.spec.ts`.
|
кнопки Generate. `web/e2e/generators.spec.ts`.
|
||||||
@@ -117,6 +117,9 @@
|
|||||||
сигнатуры, quality 10/90 меняет размер файла; `web/e2e/pipeline.spec.ts`.
|
сигнатуры, quality 10/90 меняет размер файла; `web/e2e/pipeline.spec.ts`.
|
||||||
- [x] **Malformed/special PNG:** CRC, truncated, palette и 16-bit fixtures
|
- [x] **Malformed/special PNG:** CRC, truncated, palette и 16-bit fixtures
|
||||||
проходят через upload без краша; `web/e2e/png-fixtures.spec.ts`.
|
проходят через 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`.
|
||||||
|
|
||||||
## Как долго
|
## Как долго
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# План: линтеры, тесты и качество кодовой базы
|
# План: линтеры, тесты и качество кодовой базы
|
||||||
|
|
||||||
> Статус: **draft — Q0–Q5 и Q6a–Q6d готовы к ревью; Q6e — следующий этап**.
|
> Статус: **draft — Q0–Q5 и Q6a–Q6e готовы к ревью; Q6f — следующий этап**.
|
||||||
>
|
>
|
||||||
> Источник: `docs/backlog.md`, аудит конфигурации `web/`, e2e-тестов и
|
> Источник: `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; это
|
- Сырое сообщение браузера при decode-ошибке не локализуется в Q6d; это
|
||||||
отдельная задача в backlog.
|
отдельная задача в backlog.
|
||||||
|
|
||||||
#### Q6e–Q6f. Оставшиеся направления
|
#### Q6e. Registry-wide smoke
|
||||||
|
|
||||||
- smoke всех типов инструментов из registry;
|
**Статус:** выполнен. Smoke расширен до всех 122 production tools из registry:
|
||||||
- component-тесты `SchemaToolView`, `SchemaPreview` и schema fields;
|
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.
|
- browser matrix, mobile viewport, visual snapshots и coverage thresholds.
|
||||||
|
|
||||||
Не применять один одинаковый smoke-сценарий к image-, text-, generator- и
|
Не применять один одинаковый smoke-сценарий к image-, text-, generator- и
|
||||||
|
|||||||
@@ -62,15 +62,16 @@ browser-контракт: critical CRC и truncated отклоняются, anci
|
|||||||
## 5. Helpers и `test.fixme`
|
## 5. Helpers и `test.fixme`
|
||||||
|
|
||||||
`web/e2e/helpers/page.ts` владеет общими операциями `openTool`, `uploadImage`,
|
`web/e2e/helpers/page.ts` владеет общими операциями `openTool`, `uploadImage`,
|
||||||
`resultImage`, `sourceImage`, `verdictStatus`, `textResult`, `errorAlert`,
|
`renderText`, `resultImage`, `sourceImage`, `verdictStatus`, `textResult`,
|
||||||
`emptyState`, `rangeInput`, `downloadResult`, `downloadResultFile` и
|
`errorAlert`, `emptyState`, `rangeInput`, `downloadResult`, `downloadResultFile`
|
||||||
`downloadResultBytes`. Специфика должна вызывать эти helpers, а не повторять
|
и `downloadResultBytes`. Специфика должна вызывать эти helpers, а не повторять
|
||||||
raw-селекторы. Инструменты с `result: "files"` проверяются отдельным
|
raw-селекторы. Harness выбирается по контракту: image — upload + `resultImage`,
|
||||||
ZIP-сценарием: общий image-smoke не применяется к ним.
|
text — `renderText` + результат, generator — auto-run без upload, verdict —
|
||||||
|
`verdictStatus`, files — отдельный ZIP-сценарий.
|
||||||
|
|
||||||
`test.fixme` допускается только для зарегистрированной проблемы в backlog или
|
`test.fixme` допускается только для зарегистрированной проблемы в backlog или
|
||||||
checklist. В комментарии указывается ссылка на неё. После исправления тест и
|
checklist. В комментарии указывается ссылка на неё. После исправления тест и
|
||||||
ссылка обновляются вместе; в текущем Q6d baseline активных fixme нет.
|
ссылка обновляются вместе; в текущем Q6e baseline активных fixme нет.
|
||||||
|
|
||||||
## 6. Проверки
|
## 6. Проверки
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,29 @@ import {
|
|||||||
trackErrors,
|
trackErrors,
|
||||||
} from "./helpers/page";
|
} 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.describe("generators", () => {
|
||||||
test("single-color-png auto-runs and reacts to parameters", async ({
|
test("single-color-png auto-runs and reacts to parameters", async ({
|
||||||
page,
|
page,
|
||||||
@@ -24,10 +47,16 @@ test.describe("generators", () => {
|
|||||||
expectNoErrors(sink);
|
expectNoErrors(sink);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create-empty-png page opens without errors", async ({ page }) => {
|
for (const id of GENERATOR_IDS) {
|
||||||
|
test(`tool ${id} produces a result image`, async ({ page }) => {
|
||||||
const sink = trackErrors(page);
|
const sink = trackErrors(page);
|
||||||
await openTool(page, "create-empty-png");
|
await openTool(page, id);
|
||||||
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
|
await expect(resultImage(page)).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Generate" })).toHaveCount(
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
await expectNoErrorAlert(page);
|
||||||
expectNoErrors(sink);
|
expectNoErrors(sink);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -69,6 +69,12 @@ export function rangeInput(page: Page, name: RegExp | string): Locator {
|
|||||||
return page.getByRole("slider", { name });
|
return page.getByRole("slider", { name });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function renderText(page: Page, value: string): Promise<void> {
|
||||||
|
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<void> {
|
export async function uploadImage(page: Page, file: SourceFile): Promise<void> {
|
||||||
const input = page.locator('input[type="file"]');
|
const input = page.locator('input[type="file"]');
|
||||||
await expect(input).toHaveCount(1);
|
await expect(input).toHaveCount(1);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
expectNoErrorAlert,
|
expectNoErrorAlert,
|
||||||
expectNoErrors,
|
expectNoErrors,
|
||||||
openTool,
|
openTool,
|
||||||
|
renderText,
|
||||||
resultImage,
|
resultImage,
|
||||||
trackErrors,
|
trackErrors,
|
||||||
uploadImage,
|
uploadImage,
|
||||||
@@ -30,14 +31,14 @@ for (const [id, input] of [
|
|||||||
"rgb-values-to-png",
|
"rgb-values-to-png",
|
||||||
Array.from({ length: 32 }, () => "rgba(255,0,0,255)").join(" "),
|
Array.from({ length: 32 }, () => "rgba(255,0,0,255)").join(" "),
|
||||||
],
|
],
|
||||||
|
["data-uri-to-png", `data:image/png;base64,${tinyBase64}`],
|
||||||
["svg-to-png", svgMarkup],
|
["svg-to-png", svgMarkup],
|
||||||
] as const) {
|
] as const) {
|
||||||
test(`text input → ${id} produces result image`, async ({ page }) => {
|
test(`text input → ${id} produces result image`, async ({ page }) => {
|
||||||
const sink = trackErrors(page);
|
const sink = trackErrors(page);
|
||||||
await openTool(page, id);
|
await openTool(page, id);
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await page.getByRole("textbox", { name: "Text data" }).fill(input);
|
await renderText(page, input);
|
||||||
await page.getByRole("button", { name: "Render text" }).click();
|
|
||||||
await expect(resultImage(page)).toBeVisible();
|
await expect(resultImage(page)).toBeVisible();
|
||||||
}).toPass({ timeout: 25_000 });
|
}).toPass({ timeout: 25_000 });
|
||||||
await expectNoErrorAlert(page);
|
await expectNoErrorAlert(page);
|
||||||
@@ -60,17 +61,13 @@ test("verify-is-png renders different verdicts for valid and invalid input", asy
|
|||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
await openTool(page, "verify-is-png");
|
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);
|
const verdict = verdictStatus(page);
|
||||||
|
|
||||||
await input.fill(tinyBase64);
|
await renderText(page, tinyBase64);
|
||||||
await render.click();
|
|
||||||
await expect(verdict).toBeVisible();
|
await expect(verdict).toBeVisible();
|
||||||
const validText = await verdict.textContent();
|
const validText = await verdict.textContent();
|
||||||
|
|
||||||
await input.fill("aGVsbG8=");
|
await renderText(page, "aGVsbG8=");
|
||||||
await render.click();
|
|
||||||
await expect(verdict).toBeVisible();
|
await expect(verdict).toBeVisible();
|
||||||
const invalidText = await verdict.textContent();
|
const invalidText = await verdict.textContent();
|
||||||
expect(invalidText).not.toBe(validText);
|
expect(invalidText).not.toBe(validText);
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ const CASES: { id: string; kind: Kind; file?: SourceFile }[] = [
|
|||||||
{ id: "convert-png-to-webp", kind: "image" },
|
{ id: "convert-png-to-webp", kind: "image" },
|
||||||
{ id: "png-to-bmp", kind: "image" },
|
{ id: "png-to-bmp", kind: "image" },
|
||||||
{ id: "png-to-base64", kind: "text-out" },
|
{ 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 ──────────────────────────────────────────────────
|
// ── alpha ──────────────────────────────────────────────────
|
||||||
{ id: "remove-background-png", kind: "image", file: transparentPng },
|
{ id: "remove-background-png", kind: "image", file: transparentPng },
|
||||||
{ id: "remove-color-from-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: "auto-contrast-png", kind: "image" },
|
||||||
{ id: "png-to-hsl", kind: "image" },
|
{ id: "png-to-hsl", kind: "image" },
|
||||||
{ id: "png-to-cmyk", 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 ───────────────────────────────────────────────
|
// ── geometry ───────────────────────────────────────────────
|
||||||
{ id: "flip-png", kind: "image" },
|
{ id: "flip-png", kind: "image" },
|
||||||
{ id: "rotate-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: "add-border-png", kind: "image" },
|
||||||
{ id: "tile-png", kind: "image" },
|
{ id: "tile-png", kind: "image" },
|
||||||
{ id: "change-canvas-size-png", kind: "image" },
|
{ id: "change-canvas-size-png", kind: "image" },
|
||||||
|
|||||||
Reference in New Issue
Block a user