mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
chore: clear lint rules warns: comments, tests
This commit is contained in:
@@ -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],
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user