From da40616caa9d7266d66d7728cdcfd3bebd99cd70 Mon Sep 17 00:00:00 2001 From: userjmmm Date: Wed, 30 Sep 2026 15:45:47 +0900 Subject: [PATCH 01/10] Add favicon selection tests for `getFaviconUrl()` Cover the common favicon cases described in the issue: case-insensitive `.svg` suffixes, `type="image/svg+xml"`, `sizes="any"`, and multiple `rel` tokens. Reset fetch-mock in an `afterEach` hook so the cleanup runs even when a test fails, keeping each test isolated from the others. Assisted-by: Claude Code:claude-opus-4-8 --- packages/cli/src/nodeinfo.test.ts | 107 +++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/nodeinfo.test.ts b/packages/cli/src/nodeinfo.test.ts index 5e0d2f111..3dba827b6 100644 --- a/packages/cli/src/nodeinfo.test.ts +++ b/packages/cli/src/nodeinfo.test.ts @@ -1,7 +1,7 @@ -import assert from "node:assert/strict"; -import { test } from "node:test"; import { Chalk } from "chalk"; import fetchMock from "fetch-mock"; +import assert from "node:assert/strict"; +import { afterEach, test } from "node:test"; import { getAsciiArt, getFaviconUrl, @@ -10,6 +10,10 @@ import { rgbTo256Color, } from "./nodeinfo.ts"; +afterEach(() => { + fetchMock.hardReset(); +}); + const HTML_WITH_SMALL_ICON = ` @@ -137,6 +141,105 @@ test("getFaviconUrl - uppercase svg icons with query and hash fall back to /favi fetchMock.hardReset(); }); +const HTML_WITH_SVG_TYPE_ONLY = ` + + + + Test Site + + +Test + +`; + +test("getFaviconUrl - svg icons with type attribute only falls back to /favicon.ico", async () => { + fetchMock.spyGlobal(); + + fetchMock.get("https://example.com/", { + body: HTML_WITH_SVG_TYPE_ONLY, + headers: { "Content-Type": "text/html" }, + }); + + const result = await getFaviconUrl("https://example.com/"); + assert.equal(result.href, "https://example.com/favicon.ico"); +}); + +const HTML_WITH_SIZES_ANY_ICON = ` + + + + Test Site + + +Test + +`; + +test("getFaviconUrl - icons with sizes='any' is selected", async () => { + fetchMock.spyGlobal(); + + fetchMock.get("https://example.com/", { + body: HTML_WITH_SIZES_ANY_ICON, + headers: { "Content-Type": "text/html" }, + }); + + const result = await getFaviconUrl("https://example.com/"); + assert.equal(result.href, "https://example.com/favicon.png"); + + fetchMock.hardReset(); +}); + +const HTML_WITH_PREFERRED_BITMAP_ICON = ` + + + + Test Site + + + +Test + +`; + +test("getFaviconUrl - prefer bitmap icons", async () => { + fetchMock.spyGlobal(); + + fetchMock.get("https://example.com/", { + body: HTML_WITH_PREFERRED_BITMAP_ICON, + headers: { "Content-Type": "text/html" }, + }); + + const result = await getFaviconUrl("https://example.com/"); + assert.equal(result.href, "https://example.com/favicon.png"); + + fetchMock.hardReset(); +}); + +const HTML_WITH_MULTIPLE_REL_TOKENS = ` + + + + Test Site + + +Test + +`; + +test("getFaviconUrl - icon with multiple rel tokens", async () => { + fetchMock.spyGlobal(); + + fetchMock.get("https://example.com/", { + body: HTML_WITH_MULTIPLE_REL_TOKENS, + headers: { "Content-Type": "text/html" }, + }); + + const result = await getFaviconUrl("https://example.com/"); + assert.equal(result.href, "https://example.com/favicon.png"); + + fetchMock.hardReset(); +}); + const HTML_WITHOUT_ICON = ` From c17003bafee4f4c54611fe876ec37ea5a5daf8d7 Mon Sep 17 00:00:00 2001 From: userjmmm Date: Wed, 30 Sep 2026 16:20:51 +0900 Subject: [PATCH 02/10] Detect SVG favicons declared via type attribute --- packages/cli/src/nodeinfo.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/nodeinfo.ts b/packages/cli/src/nodeinfo.ts index 1ed07e136..6caffc3db 100644 --- a/packages/cli/src/nodeinfo.ts +++ b/packages/cli/src/nodeinfo.ts @@ -378,6 +378,7 @@ export async function getFaviconUrl( if ("href" in attrs) { const parsedUrl = new URL(attrs.href, response.url); if (parsedUrl.pathname.toLowerCase().endsWith(".svg")) continue; + if (attrs.type?.toLowerCase()?.trim() === "image/svg+xml") continue; return parsedUrl; } } From 7a5dfa9806b85e5425c2292be37c59f782ad241f Mon Sep 17 00:00:00 2001 From: userjmmm Date: Wed, 30 Sep 2026 17:09:58 +0900 Subject: [PATCH 03/10] Add changelog entry for favicon selection --- changes.d/cli/improve-favicon-selection.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changes.d/cli/improve-favicon-selection.md diff --git a/changes.d/cli/improve-favicon-selection.md b/changes.d/cli/improve-favicon-selection.md new file mode 100644 index 000000000..54f4dd9ed --- /dev/null +++ b/changes.d/cli/improve-favicon-selection.md @@ -0,0 +1,3 @@ + - Changed the `fedify nodeinfo` favicon selector to skip SVG icons declared + with `type="image/svg+xml"`, not just those with a `.svg` URL, so it + renders a usable bitmap icon instead. [[#893] by Lee Jeongmin] From 31c0628863e6edb7093ed88814f260ccdb3ba271 Mon Sep 17 00:00:00 2001 From: userjmmm Date: Sat, 3 Oct 2026 16:25:09 +0900 Subject: [PATCH 04/10] Remove redundant `fetchMock.hardReset()` per test --- packages/cli/src/nodeinfo.test.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/cli/src/nodeinfo.test.ts b/packages/cli/src/nodeinfo.test.ts index 3dba827b6..44627b842 100644 --- a/packages/cli/src/nodeinfo.test.ts +++ b/packages/cli/src/nodeinfo.test.ts @@ -36,8 +36,6 @@ test("getFaviconUrl - small favicon.ico and apple-touch-icon.png", async () => { const result = await getFaviconUrl("https://example.com/"); assert.equal(result.href, "https://example.com/apple-touch-icon.png"); - - fetchMock.hardReset(); }); const HTML_WITH_ICON = ` @@ -62,8 +60,6 @@ test("getFaviconUrl - favicon.ico and apple-touch-icon.png", async () => { const result = await getFaviconUrl("https://example.com/"); assert.equal(result.href, "https://example.com/favicon.ico"); - - fetchMock.hardReset(); }); const HTML_WITH_SVG_ONLY = ` @@ -87,8 +83,6 @@ test("getFaviconUrl - svg icons only falls back to /favicon.ico", async () => { const result = await getFaviconUrl("https://example.com/"); assert.equal(result.href, "https://example.com/favicon.ico"); - - fetchMock.hardReset(); }); const HTML_WITH_UPPERCASE_SVG_ONLY = ` @@ -112,8 +106,6 @@ test("getFaviconUrl - uppercase svg icons only falls back to /favicon.ico", asyn const result = await getFaviconUrl("https://example.com/"); assert.equal(result.href, "https://example.com/favicon.ico"); - - fetchMock.hardReset(); }); const HTML_WITH_UPPERCASE_SVG_WITH_QUERY = ` @@ -137,8 +129,6 @@ test("getFaviconUrl - uppercase svg icons with query and hash fall back to /favi const result = await getFaviconUrl("https://example.com/"); assert.equal(result.href, "https://example.com/favicon.ico"); - - fetchMock.hardReset(); }); const HTML_WITH_SVG_TYPE_ONLY = ` @@ -185,8 +175,6 @@ test("getFaviconUrl - icons with sizes='any' is selected", async () => { const result = await getFaviconUrl("https://example.com/"); assert.equal(result.href, "https://example.com/favicon.png"); - - fetchMock.hardReset(); }); const HTML_WITH_PREFERRED_BITMAP_ICON = ` @@ -211,8 +199,6 @@ test("getFaviconUrl - prefer bitmap icons", async () => { const result = await getFaviconUrl("https://example.com/"); assert.equal(result.href, "https://example.com/favicon.png"); - - fetchMock.hardReset(); }); const HTML_WITH_MULTIPLE_REL_TOKENS = ` @@ -236,8 +222,6 @@ test("getFaviconUrl - icon with multiple rel tokens", async () => { const result = await getFaviconUrl("https://example.com/"); assert.equal(result.href, "https://example.com/favicon.png"); - - fetchMock.hardReset(); }); const HTML_WITHOUT_ICON = ` @@ -260,8 +244,6 @@ test("getFaviconUrl - falls back to /favicon.ico", async () => { const result = await getFaviconUrl("https://example.com/"); assert.equal(result.href, "https://example.com/favicon.ico"); - - fetchMock.hardReset(); }); test("rgbTo256Color - check RGB cube", () => { From 795115a7e256f49eca7d26a5ef9f1191a9f39129 Mon Sep 17 00:00:00 2001 From: userjmmm Date: Sat, 3 Oct 2026 18:45:49 +0900 Subject: [PATCH 05/10] Add favicon selection test for multiple sizes --- packages/cli/src/nodeinfo.test.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/cli/src/nodeinfo.test.ts b/packages/cli/src/nodeinfo.test.ts index 44627b842..f572fce78 100644 --- a/packages/cli/src/nodeinfo.test.ts +++ b/packages/cli/src/nodeinfo.test.ts @@ -177,6 +177,30 @@ test("getFaviconUrl - icons with sizes='any' is selected", async () => { assert.equal(result.href, "https://example.com/favicon.png"); }); +const HTML_WITH_MULTI_SIZE_ICON = ` + + + + Test Site + + + +Test + +`; + +test("getFaviconUrl - icons with multiple sizes", async () => { + fetchMock.spyGlobal(); + + fetchMock.get("https://example.com/", { + body: HTML_WITH_MULTI_SIZE_ICON, + headers: { "Content-Type": "text/html" }, + }); + + const result = await getFaviconUrl("https://example.com/"); + assert.equal(result.href, "https://example.com/favicon.ico"); +}); + const HTML_WITH_PREFERRED_BITMAP_ICON = ` From 06116737b5f439390a4bf8eccd7a692ba862895d Mon Sep 17 00:00:00 2001 From: userjmmm Date: Sat, 3 Oct 2026 19:06:57 +0900 Subject: [PATCH 06/10] Consider all declared favicon sizes - Adds the `g` flag so every declared size is matched, not just the first. - Rewrites the size check to keep the icon when any single size is large enough. Assisted-by: Claude Code:claude-opus-4-8 --- packages/cli/src/nodeinfo.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/nodeinfo.ts b/packages/cli/src/nodeinfo.ts index 6caffc3db..fde225eca 100644 --- a/packages/cli/src/nodeinfo.ts +++ b/packages/cli/src/nodeinfo.ts @@ -371,10 +371,13 @@ export async function getFaviconUrl( } const rel = attrs.rel?.toLowerCase()?.trim()?.split(/\s+/) ?? []; if (!rel.includes("icon") && !rel.includes("apple-touch-icon")) continue; - if ("sizes" in attrs && attrs.sizes.match(/\d+x\d+/)) { - const [w, h] = attrs.sizes.split("x").map((v) => Number.parseInt(v)); - if (w < 38 || h < 19) continue; - } + const tokens = attrs.sizes?.match(/\d+x\d+/g); + if ( + tokens != null && !tokens.some((token) => { + const [w, h] = token.split("x").map((v) => Number.parseInt(v)); + return w >= 38 && h >= 19; + }) + ) continue; if ("href" in attrs) { const parsedUrl = new URL(attrs.href, response.url); if (parsedUrl.pathname.toLowerCase().endsWith(".svg")) continue; From 405a837792d2318fe012349628a0b61c7a7d9bbf Mon Sep 17 00:00:00 2001 From: userjmmm Date: Sat, 3 Oct 2026 19:50:11 +0900 Subject: [PATCH 07/10] Place the favicon changelog under 2.5.0 and add PR reference --- CHANGES.md | 12 ++++++++++++ changes.d/cli/improve-favicon-selection.md | 9 ++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cdac996b2..db8b4bc88 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,18 @@ Version 2.5.0 To be released. +### @fedify/cli + + - Changed the `fedify nodeinfo` favicon selector to pick a usable bitmap + icon more often. It now skips SVG icons declared with + `type="image/svg+xml"`, not just those with a `.svg` URL, and it considers + every declared size instead of only the first, so an icon offering a large + size is no longer discarded because its smallest size is too small. + [[#893], [#1187] by Lee Jeongmin\] + +[#893]: https://github.com/fedify-dev/fedify/issues/893 +[#1187]: https://github.com/fedify-dev/fedify/issues/1187 + Version 2.4.0 ------------- diff --git a/changes.d/cli/improve-favicon-selection.md b/changes.d/cli/improve-favicon-selection.md index 54f4dd9ed..48c86c53f 100644 --- a/changes.d/cli/improve-favicon-selection.md +++ b/changes.d/cli/improve-favicon-selection.md @@ -1,3 +1,6 @@ - - Changed the `fedify nodeinfo` favicon selector to skip SVG icons declared - with `type="image/svg+xml"`, not just those with a `.svg` URL, so it - renders a usable bitmap icon instead. [[#893] by Lee Jeongmin] + - Changed the `fedify nodeinfo` favicon selector to pick a usable bitmap + icon more often. It now skips SVG icons declared with + `type="image/svg+xml"`, not just those with a `.svg` URL, and it considers + every declared size instead of only the first, so an icon offering a large + size is no longer discarded because its smallest size is too small. + [[#893], [#1187] by Lee Jeongmin] From 47546c6104557ac769c056ab5cefae6fee0b3cb8 Mon Sep 17 00:00:00 2001 From: userjmmm Date: Sun, 4 Oct 2026 15:05:57 +0900 Subject: [PATCH 08/10] Test uppercase .SVG suffix independently of MIME --- packages/cli/src/nodeinfo.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/nodeinfo.test.ts b/packages/cli/src/nodeinfo.test.ts index f572fce78..08ce279ab 100644 --- a/packages/cli/src/nodeinfo.test.ts +++ b/packages/cli/src/nodeinfo.test.ts @@ -90,7 +90,7 @@ const HTML_WITH_UPPERCASE_SVG_ONLY = ` Test Site - + Test @@ -113,7 +113,7 @@ const HTML_WITH_UPPERCASE_SVG_WITH_QUERY = ` Test Site - + Test From de6ba714a669b20bc56b75d1a66f8fa9793994b5 Mon Sep 17 00:00:00 2001 From: userjmmm Date: Sun, 4 Oct 2026 15:22:09 +0900 Subject: [PATCH 09/10] Use a distinct candidate URL in multi-size test --- packages/cli/src/nodeinfo.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/nodeinfo.test.ts b/packages/cli/src/nodeinfo.test.ts index 08ce279ab..0ebd2b8f4 100644 --- a/packages/cli/src/nodeinfo.test.ts +++ b/packages/cli/src/nodeinfo.test.ts @@ -182,7 +182,7 @@ const HTML_WITH_MULTI_SIZE_ICON = ` Test Site - + Test @@ -198,7 +198,7 @@ test("getFaviconUrl - icons with multiple sizes", async () => { }); const result = await getFaviconUrl("https://example.com/"); - assert.equal(result.href, "https://example.com/favicon.ico"); + assert.equal(result.href, "https://example.com/multi-size-icon.ico"); }); const HTML_WITH_PREFERRED_BITMAP_ICON = ` From bbfad72ede17f8b35aae82434e9fc10bf30ec5aa Mon Sep 17 00:00:00 2001 From: userjmmm Date: Sun, 4 Oct 2026 15:29:39 +0900 Subject: [PATCH 10/10] Point the changelog PR reference to its PR URL --- CHANGES.md | 2 +- changes.d/cli/improve-favicon-selection.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index db8b4bc88..42690e03a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,7 +18,7 @@ To be released. [[#893], [#1187] by Lee Jeongmin\] [#893]: https://github.com/fedify-dev/fedify/issues/893 -[#1187]: https://github.com/fedify-dev/fedify/issues/1187 +[#1187]: https://github.com/fedify-dev/fedify/pull/1187 Version 2.4.0 diff --git a/changes.d/cli/improve-favicon-selection.md b/changes.d/cli/improve-favicon-selection.md index 48c86c53f..af631d7fe 100644 --- a/changes.d/cli/improve-favicon-selection.md +++ b/changes.d/cli/improve-favicon-selection.md @@ -1,3 +1,8 @@ +--- +links: + '#1187': https://github.com/fedify-dev/fedify/pull/1187 + '#893': https://github.com/fedify-dev/fedify/issues/893 +--- - Changed the `fedify nodeinfo` favicon selector to pick a usable bitmap icon more often. It now skips SVG icons declared with `type="image/svg+xml"`, not just those with a `.svg` URL, and it considers