diff --git a/src/widgets/calendar/agenda.test.jsx b/src/widgets/calendar/agenda.test.jsx new file mode 100644 index 000000000..c69bb6f1c --- /dev/null +++ b/src/widgets/calendar/agenda.test.jsx @@ -0,0 +1,64 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { DateTime } from "luxon"; +import { describe, expect, it, vi } from "vitest"; + +const { EventStub, compareDateTimezoneStub } = vi.hoisted(() => ({ + EventStub: vi.fn(({ event, showDate, showTime }) => ( +
+ {event.title} +
+ )), + compareDateTimezoneStub: vi.fn( + (date, event) => date.startOf("day").toISODate() === event.date.startOf("day").toISODate(), + ), +})); + +vi.mock("./event", () => ({ + default: EventStub, + compareDateTimezone: compareDateTimezoneStub, +})); + +import Agenda from "./agenda"; + +describe("widgets/calendar/agenda", () => { + it("renders an empty placeholder when showDate is not set", () => { + const { container } = render(); + expect(container.textContent).toBe(""); + }); + + it("renders a no-events placeholder when there are no events in range", () => { + render(); + expect(screen.getByText("calendar.noEventsToday")).toBeInTheDocument(); + expect(EventStub).toHaveBeenCalled(); + }); + + it("filters by previousDays, sorts, and enforces maxEvents", () => { + const showDate = DateTime.local(2099, 1, 2).startOf("day"); + const service = { widget: { previousDays: 0, maxEvents: 2, showTime: true } }; + + const events = { + old: { title: "Old", date: DateTime.local(2099, 1, 1, 0, 0), color: "gray" }, + a: { title: "A", date: DateTime.local(2099, 1, 2, 10, 0), color: "gray" }, + b: { title: "B", date: DateTime.local(2099, 1, 3, 10, 0), color: "gray" }, + c: { title: "C", date: DateTime.local(2099, 1, 4, 10, 0), color: "gray" }, + }; + + render(); + + // Old is filtered out, C is sliced out by maxEvents. + expect(screen.queryByText("Old")).toBeNull(); + expect(screen.getByText("A")).toBeInTheDocument(); + expect(screen.getByText("B")).toBeInTheDocument(); + expect(screen.queryByText("C")).toBeNull(); + + const renderedEvents = screen.getAllByTestId("event"); + expect(renderedEvents).toHaveLength(2); + + // showTime is only true for the selected day. + const [first, second] = renderedEvents; + expect(first).toHaveAttribute("data-showtime", "1"); + expect(second).toHaveAttribute("data-showtime", "0"); + }); +}); diff --git a/src/widgets/calendar/event.test.jsx b/src/widgets/calendar/event.test.jsx new file mode 100644 index 000000000..56b37b159 --- /dev/null +++ b/src/widgets/calendar/event.test.jsx @@ -0,0 +1,56 @@ +// @vitest-environment jsdom + +import { fireEvent, render, screen } from "@testing-library/react"; +import { DateTime } from "luxon"; +import { describe, expect, it } from "vitest"; + +import Event, { compareDateTimezone } from "./event"; + +describe("widgets/calendar/event", () => { + it("renders an anchor when a url is provided and toggles additional text on hover", () => { + const date = DateTime.fromISO("2099-01-01T13:00:00.000Z").setZone("utc"); + + render( + , + ); + + const link = screen.getByRole("link", { name: /primary/i }); + expect(link).toHaveAttribute("href", "https://example.com"); + expect(link).toHaveAttribute("target", "_blank"); + expect(link).toHaveAttribute("rel", "noopener noreferrer"); + + // time is rendered when showTime=true + expect(link.textContent).toContain("13:00"); + + // default shows title, hover shows `additional` + expect(screen.getByText("Primary")).toBeInTheDocument(); + expect(screen.queryByText("More info")).toBeNull(); + + fireEvent.mouseEnter(link); + expect(screen.getByText("More info")).toBeInTheDocument(); + + fireEvent.mouseLeave(link); + expect(screen.getByText("Primary")).toBeInTheDocument(); + + // completed icon from react-icons renders an SVG + expect(link.querySelector("svg")).toBeTruthy(); + }); + + it("compareDateTimezone matches dates by day", () => { + const day = DateTime.fromISO("2099-01-01T00:00:00.000Z").setZone("utc"); + expect(compareDateTimezone(day, { date: DateTime.fromISO("2099-01-01T23:59:00.000Z").setZone("utc") })).toBe(true); + expect(compareDateTimezone(day, { date: DateTime.fromISO("2099-01-02T00:00:00.000Z").setZone("utc") })).toBe(false); + }); +}); diff --git a/src/widgets/calendar/integrations/ical.test.jsx b/src/widgets/calendar/integrations/ical.test.jsx new file mode 100644 index 000000000..cd1764c37 --- /dev/null +++ b/src/widgets/calendar/integrations/ical.test.jsx @@ -0,0 +1,64 @@ +// @vitest-environment jsdom + +import { render, waitFor } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +const { useWidgetAPI } = vi.hoisted(() => ({ + useWidgetAPI: vi.fn(), +})); + +vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI })); + +import Integration from "./ical"; + +describe("widgets/calendar/integrations/ical", () => { + it("adds parsed events within the date range", async () => { + useWidgetAPI.mockReturnValue({ + data: { + data: [ + "BEGIN:VCALENDAR", + "VERSION:2.0", + "PRODID:-//Test//EN", + "BEGIN:VEVENT", + "UID:uid1", + "DTSTAMP:20990101T000000Z", + "DTSTART:20990101T130000Z", + "DTEND:20990101T140000Z", + "SUMMARY:Test Event", + "LOCATION:Office", + "URL:https://example.com", + "END:VEVENT", + "END:VCALENDAR", + "", + ].join("\n"), + }, + error: undefined, + }); + + const setEvents = vi.fn(); + render( + , + ); + + await waitFor(() => expect(setEvents).toHaveBeenCalled()); + + const updater = setEvents.mock.calls[0][0]; + const next = updater({}); + const entries = Object.values(next); + expect(entries).toHaveLength(1); + + const [event] = entries; + expect(event.title).toBe("Work: Test Event"); + expect(event.color).toBe("blue"); + expect(event.type).toBe("ical"); + expect(event.additional).toBe("Office"); + expect(event.url).toBe("https://example.com"); + expect(event.isCompleted).toBe(false); + }); +}); diff --git a/src/widgets/calendar/integrations/lidarr.test.jsx b/src/widgets/calendar/integrations/lidarr.test.jsx new file mode 100644 index 000000000..88ce51c51 --- /dev/null +++ b/src/widgets/calendar/integrations/lidarr.test.jsx @@ -0,0 +1,39 @@ +// @vitest-environment jsdom + +import { render, waitFor } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +const { useWidgetAPI } = vi.hoisted(() => ({ + useWidgetAPI: vi.fn(), +})); + +vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI })); + +import Integration from "./lidarr"; + +describe("widgets/calendar/integrations/lidarr", () => { + it("adds release events", async () => { + useWidgetAPI.mockReturnValue({ + data: [ + { artist: { artistName: "Artist" }, title: "Album", releaseDate: "2099-01-01T00:00:00.000Z", grabbed: true }, + ], + error: undefined, + }); + + const setEvents = vi.fn(); + render( + , + ); + + await waitFor(() => expect(setEvents).toHaveBeenCalled()); + + const next = setEvents.mock.calls[0][0]({}); + expect(Object.keys(next)).toEqual(["Artist - Album"]); + expect(next["Artist - Album"].isCompleted).toBe(true); + }); +}); diff --git a/src/widgets/calendar/integrations/radarr.test.jsx b/src/widgets/calendar/integrations/radarr.test.jsx new file mode 100644 index 000000000..b5c4568b9 --- /dev/null +++ b/src/widgets/calendar/integrations/radarr.test.jsx @@ -0,0 +1,49 @@ +// @vitest-environment jsdom + +import { render, waitFor } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +const { useWidgetAPI } = vi.hoisted(() => ({ + useWidgetAPI: vi.fn(), +})); + +vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI })); + +import Integration from "./radarr"; + +describe("widgets/calendar/integrations/radarr", () => { + it("adds cinema/physical/digital events", async () => { + useWidgetAPI.mockReturnValue({ + data: [ + { + title: "Movie", + titleSlug: "movie", + hasFile: false, + inCinemas: "2099-01-01T00:00:00.000Z", + physicalRelease: "2099-01-02T00:00:00.000Z", + digitalRelease: "2099-01-03T00:00:00.000Z", + }, + ], + error: undefined, + }); + + const setEvents = vi.fn(); + render( + , + ); + + await waitFor(() => expect(setEvents).toHaveBeenCalled()); + + const next = setEvents.mock.calls[0][0]({}); + const keys = Object.keys(next); + expect(keys.some((k) => k.includes("calendar.inCinemas"))).toBe(true); + expect(keys.some((k) => k.includes("calendar.physicalRelease"))).toBe(true); + expect(keys.some((k) => k.includes("calendar.digitalRelease"))).toBe(true); + expect(Object.values(next)[0].url).toBe("https://radarr.example/movie/movie"); + }); +}); diff --git a/src/widgets/calendar/integrations/readarr.test.jsx b/src/widgets/calendar/integrations/readarr.test.jsx new file mode 100644 index 000000000..f3955a833 --- /dev/null +++ b/src/widgets/calendar/integrations/readarr.test.jsx @@ -0,0 +1,48 @@ +// @vitest-environment jsdom + +import { render, waitFor } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +const { useWidgetAPI } = vi.hoisted(() => ({ + useWidgetAPI: vi.fn(), +})); + +vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI })); + +import Integration from "./readarr"; + +describe("widgets/calendar/integrations/readarr", () => { + it("adds release events with author name", async () => { + useWidgetAPI.mockReturnValue({ + data: [ + { + title: "Book", + seriesTitle: "Series", + releaseDate: "2099-01-01T00:00:00.000Z", + grabbed: false, + author: { authorName: "Author" }, + authorTitle: "Author Book", + }, + ], + error: undefined, + }); + + const setEvents = vi.fn(); + render( + , + ); + + await waitFor(() => expect(setEvents).toHaveBeenCalled()); + + const next = setEvents.mock.calls[0][0]({}); + const [key] = Object.keys(next); + expect(key).toContain("Author"); + expect(key).toContain("Book"); + expect(key).toContain("(Series)"); + }); +}); diff --git a/src/widgets/calendar/integrations/sonarr.test.jsx b/src/widgets/calendar/integrations/sonarr.test.jsx new file mode 100644 index 000000000..748e35e18 --- /dev/null +++ b/src/widgets/calendar/integrations/sonarr.test.jsx @@ -0,0 +1,48 @@ +// @vitest-environment jsdom + +import { render, waitFor } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +const { useWidgetAPI } = vi.hoisted(() => ({ + useWidgetAPI: vi.fn(), +})); + +vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI })); + +import Integration from "./sonarr"; + +describe("widgets/calendar/integrations/sonarr", () => { + it("adds episode events", async () => { + useWidgetAPI.mockReturnValue({ + data: [ + { + series: { title: "Show", titleSlug: "show" }, + seasonNumber: 1, + episodeNumber: 2, + airDateUtc: "2099-01-01T00:00:00.000Z", + hasFile: true, + }, + ], + error: undefined, + }); + + const setEvents = vi.fn(); + render( + , + ); + + await waitFor(() => expect(setEvents).toHaveBeenCalled()); + + const next = setEvents.mock.calls[0][0]({}); + const [entry] = Object.values(next); + expect(entry.title).toBe("Show"); + expect(entry.additional).toBe("S1 E2"); + expect(entry.url).toBe("https://sonarr.example/series/show"); + expect(entry.isCompleted).toBe(true); + }); +}); diff --git a/src/widgets/calendar/monthly.test.jsx b/src/widgets/calendar/monthly.test.jsx new file mode 100644 index 000000000..f9dbc5eee --- /dev/null +++ b/src/widgets/calendar/monthly.test.jsx @@ -0,0 +1,71 @@ +// @vitest-environment jsdom + +import { fireEvent, render, screen } from "@testing-library/react"; +import { DateTime } from "luxon"; +import { describe, expect, it, vi } from "vitest"; + +const { EventStub, compareDateTimezoneStub } = vi.hoisted(() => ({ + EventStub: vi.fn(({ event }) =>
{event.title}
), + compareDateTimezoneStub: vi.fn( + (date, event) => date.startOf("day").toISODate() === event.date.startOf("day").toISODate(), + ), +})); + +vi.mock("./event", () => ({ + default: EventStub, + compareDateTimezone: compareDateTimezoneStub, +})); + +import Monthly from "./monthly"; + +describe("widgets/calendar/monthly", () => { + it("renders an empty placeholder when showDate is not set", () => { + const { container } = render( + {}} + currentDate={DateTime.now()} + />, + ); + expect(container.textContent).toBe(""); + }); + + it("navigates months and renders day events", () => { + const setShowDate = vi.fn(); + const showDate = DateTime.local(2099, 2, 15).startOf("day"); + const currentDate = DateTime.local(2099, 2, 4).startOf("day"); + const service = { widget: { maxEvents: 10, showTime: false } }; + + const events = { + e1: { title: "Today Event", date: DateTime.local(2099, 2, 15, 10, 0), color: "zinc" }, + e2: { title: "Other Event", date: DateTime.local(2099, 2, 16, 10, 0), color: "zinc" }, + }; + + render( + , + ); + + expect(screen.getByText("Today Event")).toBeInTheDocument(); + expect(screen.queryByText("Other Event")).toBeNull(); + + fireEvent.click(screen.getByRole("button", { name: ">" })); + expect(setShowDate).toHaveBeenCalled(); + expect(setShowDate.mock.calls[0][0].toISODate()).toBe(showDate.plus({ months: 1 }).startOf("day").toISODate()); + + fireEvent.click(screen.getByRole("button", { name: "<" })); + expect(setShowDate.mock.calls[1][0].toISODate()).toBe(showDate.minus({ months: 1 }).startOf("day").toISODate()); + + fireEvent.click(screen.getByRole("button", { name: showDate.toFormat("MMMM y") })); + expect(setShowDate.mock.calls[2][0].toISODate()).toBe(currentDate.startOf("day").toISODate()); + }); +}); diff --git a/src/widgets/jdownloader/tools.test.js b/src/widgets/jdownloader/tools.test.js new file mode 100644 index 000000000..0b44f871d --- /dev/null +++ b/src/widgets/jdownloader/tools.test.js @@ -0,0 +1,39 @@ +import crypto from "crypto"; +import { describe, expect, it, vi } from "vitest"; + +import { createEncryptionToken, decrypt, encrypt, sha256, uniqueRid, validateRid } from "./tools"; + +describe("widgets/jdownloader/tools", () => { + it("sha256 returns a 32-byte buffer", () => { + expect(sha256("hello")).toBeInstanceOf(Buffer); + expect(sha256("hello")).toHaveLength(32); + }); + + it("uniqueRid returns an integer", () => { + vi.spyOn(Math, "random").mockReturnValueOnce(0.123); + expect(uniqueRid()).toBeTypeOf("number"); + expect(Number.isInteger(uniqueRid())).toBe(true); + Math.random.mockRestore(); + }); + + it("validateRid throws when mismatched", () => { + expect(() => validateRid({ rid: 1 }, 2)).toThrow(/RequestID mismatch/i); + expect(validateRid({ rid: 5 }, 5)).toEqual({ rid: 5 }); + }); + + it("encrypt/decrypt roundtrip with a 32-byte ivKey", () => { + const ivKey = crypto.randomBytes(32); + const plaintext = "secret"; + const encrypted = encrypt(plaintext, ivKey); + const decrypted = decrypt(encrypted, ivKey); + expect(decrypted).toBe(plaintext); + }); + + it("createEncryptionToken merges buffers and hashes", () => { + const oldToken = Buffer.from("aa", "hex"); + const updateToken = "bb"; + const token = createEncryptionToken(oldToken, updateToken); + expect(token).toBeInstanceOf(Buffer); + expect(token).toHaveLength(32); + }); +}); diff --git a/src/widgets/openmediavault/methods/downloader_get_downloadlist.test.jsx b/src/widgets/openmediavault/methods/downloader_get_downloadlist.test.jsx new file mode 100644 index 000000000..7875caa20 --- /dev/null +++ b/src/widgets/openmediavault/methods/downloader_get_downloadlist.test.jsx @@ -0,0 +1,42 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import { renderWithProviders } from "test-utils/render-with-providers"; + +const { useWidgetAPI } = vi.hoisted(() => ({ + useWidgetAPI: vi.fn(), +})); + +vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI })); + +import Component from "./downloader_get_downloadlist"; + +describe("widgets/openmediavault/methods/downloader_get_downloadlist", () => { + it("renders '-' values when data is missing", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: undefined }); + + renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(screen.getByText("openmediavault.downloading")).toBeInTheDocument(); + expect(screen.getByText("openmediavault.total")).toBeInTheDocument(); + expect(screen.getAllByText("-").length).toBeGreaterThan(0); + }); + + it("counts downloading and total items", () => { + useWidgetAPI.mockReturnValue({ + data: { response: { data: [{ downloading: true }, { downloading: false }, { downloading: true }] } }, + error: undefined, + }); + + renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(screen.getByText("2")).toBeInTheDocument(); + expect(screen.getByText("3")).toBeInTheDocument(); + }); +}); diff --git a/src/widgets/openmediavault/methods/services_get_status.test.jsx b/src/widgets/openmediavault/methods/services_get_status.test.jsx new file mode 100644 index 000000000..d77d02cbb --- /dev/null +++ b/src/widgets/openmediavault/methods/services_get_status.test.jsx @@ -0,0 +1,35 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import { renderWithProviders } from "test-utils/render-with-providers"; + +const { useWidgetAPI } = vi.hoisted(() => ({ + useWidgetAPI: vi.fn(), +})); + +vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI })); + +import Component from "./services_get_status"; + +describe("widgets/openmediavault/methods/services_get_status", () => { + it("counts running/stopped/total services", () => { + useWidgetAPI.mockReturnValue({ + data: { response: { data: [{ running: true }, { running: false }, { running: true }] } }, + error: undefined, + }); + + renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(screen.getByText("openmediavault.running")).toBeInTheDocument(); + expect(screen.getByText("openmediavault.stopped")).toBeInTheDocument(); + expect(screen.getByText("openmediavault.total")).toBeInTheDocument(); + + expect(screen.getByText("2")).toBeInTheDocument(); + expect(screen.getByText("1")).toBeInTheDocument(); + expect(screen.getByText("3")).toBeInTheDocument(); + }); +}); diff --git a/src/widgets/openmediavault/methods/smart_get_list.test.jsx b/src/widgets/openmediavault/methods/smart_get_list.test.jsx new file mode 100644 index 000000000..5ba7546c6 --- /dev/null +++ b/src/widgets/openmediavault/methods/smart_get_list.test.jsx @@ -0,0 +1,41 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import { renderWithProviders } from "test-utils/render-with-providers"; + +const { useWidgetAPI } = vi.hoisted(() => ({ + useWidgetAPI: vi.fn(), +})); + +vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI })); + +import Component from "./smart_get_list"; + +describe("widgets/openmediavault/methods/smart_get_list", () => { + it("counts passed/failed monitored disks", () => { + useWidgetAPI.mockReturnValue({ + data: { + response: { + output: JSON.stringify({ + data: [ + { monitor: true, overallstatus: "GOOD" }, + { monitor: true, overallstatus: "BAD" }, + { monitor: false, overallstatus: "BAD" }, + ], + }), + }, + }, + error: undefined, + }); + + renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(screen.getByText("openmediavault.passed")).toBeInTheDocument(); + expect(screen.getByText("openmediavault.failed")).toBeInTheDocument(); + expect(screen.getAllByText("1")).toHaveLength(2); + }); +}); diff --git a/src/widgets/openwrt/methods/interface.test.jsx b/src/widgets/openwrt/methods/interface.test.jsx new file mode 100644 index 000000000..80d87ec9c --- /dev/null +++ b/src/widgets/openwrt/methods/interface.test.jsx @@ -0,0 +1,37 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import { renderWithProviders } from "test-utils/render-with-providers"; + +const { useWidgetAPI } = vi.hoisted(() => ({ + useWidgetAPI: vi.fn(), +})); + +vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI })); + +import Component from "./interface"; + +describe("widgets/openwrt/methods/interface", () => { + it("returns null while loading", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: undefined }); + const { container } = renderWithProviders(); + expect(container.firstChild).toBeNull(); + }); + + it("renders status and byte counters when loaded", () => { + useWidgetAPI.mockReturnValue({ data: { up: true, bytesTx: 100, bytesRx: 200 }, error: undefined }); + + renderWithProviders(, { settings: { hideErrors: false } }); + + expect(screen.getByText("widget.status")).toBeInTheDocument(); + expect(screen.getByText("openwrt.bytesTx")).toBeInTheDocument(); + expect(screen.getByText("openwrt.bytesRx")).toBeInTheDocument(); + + // t("common.bytes") mock returns the numeric value as a string. + expect(screen.getByText("100")).toBeInTheDocument(); + expect(screen.getByText("200")).toBeInTheDocument(); + expect(screen.getByText("openwrt.up")).toBeInTheDocument(); + }); +}); diff --git a/src/widgets/openwrt/methods/system.test.jsx b/src/widgets/openwrt/methods/system.test.jsx new file mode 100644 index 000000000..122665243 --- /dev/null +++ b/src/widgets/openwrt/methods/system.test.jsx @@ -0,0 +1,33 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import { renderWithProviders } from "test-utils/render-with-providers"; + +const { useWidgetAPI } = vi.hoisted(() => ({ + useWidgetAPI: vi.fn(), +})); + +vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI })); + +import Component from "./system"; + +describe("widgets/openwrt/methods/system", () => { + it("returns null while loading", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: undefined }); + const { container } = renderWithProviders(); + expect(container.firstChild).toBeNull(); + }); + + it("renders uptime and cpu load when loaded", () => { + useWidgetAPI.mockReturnValue({ data: { uptime: 123, cpuLoad: "0.5" }, error: undefined }); + + renderWithProviders(, { settings: { hideErrors: false } }); + + expect(screen.getByText("openwrt.uptime")).toBeInTheDocument(); + expect(screen.getByText("openwrt.cpuLoad")).toBeInTheDocument(); + expect(screen.getByText("123")).toBeInTheDocument(); + expect(screen.getByText("0.5")).toBeInTheDocument(); + }); +}); diff --git a/src/widgets/truenas/pool.test.jsx b/src/widgets/truenas/pool.test.jsx new file mode 100644 index 000000000..2ae31b323 --- /dev/null +++ b/src/widgets/truenas/pool.test.jsx @@ -0,0 +1,19 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import Pool from "./pool"; + +describe("widgets/truenas/pool", () => { + it("renders pool name, usage percent, and status color", () => { + const { container } = render(); + expect(screen.getByText("tank")).toBeInTheDocument(); + + // 50 / 100 => 50% + expect(container.textContent).toContain("(50%)"); + + // status color reflects healthy=false + expect(container.querySelector(".bg-yellow-500")).toBeTruthy(); + }); +});