diff --git a/src/widgets/booklore/component.test.jsx b/src/widgets/booklore/component.test.jsx new file mode 100644 index 000000000..efa001f9e --- /dev/null +++ b/src/widgets/booklore/component.test.jsx @@ -0,0 +1,50 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { beforeEach, 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 "./component"; + +describe("widgets/booklore/component", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders placeholders while loading", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: undefined }); + + const { container } = renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(4); + expect(screen.getByText("booklore.libraries")).toBeInTheDocument(); + expect(screen.getByText("booklore.books")).toBeInTheDocument(); + expect(screen.getByText("booklore.reading")).toBeInTheDocument(); + expect(screen.getByText("booklore.finished")).toBeInTheDocument(); + }); + + it("renders values with nullish fallback defaults", () => { + useWidgetAPI.mockReturnValue({ + data: { libraries: 1, books: 2, finished: 4 }, // reading missing -> 0 + error: undefined, + }); + + renderWithProviders(, { settings: { hideErrors: false } }); + + expect(screen.getByText("1")).toBeInTheDocument(); + expect(screen.getByText("2")).toBeInTheDocument(); + expect(screen.getByText("0")).toBeInTheDocument(); + expect(screen.getByText("4")).toBeInTheDocument(); + }); +}); diff --git a/src/widgets/firefly/component.test.jsx b/src/widgets/firefly/component.test.jsx new file mode 100644 index 000000000..2d4dcdf44 --- /dev/null +++ b/src/widgets/firefly/component.test.jsx @@ -0,0 +1,74 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { beforeEach, 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 "./component"; + +describe("widgets/firefly/component", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders placeholders while loading", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: undefined }); + + const { container } = renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(2); + expect(screen.getByText("firefly.networth")).toBeInTheDocument(); + expect(screen.getByText("firefly.budget")).toBeInTheDocument(); + }); + + it("renders error UI when either request errors", () => { + useWidgetAPI + .mockReturnValueOnce({ data: undefined, error: { message: "nope" } }) // summary + .mockReturnValueOnce({ data: undefined, error: undefined }); // budgets + + renderWithProviders(, { settings: { hideErrors: false } }); + + // The widget uses a string error, which Error normalizes to { message }. + expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0); + expect(screen.getByText("Failed to load Firefly account summary and budgets")).toBeInTheDocument(); + }); + + it("renders net worth and budget summary", () => { + useWidgetAPI + .mockReturnValueOnce({ + data: { "net-worth-in-EUR": { value_parsed: "100" } }, + error: undefined, + }) + .mockReturnValueOnce({ + data: { + data: [ + { + type: "available_budgets", + attributes: { + amount: "100", + currency_symbol: "$", + spent_in_budgets: [{ sum: "-10" }], + }, + }, + ], + }, + error: undefined, + }); + + renderWithProviders(, { settings: { hideErrors: false } }); + + expect(screen.getByText("100")).toBeInTheDocument(); + expect(screen.getByText("$ 10 / $ 100")).toBeInTheDocument(); + }); +}); diff --git a/src/widgets/jellystat/component.test.jsx b/src/widgets/jellystat/component.test.jsx new file mode 100644 index 000000000..ecb822496 --- /dev/null +++ b/src/widgets/jellystat/component.test.jsx @@ -0,0 +1,65 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { beforeEach, 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 "./component"; + +describe("widgets/jellystat/component", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("defaults invalid days to 30 and renders placeholders while loading", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: undefined }); + + const service = { widget: { type: "jellystat", days: -1 } }; + const { container } = renderWithProviders(, { settings: { hideErrors: false } }); + + expect(service.widget.days).toBe(30); + expect(useWidgetAPI).toHaveBeenCalledWith(service.widget, "getViewsByLibraryType", { days: 30 }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(4); + expect(screen.getByText("jellystat.songs")).toBeInTheDocument(); + expect(screen.getByText("jellystat.movies")).toBeInTheDocument(); + expect(screen.getByText("jellystat.episodes")).toBeInTheDocument(); + expect(screen.getByText("jellystat.other")).toBeInTheDocument(); + }); + + it("renders error UI when widget API errors", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } }); + + renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0); + }); + + it("renders values when loaded", () => { + useWidgetAPI.mockReturnValue({ + data: { Audio: 1, Movie: 2, Series: 3, Other: 4 }, + error: undefined, + }); + + const { container } = renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(4); + expect(screen.getByText("1")).toBeInTheDocument(); + expect(screen.getByText("2")).toBeInTheDocument(); + expect(screen.getByText("3")).toBeInTheDocument(); + expect(screen.getByText("4")).toBeInTheDocument(); + }); +}); diff --git a/src/widgets/nextcloud/component.test.jsx b/src/widgets/nextcloud/component.test.jsx new file mode 100644 index 000000000..e69d219c9 --- /dev/null +++ b/src/widgets/nextcloud/component.test.jsx @@ -0,0 +1,83 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { beforeEach, 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 "./component"; + +describe("widgets/nextcloud/component", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders default placeholders (no cpu/memory blocks when fields are unset)", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: undefined }); + + const { container } = renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(4); + expect(screen.queryByText("nextcloud.cpuload")).toBeNull(); + expect(screen.queryByText("nextcloud.memoryusage")).toBeNull(); + expect(screen.getByText("nextcloud.freespace")).toBeInTheDocument(); + expect(screen.getByText("nextcloud.activeusers")).toBeInTheDocument(); + expect(screen.getByText("nextcloud.numfiles")).toBeInTheDocument(); + expect(screen.getByText("nextcloud.numshares")).toBeInTheDocument(); + }); + + it("respects widget.fields and renders computed values", () => { + useWidgetAPI.mockReturnValue({ + data: { + ocs: { + data: { + nextcloud: { + system: { + cpuload: [0.5], + mem_total: "100", + mem_free: "50", + freespace: 1024, + }, + storage: { num_files: 1 }, + shares: { num_shares: 2 }, + }, + activeUsers: { last24hours: 3 }, + }, + }, + }, + error: undefined, + }); + + // 4 fields triggers the legacy behavior where CPU + memory are shown; + // Container then filters to exactly these fields. + const service = { + widget: { type: "nextcloud", fields: ["cpuload", "memoryusage", "freespace", "activeusers"] }, + }; + + const { container } = renderWithProviders(, { settings: { hideErrors: false } }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(4); + expect(screen.getByText("nextcloud.cpuload")).toBeInTheDocument(); + expect(screen.getByText("nextcloud.memoryusage")).toBeInTheDocument(); + expect(screen.getByText("nextcloud.freespace")).toBeInTheDocument(); + expect(screen.getByText("nextcloud.activeusers")).toBeInTheDocument(); + expect(screen.queryByText("nextcloud.numfiles")).toBeNull(); + expect(screen.queryByText("nextcloud.numshares")).toBeNull(); + + // Values: cpu load 0.5, memory usage 50, freespace 1024, active users 3. + expect(screen.getByText("0.5")).toBeInTheDocument(); + expect(screen.getByText("50")).toBeInTheDocument(); + expect(screen.getByText("1024")).toBeInTheDocument(); + expect(screen.getByText("3")).toBeInTheDocument(); + }); +}); diff --git a/src/widgets/peanut/component.test.jsx b/src/widgets/peanut/component.test.jsx new file mode 100644 index 000000000..b7a0b1423 --- /dev/null +++ b/src/widgets/peanut/component.test.jsx @@ -0,0 +1,52 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { beforeEach, 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 "./component"; + +describe("widgets/peanut/component", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders placeholders while loading", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: undefined }); + + const { container } = renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(3); + expect(screen.getByText("peanut.battery_charge")).toBeInTheDocument(); + expect(screen.getByText("peanut.ups_load")).toBeInTheDocument(); + expect(screen.getByText("peanut.ups_status")).toBeInTheDocument(); + }); + + it("renders legacy field mapping and status translation", () => { + useWidgetAPI.mockReturnValue({ + data: { + "battery.charge": 55, + "ups.load": 12, + "ups.status": "OL", + }, + error: undefined, + }); + + renderWithProviders(, { settings: { hideErrors: false } }); + + expect(screen.getByText("55")).toBeInTheDocument(); + expect(screen.getByText("12")).toBeInTheDocument(); + expect(screen.getByText("peanut.online")).toBeInTheDocument(); + }); +}); diff --git a/src/widgets/proxmoxbackupserver/component.test.jsx b/src/widgets/proxmoxbackupserver/component.test.jsx new file mode 100644 index 000000000..c4a2ddd0a --- /dev/null +++ b/src/widgets/proxmoxbackupserver/component.test.jsx @@ -0,0 +1,77 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { beforeEach, 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 "./component"; + +describe("widgets/proxmoxbackupserver/component", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders placeholders while loading", () => { + useWidgetAPI + .mockReturnValueOnce({ data: undefined, error: undefined }) // datastore + .mockReturnValueOnce({ data: undefined, error: undefined }) // tasks + .mockReturnValueOnce({ data: undefined, error: undefined }); // host + + const { container } = renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(4); + expect(screen.getByText("proxmoxbackupserver.datastore_usage")).toBeInTheDocument(); + expect(screen.getByText("proxmoxbackupserver.failed_tasks_24h")).toBeInTheDocument(); + expect(screen.getByText("proxmoxbackupserver.cpu_usage")).toBeInTheDocument(); + expect(screen.getByText("proxmoxbackupserver.memory_usage")).toBeInTheDocument(); + }); + + it("renders error UI when any endpoint errors", () => { + useWidgetAPI + .mockReturnValueOnce({ data: undefined, error: undefined }) + .mockReturnValueOnce({ data: undefined, error: { message: "nope" } }) + .mockReturnValueOnce({ data: undefined, error: undefined }); + + renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0); + }); + + it("renders computed values and caps failed tasks at 99+", () => { + useWidgetAPI + .mockReturnValueOnce({ + data: { + data: [ + { store: "ds1", used: 50, total: 100 }, + { store: "ds2", used: 25, total: 50 }, + ], + }, + error: undefined, + }) + .mockReturnValueOnce({ data: { total: 1000 }, error: undefined }) + .mockReturnValueOnce({ data: { data: { cpu: 0.2, memory: { used: 1, total: 4 } } }, error: undefined }); + + renderWithProviders(, { + settings: { hideErrors: false }, + }); + + // datastore usage for ds2: 25/50*100 = 50 + expect(screen.getByText("50")).toBeInTheDocument(); + expect(screen.getByText("20")).toBeInTheDocument(); // cpu usage + expect(screen.getByText("25")).toBeInTheDocument(); // memory usage + expect(screen.getByText("99+")).toBeInTheDocument(); + }); +}); diff --git a/src/widgets/rutorrent/component.test.jsx b/src/widgets/rutorrent/component.test.jsx new file mode 100644 index 000000000..36ab86ae0 --- /dev/null +++ b/src/widgets/rutorrent/component.test.jsx @@ -0,0 +1,63 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { beforeEach, 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 "./component"; + +describe("widgets/rutorrent/component", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders placeholders while loading", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: undefined }); + + const { container } = renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(3); + expect(screen.getByText("rutorrent.active")).toBeInTheDocument(); + expect(screen.getByText("rutorrent.upload")).toBeInTheDocument(); + expect(screen.getByText("rutorrent.download")).toBeInTheDocument(); + }); + + it("renders error UI when widget API errors", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } }); + + renderWithProviders(, { settings: { hideErrors: false } }); + + expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0); + }); + + it("renders computed active/upload/download values", () => { + useWidgetAPI.mockReturnValue({ + data: [ + { "d.get_state": "1", "d.get_up_rate": "10", "d.get_down_rate": "5" }, + { "d.get_state": "0", "d.get_up_rate": "20", "d.get_down_rate": "15" }, + { "d.get_state": "1", "d.get_up_rate": "0", "d.get_down_rate": "0" }, + ], + error: undefined, + }); + + const { container } = renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(3); + expect(screen.getByText("2")).toBeInTheDocument(); // active torrents + expect(screen.getByText("30")).toBeInTheDocument(); // upload sum (common.byterate mocked) + expect(screen.getByText("20")).toBeInTheDocument(); // download sum (common.byterate mocked) + }); +}); diff --git a/src/widgets/swagdashboard/component.test.jsx b/src/widgets/swagdashboard/component.test.jsx new file mode 100644 index 000000000..6c6a83ac8 --- /dev/null +++ b/src/widgets/swagdashboard/component.test.jsx @@ -0,0 +1,53 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { beforeEach, 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 "./component"; + +describe("widgets/swagdashboard/component", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders placeholders while loading", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: undefined }); + + const { container } = renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(4); + expect(screen.getByText("swagdashboard.proxied")).toBeInTheDocument(); + expect(screen.getByText("swagdashboard.auth")).toBeInTheDocument(); + expect(screen.getByText("swagdashboard.outdated")).toBeInTheDocument(); + expect(screen.getByText("swagdashboard.banned")).toBeInTheDocument(); + }); + + it("renders values when loaded", () => { + useWidgetAPI.mockReturnValue({ + data: { proxied: 1, auth: 2, outdated: 3, banned: 4 }, + error: undefined, + }); + + const { container } = renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(4); + expect(screen.getByText("1")).toBeInTheDocument(); + expect(screen.getByText("2")).toBeInTheDocument(); + expect(screen.getByText("3")).toBeInTheDocument(); + expect(screen.getByText("4")).toBeInTheDocument(); + }); +}); diff --git a/src/widgets/truenas/component.test.jsx b/src/widgets/truenas/component.test.jsx new file mode 100644 index 000000000..bf9697259 --- /dev/null +++ b/src/widgets/truenas/component.test.jsx @@ -0,0 +1,81 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { beforeEach, 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, +})); + +// Pool is rendered outside of the main Container; stub it to a simple marker. +vi.mock("widgets/truenas/pool", () => ({ + default: ({ name, healthy, allocated, free }) => ( +
+ ), +})); + +import Component from "./component"; + +describe("widgets/truenas/component", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders placeholders while loading (no pools)", () => { + useWidgetAPI.mockImplementation(() => ({ data: undefined, error: undefined })); + + const { container } = renderWithProviders(, { + settings: { hideErrors: false }, + }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(3); + expect(screen.getByText("truenas.load")).toBeInTheDocument(); + expect(screen.getByText("truenas.uptime")).toBeInTheDocument(); + expect(screen.getByText("truenas.alerts")).toBeInTheDocument(); + expect(screen.queryByTestId("truenas-pool")).toBeNull(); + }); + + it("renders values and pool list when enablePools is on and data is present", () => { + useWidgetAPI.mockImplementation((widget, endpoint) => { + if (endpoint === "alerts") return { data: { pending: 7 }, error: undefined }; + if (endpoint === "status") return { data: { loadavg: [1.23], uptime_seconds: 3600 }, error: undefined }; + if (endpoint === "pools") return { data: [{ id: "1", name: "tank", healthy: true }], error: undefined }; + if (endpoint === "dataset") + return { + data: [{ pool: "tank", name: "tank", used: { parsed: 10 }, available: { parsed: 20 } }], + error: undefined, + }; + return { data: undefined, error: undefined }; + }); + + const { container } = renderWithProviders( + , + { + settings: { hideErrors: false }, + }, + ); + + expect(container.querySelectorAll(".service-block")).toHaveLength(3); + expect(screen.getByText("1.23")).toBeInTheDocument(); + expect(screen.getByText("3600")).toBeInTheDocument(); // common.duration mocked + expect(screen.getByText("7")).toBeInTheDocument(); + + const pool = screen.getByTestId("truenas-pool"); + expect(pool.getAttribute("data-name")).toBe("tank"); + expect(pool.getAttribute("data-healthy")).toBe("true"); + expect(pool.getAttribute("data-allocated")).toBe("10"); + expect(pool.getAttribute("data-free")).toBe("20"); + }); +}); diff --git a/src/widgets/unraid/component.test.jsx b/src/widgets/unraid/component.test.jsx new file mode 100644 index 000000000..e6b82743d --- /dev/null +++ b/src/widgets/unraid/component.test.jsx @@ -0,0 +1,67 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { beforeEach, 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 "./component"; + +describe("widgets/unraid/component", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("defaults widget.fields and filters down to 4 visible blocks while loading", () => { + useWidgetAPI.mockReturnValue({ data: undefined, error: undefined }); + + const service = { widget: { type: "unraid" } }; + const { container } = renderWithProviders(, { settings: { hideErrors: false } }); + + // Component sets default fields + expect(service.widget.fields).toEqual(["status", "cpu", "memoryPercent", "notifications"]); + + // Container filters the many placeholder Blocks down to the selected fields. + expect(container.querySelectorAll(".service-block")).toHaveLength(4); + expect(screen.getByText("unraid.status")).toBeInTheDocument(); + expect(screen.getByText("unraid.cpu")).toBeInTheDocument(); + expect(screen.getByText("unraid.notifications")).toBeInTheDocument(); + expect(screen.getByText("unraid.memoryUsed")).toBeInTheDocument(); + expect(screen.queryByText("unraid.memoryAvailable")).toBeNull(); + }); + + it("renders values for the default fields", () => { + useWidgetAPI.mockReturnValue({ + data: { + arrayState: "started", + cpuPercent: 12, + memoryAvailable: 100, + memoryUsed: 50, + memoryUsedPercent: 33, + unreadNotifications: 7, + arrayUsed: 1, + arrayFree: 2, + arrayUsedPercent: 3, + caches: {}, + }, + error: undefined, + }); + + const service = { widget: { type: "unraid" } }; + const { container } = renderWithProviders(, { settings: { hideErrors: false } }); + + expect(container.querySelectorAll(".service-block")).toHaveLength(4); + expect(screen.getByText("unraid.started")).toBeInTheDocument(); + expect(screen.getByText("12")).toBeInTheDocument(); + expect(screen.getByText("33")).toBeInTheDocument(); + expect(screen.getByText("7")).toBeInTheDocument(); + }); +}); diff --git a/vitest.setup.js b/vitest.setup.js index 946eff05e..7129f114c 100644 --- a/vitest.setup.js +++ b/vitest.setup.js @@ -4,7 +4,8 @@ import { cleanup } from "@testing-library/react"; import { afterEach, vi } from "vitest"; afterEach(() => { - cleanup(); + // Node-environment tests shouldn't require jsdom; guard cleanup accordingly. + if (typeof document !== "undefined") cleanup(); }); // implement a couple of common formatters mocked in next-i18next @@ -13,6 +14,10 @@ vi.mock("next-i18next", () => ({ t: (key, opts) => { if (key === "common.number") return String(opts?.value ?? ""); if (key === "common.percent") return String(opts?.value ?? ""); + if (key === "common.bytes") return String(opts?.value ?? ""); + if (key === "common.bbytes") return String(opts?.value ?? ""); + if (key === "common.byterate") return String(opts?.value ?? ""); + if (key === "common.duration") return String(opts?.value ?? ""); return key; }, }),