Add some more tests

This commit is contained in:
shamoon
2026-02-03 09:46:17 -08:00
parent bf97388014
commit 45d5086156
10 changed files with 760 additions and 0 deletions

View File

@@ -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";
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/komga/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "komga", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("komga.libraries")).toBeInTheDocument();
expect(screen.getByText("komga.series")).toBeInTheDocument();
expect(screen.getByText("komga.books")).toBeInTheDocument();
});
it("renders error UI when widget API errors", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } });
renderWithProviders(<Component service={{ widget: { type: "komga", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("renders library/series/book totals when loaded", () => {
useWidgetAPI.mockReturnValue({
data: {
libraries: [{ id: 1 }, { id: 2 }],
series: { totalElements: 10 },
books: { totalElements: 20 },
},
error: undefined,
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "komga", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "komga.libraries", 2);
expectBlockValue(container, "komga.series", 10);
expectBlockValue(container, "komga.books", 20);
});
});

View File

@@ -0,0 +1,80 @@
// @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";
describe("widgets/komodo/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("defaults fields for stacks view and skips containers endpoint when showStacks=true and showSummary=false", () => {
useWidgetAPI
.mockReturnValueOnce({ data: undefined, error: undefined }) // containers (disabled)
.mockReturnValueOnce({ data: undefined, error: undefined }) // stacks
.mockReturnValueOnce({ data: undefined, error: undefined }); // servers (disabled)
const service = { widget: { type: "komodo", showStacks: true, showSummary: false } };
const { container } = renderWithProviders(<Component service={service} />, { settings: { hideErrors: false } });
expect(service.widget.fields).toEqual(["total", "running", "down", "unhealthy"]);
expect(useWidgetAPI.mock.calls[0][1]).toBe(""); // containersEndpoint
expect(useWidgetAPI.mock.calls[1][1]).toBe("stacks");
expect(useWidgetAPI.mock.calls[2][1]).toBe(""); // serversEndpoint
// Default fields filter out "unknown" which is rendered but not in widget.fields.
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("komodo.total")).toBeInTheDocument();
expect(screen.getByText("komodo.running")).toBeInTheDocument();
expect(screen.getByText("komodo.down")).toBeInTheDocument();
expect(screen.getByText("komodo.unhealthy")).toBeInTheDocument();
expect(screen.queryByText("komodo.unknown")).toBeNull();
});
it("renders computed down=stopped+down for stacks view", () => {
useWidgetAPI
.mockReturnValueOnce({ data: undefined, error: undefined }) // containers (disabled)
.mockReturnValueOnce({
data: { total: 10, running: 7, stopped: 1, down: 2, unhealthy: 3, unknown: 4 },
error: undefined,
})
.mockReturnValueOnce({ data: undefined, error: undefined }); // servers (disabled)
const { container } = renderWithProviders(
<Component service={{ widget: { type: "komodo", showStacks: true, showSummary: false } }} />,
{ settings: { hideErrors: false } },
);
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("10")).toBeInTheDocument();
expect(screen.getByText("7")).toBeInTheDocument();
const downBlock = findServiceBlockByLabel(container, "komodo.down");
expect(downBlock).toBeTruthy();
expect(downBlock.textContent).toContain("3"); // stopped(1) + down(2)
});
it("renders summary view ratios when showSummary=true", () => {
useWidgetAPI
.mockReturnValueOnce({ data: { total: 5, running: 4 }, error: undefined }) // containers
.mockReturnValueOnce({ data: { total: 2, running: 1 }, error: undefined }) // stacks
.mockReturnValueOnce({ data: { total: 1, healthy: 1 }, error: undefined }); // servers
const { container } = renderWithProviders(
<Component service={{ widget: { type: "komodo", showSummary: true } }} />,
{ settings: { hideErrors: false } },
);
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("1 / 1")).toBeInTheDocument();
expect(screen.getByText("1 / 2")).toBeInTheDocument();
expect(screen.getByText("4 / 5")).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,83 @@
// @vitest-environment jsdom
import { screen } 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/kopia/component", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
vi.setSystemTime(new Date("2020-01-01T00:00:00Z"));
});
afterEach(() => {
vi.useRealTimers();
});
it("renders placeholders when status data is missing or source filter finds nothing", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(
<Component service={{ widget: { type: "kopia", snapshotHost: "nope" } }} />,
{ settings: { hideErrors: false } },
);
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("kopia.status")).toBeInTheDocument();
expect(screen.getByText("kopia.size")).toBeInTheDocument();
expect(screen.getByText("kopia.lastrun")).toBeInTheDocument();
expect(screen.getByText("kopia.nextrun")).toBeInTheDocument();
});
it("renders error UI when widget API errors", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } });
renderWithProviders(<Component service={{ widget: { type: "kopia" } }} />, { settings: { hideErrors: false } });
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("renders filtered snapshot status, size, and relative last/next run times", () => {
useWidgetAPI.mockReturnValue({
data: {
sources: [
{
source: { host: "hostA", path: "/data" },
status: "OK",
lastSnapshot: {
startTime: "2019-12-31T22:00:00Z", // 2 hours ago
stats: { errorCount: 0, totalSize: 1024 },
},
nextSnapshotTime: "2020-01-01T00:30:00Z", // 30 minutes ahead
},
],
},
error: undefined,
});
const { container } = renderWithProviders(
<Component service={{ widget: { type: "kopia", snapshotHost: "hostA", snapshotPath: "/data" } }} />,
{ settings: { hideErrors: false } },
);
expectBlockValue(container, "kopia.status", "OK");
expectBlockValue(container, "kopia.size", 1024);
expectBlockValue(container, "kopia.lastrun", "2 h");
expectBlockValue(container, "kopia.nextrun", "30 m");
});
});

View File

@@ -0,0 +1,68 @@
// @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 { useSWR } = vi.hoisted(() => ({ useSWR: vi.fn() }));
vi.mock("swr", () => ({ default: useSWR }));
import Component from "./component";
describe("widgets/kubernetes/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useSWR.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(
<Component service={{ widget: { type: "kubernetes", namespace: "ns", app: "app" } }} />,
{ settings: { hideErrors: false } },
);
expect(useSWR.mock.calls[0][0]).toContain("/api/kubernetes/status/ns/app?");
expect(useSWR.mock.calls[1][0]).toContain("/api/kubernetes/stats/ns/app?");
expect(container.querySelectorAll(".service-block")).toHaveLength(2);
expect(screen.getByText("docker.cpu")).toBeInTheDocument();
expect(screen.getByText("docker.mem")).toBeInTheDocument();
});
it("renders offline status when status endpoint reports non-running state", () => {
useSWR.mockImplementation((key) => {
if (String(key).includes("/status/")) return { data: { status: "stopped" }, error: undefined };
if (String(key).includes("/stats/")) return { data: { stats: { cpu: 0.1, mem: 10 } }, error: undefined };
return { data: undefined, error: undefined };
});
renderWithProviders(<Component service={{ widget: { type: "kubernetes", namespace: "ns", app: "app" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getByText("docker.offline")).toBeInTheDocument();
expect(screen.getByText("widget.status")).toBeInTheDocument();
});
it("renders cpu percent when cpuLimit is present, otherwise raw cpu number", () => {
useSWR.mockImplementation((key) => {
if (String(key).includes("/status/")) return { data: { status: "running" }, error: undefined };
if (String(key).includes("/stats/"))
return {
data: { stats: { cpuLimit: true, cpuUsage: 12.3, cpu: 0.0001, mem: 1024 } },
error: undefined,
};
return { data: undefined, error: undefined };
});
renderWithProviders(<Component service={{ widget: { type: "kubernetes", namespace: "ns", app: "app" } }} />, {
settings: { hideErrors: false },
});
// With cpuLimit=true, cpuUsage is formatted via common.percent mock -> string value.
expect(screen.getByText("12.3")).toBeInTheDocument();
expect(screen.getByText("1024")).toBeInTheDocument();
});
});

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";
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/lidarr/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI
.mockReturnValueOnce({ data: undefined, error: undefined }) // artist
.mockReturnValueOnce({ data: undefined, error: undefined }) // wanted/missing
.mockReturnValueOnce({ data: undefined, error: undefined }); // queue/status
const { container } = renderWithProviders(<Component service={{ widget: { type: "lidarr", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("lidarr.wanted")).toBeInTheDocument();
expect(screen.getByText("lidarr.queued")).toBeInTheDocument();
expect(screen.getByText("lidarr.artists")).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(<Component service={{ widget: { type: "lidarr", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("renders wanted/queued/artist counts when loaded", () => {
useWidgetAPI
.mockReturnValueOnce({ data: [{ id: 1 }, { id: 2 }], error: undefined })
.mockReturnValueOnce({ data: { totalRecords: 10 }, error: undefined })
.mockReturnValueOnce({ data: { totalCount: 3 }, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "lidarr", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "lidarr.wanted", 10);
expectBlockValue(container, "lidarr.queued", 3);
expectBlockValue(container, "lidarr.artists", 2);
});
});

View File

@@ -0,0 +1,88 @@
// @vitest-environment jsdom
import { screen, waitFor } 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/linkwarden/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI
.mockReturnValueOnce({ data: undefined, error: undefined }) // collections
.mockReturnValueOnce({ data: undefined, error: undefined }); // tags
const { container } = renderWithProviders(
<Component service={{ widget: { type: "linkwarden", url: "http://x" } }} />,
{ settings: { hideErrors: false } },
);
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("linkwarden.links")).toBeInTheDocument();
expect(screen.getByText("linkwarden.collections")).toBeInTheDocument();
expect(screen.getByText("linkwarden.tags")).toBeInTheDocument();
});
it("renders error UI when either endpoint errors", () => {
useWidgetAPI
.mockReturnValueOnce({ data: undefined, error: { message: "nope" } })
.mockReturnValueOnce({ data: undefined, error: undefined });
renderWithProviders(<Component service={{ widget: { type: "linkwarden", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("computes totalLinks from collection _count.links once both endpoints are loaded", async () => {
useWidgetAPI.mockImplementation((widget, endpoint) => {
if (endpoint === "collections") {
return {
data: {
response: [
// eslint-disable-next-line no-underscore-dangle
{ _count: { links: 2 } },
// eslint-disable-next-line no-underscore-dangle
{ _count: { links: 4 } },
],
},
error: undefined,
};
}
if (endpoint === "tags") {
return { data: { response: [{ id: 1 }, { id: 2 }, { id: 3 }] }, error: undefined };
}
return { data: undefined, error: undefined };
});
const { container } = renderWithProviders(
<Component service={{ widget: { type: "linkwarden", url: "http://x" } }} />,
{ settings: { hideErrors: false } },
);
await waitFor(() => {
expectBlockValue(container, "linkwarden.links", 6);
});
expectBlockValue(container, "linkwarden.collections", 2);
expectBlockValue(container, "linkwarden.tags", 3);
});
});

View File

@@ -0,0 +1,106 @@
// @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/lubelogger/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(
<Component service={{ widget: { type: "lubelogger", url: "http://x" } }} />,
{ settings: { hideErrors: false } },
);
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("lubelogger.vehicles")).toBeInTheDocument();
expect(screen.getByText("lubelogger.serviceRecords")).toBeInTheDocument();
expect(screen.getByText("lubelogger.reminders")).toBeInTheDocument();
});
it("renders error UI when widget API errors", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } });
renderWithProviders(<Component service={{ widget: { type: "lubelogger", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("filters to vehicleID and renders next reminder details when found", () => {
useWidgetAPI.mockReturnValue({
data: [
{
vehicleData: { id: 1, year: 2020, model: "Model A" },
veryUrgentReminderCount: 1,
urgentReminderCount: 2,
notUrgentReminderCount: 3,
serviceRecordCount: 5,
nextReminder: { dueDate: 123 },
},
{
vehicleData: { id: 2, year: 2021, model: "Model B" },
veryUrgentReminderCount: 0,
urgentReminderCount: 0,
notUrgentReminderCount: 0,
serviceRecordCount: 1,
nextReminder: null,
},
],
error: undefined,
});
const { container } = renderWithProviders(
<Component service={{ widget: { type: "lubelogger", url: "http://x", vehicleID: 1 } }} />,
{ settings: { hideErrors: false } },
);
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expectBlockValue(container, "lubelogger.vehicle", "2020 Model A");
expectBlockValue(container, "lubelogger.serviceRecords", 5);
expectBlockValue(container, "lubelogger.reminders", 6);
expectBlockValue(container, "lubelogger.nextReminder", 123);
});
it("shows an error when vehicleID is set but not found", () => {
useWidgetAPI.mockReturnValue({
data: [
{
vehicleData: { id: 2, year: 2021, model: "Model B" },
veryUrgentReminderCount: 0,
urgentReminderCount: 0,
notUrgentReminderCount: 0,
serviceRecordCount: 0,
},
],
error: undefined,
});
renderWithProviders(<Component service={{ widget: { type: "lubelogger", url: "http://x", vehicleID: 1 } }} />, {
settings: { hideErrors: false },
});
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("Vehicle not found")).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,73 @@
// @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/mailcow/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(
<Component service={{ widget: { type: "mailcow", url: "http://x" } }} />,
{
settings: { hideErrors: false },
},
);
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("mailcow.mailboxes")).toBeInTheDocument();
expect(screen.getByText("mailcow.aliases")).toBeInTheDocument();
expect(screen.getByText("mailcow.quarantined")).toBeInTheDocument();
});
it("shows a helpful error when the API returns no domains", () => {
useWidgetAPI.mockReturnValue({ data: [], error: undefined });
renderWithProviders(<Component service={{ widget: { type: "mailcow", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("No domains found")).toBeInTheDocument();
});
it("renders computed totals when loaded", () => {
useWidgetAPI.mockReturnValue({
data: [
{ mboxes_in_domain: "2", msgs_total: "10", bytes_total: "100" },
{ mboxes_in_domain: "1", msgs_total: "5", bytes_total: "50" },
],
error: undefined,
});
const { container } = renderWithProviders(
<Component service={{ widget: { type: "mailcow", url: "http://x" } }} />,
{
settings: { hideErrors: false },
},
);
expectBlockValue(container, "mailcow.domains", 2);
expectBlockValue(container, "mailcow.mailboxes", 3);
expectBlockValue(container, "mailcow.mails", 15);
expectBlockValue(container, "mailcow.storage", 150);
});
});

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";
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/mastodon/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(
<Component service={{ widget: { type: "mastodon", url: "http://x" } }} />,
{
settings: { hideErrors: false },
},
);
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("mastodon.user_count")).toBeInTheDocument();
expect(screen.getByText("mastodon.status_count")).toBeInTheDocument();
expect(screen.getByText("mastodon.domain_count")).toBeInTheDocument();
});
it("renders error UI when widget API errors", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } });
renderWithProviders(<Component service={{ widget: { type: "mastodon", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("renders instance stats when loaded", () => {
useWidgetAPI.mockReturnValue({
data: { stats: { user_count: 1, status_count: 2, domain_count: 3 } },
error: undefined,
});
const { container } = renderWithProviders(
<Component service={{ widget: { type: "mastodon", url: "http://x" } }} />,
{
settings: { hideErrors: false },
},
);
expectBlockValue(container, "mastodon.user_count", 1);
expectBlockValue(container, "mastodon.status_count", 2);
expectBlockValue(container, "mastodon.domain_count", 3);
});
});

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/mealie/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("uses v1 endpoint by default and renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "mealie", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(useWidgetAPI.mock.calls[0][1]).toBe("statisticsv1");
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("mealie.recipes")).toBeInTheDocument();
expect(screen.getByText("mealie.users")).toBeInTheDocument();
expect(screen.getByText("mealie.categories")).toBeInTheDocument();
expect(screen.getByText("mealie.tags")).toBeInTheDocument();
});
it("uses v2 endpoint when widget.version === 2 and renders counts", () => {
useWidgetAPI.mockReturnValue({
data: { totalRecipes: 1, totalUsers: 2, totalCategories: 3, totalTags: 4 },
error: undefined,
});
const { container } = renderWithProviders(
<Component service={{ widget: { type: "mealie", url: "http://x", version: 2 } }} />,
{ settings: { hideErrors: false } },
);
expect(useWidgetAPI.mock.calls[0][1]).toBe("statisticsv2");
expectBlockValue(container, "mealie.recipes", 1);
expectBlockValue(container, "mealie.users", 2);
expectBlockValue(container, "mealie.categories", 3);
expectBlockValue(container, "mealie.tags", 4);
});
});