import { describe, it, expect, vi, afterEach } from "vitest"; import { render, screen, cleanup } from "@testing-library/react"; vi.mock("react-i18next", () => ({ useTranslation: () => ({ t: (key: string) => key }), })); import { VersionBadge } from "../../components/version-badge"; afterEach(cleanup); // `t` is mocked to the identity above, so every label below is the raw key. const RELEASE_URL = "https://github.com/Termix-SSH/Termix/releases/tag/release-2.6.1-tag"; const LINK_LABEL = "versionCheck.updateLinkLabel"; const UPDATE_TEXT = "dashboard.updateAvailable".toUpperCase(); describe("VersionBadge", () => { it("links to the release when an update is available", () => { render(); // Queried by its accessible name: the badge text alone ("UPDATE // AVAILABLE") never says where the link goes. const link = screen.getByRole("link", { name: LINK_LABEL }); expect(link.getAttribute("href")).toBe(RELEASE_URL); expect(link.getAttribute("target")).toBe("_blank"); expect(link.getAttribute("rel")).toContain("noopener"); expect(link.getAttribute("title")).toBe(LINK_LABEL); expect(link.textContent).toBe(UPDATE_TEXT); }); it("keeps the yellow update treatment on the link", () => { render(); const className = screen.getByRole("link").className; expect(className).toContain("bg-yellow-500/20"); expect(className).toContain("text-yellow-400"); expect(className).toContain("hover:underline"); }); it("stays an inert badge when the response carried no release URL", () => { // A dead anchor is worse than no anchor: it looks actionable and isn't. render(); expect(screen.queryByRole("link")).toBeNull(); expect(screen.getByText(UPDATE_TEXT)).toBeTruthy(); }); it("stays inert when up to date, even if a release URL is known", () => { render(); expect(screen.queryByRole("link")).toBeNull(); expect(screen.getByText("dashboardTab.stable")).toBeTruthy(); }); it("stays inert on the beta channel, which the release page does not serve", () => { render(); expect(screen.queryByRole("link")).toBeNull(); expect(screen.getByText("dashboard.beta".toUpperCase())).toBeTruthy(); }); // The dashboard's stats bar sits in a flex column, so its badge needs // `w-fit` to stop the background stretching the full column width. The // profile panel's badge does not. Both must come out of one component. it("appends the caller's className without dropping the status classes", () => { render( , ); const className = screen.getByRole("link").className; expect(className).toContain("w-fit"); expect(className).toContain("bg-yellow-500/20"); }); it("applies the className on the inert span too", () => { render(); const badge = screen.getByText("dashboardTab.stable"); expect(badge.tagName).toBe("SPAN"); expect(badge.className).toContain("w-fit"); expect(badge.className).toContain("bg-accent-brand/20"); }); it("emits no stray whitespace class when no className is passed", () => { render(); const badge = screen.getByText("dashboardTab.stable"); expect(badge.className).toBe(badge.className.trim()); }); }); it("does not claim the installed version is current when the update check failed", () => { const { container } = render(); expect(container.textContent).toBe(""); });