1import { describe, expect, it } from "vite-plus/test";
2
3import { inferImageExtension, parseBase64DataUrl } from "./imageMime.ts";
4
5describe("imageMime", () => {
6 it("parses base64 data URL with mime type", () => {
7 expect(parseBase64DataUrl("data:image/png;base64,SGVsbG8=")).toEqual({
8 mimeType: "image/png",
9 base64: "SGVsbG8=",
10 });
11 });
12
13 it("parses base64 data URL with mime parameters", () => {
14 expect(parseBase64DataUrl("data:image/png;charset=utf-8;base64,SGVsbG8=")).toEqual({
15 mimeType: "image/png",
16 base64: "SGVsbG8=",
17 });
18 });
19
20 it("rejects non-base64 data URL", () => {
21 expect(parseBase64DataUrl("data:image/png;charset=utf-8,hello")).toBeNull();
22 });
23
24 it("rejects missing mime type", () => {
25 expect(parseBase64DataUrl("data:;base64,SGVsbG8=")).toBeNull();
26 });
27
28 it("parses base64 data URL with spaces in payload", () => {
29 expect(parseBase64DataUrl("data:image/png;base64,SGVs bG8=\n")).toEqual({
30 mimeType: "image/png",
31 base64: "SGVsbG8=",
32 });
33 });
34
35 it("rejects payload with characters outside the base64 alphabet", () => {
36 expect(parseBase64DataUrl("data:image/png;base64,SGVs!bG8=")).toBeNull();
37 expect(parseBase64DataUrl("data:image/png;base64,SGVs,bG8=")).toBeNull();
38 });
39
40 it("rejects structurally malformed base64", () => {
41 // '=' before the trailing padding position
42 expect(parseBase64DataUrl("data:image/png;base64,AB=CD===")).toBeNull();
43 expect(parseBase64DataUrl("data:image/png;base64,SGV=bG8=")).toBeNull();
44 // more than two padding characters
45 expect(parseBase64DataUrl("data:image/png;base64,SGVsbG8=====AAA")).toBeNull();
46 // length not a multiple of 4
47 expect(parseBase64DataUrl("data:image/png;base64,SGVsbG8")).toBeNull();
48 });
49
50 it("accepts base64 with one or two trailing padding characters", () => {
51 expect(parseBase64DataUrl("data:image/png;base64,SGVsbA==")).toEqual({
52 mimeType: "image/png",
53 base64: "SGVsbA==",
54 });
55 expect(parseBase64DataUrl("data:image/png;base64,SGVsbG8h")).toEqual({
56 mimeType: "image/png",
57 base64: "SGVsbG8h",
58 });
59 });
60
61 it("rejects empty and whitespace-only payloads", () => {
62 expect(parseBase64DataUrl("data:image/png;base64,")).toBeNull();
63 expect(parseBase64DataUrl("data:image/png;base64, \r\n")).toBeNull();
64 });
65
66 it("parses a case-insensitive scheme and mime type", () => {
67 expect(parseBase64DataUrl("DATA:IMAGE/PNG;BASE64,SGVsbG8=")).toEqual({
68 mimeType: "image/png",
69 base64: "SGVsbG8=",
70 });
71 });
72
73 it("parses a multi-megabyte payload from a deep call stack", () => {
74 // Regression: matching the payload with a regex borrowed the JS call
75 // stack, so a ~10 MB image parsed inside fiber execution threw
76 // "RangeError: Maximum call stack size exceeded".
77 const dataUrl = `data:image/png;base64,${"A".repeat(14_000_000)}`;
78 const atDepth = (depth: number): ReturnType<typeof parseBase64DataUrl> =>
79 depth === 0 ? parseBase64DataUrl(dataUrl) : atDepth(depth - 1);
80 const findMaxDepth = (depth: number): number => {
81 try {
82 return findMaxDepth(depth + 1);
83 } catch {
84 return depth;
85 }
86 };
87 const result = atDepth(Math.floor(findMaxDepth(0) * 0.85));
88 expect(result?.mimeType).toBe("image/png");
89 expect(result?.base64.length).toBe(14_000_000);
90 });
91
92 it("does not read inherited keys from mime extension map", () => {
93 expect(inferImageExtension({ mimeType: "constructor" })).toBe(".bin");
94 });
95});