File size: 5,331 Bytes
eb3f11e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | // Image resize utility tests cover Rastermill probing/encoding decisions,
// base64 byte budgets, and coordinate notes for resized inline images.
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
convertImageToPng: vi.fn(),
encode: vi.fn(),
probe: vi.fn(),
}));
vi.mock("../../media/image-ops.js", () => ({
convertImageToPng: mocks.convertImageToPng,
createImageProcessor: () => ({
encode: mocks.encode,
probe: mocks.probe,
}),
}));
import { processImage } from "./image-resize.js";
describe("image resize utility", () => {
beforeEach(() => {
mocks.convertImageToPng.mockReset();
mocks.encode.mockReset();
mocks.probe.mockReset();
});
it("keeps images that exactly fit the inline limits", async () => {
const input = "a".repeat(4.5 * 1024 * 1024);
mocks.probe.mockResolvedValue({
bytes: Buffer.byteLength(input, "base64"),
format: "png",
hasAlpha: false,
height: 20,
orientation: null,
width: 10,
});
const result = await processImage(
{ type: "image", data: input, mimeType: "image/png" },
{ autoResizeImages: true },
);
expect(result).toStrictEqual({
ok: true,
image: { type: "image", data: input, mimeType: "image/png" },
hints: [],
});
expect(mocks.encode).not.toHaveBeenCalled();
});
it("uses Rastermill limits, base64 budget, and orientation-aware source dimensions", async () => {
// EXIF orientation swaps source dimensions before coordinate mapping so UI
// click coordinates can be translated back to the original image.
const inputBuffer = Buffer.from("large image");
const outputBuffer = Buffer.from("jpeg output");
mocks.probe.mockResolvedValue({
bytes: inputBuffer.byteLength,
format: "jpeg",
hasAlpha: false,
height: 1200,
orientation: 6,
width: 3000,
});
mocks.encode.mockResolvedValue({
base64Bytes: Buffer.byteLength(outputBuffer.toString("base64"), "utf8"),
bytes: outputBuffer.byteLength,
chosen: { format: "jpeg", quality: 70 },
data: outputBuffer,
format: "jpeg",
height: 1600,
metadata: "stripped",
mimeType: "image/jpeg",
resized: true,
width: 640,
withinBudget: true,
});
const result = await processImage(
{ type: "image", data: inputBuffer.toString("base64"), mimeType: "image/jpeg" },
{ autoResizeImages: true },
);
expect(mocks.encode).toHaveBeenCalledWith(inputBuffer, {
format: "auto",
limits: {
maxHeight: 2_000,
maxWidth: 2_000,
},
maxBase64Bytes: 4.5 * 1024 * 1024,
opaque: { format: "jpeg", quality: 80 },
search: {
compressionLevel: [6, 9],
quality: [80, 85, 70, 55, 40, 35],
},
transparent: { format: "png" },
});
expect(result).toStrictEqual({
ok: true,
image: {
type: "image",
data: outputBuffer.toString("base64"),
mimeType: "image/jpeg",
},
hints: [
"[Image: original 1200x3000, displayed at 640x1600. Multiply coordinates by 1.88 to map to original image.]",
],
});
});
it("omits images when Rastermill cannot satisfy the base64 budget", async () => {
const inputBuffer = Buffer.from("too large");
mocks.probe.mockResolvedValue({
bytes: inputBuffer.byteLength,
format: "png",
hasAlpha: false,
height: 4000,
orientation: null,
width: 4000,
});
mocks.encode.mockResolvedValue({
base64Bytes: 120,
bytes: 90,
chosen: { format: "png" },
data: Buffer.alloc(90),
format: "png",
height: 1,
metadata: "stripped",
mimeType: "image/png",
resized: true,
width: 1,
withinBudget: false,
});
await expect(
processImage(
{ type: "image", data: inputBuffer.toString("base64"), mimeType: "image/png" },
{ autoResizeImages: true },
),
).resolves.toStrictEqual({
ok: false,
message: "[Image omitted: could not be resized below the inline image size limit.]",
});
});
it("does not add coordinate hints when Rastermill only re-encodes the image", async () => {
const input = "a".repeat(4.5 * 1024 * 1024 + 1);
const outputBuffer = Buffer.from("re-encoded");
mocks.probe.mockResolvedValue({
bytes: Buffer.byteLength(input, "base64"),
format: "png",
hasAlpha: false,
height: 20,
orientation: null,
width: 10,
});
mocks.encode.mockResolvedValue({
base64Bytes: Buffer.byteLength(outputBuffer.toString("base64"), "utf8"),
bytes: outputBuffer.byteLength,
chosen: { format: "jpeg", quality: 80 },
data: outputBuffer,
format: "jpeg",
height: 20,
metadata: "stripped",
mimeType: "image/jpeg",
resized: false,
width: 10,
withinBudget: true,
});
await expect(
processImage(
{ type: "image", data: input, mimeType: "image/png" },
{ autoResizeImages: true },
),
).resolves.toStrictEqual({
ok: true,
image: {
type: "image",
data: outputBuffer.toString("base64"),
mimeType: "image/jpeg",
},
hints: [],
});
});
});
|