Skip to content
28 changes: 13 additions & 15 deletions packages/cli/src/capture/contactSheet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,12 @@ function tempDir(): string {
}

describe("createContactSheet", () => {
// Sharp on Windows CI runners exercises a native-binary fork per operation
// and the runner's I/O throughput varies with concurrent-job pressure. The
// default 20s ceiling has landed just-over the wall clock repeatedly (see
// PR #2492's earlier lightweighting attempt); the actual work here — two
// 16×9 PNG writes + one contact-sheet composite + one metadata probe —
// is milliseconds of compute, so the extra ceiling only absorbs runner
// I/O jitter, it does not hide a real slowdown.
it("writes PNG output when the output path uses a .png extension", async () => {
const dir = tempDir();
try {
const a = join(dir, "a.png");
const b = join(dir, "b.png");
const out = join(dir, "sheet.png");
console.time("sharp.toFile(a)");
await sharp({
create: {
width: 16,
Expand All @@ -38,8 +30,6 @@ describe("createContactSheet", () => {
})
.png()
.toFile(a);
console.timeEnd("sharp.toFile(a)");
console.time("sharp.toFile(b)");
await sharp({
create: {
width: 16,
Expand All @@ -50,21 +40,29 @@ describe("createContactSheet", () => {
})
.png()
.toFile(b);
console.timeEnd("sharp.toFile(b)");

console.time("createContactSheet");
await createContactSheet([a, b], out, {
cols: 2,
cellWidth: 16,
labelMode: "custom",
labels: ["A", "B"],
maxImages: 2,
});
console.timeEnd("createContactSheet");

console.time("sharp(out).metadata()");
// format alone would pass even if the SVG label overlay silently drew
// nothing (e.g. Fontconfig misconfigured): the label band (default
// padding=4, labelH=26 in contactSheet.ts) must contain pixels that
// aren't the label background (#1a1a1a), not just an empty rect.
const { data, info } = await sharp(out).raw().toBuffer({ resolveWithObject: true });
let nonBackgroundPixels = 0;
for (let y = 4; y < 30; y++) {
for (let x = 0; x < info.width; x++) {
const i = (y * info.width + x) * info.channels;
if (data[i] !== 26 || data[i + 1] !== 26 || data[i + 2] !== 26) nonBackgroundPixels++;
}
}
expect(nonBackgroundPixels).toBeGreaterThan(0);
await expect(sharp(out).metadata()).resolves.toMatchObject({ format: "png" });
console.timeEnd("sharp(out).metadata()");
} finally {
rmSync(dir, { recursive: true, force: true });
}
Expand Down
21 changes: 20 additions & 1 deletion packages/cli/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
import { resolve } from "node:path";
import { copyFileSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { defineConfig } from "vitest/config";

// Windows: sharp's first text render builds Fontconfig's cache for every OS font (about 9 s on a
// fresh runner). Set here, before workers fork, because an in-process env write never reaches it.
if (process.platform === "win32") {
const dir = mkdtempSync(join(tmpdir(), "hf-vitest-fontconfig-"));
process.once("exit", () => rmSync(dir, { recursive: true, force: true }));
copyFileSync(
join(process.env.WINDIR ?? "C:\\Windows", "Fonts", "arial.ttf"),
join(dir, "arial.ttf"),
);
const file = join(dir, "fonts.conf");
writeFileSync(
file,
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
`<?xml version="1.0"?><!DOCTYPE fontconfig SYSTEM "fonts.dtd"><fontconfig><dir>${dir}</dir><cachedir>${dir}</cachedir></fontconfig>`,
);
process.env.FONTCONFIG_FILE = file;
}

export default defineConfig({
resolve: {
alias: [
Expand Down
Loading