96 lines
2.8 KiB
TypeScript
96 lines
2.8 KiB
TypeScript
import { deflateSync } from "node:zlib";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { comparePngScreenshots } from "../src/browser/PngVisualDiff.js";
|
|
import {
|
|
compareWebScreenshotsInputSchema,
|
|
createWebScreenshotArtifact,
|
|
} from "../src/domain/webScreenshot.js";
|
|
|
|
describe("PNG visual diff", () => {
|
|
it("reports exact changed-pixel and channel metrics", () => {
|
|
const before = artifact(1, 1, [0, 0, 0, 255]);
|
|
const after = artifact(1, 1, [10, 0, 0, 255]);
|
|
const result = comparePngScreenshots(
|
|
compareWebScreenshotsInputSchema.parse({ before, after }),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
status: "different",
|
|
compared_pixels: 1,
|
|
changed_pixels: 1,
|
|
changed_ratio: 1,
|
|
maximum_channel_delta: 10,
|
|
mean_absolute_channel_delta: 2.5,
|
|
});
|
|
});
|
|
|
|
it("applies a channel threshold and reports dimension mismatch", () => {
|
|
const one = artifact(1, 1, [0, 0, 0, 255]);
|
|
const near = artifact(1, 1, [2, 0, 0, 255]);
|
|
expect(
|
|
comparePngScreenshots(
|
|
compareWebScreenshotsInputSchema.parse({
|
|
before: one,
|
|
after: near,
|
|
channel_threshold: 2,
|
|
}),
|
|
).status,
|
|
).toBe("identical");
|
|
expect(
|
|
comparePngScreenshots(
|
|
compareWebScreenshotsInputSchema.parse({
|
|
before: one,
|
|
after: artifact(2, 1, [0, 0, 0, 255, 0, 0, 0, 255]),
|
|
}),
|
|
),
|
|
).toMatchObject({ status: "dimension_mismatch", compared_pixels: 0 });
|
|
});
|
|
|
|
it("rejects images above the caller's decoded pixel limit", () => {
|
|
const image = artifact(2, 1, [0, 0, 0, 255, 0, 0, 0, 255]);
|
|
expect(() =>
|
|
comparePngScreenshots(
|
|
compareWebScreenshotsInputSchema.parse({
|
|
before: image,
|
|
after: image,
|
|
maximum_pixels: 1,
|
|
}),
|
|
),
|
|
).toThrow("oversized PNG");
|
|
});
|
|
});
|
|
|
|
const artifact = (width: number, height: number, pixels: readonly number[]) =>
|
|
createWebScreenshotArtifact(png(width, height, pixels));
|
|
|
|
const png = (
|
|
width: number,
|
|
height: number,
|
|
pixels: readonly number[],
|
|
): Buffer => {
|
|
const header = Buffer.alloc(13);
|
|
header.writeUInt32BE(width, 0);
|
|
header.writeUInt32BE(height, 4);
|
|
header[8] = 8;
|
|
header[9] = 6;
|
|
const rows: number[] = [];
|
|
for (let row = 0; row < height; row += 1)
|
|
rows.push(0, ...pixels.slice(row * width * 4, (row + 1) * width * 4));
|
|
return Buffer.concat([
|
|
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
|
|
chunk("IHDR", header),
|
|
chunk("IDAT", deflateSync(Buffer.from(rows))),
|
|
chunk("IEND", Buffer.alloc(0)),
|
|
]);
|
|
};
|
|
|
|
const chunk = (type: string, data: Buffer): Buffer => {
|
|
const result = Buffer.alloc(12 + data.byteLength);
|
|
result.writeUInt32BE(data.byteLength, 0);
|
|
result.write(type, 4, 4, "ascii");
|
|
data.copy(result, 8);
|
|
return result;
|
|
};
|