Test: 10 more widget components (F)

This commit is contained in:
shamoon
2026-02-03 08:29:18 -08:00
parent fb9f8990cf
commit bf97388014
10 changed files with 684 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
// @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, { homeboxDefaultFields } 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/homebox/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("defaults fields and filters to 3 blocks while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const service = { widget: { type: "homebox", url: "http://x" } };
const { container } = renderWithProviders(<Component service={service} />, { settings: { hideErrors: false } });
expect(service.widget.fields).toEqual(homeboxDefaultFields);
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("homebox.items")).toBeInTheDocument();
expect(screen.getByText("homebox.locations")).toBeInTheDocument();
expect(screen.getByText("homebox.totalValue")).toBeInTheDocument();
expect(screen.queryByText("homebox.labels")).toBeNull();
expect(screen.queryByText("homebox.users")).toBeNull();
expect(screen.queryByText("homebox.totalWithWarranty")).toBeNull();
});
it("renders error UI when widget API errors", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } });
renderWithProviders(<Component service={{ widget: { type: "homebox", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("renders values when loaded (currency formatting delegated to i18n)", () => {
useWidgetAPI.mockReturnValue({
data: {
items: 10,
totalWithWarranty: 2,
locations: 3,
labels: 4,
users: 5,
totalValue: 123.45,
currencyCode: "USD",
},
error: undefined,
});
const { container } = renderWithProviders(
<Component service={{ widget: { type: "homebox", url: "http://x" } }} />,
{
settings: { hideErrors: false },
},
);
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expectBlockValue(container, "homebox.items", 10);
expectBlockValue(container, "homebox.locations", 3);
expectBlockValue(container, "homebox.totalValue", 123.45);
});
});

View File

@@ -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/homebridge/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(
<Component service={{ widget: { type: "homebridge", url: "http://x" } }} />,
{ settings: { hideErrors: false } },
);
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("widget.status")).toBeInTheDocument();
expect(screen.getByText("homebridge.updates")).toBeInTheDocument();
expect(screen.getByText("homebridge.child_bridges")).toBeInTheDocument();
});
it("renders error UI when widget API errors", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } });
renderWithProviders(<Component service={{ widget: { type: "homebridge", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("renders update status and child bridge summary when child bridges exist", () => {
useWidgetAPI.mockReturnValue({
data: {
status: "ok",
updateAvailable: true,
plugins: { updatesAvailable: 0 },
childBridges: { total: 2, running: 1 },
},
error: undefined,
});
renderWithProviders(<Component service={{ widget: { type: "homebridge", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getByText("homebridge.ok")).toBeInTheDocument();
expect(screen.getByText("homebridge.update_available")).toBeInTheDocument();
// key is returned by the i18n mock; presence indicates the conditional block is rendered.
expect(screen.getByText("homebridge.child_bridges_status")).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,31 @@
// @vitest-environment jsdom
import { describe, expect, it } from "vitest";
import { renderWithProviders } from "test-utils/render-with-providers";
import Component from "./component";
describe("widgets/iframe/component", () => {
it("renders an iframe with the configured src/title and classes", () => {
const service = {
widget: {
type: "iframe",
name: "My Frame",
src: "http://example.test",
classes: "h-10 w-10",
allowScrolling: "no",
},
};
const { container } = renderWithProviders(<Component service={service} />, { settings: { hideErrors: false } });
const iframe = container.querySelector("iframe");
expect(iframe).toBeTruthy();
expect(iframe.getAttribute("src")).toBe("http://example.test");
expect(iframe.getAttribute("title")).toBe("My Frame");
expect(iframe.getAttribute("name")).toBe("My Frame");
expect(iframe.getAttribute("scrolling")).toBe("no");
expect(iframe.className).toContain("h-10 w-10");
});
});

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/immich/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("uses v1 endpoints and renders placeholders while loading", () => {
useWidgetAPI
.mockReturnValueOnce({ data: undefined, error: undefined }) // version
.mockReturnValueOnce({ data: undefined, error: undefined }); // stats
const { container } = renderWithProviders(<Component service={{ widget: { type: "immich", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(useWidgetAPI.mock.calls[0][1]).toBe("version");
expect(useWidgetAPI.mock.calls[1][1]).toBe("stats");
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("immich.users")).toBeInTheDocument();
expect(screen.getByText("immich.photos")).toBeInTheDocument();
expect(screen.getByText("immich.videos")).toBeInTheDocument();
expect(screen.getByText("immich.storage")).toBeInTheDocument();
});
it("selects the v1 statistics endpoint when version is > 1.84", () => {
useWidgetAPI.mockReturnValueOnce({ data: { major: 1, minor: 85 }, error: undefined }).mockReturnValueOnce({
data: { usageByUser: [{ id: 1 }, { id: 2 }], photos: 3, videos: 4, usage: "9 GiB" },
error: undefined,
});
const { container } = renderWithProviders(<Component service={{ widget: { type: "immich", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(useWidgetAPI.mock.calls[1][1]).toBe("statistics");
expectBlockValue(container, "immich.users", 2);
expectBlockValue(container, "immich.photos", 3);
expectBlockValue(container, "immich.videos", 4);
expectBlockValue(container, "immich.storage", "9 GiB");
});
it("uses v2 endpoints when widget.version === 2", () => {
useWidgetAPI.mockReturnValueOnce({ data: { major: 2, minor: 0 }, error: undefined }).mockReturnValueOnce({
data: { usageByUser: [], photos: 0, videos: 0, usage: 0 },
error: undefined,
});
renderWithProviders(<Component service={{ widget: { type: "immich", url: "http://x", version: 2 } }} />, {
settings: { hideErrors: false },
});
expect(useWidgetAPI.mock.calls[0][1]).toBe("version_v2");
expect(useWidgetAPI.mock.calls[1][1]).toBe("statistics_v2");
});
});

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

View File

@@ -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";
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/jdownloader/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const { container } = renderWithProviders(
<Component service={{ widget: { type: "jdownloader", url: "http://x" } }} />,
{ settings: { hideErrors: false } },
);
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("jdownloader.downloadCount")).toBeInTheDocument();
expect(screen.getByText("jdownloader.downloadTotalBytes")).toBeInTheDocument();
expect(screen.getByText("jdownloader.downloadBytesRemaining")).toBeInTheDocument();
expect(screen.getByText("jdownloader.downloadSpeed")).toBeInTheDocument();
});
it("calls the unified endpoint with a 30s refresh interval", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
renderWithProviders(<Component service={{ widget: { type: "jdownloader", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(useWidgetAPI.mock.calls[0][1]).toBe("unified");
expect(useWidgetAPI.mock.calls[0][2]?.refreshInterval).toBe(30000);
});
it("renders error UI when widget API errors", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: { message: "nope" } });
renderWithProviders(<Component service={{ widget: { type: "jdownloader", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
it("renders values when loaded", () => {
useWidgetAPI.mockReturnValue({
data: {
downloadCount: 1,
totalBytes: 100,
bytesRemaining: 40,
totalSpeed: 10,
},
error: undefined,
});
const { container } = renderWithProviders(
<Component service={{ widget: { type: "jdownloader", url: "http://x" } }} />,
{ settings: { hideErrors: false } },
);
expectBlockValue(container, "jdownloader.downloadCount", 1);
expectBlockValue(container, "jdownloader.downloadTotalBytes", 100);
expectBlockValue(container, "jdownloader.downloadBytesRemaining", 40);
expectBlockValue(container, "jdownloader.downloadSpeed", 10);
});
});

View File

@@ -0,0 +1,92 @@
// @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/jellyfin/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders CountBlocks placeholders while loading when enableBlocks is true", () => {
useWidgetAPI
.mockReturnValueOnce({ data: undefined, error: undefined, mutate: vi.fn() }) // sessions
.mockReturnValueOnce({ data: undefined, error: undefined }); // count
renderWithProviders(
<Component
service={{
widget: { type: "jellyfin", url: "http://x", enableBlocks: true },
}}
/>,
{ settings: { hideErrors: false } },
);
expect(screen.getByText("jellyfin.movies")).toBeInTheDocument();
expect(screen.getByText("jellyfin.series")).toBeInTheDocument();
expect(screen.getByText("jellyfin.episodes")).toBeInTheDocument();
expect(screen.getByText("jellyfin.songs")).toBeInTheDocument();
expect(screen.getAllByText("-").length).toBeGreaterThan(0);
});
it("renders the no-active message when there are no playing sessions", () => {
useWidgetAPI
.mockReturnValueOnce({ data: [], error: undefined, mutate: vi.fn() }) // sessions
.mockReturnValueOnce({
data: { MovieCount: 1, SeriesCount: 2, EpisodeCount: 3, SongCount: 4 },
error: undefined,
}); // count
renderWithProviders(
<Component
service={{
widget: { type: "jellyfin", url: "http://x", enableBlocks: true },
}}
/>,
{ settings: { hideErrors: false } },
);
expect(screen.getByText("jellyfin.no_active")).toBeInTheDocument();
expect(screen.getByText("1")).toBeInTheDocument();
expect(screen.getByText("2")).toBeInTheDocument();
expect(screen.getByText("3")).toBeInTheDocument();
expect(screen.getByText("4")).toBeInTheDocument();
});
it("renders a single now-playing entry (expanded to two rows by default)", () => {
useWidgetAPI
.mockReturnValueOnce({
data: [
{
Id: "s1",
UserName: "u1",
NowPlayingItem: { Name: "Movie1", Type: "Movie", RunTimeTicks: 600000000 },
PlayState: { PositionTicks: 0, IsPaused: false, IsMuted: false },
TranscodingInfo: { IsVideoDirect: true },
},
],
error: undefined,
mutate: vi.fn(),
})
.mockReturnValueOnce({
data: { MovieCount: 0, SeriesCount: 0, EpisodeCount: 0, SongCount: 0 },
error: undefined,
});
renderWithProviders(<Component service={{ widget: { type: "jellyfin", url: "http://x" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getByText("Movie1")).toBeInTheDocument();
// Time strings are rendered in a combined node (e.g. "00:00/01:00").
expect(screen.getByText(/00:00/)).toBeInTheDocument();
expect(screen.getByText(/01:00/)).toBeInTheDocument();
});
});

View File

@@ -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, { jellyseerrDefaultFields } from "./component";
describe("widgets/jellyseerr/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("defaults fields and filters to 3 blocks while loading when issues are not enabled", () => {
useWidgetAPI
.mockReturnValueOnce({ data: undefined, error: undefined }) // request/count
.mockReturnValueOnce({ data: undefined, error: undefined }); // issue/count disabled (endpoint = "")
const service = { widget: { type: "jellyseerr", url: "http://x" } };
const { container } = renderWithProviders(<Component service={service} />, { settings: { hideErrors: false } });
expect(service.widget.fields).toEqual(jellyseerrDefaultFields);
expect(useWidgetAPI.mock.calls[1][1]).toBe("");
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(screen.getByText("jellyseerr.pending")).toBeInTheDocument();
expect(screen.getByText("jellyseerr.approved")).toBeInTheDocument();
expect(screen.getByText("jellyseerr.available")).toBeInTheDocument();
expect(screen.queryByText("jellyseerr.issues")).toBeNull();
});
it("renders issues when enabled (and calls the issue/count endpoint)", () => {
useWidgetAPI
.mockReturnValueOnce({ data: { pending: 1, approved: 2, available: 3 }, error: undefined })
.mockReturnValueOnce({ data: { open: 1, total: 2 }, error: undefined });
const service = {
widget: { type: "jellyseerr", url: "http://x", fields: ["pending", "approved", "available", "issues"] },
};
const { container } = renderWithProviders(<Component service={service} />, { settings: { hideErrors: false } });
expect(useWidgetAPI.mock.calls[1][1]).toBe("issue/count");
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("1 / 2")).toBeInTheDocument();
});
it("renders error UI when issues are enabled and issue/count errors", () => {
useWidgetAPI
.mockReturnValueOnce({ data: { pending: 0, approved: 0, available: 0 }, error: undefined })
.mockReturnValueOnce({ data: undefined, error: { message: "nope" } });
renderWithProviders(
<Component service={{ widget: { type: "jellyseerr", url: "http://x", fields: ["issues"] } }} />,
{ settings: { hideErrors: false } },
);
expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
expect(screen.getByText("nope")).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,79 @@
// @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, { karakeepDefaultFields } 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/karakeep/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("defaults fields and filters to 4 blocks while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const service = { widget: { type: "karakeep", url: "http://x" } };
const { container } = renderWithProviders(<Component service={service} />, { settings: { hideErrors: false } });
expect(service.widget.fields).toEqual(karakeepDefaultFields);
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("karakeep.bookmarks")).toBeInTheDocument();
expect(screen.getByText("karakeep.favorites")).toBeInTheDocument();
expect(screen.getByText("karakeep.archived")).toBeInTheDocument();
expect(screen.getByText("karakeep.highlights")).toBeInTheDocument();
expect(screen.queryByText("karakeep.lists")).toBeNull();
expect(screen.queryByText("karakeep.tags")).toBeNull();
});
it("caps widget.fields at 4 entries", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const service = { widget: { type: "karakeep", fields: ["tags", "lists", "bookmarks", "favorites", "archived"] } };
const { container } = renderWithProviders(<Component service={service} />, { settings: { hideErrors: false } });
expect(service.widget.fields).toEqual(["tags", "lists", "bookmarks", "favorites"]);
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("karakeep.tags")).toBeInTheDocument();
expect(screen.getByText("karakeep.lists")).toBeInTheDocument();
});
it("renders values when loaded", () => {
useWidgetAPI.mockReturnValue({
data: {
numBookmarks: 1,
numFavorites: 2,
numArchived: 3,
numHighlights: 4,
numLists: 5,
numTags: 6,
},
error: undefined,
});
const { container } = renderWithProviders(
<Component service={{ widget: { type: "karakeep", url: "http://x" } }} />,
{
settings: { hideErrors: false },
},
);
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expectBlockValue(container, "karakeep.bookmarks", 1);
expectBlockValue(container, "karakeep.favorites", 2);
expectBlockValue(container, "karakeep.archived", 3);
expectBlockValue(container, "karakeep.highlights", 4);
});
});

View File

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