Chore: homepage tests (#6278)

This commit is contained in:
shamoon
2026-02-04 19:58:39 -08:00
committed by GitHub
parent 7d019185a3
commit 872a3600aa
558 changed files with 32606 additions and 84 deletions
+92
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();
});
});
+102
View File
@@ -0,0 +1,102 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import createMockRes from "test-utils/create-mock-res";
const { httpProxy, getServiceWidget, validateWidgetData, logger } = vi.hoisted(() => ({
httpProxy: vi.fn(),
getServiceWidget: vi.fn(),
validateWidgetData: vi.fn(() => true),
logger: {
debug: vi.fn(),
error: vi.fn(),
},
}));
vi.mock("utils/logger", () => ({
default: () => logger,
}));
vi.mock("utils/config/service-helpers", () => ({
default: getServiceWidget,
}));
vi.mock("utils/proxy/http", () => ({
httpProxy,
}));
vi.mock("utils/proxy/validate-widget-data", () => ({
default: validateWidgetData,
}));
vi.mock("widgets/widgets", () => ({
default: {
jellyfin: {
api: "{url}/{endpoint}",
},
},
}));
import jellyfinProxyHandler from "./proxy";
describe("widgets/jellyfin/proxy", () => {
beforeEach(() => {
vi.clearAllMocks();
validateWidgetData.mockReturnValue(true);
});
it("adds MediaBrowser auth header and applies an optional mapping function", async () => {
getServiceWidget.mockResolvedValue({
type: "jellyfin",
url: "http://jf",
key: "abc",
service_group: "mygroup",
service_name: "myservice",
});
httpProxy.mockResolvedValueOnce([200, "application/json", { items: [1] }]);
const req = { method: "GET", query: { group: "g", service: "svc", endpoint: "Users", index: "0" } };
const res = createMockRes();
await jellyfinProxyHandler(req, res, () => ({ mapped: true }));
expect(httpProxy).toHaveBeenCalledTimes(1);
expect(httpProxy.mock.calls[0][0].toString()).toBe("http://jf/Users");
expect(httpProxy.mock.calls[0][1].headers.Authorization).toBe(
'MediaBrowser Token="abc", Client="Homepage", Device="Homepage", DeviceId="mygroup-myservice", Version="1.0.0"',
);
expect(validateWidgetData).toHaveBeenCalledWith(expect.objectContaining({ type: "jellyfin" }), "Users", {
items: [1],
});
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ mapped: true });
expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "application/json");
});
it("returns 500 when data validation fails", async () => {
validateWidgetData.mockReturnValue(false);
getServiceWidget.mockResolvedValue({ type: "jellyfin", url: "http://jf", key: "abc" });
httpProxy.mockResolvedValueOnce([200, "application/json", { nope: true }]);
const req = { method: "GET", query: { group: "g", service: "svc", endpoint: "Users", index: "0" } };
const res = createMockRes();
await jellyfinProxyHandler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body.error.message).toBe("Invalid data");
});
it("ends the response for 204 responses", async () => {
getServiceWidget.mockResolvedValue({ type: "jellyfin", url: "http://jf", key: "abc" });
httpProxy.mockResolvedValueOnce([204, "application/json", {}]);
const req = { method: "GET", query: { group: "g", service: "svc", endpoint: "Users", index: "0" } };
const res = createMockRes();
await jellyfinProxyHandler(req, res);
expect(res.statusCode).toBe(204);
expect(res.end).toHaveBeenCalled();
});
});
+11
View File
@@ -0,0 +1,11 @@
import { describe, it } from "vitest";
import { expectWidgetConfigShape } from "test-utils/widget-config";
import widget from "./widget";
describe("jellyfin widget config", () => {
it("exports a valid widget config", () => {
expectWidgetConfigShape(widget);
});
});