chore: clear lint rules warns: comments, tests

This commit is contained in:
2026-10-08 12:55:34 +05:00
parent 0e8c2478a1
commit 7361a82c18
93 changed files with 899 additions and 940 deletions
+2 -2
View File
@@ -59,7 +59,7 @@ function findChunk(png: Buffer, type: string): number {
throw new Error(`PNG chunk ${type} not found`);
}
/** Строит валидный RGBA PNG в рантайме (8-bit, фильтр type 0, deflate/zlib). */
/** Builds a valid RGBA PNG at runtime (8-bit, filter type 0, deflate/zlib). */
export function makePng(
width: number,
height: number,
@@ -279,7 +279,7 @@ export const svgMarkup =
const PIXEL_BYTES = (r: number, g: number, b: number, a: number): string =>
`${r} ${g} ${b} ${a}`;
/** Строка для bytes-to-png / rgb-values-to-png, ровно 32 пикселя в ширину по умолчанию. */
/** Input row for bytes-to-png / rgb-values-to-png, 32 pixels per line by default. */
export function pixelRow(
count: number,
rgba: [number, number, number, number],
+8
View File
@@ -0,0 +1,8 @@
import { ru } from "../../src/lib/i18n/ru";
/** RU page title from the dictionary; RU text is never hardcoded in specs. */
export function ruPageTitle(slug: string): string {
const title = ru.pages?.[slug]?.title;
if (!title) throw new Error(`ru dictionary has no pages.${slug}.title`);
return title;
}
+4 -4
View File
@@ -76,8 +76,8 @@ export async function renderText(page: Page, value: string): Promise<void> {
}
export async function uploadImage(page: Page, file: SourceFile): Promise<void> {
// На странице может быть два input[type=file]: дропзона + кнопка Open image;
// канонический — первый (кнопка в шапке панели).
// The page can have two input[type=file]: dropzone + Open image button;
// the canonical one is the first (the button in the panel header).
const input = page.locator('input[type="file"]').first();
await expect(input).toBeAttached();
await input.setInputFiles({
@@ -117,7 +117,7 @@ export async function uploadViaDrop(
}
export async function downloadResultFile(page: Page): Promise<Download> {
// Split-кнопка: основная часть "Download" (image) или "Download ZIP" (files).
// Split button: the main part is "Download" (image) or "Download ZIP" (files).
const button = page.getByRole("button", { name: /^Download( ZIP)?$/ });
await expect(button).toBeVisible();
const [download] = await Promise.all([
@@ -156,5 +156,5 @@ export async function expectNoErrorAlert(page: Page): Promise<void> {
}
export async function expectNoErrors(sink: ErrorSink): Promise<void> {
expect(sink.errors, "нет console/page errors").toEqual([]);
expect(sink.errors, "no console/page errors").toEqual([]);
}
+10 -7
View File
@@ -1,23 +1,26 @@
import { expect, test } from "playwright/test";
import { pixelRow } from "./helpers/fixtures";
import { ruPageTitle } from "./helpers/i18n";
import { openTool, toolLink, useEnglish } from "./helpers/page";
test("поиск каталога находит по названию из другой локали", async ({
const flipTitleRu = ruPageTitle("flip-png");
test("catalog search finds a tool by a title from another locale", async ({
page,
}) => {
await useEnglish(page);
await page.goto("/list-tools");
const input = page.getByRole("textbox");
const flipCard = toolLink(page, "flip-png");
await input.fill("отразить");
await input.fill(flipTitleRu.split(" ")[0].toLowerCase());
await expect(flipCard).toContainText("Flip PNG");
await page.getByRole("button", { name: "RU", exact: true }).click();
await expect(flipCard).toContainText("Отразить PNG");
await expect(flipCard).toContainText(flipTitleRu);
await input.fill("flip");
await expect(flipCard).toContainText("Отразить PNG");
await expect(flipCard).toContainText(flipTitleRu);
});
test("RU/EN: переключение переводит заголовок инструмента", async ({
test("RU/EN: switching the locale translates the tool heading", async ({
page,
}) => {
await openTool(page, "resize-png");
@@ -25,13 +28,13 @@ test("RU/EN: переключение переводит заголовок ин
const enBtn = page.getByRole("button", { name: "EN", exact: true });
const heading = page.getByRole("heading", { level: 1 });
await ruBtn.click();
await expect(heading).toHaveText("Изменить размер PNG");
await expect(heading).toHaveText(ruPageTitle("resize-png"));
await expect(ruBtn).toHaveAttribute("aria-pressed", "true");
await enBtn.click();
await expect(heading).toHaveText("Resize PNG");
});
test("ошибка run'а локализуется и переводится при смене языка", async ({
test("run error is localized and translated on language switch", async ({
page,
}) => {
await openTool(page, "bytes-to-png");
+3 -3
View File
@@ -56,8 +56,8 @@ const decodeContracts: readonly DecodeContract[] = [
{ file: sixteenBitPng, rejectedIn: [] },
];
// На этих файлах createImageBitmap в webkit не завершается: ни результата, ни
// ошибки — контракт «accept или reject» проверить нельзя.
// On these files createImageBitmap in webkit never settles: no result and no
// error, so the "accept or reject" contract cannot be verified.
const webkitHangs = new Set([
"truncated-header.png",
"crc-bad-ancillary.png",
@@ -72,7 +72,7 @@ test.describe("PNG special fixtures", () => {
}) => {
test.fixme(
browserName === "webkit" && webkitHangs.has(file.name),
"webkit createImageBitmap зависает на этом файле",
"webkit createImageBitmap hangs on this file",
);
if (rejectedIn.includes(browserName)) {
await expectRejected(page, file);
+1 -1
View File
@@ -34,7 +34,7 @@ for (const [id, input] of [
["data-uri-to-png", `data:image/png;base64,${tinyBase64}`],
["svg-to-png", svgMarkup],
] as const) {
test(`text input → ${id} produces result image`, async ({ page }) => {
test(`text input -> ${id} produces result image`, async ({ page }) => {
const sink = trackErrors(page);
await openTool(page, id);
await expect(async () => {
+7 -7
View File
@@ -46,7 +46,7 @@ test("resize-png keeps user-set size when another image is uploaded", async ({
await uploadImage(page, opaquePng);
const width = page.getByRole("spinbutton", { name: "Width" });
await width.fill("25");
// keepAspect включён по умолчанию: высота пересчитана из 64×48 → 19
// keepAspect is on by default: height is recomputed from 64×48 -> 19
await expect(page.getByRole("spinbutton", { name: "Height" })).toHaveValue(
"19",
);
@@ -157,7 +157,7 @@ test("download format selector: JPG warns about alpha and produces JPEG", async
await expect(
page.getByRole("status").filter({ hasText: /transparency/ }),
).toBeVisible();
// у JPG в dropdown есть настройка качества
// JPG has a quality setting in the dropdown
await rangeInput(page, "Quality").fill("10");
const { name, bytes } = await downloadResultBytes(page);
expect(name).toBe("flip-png.jpg");
@@ -238,8 +238,8 @@ test("blur: changing slider updates result image", async ({ page }) => {
});
test("runs without Web Worker (fallback)", async ({ browser }, testInfo) => {
// newContext() не наследует use.baseURL из конфига — пробрасываем явно,
// иначе относительный goto() в helpers не резолвится.
// newContext() does not inherit use.baseURL from the config - pass it
// explicitly, otherwise the relative goto() in helpers does not resolve.
const context = await browser.newContext({
baseURL: testInfo.project.use.baseURL,
});
@@ -278,7 +278,7 @@ test("chain: add step extends pipeline without reload and persists", async ({
});
await expect(stepTwo).toBeVisible();
await expect(resultImage(page)).toBeVisible();
// цепочка переживает перезагрузку (localStorage)
// the chain survives a reload (localStorage)
await page.reload();
await expect(stepTwo).toBeVisible();
await expectNoErrorAlert(page);
@@ -295,8 +295,8 @@ test("chain: remove step returns to a single tool", async ({ page }) => {
exact: true,
});
await expect(stepTwo).toBeVisible();
// Клик по remove именно в карточке Grayscale: порядок шагов зависит от
// тайминга рендера и на разных движках может отличаться.
// Click remove in the Grayscale card specifically: step order depends on
// render timing and can differ between engines.
const card = page.getByRole("article").filter({ hasText: "Grayscale PNG" });
await card.getByLabel("Remove step").click();
await expect(stepTwo).toHaveCount(0);
+10 -10
View File
@@ -15,7 +15,7 @@ import {
type Kind = "image" | "text-out" | "verdict";
const CASES: { id: string; kind: Kind; file?: SourceFile }[] = [
// ── convert ────────────────────────────────────────────────
// -- convert
{ id: "convert-png-to-jpg", kind: "image" },
{ id: "convert-png-to-webp", kind: "image" },
{ id: "png-to-bmp", kind: "image" },
@@ -24,7 +24,7 @@ const CASES: { id: string; kind: Kind; file?: SourceFile }[] = [
{ 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-color-from-png", kind: "image", file: transparentPng },
{ id: "round-corners-png", kind: "image", file: transparentPng },
@@ -45,7 +45,7 @@ const CASES: { id: string; kind: Kind; file?: SourceFile }[] = [
{ id: "remove-alpha-channel-png", kind: "image", file: transparentPng },
{ id: "square-mask-png", kind: "image", file: transparentPng },
{ id: "star-mask-png", kind: "image", file: transparentPng },
// ── color ──────────────────────────────────────────────────
// -- color
{ id: "grayscale-png", kind: "image" },
{ id: "invert-colors-png", kind: "image" },
{ id: "sepia-png", kind: "image" },
@@ -70,7 +70,7 @@ const CASES: { id: string; kind: Kind; file?: SourceFile }[] = [
{ 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: "rotate-png", kind: "image" },
{ id: "rotate-free-png", kind: "image" },
@@ -87,7 +87,7 @@ const CASES: { id: string; kind: Kind; file?: SourceFile }[] = [
{ id: "change-aspect-ratio-png", kind: "image" },
{ id: "symmetric-copy-png", kind: "image" },
{ id: "shift-png", kind: "image" },
// ── filters ────────────────────────────────────────────────
// -- filters
{ id: "blur-png", kind: "image" },
{ id: "sharpen-png", kind: "image" },
{ id: "pixelate-png", kind: "image" },
@@ -96,11 +96,11 @@ const CASES: { id: string; kind: Kind; file?: SourceFile }[] = [
{ id: "silhouette-png", kind: "image" },
{ id: "randomize-pixels-png", kind: "image" },
{ id: "jpeg-artifacts-png", kind: "image" },
// ── text ───────────────────────────────────────────────────
// -- text
{ id: "add-text-png", kind: "image" },
{ id: "date-stamp-png", kind: "image" },
{ id: "watermark-tile-png", kind: "image" },
// ── analyze (image output) ─────────────────────────────────
// -- analyze (image output)
{ id: "extract-color-from-png", kind: "image", file: transparentPng },
{ id: "show-transparent-png", kind: "image", file: transparentPng },
{ id: "light-pixel-mask-png", kind: "image", file: transparentPng },
@@ -109,7 +109,7 @@ const CASES: { id: string; kind: Kind; file?: SourceFile }[] = [
{ id: "show-grayscale-pixels-png", kind: "image", file: transparentPng },
{ id: "show-color-pixels-png", kind: "image", file: transparentPng },
{ id: "dark-pixel-mask-png", kind: "image", file: transparentPng },
// ── analyze (verdict output) ───────────────────────────────
// -- analyze (verdict output)
{ id: "png-is-transparent", kind: "verdict" },
{ id: "png-is-grayscale", kind: "verdict" },
{ id: "png-orientation", kind: "verdict" },
@@ -132,8 +132,8 @@ test.describe("smoke: tools produce output without errors", () => {
}
};
// toPass: устойчивость к hydration-рейсу (ввод до гидрации SvelteKit
// может не обработаться с первого раза).
// toPass: tolerates the hydration race (input before SvelteKit
// hydration may not be processed on the first attempt).
await expect(async () => {
await openTool(page, id);
await uploadImage(page, fixture ?? opaquePng);