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
|
import { test, expect } from "@playwright/test";
// 1x1 red JPEG as base64
const TINY_JPEG_B64 =
"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkS" +
"Ew8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJ" +
"CQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy" +
"MjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACf/" +
"EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AKwA//9k=";
test.describe("Blossom image rendering", () => {
test("Chrome renders <img src> with application/octet-stream", async ({ page }) => {
await page.route("**/mock-blossom/**", async (route) => {
const jpegBytes = Buffer.from(TINY_JPEG_B64, "base64");
await route.fulfill({
status: 200,
contentType: "application/octet-stream",
body: jpegBytes,
});
});
await page.goto("/");
await page.waitForLoadState("domcontentloaded");
const result = await page.evaluate(async () => {
const img = document.createElement("img");
img.src = "/mock-blossom/test.jpg";
document.body.prepend(img);
await new Promise<void>((resolve) => {
img.onload = () => resolve();
img.onerror = () => resolve();
setTimeout(resolve, 3000);
});
return {
naturalWidth: img.naturalWidth,
naturalHeight: img.naturalHeight,
};
});
console.log("Direct <img src> with octet-stream:", result);
expect(result.naturalWidth, "Image should render despite octet-stream content-type").toBeGreaterThan(0);
});
test("ChatMessage component renders imeta images", async ({ page }) => {
// Mock blossom-like endpoint
await page.route("**/mock-blossom/**", async (route) => {
const jpegBytes = Buffer.from(TINY_JPEG_B64, "base64");
await route.fulfill({
status: 200,
contentType: "application/octet-stream",
body: jpegBytes,
});
});
await page.goto("/");
await page.waitForLoadState("domcontentloaded");
// Inject a ChatMessage-like rendering with imeta tags
const result = await page.evaluate(async () => {
const container = document.createElement("div");
container.id = "test-chat";
document.body.prepend(container);
// Simulate the imeta extraction logic from ChatMessage.svelte
const tags = [
["imeta", "url /mock-blossom/chat-image", "m image/jpeg", "x abc123"],
];
const imetaImages = tags
.filter((t) => t[0] === "imeta" && t.some((v) => v.startsWith("m image/")))
.map((t) => ({
url: t.find((v) => v.startsWith("url "))?.slice(4) || "",
mime: t.find((v) => v.startsWith("m "))?.slice(2) || "image/jpeg",
}))
.filter((m) => m.url);
// Render like the template does
for (const img of imetaImages) {
const a = document.createElement("a");
a.href = img.url;
a.target = "_blank";
const imgEl = document.createElement("img");
imgEl.src = img.url;
imgEl.alt = "";
imgEl.style.maxWidth = "100%";
imgEl.style.borderRadius = "8px";
a.appendChild(imgEl);
container.appendChild(a);
}
// Wait for images to load
await new Promise((r) => setTimeout(r, 2000));
const imgs = container.querySelectorAll("img");
const results: any[] = [];
for (const img of imgs) {
results.push({
naturalWidth: (img as HTMLImageElement).naturalWidth,
naturalHeight: (img as HTMLImageElement).naturalHeight,
src: (img as HTMLImageElement).src.substring(0, 60),
});
}
return { imageCount: imetaImages.length, rendered: results };
});
console.log("ChatMessage rendering:", JSON.stringify(result, null, 2));
expect(result.imageCount).toBe(1);
expect(result.rendered[0].naturalWidth).toBeGreaterThan(0);
});
test("imeta URL is suppressed from link rendering", async ({ page }) => {
await page.goto("/");
await page.waitForLoadState("domcontentloaded");
// Simulate the duplicate-suppression logic
const result = await page.evaluate(() => {
const tags = [
["imeta", "url https://blossom.example.com/abc123", "m image/jpeg"],
];
const content = "https://blossom.example.com/abc123";
// Extract imeta URLs
const imetaImages = tags
.filter((t) => t[0] === "imeta" && t.some((v) => v.startsWith("m image/")))
.map((t) => ({
url: t.find((v) => v.startsWith("url "))?.slice(4) || "",
}))
.filter((m) => m.url);
const imetaUrls = new Set(imetaImages.map((i) => i.url));
// Check if the content URL would be suppressed
return {
contentUrl: content,
imetaUrls: [...imetaUrls],
wouldSuppress: imetaUrls.has("https://blossom.example.com/abc123"),
};
});
console.log("Duplicate suppression:", result);
expect(result.wouldSuppress).toBe(true);
});
});
|