Add widget component tests (tandoor..unmanic)

This commit is contained in:
shamoon
2026-02-03 12:29:43 -08:00
parent aa3c8fe0e0
commit 4ffe757e33
10 changed files with 605 additions and 0 deletions

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";
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/tandoor/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "tandoor" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("tandoor.users")).toBeInTheDocument();
expect(screen.getByText("tandoor.recipes")).toBeInTheDocument();
expect(screen.getByText("tandoor.keywords")).toBeInTheDocument();
});
it("renders values when loaded (spaceData.results shape)", () => {
useWidgetAPI.mockImplementation((_widget, endpoint) => {
if (endpoint === "space") return { data: { results: [{ user_count: 1, recipe_count: 2 }] }, error: undefined };
if (endpoint === "keyword") return { data: { count: 3 }, error: undefined };
return { data: undefined, error: undefined };
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "tandoor" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "tandoor.users", 1);
expectBlockValue(container, "tandoor.recipes", 2);
expectBlockValue(container, "tandoor.keywords", 3);
});
});

View File

@@ -0,0 +1,69 @@
// @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/tautulli/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholder rows while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
renderWithProviders(<Component service={{ widget: { type: "tautulli" } }} />, { settings: { hideErrors: false } });
// Default behavior shows 2 placeholder rows, but just assert we see at least one.
expect(screen.getAllByText("-").length).toBeGreaterThan(0);
});
it("renders no-active message when there are no sessions", () => {
useWidgetAPI.mockReturnValue({
data: { response: { data: { sessions: [] } } },
error: undefined,
});
renderWithProviders(<Component service={{ widget: { type: "tautulli" } }} />, { settings: { hideErrors: false } });
expect(screen.getByText("tautulli.no_active")).toBeInTheDocument();
});
it("renders an expanded two-row entry when a single session is playing", () => {
useWidgetAPI.mockReturnValue({
data: {
response: {
data: {
sessions: [
{
session_key: "1",
full_title: "Movie",
media_type: "movie",
duration: 2000,
view_offset: 1000,
progress_percent: 50,
state: "playing",
video_decision: "direct play",
audio_decision: "direct play",
},
],
},
},
},
error: undefined,
});
renderWithProviders(<Component service={{ widget: { type: "tautulli" } }} />, { settings: { hideErrors: false } });
expect(screen.getByText("Movie")).toBeInTheDocument();
// view_offset 1s => "00:01", duration 2s => "00:02"
expect(screen.getByText(/00:01/)).toBeInTheDocument();
expect(screen.getByText(/00:02/)).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,64 @@
// @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/tdarr/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "tdarr" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("tdarr.queue")).toBeInTheDocument();
expect(screen.getByText("tdarr.processed")).toBeInTheDocument();
expect(screen.getByText("tdarr.errored")).toBeInTheDocument();
expect(screen.getByText("tdarr.saved")).toBeInTheDocument();
});
it("computes queue/processed/errored/saved when loaded", () => {
useWidgetAPI.mockReturnValue({
data: {
table1Count: "1",
table2Count: "2",
table3Count: "3",
table4Count: "4",
table5Count: "5",
table6Count: "6",
sizeDiff: "1.5",
},
error: undefined,
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "tdarr" } }} />, {
settings: { hideErrors: false },
});
// queue = 1+4, processed = 2+5, errored = 3+6
expectBlockValue(container, "tdarr.queue", 5);
expectBlockValue(container, "tdarr.processed", 7);
expectBlockValue(container, "tdarr.errored", 9);
// saved = 1.5 * 1e9
expectBlockValue(container, "tdarr.saved", 1_500_000_000);
});
});

View File

@@ -0,0 +1,66 @@
// @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, { technitiumDefaultFields } 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/technitium/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("defaults fields to 4 and filters loading placeholders accordingly", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const service = { widget: { type: "technitium" } };
const { container } = renderWithProviders(<Component service={service} />, { settings: { hideErrors: false } });
expect(service.widget.fields).toEqual(technitiumDefaultFields);
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("technitium.totalQueries")).toBeInTheDocument();
expect(screen.getByText("technitium.totalAuthoritative")).toBeInTheDocument();
expect(screen.getByText("technitium.totalCached")).toBeInTheDocument();
expect(screen.getByText("technitium.totalServerFailure")).toBeInTheDocument();
expect(screen.queryByText("technitium.totalNoError")).toBeNull();
});
it("renders selected totals with percentages when loaded", () => {
useWidgetAPI.mockReturnValue({
data: {
totalQueries: 100,
totalNoError: 50,
totalServerFailure: 25,
totalNxDomain: 25,
},
error: undefined,
});
const service = {
widget: { type: "technitium", fields: ["totalQueries", "totalNoError", "totalServerFailure", "totalNxDomain"] },
};
const { container } = renderWithProviders(<Component service={service} />, { settings: { hideErrors: false } });
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expectBlockValue(container, "technitium.totalQueries", 100);
expectBlockValue(container, "technitium.totalNoError", "50");
expectBlockValue(container, "technitium.totalNoError", "50");
expectBlockValue(container, "technitium.totalServerFailure", "25");
expectBlockValue(container, "technitium.totalNxDomain", "25");
// Percent strings are included in parens, e.g. "50 (50)"
expect(findServiceBlockByLabel(container, "technitium.totalNoError")?.textContent).toContain("(");
});
});

View File

@@ -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";
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/traefik/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "traefik" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("traefik.routers")).toBeInTheDocument();
expect(screen.getByText("traefik.services")).toBeInTheDocument();
expect(screen.getByText("traefik.middleware")).toBeInTheDocument();
});
it("renders totals when loaded", () => {
useWidgetAPI.mockReturnValue({
data: { http: { routers: { total: 1 }, services: { total: 2 }, middlewares: { total: 3 } } },
error: undefined,
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "traefik" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "traefik.routers", 1);
expectBlockValue(container, "traefik.services", 2);
expectBlockValue(container, "traefik.middleware", 3);
});
});

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/transmission/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "transmission" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("transmission.leech")).toBeInTheDocument();
expect(screen.getByText("transmission.download")).toBeInTheDocument();
expect(screen.getByText("transmission.seed")).toBeInTheDocument();
expect(screen.getByText("transmission.upload")).toBeInTheDocument();
});
it("computes leech/seed counts and upload/download rates when loaded", () => {
useWidgetAPI.mockReturnValue({
data: {
arguments: {
torrents: [
{ rateDownload: 10, rateUpload: 1, percentDone: 1 },
{ rateDownload: 5, rateUpload: 2, percentDone: 0.5 },
],
},
},
error: undefined,
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "transmission" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "transmission.leech", 1);
expectBlockValue(container, "transmission.seed", 1);
expectBlockValue(container, "transmission.download", 15);
expectBlockValue(container, "transmission.upload", 3);
});
});

View File

@@ -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";
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/trilium/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "trilium" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("trilium.version")).toBeInTheDocument();
expect(screen.getByText("trilium.notesCount")).toBeInTheDocument();
expect(screen.getByText("trilium.dbSize")).toBeInTheDocument();
});
it("renders metrics when loaded", () => {
useWidgetAPI.mockReturnValue({
data: { version: { app: "1.0.0" }, database: { activeNotes: 2 }, statistics: { databaseSizeBytes: 1024 } },
error: undefined,
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "trilium" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "trilium.version", "v1.0.0");
expectBlockValue(container, "trilium.notesCount", 2);
expectBlockValue(container, "trilium.dbSize", 1024);
});
});

View File

@@ -0,0 +1,57 @@
// @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/tubearchivist/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "tubearchivist" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("tubearchivist.downloads")).toBeInTheDocument();
expect(screen.getByText("tubearchivist.videos")).toBeInTheDocument();
expect(screen.getByText("tubearchivist.channels")).toBeInTheDocument();
expect(screen.getByText("tubearchivist.playlists")).toBeInTheDocument();
});
it("renders counts when loaded", () => {
useWidgetAPI.mockImplementation((_widget, endpoint) => {
if (endpoint === "downloads") return { data: { pending: 1 }, error: undefined };
if (endpoint === "videos") return { data: { doc_count: 2 }, error: undefined };
if (endpoint === "channels") return { data: { doc_count: 3 }, error: undefined };
if (endpoint === "playlists") return { data: { doc_count: 4 }, error: undefined };
return { data: undefined, error: undefined };
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "tubearchivist" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "tubearchivist.downloads", 1);
expectBlockValue(container, "tubearchivist.videos", 2);
expectBlockValue(container, "tubearchivist.channels", 3);
expectBlockValue(container, "tubearchivist.playlists", 4);
});
});

View File

@@ -0,0 +1,82 @@
// @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/unifi/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders when default site isn't available yet", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "unifi" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getByText("unifi.uptime")).toBeInTheDocument();
expect(screen.getByText("unifi.wan")).toBeInTheDocument();
expect(screen.getByText("unifi.lan_users")).toBeInTheDocument();
expect(screen.getByText("unifi.wlan_users")).toBeInTheDocument();
// 4 blocks if all are rendered.
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
});
it("renders a site-not-found error when widget.site doesn't match", () => {
useWidgetAPI.mockReturnValue({
data: { data: [{ name: "default", desc: "Default", health: [] }] },
error: undefined,
});
renderWithProviders(<Component service={{ widget: { type: "unifi", site: "Nope" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("Site 'Nope' not found")).toBeInTheDocument();
});
it("renders uptime, wan and user counts when site data is present", () => {
useWidgetAPI.mockReturnValue({
data: {
data: [
{
name: "default",
desc: "Default",
health: [
{ subsystem: "wan", status: "ok", num_user: 0, num_adopted: 0, "gw_system-stats": { uptime: 86400 } },
{ subsystem: "lan", status: "ok", num_user: 2, num_adopted: 5 },
{ subsystem: "wlan", status: "ok", num_user: 3, num_adopted: 6 },
],
},
],
},
error: undefined,
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "unifi" } }} />, {
settings: { hideErrors: false },
});
// uptime includes unifi.days suffix.
expect(findServiceBlockByLabel(container, "unifi.uptime")?.textContent).toContain("unifi.days");
expectBlockValue(container, "unifi.wan", "unifi.up");
expectBlockValue(container, "unifi.lan_users", 2);
expectBlockValue(container, "unifi.wlan_users", 3);
});
});

View File

@@ -0,0 +1,49 @@
// @vitest-environment jsdom
import { screen, waitFor } from "@testing-library/react";
import { afterEach, 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/unmanic/component", () => {
const originalFetch = globalThis.fetch;
beforeEach(() => {
vi.clearAllMocks();
globalThis.fetch = vi.fn(async () => ({ json: async () => ({ recordsTotal: 7 }) }));
});
afterEach(() => {
globalThis.fetch = originalFetch;
});
it("renders placeholders while loading pending data, then renders worker + pending stats", async () => {
useWidgetAPI.mockReturnValue({ data: { active_workers: 1, total_workers: 2 }, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "unmanic" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getByText("unmanic.active_workers")).toBeInTheDocument();
expect(screen.getByText("unmanic.total_workers")).toBeInTheDocument();
expect(screen.getByText("unmanic.records_total")).toBeInTheDocument();
await waitFor(() => {
expectBlockValue(container, "unmanic.active_workers", 1);
expectBlockValue(container, "unmanic.total_workers", 2);
expectBlockValue(container, "unmanic.records_total", 7);
});
});
});