mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 13:36:36 +00:00
test: add new split tool tests
This commit is contained in:
@@ -32,8 +32,8 @@ ZIP-архивом.
|
|||||||
`zipSync`.
|
`zipSync`.
|
||||||
5. **`core/errors.ts`:** ключ `errors.tooManyParts` (страховочный лимит
|
5. **`core/errors.ts`:** ключ `errors.tooManyParts` (страховочный лимит
|
||||||
cols·rows ≤ 1000; при максимуме 6×6 недостижим).
|
cols·rows ≤ 1000; при максимуме 6×6 недостижим).
|
||||||
6. **`registry/geometry.ts`:** схема (`columns`, `rows`, дефолт 2×2, min 1,
|
6. **`registry/geometry.ts`:** схема (`columns`, `rows`, дефолт 2×2, min 1, max
|
||||||
max 6 — максимум 36 частей) + entry `split-into-parts-png` в `geometryEntries`.
|
6 — максимум 36 частей) + entry `split-into-parts-png` в `geometryEntries`.
|
||||||
7. **Executor:** сериализация/десериализация `FileResult` в `executor.worker.ts`
|
7. **Executor:** сериализация/десериализация `FileResult` в `executor.worker.ts`
|
||||||
и тип ветки в `executor.ts`.
|
и тип ветки в `executor.ts`.
|
||||||
8. **UI:** `SchemaToolView.svelte` (`fileResult`, Download → zip),
|
8. **UI:** `SchemaToolView.svelte` (`fileResult`, Download → zip),
|
||||||
@@ -66,8 +66,7 @@ ZIP-архивом.
|
|||||||
|
|
||||||
## Нейминг файлов в ZIP
|
## Нейминг файлов в ZIP
|
||||||
|
|
||||||
`part-01-01.png` … `part-<row>-<col>.png` (row, col нумеруются с 1, ведущий ноль
|
`part-<row>-<col>.png` (row, col нумеруются с 1, при 2×2 — `part-1-1.png`).
|
||||||
для выравнивания по числу строк/столбцов).
|
|
||||||
|
|
||||||
## Верификация
|
## Верификация
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import {
|
|||||||
flip,
|
flip,
|
||||||
resize,
|
resize,
|
||||||
rotate90,
|
rotate90,
|
||||||
|
splitToParts,
|
||||||
tile,
|
tile,
|
||||||
} from "./geometry";
|
} from "./geometry";
|
||||||
import { makeImage } from "./test-helpers";
|
import { expectImageEqual, makeImage } from "./test-helpers";
|
||||||
|
import type { PixelImage } from "./types";
|
||||||
|
|
||||||
const square = () =>
|
const square = () =>
|
||||||
makeImage(2, 2, [
|
makeImage(2, 2, [
|
||||||
@@ -210,3 +212,113 @@ describe("resize", () => {
|
|||||||
expect(() => resize(img, 10.5, 10)).toThrow(/errors\.sizeInt/);
|
expect(() => resize(img, 10.5, 10)).toThrow(/errors\.sizeInt/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("splitToParts", () => {
|
||||||
|
function grid(w: number, h: number): PixelImage {
|
||||||
|
const pixels: number[][] = [];
|
||||||
|
for (let y = 0; y < h; y++) {
|
||||||
|
for (let x = 0; x < w; x++) {
|
||||||
|
const v = y * w + x + 1;
|
||||||
|
pixels.push([v, v, v, 255]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return makeImage(w, h, pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
it("ровное деление 4x4 на 2x2 даёт четыре части 2x2 в row-major порядке", () => {
|
||||||
|
const parts = splitToParts(grid(4, 4), 2, 2);
|
||||||
|
expect(parts).toHaveLength(4);
|
||||||
|
for (const part of parts) {
|
||||||
|
expect(part.width).toBe(2);
|
||||||
|
expect(part.height).toBe(2);
|
||||||
|
}
|
||||||
|
expectImageEqual(parts[0], [
|
||||||
|
[1, 1, 1, 255],
|
||||||
|
[2, 2, 2, 255],
|
||||||
|
[5, 5, 5, 255],
|
||||||
|
[6, 6, 6, 255],
|
||||||
|
]);
|
||||||
|
expectImageEqual(parts[1], [
|
||||||
|
[3, 3, 3, 255],
|
||||||
|
[4, 4, 4, 255],
|
||||||
|
[7, 7, 7, 255],
|
||||||
|
[8, 8, 8, 255],
|
||||||
|
]);
|
||||||
|
expectImageEqual(parts[2], [
|
||||||
|
[9, 9, 9, 255],
|
||||||
|
[10, 10, 10, 255],
|
||||||
|
[13, 13, 13, 255],
|
||||||
|
[14, 14, 14, 255],
|
||||||
|
]);
|
||||||
|
expectImageEqual(parts[3], [
|
||||||
|
[11, 11, 11, 255],
|
||||||
|
[12, 12, 12, 255],
|
||||||
|
[15, 15, 15, 255],
|
||||||
|
[16, 16, 16, 255],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("неделимый размер: канвас дополняется прозрачным, части одинаковые", () => {
|
||||||
|
const parts = splitToParts(grid(5, 3), 2, 2);
|
||||||
|
expect(parts).toHaveLength(4);
|
||||||
|
for (const part of parts) {
|
||||||
|
expect(part.width).toBe(3);
|
||||||
|
expect(part.height).toBe(2);
|
||||||
|
}
|
||||||
|
expectImageEqual(parts[0], [
|
||||||
|
[1, 1, 1, 255],
|
||||||
|
[2, 2, 2, 255],
|
||||||
|
[3, 3, 3, 255],
|
||||||
|
[6, 6, 6, 255],
|
||||||
|
[7, 7, 7, 255],
|
||||||
|
[8, 8, 8, 255],
|
||||||
|
]);
|
||||||
|
expectImageEqual(parts[1], [
|
||||||
|
[4, 4, 4, 255],
|
||||||
|
[5, 5, 5, 255],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[9, 9, 9, 255],
|
||||||
|
[10, 10, 10, 255],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
]);
|
||||||
|
expectImageEqual(parts[2], [
|
||||||
|
[11, 11, 11, 255],
|
||||||
|
[12, 12, 12, 255],
|
||||||
|
[13, 13, 13, 255],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
]);
|
||||||
|
expectImageEqual(parts[3], [
|
||||||
|
[14, 14, 14, 255],
|
||||||
|
[15, 15, 15, 255],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("одна колонка или строка — полосы без паддинга", () => {
|
||||||
|
const parts = splitToParts(grid(3, 4), 1, 2);
|
||||||
|
expect(parts).toHaveLength(2);
|
||||||
|
expect(parts[0].width).toBe(3);
|
||||||
|
expect(parts[0].height).toBe(2);
|
||||||
|
expect(parts[1].height).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("части в row-major порядке: последний кусок содержит правый нижний пиксель", () => {
|
||||||
|
const img = grid(5, 3);
|
||||||
|
const parts = splitToParts(img, 2, 2);
|
||||||
|
expect(parts[parts.length - 1].data[0]).toBe(14);
|
||||||
|
expect(parts[parts.length - 1].data[4]).toBe(15);
|
||||||
|
expect(parts[parts.length - 1].data[8]).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("дробные и нулевые значения колонок/строк нормализуются", () => {
|
||||||
|
const parts = splitToParts(grid(6, 6), 2.9, 0);
|
||||||
|
expect(parts).toHaveLength(2);
|
||||||
|
expect(parts[0].width).toBe(3);
|
||||||
|
expect(parts[0].height).toBe(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { TOOLS } from ".";
|
|||||||
import { PREVIEW_GROUPS } from "../catalog";
|
import { PREVIEW_GROUPS } from "../catalog";
|
||||||
import type { PixelImage } from "../core/types";
|
import type { PixelImage } from "../core/types";
|
||||||
import { defaultSchemaParams, sanitizeSchemaParams } from "../registry-schema";
|
import { defaultSchemaParams, sanitizeSchemaParams } from "../registry-schema";
|
||||||
import type { ToolResult } from "./types";
|
import type { FileResult, ToolResult } from "./types";
|
||||||
|
|
||||||
function asImage(result: ToolResult): PixelImage {
|
function asImage(result: ToolResult): PixelImage {
|
||||||
if (typeof result === "string" || "files" in result) {
|
if (typeof result === "string" || "files" in result) {
|
||||||
@@ -517,6 +517,90 @@ describe("registry-new (переведённые инструменты)", () =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("split-into-parts-png", () => {
|
||||||
|
it("объявлен с result=files и дефолтом 2x2", () => {
|
||||||
|
const tool = TOOLS.find((t) => t.id === "split-into-parts-png")!;
|
||||||
|
expect(tool).toBeDefined();
|
||||||
|
expect(tool.result).toBe("files");
|
||||||
|
const defaults = sanitizeSchemaParams(tool.schema, {});
|
||||||
|
expect(defaults.columns).toBe(2);
|
||||||
|
expect(defaults.rows).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("на ровном размере даёт cols*rows частей одинакового размера", async () => {
|
||||||
|
const tool = TOOLS.find((t) => t.id === "split-into-parts-png")!;
|
||||||
|
const files = asFiles(
|
||||||
|
await tool.run({
|
||||||
|
source: solid(100, 100),
|
||||||
|
params: sanitizeSchemaParams(tool.schema, {
|
||||||
|
columns: 2,
|
||||||
|
rows: 2,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
).files;
|
||||||
|
expect(files).toHaveLength(4);
|
||||||
|
for (const file of files) {
|
||||||
|
expect(file.image.width).toBe(50);
|
||||||
|
expect(file.image.height).toBe(50);
|
||||||
|
}
|
||||||
|
expect(files.map((f) => f.name)).toEqual([
|
||||||
|
"part-1-1.png",
|
||||||
|
"part-1-2.png",
|
||||||
|
"part-2-1.png",
|
||||||
|
"part-2-2.png",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("на неделимом размере части строго равные (канвас дополняется)", async () => {
|
||||||
|
const tool = TOOLS.find((t) => t.id === "split-into-parts-png")!;
|
||||||
|
const files = asFiles(
|
||||||
|
await tool.run({
|
||||||
|
source: solid(101, 77),
|
||||||
|
params: sanitizeSchemaParams(tool.schema, {
|
||||||
|
columns: 3,
|
||||||
|
rows: 2,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
).files;
|
||||||
|
expect(files).toHaveLength(6);
|
||||||
|
for (const file of files) {
|
||||||
|
expect(file.image.width).toBe(34);
|
||||||
|
expect(file.image.height).toBe(39);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("работает максимум 6x6 = 36 частей", async () => {
|
||||||
|
const tool = TOOLS.find((t) => t.id === "split-into-parts-png")!;
|
||||||
|
const files = asFiles(
|
||||||
|
await tool.run({
|
||||||
|
source: solid(120, 120),
|
||||||
|
params: sanitizeSchemaParams(tool.schema, {
|
||||||
|
columns: 6,
|
||||||
|
rows: 6,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
).files;
|
||||||
|
expect(files).toHaveLength(36);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("выбрасывает errors.tooManyParts при переполнении", () => {
|
||||||
|
const tool = TOOLS.find((t) => t.id === "split-into-parts-png")!;
|
||||||
|
expect(() =>
|
||||||
|
tool.run({
|
||||||
|
source: solid(120, 120),
|
||||||
|
params: { columns: 1000, rows: 1000 },
|
||||||
|
}),
|
||||||
|
).toThrow("errors.tooManyParts");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function asFiles(result: ToolResult): FileResult {
|
||||||
|
if (typeof result === "string" || !("files" in result)) {
|
||||||
|
throw new Error("expected a files result");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function solid(width: number, height: number) {
|
function solid(width: number, height: number) {
|
||||||
const data = new Uint8ClampedArray(width * height * 4).fill(255);
|
const data = new Uint8ClampedArray(width * height * 4).fill(255);
|
||||||
return { width, height, data };
|
return { width, height, data };
|
||||||
|
|||||||
Reference in New Issue
Block a user