jsmdcui 0.11.2 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,116 @@
1
+ import { expect, test } from "bun:test";
2
+ import { mkdtemp, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import {
6
+ buildHtmlBundleImageMap,
7
+ canonicalHtmlBundleImageHref,
8
+ decodeHtmlAttribute,
9
+ findHtmlBundleAsset,
10
+ findHtmlBundleImageAsset,
11
+ htmlBundleImageAssetPath,
12
+ } from "../single-exe/assetsHelper.js";
13
+
14
+ const homepage = {
15
+ files: [
16
+ {
17
+ input: "../../images/pixel.png",
18
+ path: "/$bunfs/root/pixel-abc123.png",
19
+ loader: "file",
20
+ headers: { "content-type": "image/png" },
21
+ },
22
+ {
23
+ input: "scripts/app.js",
24
+ path: "/$bunfs/root/app-test.js",
25
+ loader: "file",
26
+ headers: { "content-type": "text/javascript" },
27
+ },
28
+ ],
29
+ };
30
+
31
+ test("HTML bundle asset lookup matches the compiled public asset path", () => {
32
+ expect(findHtmlBundleAsset(homepage, "/pixel-abc123.png"))
33
+ .toBe(homepage.files[0]);
34
+ expect(htmlBundleImageAssetPath(homepage, "/pixel-abc123.png?version=2"))
35
+ .toBe("/$bunfs/root/pixel-abc123.png");
36
+ });
37
+
38
+ test("HTML bundle image lookup rejects non-images and unavailable development bundles", () => {
39
+ expect(findHtmlBundleImageAsset(homepage, "/app-test.js")).toBeNull();
40
+ expect(findHtmlBundleAsset({}, "/pixel-abc123.png")).toBeNull();
41
+ expect(findHtmlBundleAsset(null, "/pixel-abc123.png")).toBeNull();
42
+ });
43
+
44
+ test("HTML bundle image map joins preserved Markdown hrefs to compiled bunfs assets", async () => {
45
+ const directory = await mkdtemp(join(tmpdir(), "jsmdcui-html-assets-"));
46
+ const htmlPath = join(directory, "app.html");
47
+ await writeFile(htmlPath, [
48
+ '<img src="/dms-ajghf5bd.jpg" data-mdcui-src="jsmdcui/single-exe/dms.jpg">',
49
+ '<img src="/unicode-xyz.png" data-mdcui-src="%E5%9C%96%E7%89%87/%E6%B8%AC%E8%A9%A6%20%E5%9C%96.png">',
50
+ '<img src="/query-qwe.png" data-mdcui-src="./query.png?x=1&amp;y=2#preview">',
51
+ ].join("\n"));
52
+ const compiledHomepage = {
53
+ index: htmlPath,
54
+ files: [
55
+ {
56
+ input: "dms.jpg",
57
+ path: "/$bunfs/root/dms-ajghf5bd.jpg",
58
+ loader: "file",
59
+ headers: { "content-type": "image/jpeg" },
60
+ },
61
+ {
62
+ input: "../../unicode.png",
63
+ path: "/$bunfs/root/unicode-xyz.png",
64
+ loader: "file",
65
+ headers: { "content-type": "image/png" },
66
+ },
67
+ {
68
+ input: "../query.png",
69
+ path: "/$bunfs/root/query-qwe.png",
70
+ loader: "file",
71
+ headers: { "content-type": "image/png" },
72
+ },
73
+ ],
74
+ };
75
+
76
+ const images = await buildHtmlBundleImageMap(compiledHomepage);
77
+ expect(images.get("jsmdcui/single-exe/dms.jpg"))
78
+ .toBe("/$bunfs/root/dms-ajghf5bd.jpg");
79
+ expect(images.get("圖片/測試 圖.png"))
80
+ .toBe("/$bunfs/root/unicode-xyz.png");
81
+ expect(images.get("./query.png?x=1&y=2#preview"))
82
+ .toBe("/$bunfs/root/query-qwe.png");
83
+ });
84
+
85
+ test("HTML bundle href canonicalization decodes serialization without resolving paths", () => {
86
+ expect(decodeHtmlAttribute("./a.png?x=1&amp;y=2")).toBe("./a.png?x=1&y=2");
87
+ expect(canonicalHtmlBundleImageHref("%E5%9C%96%E7%89%87/a%20b.png"))
88
+ .toBe("圖片/a b.png");
89
+ expect(canonicalHtmlBundleImageHref("./a.png?x=1&amp;y=2", { htmlAttribute: true }))
90
+ .toBe("./a.png?x=1&y=2");
91
+ });
92
+
93
+ test("HTML bundle image map accepts a custom source attribute", async () => {
94
+ const directory = await mkdtemp(join(tmpdir(), "jsmdcui-html-assets-custom-"));
95
+ const htmlPath = join(directory, "custom.html");
96
+ await writeFile(
97
+ htmlPath,
98
+ '<img src="/photo-custom123.jpg" data-original-image="./photos/a.jpg">',
99
+ );
100
+ const customHomepage = {
101
+ index: htmlPath,
102
+ files: [{
103
+ input: "../../photos/a.jpg",
104
+ path: "/$bunfs/root/photo-custom123.jpg",
105
+ loader: "file",
106
+ headers: { "content-type": "image/jpeg" },
107
+ }],
108
+ };
109
+
110
+ const images = await buildHtmlBundleImageMap(
111
+ customHomepage,
112
+ "data-original-image",
113
+ );
114
+ expect(images.get("./photos/a.jpg"))
115
+ .toBe("/$bunfs/root/photo-custom123.jpg");
116
+ });
@@ -26,6 +26,28 @@ test("Bun-rendered Markdown image links reserve rows and retain Kitty data", asy
26
26
  expect(Bun.stripANSI(result.rendered)).toContain("📷 pixel");
27
27
  });
28
28
 
29
+ test("Kitty images prefer lazily supplied compiled HTML bundle assets", async () => {
30
+ const dir = await mkdtemp(join(tmpdir(), "jsmdcui-kitty-bundled-"));
31
+ const bundledPath = join(dir, "pixel-bundled.png");
32
+ await writeFile(bundledPath, ONE_PIXEL_PNG);
33
+ const ansi = Bun.markdown.ansi("![pixel](./images/pixel.png?version=2#preview)", {
34
+ hyperlinks: true,
35
+ columns: 40,
36
+ });
37
+ let imageMapRequests = 0;
38
+ const result = await prepareKittyImages(ansi, join(dir, "missing-app.md"), 40, {
39
+ getBundledImageMap: async () => {
40
+ imageMapRequests++;
41
+ return new Map([["./images/pixel.png?version=2#preview", bundledPath]]);
42
+ },
43
+ });
44
+
45
+ expect(imageMapRequests).toBe(1);
46
+ expect(result.images).toHaveLength(1);
47
+ expect(result.images[0].path).toBe(bundledPath);
48
+ expect(result.images[0].data.equals(ONE_PIXEL_PNG)).toBe(true);
49
+ });
50
+
29
51
  test("Kitty compat converts compressed images to standard PNG payloads", async () => {
30
52
  const dir = await mkdtemp(join(tmpdir(), "jsmdcui-kitty-compat-"));
31
53
  const markdownPath = join(dir, "app.md");
@@ -0,0 +1,14 @@
1
+ import { expect, test } from "bun:test";
2
+ import { kittyModeFromEnvironment } from "../src/cui/kitty-mode.mjs";
3
+
4
+ test("Kitty mode accepts supported environment values case-insensitively", () => {
5
+ expect(kittyModeFromEnvironment("compat")).toBe("compat");
6
+ expect(kittyModeFromEnvironment(" EXTENDED ")).toBe("extended");
7
+ expect(kittyModeFromEnvironment("off")).toBe("off");
8
+ });
9
+
10
+ test("Kitty mode defaults invalid or missing environment values to off", () => {
11
+ expect(kittyModeFromEnvironment()).toBe("off");
12
+ expect(kittyModeFromEnvironment("yes")).toBe("off");
13
+ expect(kittyModeFromEnvironment("")).toBe("off");
14
+ });
@@ -19,6 +19,7 @@ test("createWui constrains images to their content width", async () => {
19
19
  const html = await createWui("![wide](wide.png)", markdownPath);
20
20
 
21
21
  expect(html).toContain("img {\n max-width: 100%;\n height: auto;\n}");
22
+ expect(html).toContain('src="wide.png" alt="wide" data-mdcui-src="wide.png"');
22
23
  expect(html.match(/max-width: 100%/g)).toHaveLength(1);
23
24
  expect(html.match(/image\.md\.front\.js/g)).toHaveLength(1);
24
25
  });
@@ -31,6 +32,7 @@ test("createWui injects the image rule into an existing document head", async ()
31
32
  const html = await createWui(source, markdownPath);
32
33
 
33
34
  expect(html.indexOf("max-width: 100%")).toBeLessThan(html.indexOf("</head>"));
35
+ expect(html).toContain('src="wide.png" data-mdcui-src="wide.png"');
34
36
  expect(html.match(/document\.md\.front\.js/g)).toHaveLength(1);
35
37
  });
36
38