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("");
});