Files
manage/frontend/src/widgets/__tests__/GrafanaPanelWidget.test.tsx
T
Developer b877a32ad8 Add Jellyfin Now Playing + Grafana Panel embed widgets
Two new additive widget kinds:

Jellyfin 'now_playing': like the existing 'activity' widget but filters
to only sessions with active playback (NowPlayingItem present + not
paused). Shows who's actually watching right now. The 'activity' kind
is unchanged (shows all sessions including idle).

Grafana 'panel': embeds a single Grafana panel directly in the app via
an iframe, using Grafana's /d-solo/ endpoint (renders one panel without
dashboard chrome, kiosk=tv). Configurable dashboard_uid, panel_id, and
time range (from/to, defaults now-1h/now). Includes a fallback 'Open in
Grafana' link for when embedding is blocked by X-Frame-Options/CSP. The
'link' kind is unchanged (still builds a deep-link URL).

Backend: new widget configs + definitions on jellyfin/grafana; source
adapter logic (session filter for now_playing; d-solo embed URL for
panel); 6 new tests.

Frontend: JellyfinNowPlayingWidget + GrafanaPanelWidget components;
registry bindings; 6 new tests.

278 backend tests pass (+6); 127 frontend tests pass (+6); lint/build
green both sides.
2026-06-28 15:26:43 +00:00

79 lines
2.2 KiB
TypeScript

import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { GrafanaPanelWidget } from "../GrafanaPanelWidget";
import type { WidgetInstance } from "../../types";
vi.mock("../../hooks/useWidgets", () => ({
useWidgetData: vi.fn(),
}));
const widget: WidgetInstance = {
id: "w2",
service_id: "s2",
widget_kind: "panel",
title: "CPU Usage",
config: {},
enabled: true,
sort_order: 0,
created_at: 0,
updated_at: 0,
};
describe("GrafanaPanelWidget", () => {
it("renders an iframe with the embed URL", async () => {
const { useWidgetData } = await import("../../hooks/useWidgets");
vi.mocked(useWidgetData).mockReturnValue({
data: {
widget_id: "w2",
data: {
embed_url:
"http://grafana:3000/d-solo/ov/manage?panelId=4&from=now-1h&to=now&kiosk=tv",
},
fetched_at: 0,
},
isLoading: false,
} as never);
const { container } = render(
<GrafanaPanelWidget widget={widget} refreshIntervalMs={0} />,
);
const iframe = container.querySelector("iframe");
expect(iframe).not.toBeNull();
expect(iframe?.getAttribute("src")).toContain("d-solo/ov/manage");
expect(iframe?.getAttribute("src")).toContain("panelId=4");
});
it("renders Open in Grafana fallback link", async () => {
const { useWidgetData } = await import("../../hooks/useWidgets");
vi.mocked(useWidgetData).mockReturnValue({
data: {
widget_id: "w2",
data: {
embed_url:
"http://grafana:3000/d-solo/ov/manage?panelId=4&from=now-1h&to=now&kiosk=tv",
},
fetched_at: 0,
},
isLoading: false,
} as never);
render(<GrafanaPanelWidget widget={widget} refreshIntervalMs={0} />);
expect(screen.getByText("Open in Grafana")).toBeInTheDocument();
});
it("renders error state", async () => {
const { useWidgetData } = await import("../../hooks/useWidgets");
vi.mocked(useWidgetData).mockReturnValue({
data: {
widget_id: "w2",
error: "dashboard_uid is required",
fetched_at: 0,
},
isLoading: false,
} as never);
render(<GrafanaPanelWidget widget={widget} refreshIntervalMs={0} />);
expect(screen.getByText("dashboard_uid is required")).toBeInTheDocument();
});
});