import fs from "node:fs"; import http from "node:http"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { tryHandleProjectFaviconRequest } from "./projectFaviconRoute"; interface HttpResponse { statusCode: number; contentType: string | null; body: string; } const tempDirs: string[] = []; function makeTempDir(prefix: string): string { const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); tempDirs.push(dir); return dir; } async function withRouteServer( run: (baseUrl: string) => Promise, ): Promise { const server = http.createServer((req, res) => { const url = new URL(req.url ?? "/", "http://127.0.0.1"); if (tryHandleProjectFaviconRequest(url, res)) { return; } res.writeHead(404, { "Content-Type": "text/plain" }); res.end("Not Found"); }); await new Promise((resolve, reject) => { server.listen(0, "127.0.0.1", (error?: Error) => { if (error) { reject(error); return; } resolve(); }); }); const address = server.address(); if (typeof address !== "object" || address === null) { throw new Error("Expected server address to be an object"); } const baseUrl = `http://127.0.0.1:${address.port}`; try { await run(baseUrl); } finally { await new Promise((resolve, reject) => { server.close((error?: Error) => { if (error) { reject(error); return; } resolve(); }); }); } } async function request(baseUrl: string, pathname: string): Promise { const response = await fetch(`${baseUrl}${pathname}`); return { statusCode: response.status, contentType: response.headers.get("content-type"), body: await response.text(), }; } describe("tryHandleProjectFaviconRequest", () => { afterEach(() => { for (const dir of tempDirs.splice(0, tempDirs.length)) { fs.rmSync(dir, { recursive: true, force: true }); } }); it("returns 400 when cwd is missing", async () => { await withRouteServer(async (baseUrl) => { const response = await request(baseUrl, "/api/project-favicon"); expect(response.statusCode).toBe(400); expect(response.body).toBe("Missing cwd parameter"); }); }); it("serves a well-known favicon file from the project root", async () => { const projectDir = makeTempDir("t3code-favicon-route-root-"); fs.writeFileSync(path.join(projectDir, "favicon.svg"), "favicon", "utf8"); await withRouteServer(async (baseUrl) => { const pathname = `/api/project-favicon?cwd=${encodeURIComponent(projectDir)}`; const response = await request(baseUrl, pathname); expect(response.statusCode).toBe(200); expect(response.contentType).toContain("image/svg+xml"); expect(response.body).toBe("favicon"); }); }); it("resolves icon href from source files when no well-known favicon exists", async () => { const projectDir = makeTempDir("t3code-favicon-route-source-"); const iconPath = path.join(projectDir, "public", "brand", "logo.svg"); fs.mkdirSync(path.dirname(iconPath), { recursive: true }); fs.writeFileSync(path.join(projectDir, "index.html"), ''); fs.writeFileSync(iconPath, "brand", "utf8"); await withRouteServer(async (baseUrl) => { const pathname = `/api/project-favicon?cwd=${encodeURIComponent(projectDir)}`; const response = await request(baseUrl, pathname); expect(response.statusCode).toBe(200); expect(response.contentType).toContain("image/svg+xml"); expect(response.body).toBe("brand"); }); }); it("resolves icon link when href appears before rel in HTML", async () => { const projectDir = makeTempDir("t3code-favicon-route-html-order-"); const iconPath = path.join(projectDir, "public", "brand", "logo.svg"); fs.mkdirSync(path.dirname(iconPath), { recursive: true }); fs.writeFileSync(path.join(projectDir, "index.html"), ''); fs.writeFileSync(iconPath, "brand-html-order", "utf8"); await withRouteServer(async (baseUrl) => { const pathname = `/api/project-favicon?cwd=${encodeURIComponent(projectDir)}`; const response = await request(baseUrl, pathname); expect(response.statusCode).toBe(200); expect(response.contentType).toContain("image/svg+xml"); expect(response.body).toBe("brand-html-order"); }); }); it("resolves object-style icon metadata when href appears before rel", async () => { const projectDir = makeTempDir("t3code-favicon-route-obj-order-"); const iconPath = path.join(projectDir, "public", "brand", "obj.svg"); fs.mkdirSync(path.dirname(iconPath), { recursive: true }); fs.mkdirSync(path.join(projectDir, "src"), { recursive: true }); fs.writeFileSync( path.join(projectDir, "src", "root.tsx"), 'const links = [{ href: "/brand/obj.svg", rel: "icon" }];', "utf8", ); fs.writeFileSync(iconPath, "brand-obj-order", "utf8"); await withRouteServer(async (baseUrl) => { const pathname = `/api/project-favicon?cwd=${encodeURIComponent(projectDir)}`; const response = await request(baseUrl, pathname); expect(response.statusCode).toBe(200); expect(response.contentType).toContain("image/svg+xml"); expect(response.body).toBe("brand-obj-order"); }); }); it("serves a fallback favicon when no icon exists", async () => { const projectDir = makeTempDir("t3code-favicon-route-fallback-"); await withRouteServer(async (baseUrl) => { const pathname = `/api/project-favicon?cwd=${encodeURIComponent(projectDir)}`; const response = await request(baseUrl, pathname); expect(response.statusCode).toBe(200); expect(response.contentType).toContain("image/svg+xml"); expect(response.body).toContain('data-fallback="project-favicon"'); }); }); });