Add widget component tests (netdata..opnsense)

This commit is contained in:
shamoon
2026-02-03 12:06:13 -08:00
parent a262e7ec5c
commit 7f77b2cf15
10 changed files with 589 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
// @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";
import { findServiceBlockByLabel } from "test-utils/widget-assertions";
const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
import Component from "./component";
function expectBlockValue(container, label, value) {
const block = findServiceBlockByLabel(container, label);
expect(block, `missing block for ${label}`).toBeTruthy();
expect(block.textContent).toContain(String(value));
}
describe("widgets/netdata/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "netdata" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(2);
expect(screen.getByText("netdata.warnings")).toBeInTheDocument();
expect(screen.getByText("netdata.criticals")).toBeInTheDocument();
});
it("renders error UI when endpoint errors", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } });
renderWithProviders(<Component service={{ widget: { type: "netdata" } }} />, { settings: { hideErrors: false } });
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("renders warning and critical alarm counts", () => {
useWidgetAPI.mockReturnValue({
data: { alarms: { warning: 3, critical: 1 } },
error: undefined,
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "netdata" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "netdata.warnings", 3);
expectBlockValue(container, "netdata.criticals", 1);
});
});

View File

@@ -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/nextdns/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders waiting status while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
renderWithProviders(<Component service={{ widget: { type: "nextdns" } }} />, { settings: { hideErrors: false } });
expect(screen.getByText("widget.status")).toBeInTheDocument();
expect(screen.getByText("nextdns.wait")).toBeInTheDocument();
});
it("renders no-devices status when data array is empty", () => {
useWidgetAPI.mockReturnValue({ data: { data: [] }, error: undefined });
renderWithProviders(<Component service={{ widget: { type: "nextdns" } }} />, { settings: { hideErrors: false } });
expect(screen.getByText("nextdns.no_devices")).toBeInTheDocument();
});
it("renders a block per device status with query counts", () => {
useWidgetAPI.mockReturnValue({
data: {
data: [
{ status: "nextdns.active", queries: 10 },
{ status: "nextdns.offline", queries: 2 },
],
},
error: undefined,
});
renderWithProviders(<Component service={{ widget: { type: "nextdns" } }} />, { settings: { hideErrors: false } });
expect(screen.getByText("nextdns.active")).toBeInTheDocument();
expect(screen.getByText("nextdns.offline")).toBeInTheDocument();
expect(screen.getByText("10")).toBeInTheDocument();
expect(screen.getByText("2")).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,61 @@
// @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";
import { findServiceBlockByLabel } from "test-utils/widget-assertions";
const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
import Component from "./component";
function expectBlockValue(container, label, value) {
const block = findServiceBlockByLabel(container, label);
expect(block, `missing block for ${label}`).toBeTruthy();
expect(block.textContent).toContain(String(value));
}
describe("widgets/npm/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "npm" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("npm.enabled")).toBeInTheDocument();
expect(screen.getByText("npm.disabled")).toBeInTheDocument();
expect(screen.getByText("npm.total")).toBeInTheDocument();
});
it("renders error UI when endpoint errors", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } });
renderWithProviders(<Component service={{ widget: { type: "npm" } }} />, { settings: { hideErrors: false } });
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("renders enabled/disabled/total host counts", () => {
useWidgetAPI.mockReturnValue({
data: [{ enabled: true }, { enabled: false }, { enabled: 1 }, { enabled: 0 }, { enabled: true }],
error: undefined,
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "npm" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "npm.enabled", 3);
expectBlockValue(container, "npm.disabled", 2);
expectBlockValue(container, "npm.total", 5);
});
});

View File

@@ -0,0 +1,61 @@
// @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";
import { findServiceBlockByLabel } from "test-utils/widget-assertions";
const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
import Component from "./component";
function expectBlockValue(container, label, value) {
const block = findServiceBlockByLabel(container, label);
expect(block, `missing block for ${label}`).toBeTruthy();
expect(block.textContent).toContain(String(value));
}
describe("widgets/nzbget/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "nzbget" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("nzbget.rate")).toBeInTheDocument();
expect(screen.getByText("nzbget.remaining")).toBeInTheDocument();
expect(screen.getByText("nzbget.downloaded")).toBeInTheDocument();
});
it("renders error UI when endpoint errors", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } });
renderWithProviders(<Component service={{ widget: { type: "nzbget" } }} />, { settings: { hideErrors: false } });
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("renders rate and sizes when loaded", () => {
useWidgetAPI.mockReturnValue({
data: { DownloadRate: 1234, RemainingSizeMB: 2, DownloadedSizeMB: 3 },
error: undefined,
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "nzbget" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "nzbget.rate", 1234);
expectBlockValue(container, "nzbget.remaining", 2 * 1024 * 1024);
expectBlockValue(container, "nzbget.downloaded", 3 * 1024 * 1024);
});
});

View File

@@ -0,0 +1,76 @@
// @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";
import { findServiceBlockByLabel } from "test-utils/widget-assertions";
const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
import Component from "./component";
function expectBlockValue(container, label, value) {
const block = findServiceBlockByLabel(container, label);
expect(block, `missing block for ${label}`).toBeTruthy();
expect(block.textContent).toContain(String(value));
}
describe("widgets/octoprint/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders minimal placeholder while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "octoprint" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(1);
expect(screen.getByText("octoprint.printer_state")).toBeInTheDocument();
});
it("renders state from job_stats when printer_stats errors but job_stats is available", () => {
useWidgetAPI.mockImplementation((_widget, endpoint) => {
if (endpoint === "printer_stats") return { data: undefined, error: { message: "printer nope" } };
if (endpoint === "job_stats") return { data: { state: "Paused" }, error: undefined };
return { data: undefined, error: undefined };
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "octoprint" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "octoprint.printer_state", "Paused");
expect(screen.queryByText("printer nope")).toBeNull();
});
it("renders job completion block when printing and completion is present", () => {
useWidgetAPI.mockImplementation((_widget, endpoint) => {
if (endpoint === "printer_stats") {
return {
data: {
state: { text: "Printing" },
temperature: { tool0: { actual: 200 }, bed: { actual: 60 } },
},
error: undefined,
};
}
if (endpoint === "job_stats") return { data: { progress: { completion: 12.3456 } }, error: undefined };
return { data: undefined, error: undefined };
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "octoprint" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expectBlockValue(container, "octoprint.printer_state", "Printing");
expectBlockValue(container, "octoprint.temp_tool", "200");
expectBlockValue(container, "octoprint.temp_bed", "60");
expectBlockValue(container, "octoprint.job_completion", "12.35%");
});
});

View File

@@ -0,0 +1,58 @@
// @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";
import { findServiceBlockByLabel } from "test-utils/widget-assertions";
const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
import Component from "./component";
function expectBlockValue(container, label, value) {
const block = findServiceBlockByLabel(container, label);
expect(block, `missing block for ${label}`).toBeTruthy();
expect(block.textContent).toContain(String(value));
}
describe("widgets/ombi/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "ombi" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("ombi.pending")).toBeInTheDocument();
expect(screen.getByText("ombi.approved")).toBeInTheDocument();
expect(screen.getByText("ombi.available")).toBeInTheDocument();
});
it("renders error UI when endpoint errors", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } });
renderWithProviders(<Component service={{ widget: { type: "ombi" } }} />, { settings: { hideErrors: false } });
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("renders request counts when loaded", () => {
useWidgetAPI.mockReturnValue({ data: { pending: 1, approved: 2, available: 3 }, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "ombi" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "ombi.pending", 1);
expectBlockValue(container, "ombi.approved", 2);
expectBlockValue(container, "ombi.available", 3);
});
});

View File

@@ -0,0 +1,71 @@
// @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";
import { findServiceBlockByLabel } from "test-utils/widget-assertions";
const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
import Component from "./component";
function expectBlockValue(container, label, value) {
const block = findServiceBlockByLabel(container, label);
expect(block, `missing block for ${label}`).toBeTruthy();
expect(block.textContent).toContain(String(value));
}
describe("widgets/opendtu/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "opendtu" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("opendtu.yieldDay")).toBeInTheDocument();
expect(screen.getByText("opendtu.relativePower")).toBeInTheDocument();
expect(screen.getByText("opendtu.absolutePower")).toBeInTheDocument();
expect(screen.getByText("opendtu.limit")).toBeInTheDocument();
});
it("renders error UI when endpoint errors", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } });
renderWithProviders(<Component service={{ widget: { type: "opendtu" } }} />, { settings: { hideErrors: false } });
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("renders totals and computed relative power", () => {
useWidgetAPI.mockReturnValue({
data: {
total: {
YieldDay: { v: 12.4, u: "kWh" },
Power: { v: 250, u: "W" },
},
inverters: [{ limit_absolute: 200 }, { limit_absolute: 300 }],
},
error: undefined,
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "opendtu" } }} />, {
settings: { hideErrors: false },
});
// yieldDay is rounded and has unit appended.
expectBlockValue(container, "opendtu.yieldDay", "12kWh");
// relative power is percent of power / totalLimit (250/500*100 = 50)
expectBlockValue(container, "opendtu.relativePower", "50");
expectBlockValue(container, "opendtu.absolutePower", "250W");
expectBlockValue(container, "opendtu.limit", "500W");
});
});

View File

@@ -0,0 +1,38 @@
// @vitest-environment jsdom
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
const { ServicesGetStatus, SmartGetList, DownloaderGetDownloadList } = vi.hoisted(() => ({
ServicesGetStatus: vi.fn(() => <div data-testid="services.getStatus" />),
SmartGetList: vi.fn(() => <div data-testid="smart.getListBg" />),
DownloaderGetDownloadList: vi.fn(() => <div data-testid="downloader.getDownloadList" />),
}));
vi.mock("./methods/services_get_status", () => ({ default: ServicesGetStatus }));
vi.mock("./methods/smart_get_list", () => ({ default: SmartGetList }));
vi.mock("./methods/downloader_get_downloadlist", () => ({ default: DownloaderGetDownloadList }));
import Component from "./component";
describe("widgets/openmediavault/component", () => {
it("routes services.getStatus method to ServicesGetStatus", () => {
render(<Component service={{ widget: { type: "openmediavault", method: "services.getStatus" } }} />);
expect(screen.getByTestId("services.getStatus")).toBeInTheDocument();
});
it("routes smart.getListBg method to SmartGetList", () => {
render(<Component service={{ widget: { type: "openmediavault", method: "smart.getListBg" } }} />);
expect(screen.getByTestId("smart.getListBg")).toBeInTheDocument();
});
it("routes downloader.getDownloadList method to DownloaderGetDownloadList", () => {
render(<Component service={{ widget: { type: "openmediavault", method: "downloader.getDownloadList" } }} />);
expect(screen.getByTestId("downloader.getDownloadList")).toBeInTheDocument();
});
it("returns null for unknown methods", () => {
const { container } = render(<Component service={{ widget: { type: "openmediavault", method: "nope" } }} />);
expect(container.firstChild).toBeNull();
});
});

View File

@@ -0,0 +1,26 @@
// @vitest-environment jsdom
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
const { Interface, System } = vi.hoisted(() => ({
Interface: vi.fn(() => <div data-testid="openwrt.interface" />),
System: vi.fn(() => <div data-testid="openwrt.system" />),
}));
vi.mock("./methods/interface", () => ({ default: Interface }));
vi.mock("./methods/system", () => ({ default: System }));
import Component from "./component";
describe("widgets/openwrt/component", () => {
it("renders System when interfaceName is not set", () => {
render(<Component service={{ widget: { type: "openwrt" } }} />);
expect(screen.getByTestId("openwrt.system")).toBeInTheDocument();
});
it("renders Interface when interfaceName is set", () => {
render(<Component service={{ widget: { type: "openwrt", interfaceName: "eth0" } }} />);
expect(screen.getByTestId("openwrt.interface")).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,86 @@
// @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";
import { findServiceBlockByLabel } from "test-utils/widget-assertions";
const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
import Component from "./component";
function expectBlockValue(container, label, value) {
const block = findServiceBlockByLabel(container, label);
expect(block, `missing block for ${label}`).toBeTruthy();
expect(block.textContent).toContain(String(value));
}
describe("widgets/opnsense/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "opnsense" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("opnsense.cpu")).toBeInTheDocument();
expect(screen.getByText("opnsense.memory")).toBeInTheDocument();
expect(screen.getByText("opnsense.wanUpload")).toBeInTheDocument();
expect(screen.getByText("opnsense.wanDownload")).toBeInTheDocument();
});
it("renders error UI when either endpoint errors", () => {
useWidgetAPI.mockImplementation((_widget, endpoint) => {
if (endpoint === "activity") return { data: undefined, error: { message: "nope" } };
return { data: undefined, error: undefined };
});
renderWithProviders(<Component service={{ widget: { type: "opnsense" } }} />, { settings: { hideErrors: false } });
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("parses activity headers and renders WAN rx/tx for selected interface", () => {
useWidgetAPI.mockImplementation((_widget, endpoint) => {
if (endpoint === "activity") {
return {
data: {
headers: ["", "", "CPU: 75.00% idle", "Mem: 123M Active, 456M Inact, 789M Wired"],
},
error: undefined,
};
}
if (endpoint === "interface") {
return {
data: {
interfaces: {
wan2: { "bytes transmitted": 1000, "bytes received": 2000 },
wan: { "bytes transmitted": 1, "bytes received": 2 },
},
},
error: undefined,
};
}
return { data: undefined, error: undefined };
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "opnsense", wan: "wan2" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "opnsense.cpu", "25.00");
expectBlockValue(container, "opnsense.memory", "123M");
expectBlockValue(container, "opnsense.wanUpload", 1000);
expectBlockValue(container, "opnsense.wanDownload", 2000);
});
});