fix(server): prefer portable project icons
Project icon discovery could select an ICO that native clients cannot decode even when a renderable SVG or PNG was available. Keep ICO as the final fallback and recognize common sized icon names.
This commit is contained in:
@@ -125,11 +125,11 @@ describe("findProjectIcon", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("prioritizes favicon over logo", async () => {
|
it("prioritizes favicon over logo", async () => {
|
||||||
writeFileSync(join(tempDir, "favicon.ico"), "favicon");
|
writeFileSync(join(tempDir, "favicon.png"), "favicon");
|
||||||
writeFileSync(join(tempDir, "logo.png"), "logo");
|
writeFileSync(join(tempDir, "logo.png"), "logo");
|
||||||
|
|
||||||
const result = await findProjectIcon(tempDir);
|
const result = await findProjectIcon(tempDir);
|
||||||
expect(result).toBe(join(tempDir, "favicon.ico"));
|
expect(result).toBe(join(tempDir, "favicon.png"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prioritizes priority dirs over root", async () => {
|
it("prioritizes priority dirs over root", async () => {
|
||||||
@@ -180,6 +180,14 @@ describe("findProjectIcon", () => {
|
|||||||
expect(result).toBe(join(tempDir, "apple-touch-icon.png"));
|
expect(result).toBe(join(tempDir, "apple-touch-icon.png"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("prefers an Apple touch PNG over ICO", async () => {
|
||||||
|
writeFileSync(join(tempDir, "favicon.ico"), "ico");
|
||||||
|
writeFileSync(join(tempDir, "apple-touch-icon.png"), "apple icon");
|
||||||
|
|
||||||
|
const result = await findProjectIcon(tempDir);
|
||||||
|
expect(result).toBe(join(tempDir, "apple-touch-icon.png"));
|
||||||
|
});
|
||||||
|
|
||||||
it("finds icon-*.png patterns", async () => {
|
it("finds icon-*.png patterns", async () => {
|
||||||
writeFileSync(join(tempDir, "icon-192.png"), "192 icon");
|
writeFileSync(join(tempDir, "icon-192.png"), "192 icon");
|
||||||
|
|
||||||
@@ -187,6 +195,19 @@ describe("findProjectIcon", () => {
|
|||||||
expect(result).toBe(join(tempDir, "icon-192.png"));
|
expect(result).toBe(join(tempDir, "icon-192.png"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
"favicon-32x32.png",
|
||||||
|
"apple-touch-icon-180x180.png",
|
||||||
|
"android-chrome-192x192.png",
|
||||||
|
"safari-pinned-tab.svg",
|
||||||
|
"mstile-150x150.png",
|
||||||
|
])("finds common sized icon %s", async (fileName) => {
|
||||||
|
writeFileSync(join(tempDir, fileName), "icon");
|
||||||
|
|
||||||
|
const result = await findProjectIcon(tempDir);
|
||||||
|
expect(result).toBe(join(tempDir, fileName));
|
||||||
|
});
|
||||||
|
|
||||||
it("handles non-existent directory gracefully", async () => {
|
it("handles non-existent directory gracefully", async () => {
|
||||||
const result = await findProjectIcon(join(tempDir, "nonexistent"));
|
const result = await findProjectIcon(join(tempDir, "nonexistent"));
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
|
|||||||
@@ -5,25 +5,31 @@ import { extname, join } from "path";
|
|||||||
* Icon file patterns to search for, in priority order.
|
* Icon file patterns to search for, in priority order.
|
||||||
* Patterns starting with '*' are glob patterns (e.g., icon-*.png).
|
* Patterns starting with '*' are glob patterns (e.g., icon-*.png).
|
||||||
*
|
*
|
||||||
* Within each name, SVG and PNG outrank ICO: every client renders them (React
|
* SVG and PNG outrank ICO across all known names: every client renders them
|
||||||
* Native's <Image> cannot decode ICO at all) and they scale better. ICO is the
|
* and they scale better. ICO is the final fallback because native clients
|
||||||
* last resort for each name, not the first.
|
* cannot decode containers whose frames use the legacy bitmap format.
|
||||||
*/
|
*/
|
||||||
export const ICON_PATTERNS = [
|
export const ICON_PATTERNS = [
|
||||||
"favicon.svg",
|
"favicon.svg",
|
||||||
"favicon.png",
|
"favicon.png",
|
||||||
"favicon.ico",
|
"favicon-*.svg",
|
||||||
|
"favicon-*.png",
|
||||||
"favico.svg",
|
"favico.svg",
|
||||||
"favico.png",
|
"favico.png",
|
||||||
"favico.ico",
|
|
||||||
"icon.svg",
|
"icon.svg",
|
||||||
"icon.png",
|
"icon.png",
|
||||||
"app-icon.svg",
|
"app-icon.svg",
|
||||||
"app-icon.png",
|
"app-icon.png",
|
||||||
"apple-touch-icon.png",
|
"apple-touch-icon.png",
|
||||||
|
"apple-touch-icon-*.png",
|
||||||
"icon-*.png",
|
"icon-*.png",
|
||||||
|
"android-chrome-*.png",
|
||||||
|
"safari-pinned-tab.svg",
|
||||||
|
"mstile-*.png",
|
||||||
"logo.svg",
|
"logo.svg",
|
||||||
"logo.png",
|
"logo.png",
|
||||||
|
"favicon.ico",
|
||||||
|
"favico.ico",
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user