import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { ProjectIconPicker } from "./ProjectIconPicker";
import {
PROJECT_ICON_CATALOG,
ProjectIcon,
findProjectIcon,
resolveProjectIcon,
toEmojiIconRef,
} from "./projectIcons";
describe("project emoji catalogue", () => {
it("contains only stable Unicode emoji references and no bundled artwork metadata", () => {
expect(PROJECT_ICON_CATALOG.length).toBeGreaterThan(90);
for (const entry of PROJECT_ICON_CATALOG) {
expect(entry.set).toBe("emoji");
expect(entry.name).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/);
expect(entry.glyph).toBeTruthy();
expect(entry).not.toHaveProperty("assetSrc");
expect(entry).not.toHaveProperty("legal");
}
});
it("resolves known legacy references to emoji and unknown references to an initial", () => {
expect(resolveProjectIcon({ set: "devicon", name: "python" })?.glyph).toBe("🐍");
expect(toEmojiIconRef({ set: "lucide", name: "star" })).toEqual({ set: "emoji", name: "star" });
expect(findProjectIcon({ set: "devicon", name: "python" })).toBeNull();
const { getByRole } = render();
expect(getByRole("img", { name: "U" }).textContent).toBe("U");
expect(getByRole("img").dataset.resolvedProjectIcon).toBe("initial");
});
it("keeps legacy alphabet initials readable", () => {
const { getByRole } = render();
expect(getByRole("img", { name: "Z" }).textContent).toBe("Z");
});
it("uses the project initial when no icon is configured", () => {
const { getByRole } = render();
expect(getByRole("img", { name: "A" }).textContent).toBe("A");
expect(getByRole("img").dataset.projectIcon).toBe("none");
});
it("keeps the full emoji artwork available for grayscale watermark rendering", () => {
const { getByTestId } = render(
<>
>,
);
expect(getByTestId("product-icon").textContent).toBe("⭐");
expect(getByTestId("watermark-icon").textContent).toBe("⭐");
expect((getByTestId("watermark-icon") as HTMLElement).style.fontFamily).toContain("Color Emoji");
});
});
describe("ProjectIconPicker", () => {
function renderPicker(value = null as Parameters[0]["value"]) {
render();
}
it("shows phone-like emoji categories only", () => {
renderPicker();
for (const label of ["Smileys", "Animaux et nature", "Nourriture et boissons", "Activités", "Voyages et lieux", "Objets", "Symboles"]) {
expect(screen.getByText(label)).toBeTruthy();
}
expect(screen.queryByText("Technologies / Frameworks")).toBeNull();
expect(screen.queryByText("Lettres")).toBeNull();
});
it.each([
["python", "Snake"],
["docker", "Package"],
["space launch", "Rocket"],
["database", "Floppy disk"],
])("finds %s using the English search metadata", (query, label) => {
renderPicker();
fireEvent.change(screen.getByLabelText("Search icons in English"), { target: { value: query } });
expect(screen.getByRole("option", { name: label })).toBeTruthy();
});
it("selects and persists an emoji reference", () => {
const onApply = vi.fn();
render();
fireEvent.change(screen.getByLabelText("Search icons in English"), { target: { value: "rocket" } });
fireEvent.click(screen.getByRole("option", { name: "Rocket" }));
fireEvent.click(screen.getByRole("button", { name: "Appliquer" }));
expect(onApply).toHaveBeenCalledWith({ set: "emoji", name: "rocket" });
});
it("normalizes a known legacy selection before applying it", () => {
const onApply = vi.fn();
render();
fireEvent.click(screen.getByRole("button", { name: "Appliquer" }));
expect(onApply).toHaveBeenCalledWith({ set: "emoji", name: "snake" });
});
});