fix: block PDF uploads, fix PBM/PGM/PPM batch failures, add tests

- Added isImageFile() filter to all drop handlers (dropzone, collage,
  file-upload-area) so PDFs and non-image files are rejected on drop.
  Previously only the file picker's accept attribute filtered; drag-and-
  drop accepted anything.

- Added ppm, pgm, pbm to CLI_DECODED_FORMATS in file-validation.ts.
  These formats were missing, causing Sharp metadata checks to fail for
  some files during batch validation, which silently dropped them from
  results ("File not found in batch results").

- Added integration tests for PBM, PGM, PPM, TIFF, QOI, JP2, SVGZ
  single-file processing, plus a test confirming PDF is rejected.
This commit is contained in:
SnapOtter
2026-05-11 17:33:51 +08:00
parent a52a3b7e87
commit 50d1f532d1
5 changed files with 119 additions and 3 deletions
+76 -1
View File
@@ -2,6 +2,81 @@ import { FileImage, Upload } from "lucide-react";
import { type DragEvent, useCallback, useState } from "react";
import { cn } from "@/lib/utils";
const IMAGE_EXTENSIONS = new Set([
"jpg",
"jpeg",
"png",
"gif",
"webp",
"svg",
"bmp",
"avif",
"tiff",
"tif",
"ico",
"heic",
"heif",
"hif",
"jxl",
"apng",
"dng",
"cr2",
"cr3",
"nef",
"nrw",
"arw",
"orf",
"rw2",
"raf",
"pef",
"3fr",
"iiq",
"srw",
"x3f",
"rwl",
"gpr",
"fff",
"mrw",
"mef",
"kdc",
"dcr",
"erf",
"ptx",
"tga",
"psd",
"exr",
"hdr",
"svgz",
"jp2",
"j2k",
"j2c",
"jpc",
"jpf",
"jpx",
"qoi",
"eps",
"epsf",
"dds",
"cur",
"dpx",
"cin",
"fits",
"fit",
"fts",
"pbm",
"pgm",
"ppm",
"pnm",
"pam",
"pfm",
]);
export function isImageFile(file: File): boolean {
if (file.type.startsWith("image/")) return true;
const ext = file.name.split(".").pop()?.toLowerCase() ?? "";
return IMAGE_EXTENSIONS.has(ext);
}
interface DropzoneProps {
onFiles?: (files: File[]) => void;
accept?: string;
@@ -41,7 +116,7 @@ export function Dropzone({
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
const files = Array.from(e.dataTransfer.files);
const files = Array.from(e.dataTransfer.files).filter(isImageFile);
if (files.length > 0) onFiles?.(files);
},
[onFiles],
@@ -1,5 +1,6 @@
import { Upload } from "lucide-react";
import { useState } from "react";
import { isImageFile } from "@/components/common/dropzone";
import { cn } from "@/lib/utils";
import { useFilesPageStore } from "@/stores/files-page-store";
@@ -19,7 +20,7 @@ export function FileUploadArea() {
function handleDrop(e: React.DragEvent) {
e.preventDefault();
setDragging(false);
const files = Array.from(e.dataTransfer.files).filter((f) => f.type.startsWith("image/"));
const files = Array.from(e.dataTransfer.files).filter(isImageFile);
if (files.length > 0) uploadFiles(files);
}
@@ -23,6 +23,7 @@ import {
X,
} from "lucide-react";
import { type DragEvent, useCallback, useEffect, useRef, useState } from "react";
import { isImageFile } from "@/components/common/dropzone";
import { type CollageTemplate, getTemplateById } from "@/lib/collage-templates";
import { cn } from "@/lib/utils";
import type { CellTransform, CollageImage } from "@/stores/collage-store";
@@ -95,7 +96,7 @@ function UploadArea() {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
const files = Array.from(e.dataTransfer.files);
const files = Array.from(e.dataTransfer.files).filter(isImageFile);
if (files.length > 0) addImages(files);
},
[addImages],