diff --git a/packages/widgets/src/media-server/component.module.css b/packages/widgets/src/media-server/component.module.css
index 3987924f4..321f7ddfa 100644
--- a/packages/widgets/src/media-server/component.module.css
+++ b/packages/widgets/src/media-server/component.module.css
@@ -1,3 +1,23 @@
+.root {
+ min-width: 0;
+ min-height: 0;
+ overflow: hidden;
+}
+
+.tablePaper {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ min-width: 0;
+ min-height: 0;
+ overflow: hidden;
+}
+
+.tableContainer {
+ flex: 1;
+ min-height: 0;
+}
+
.sessionRow {
cursor: pointer;
@@ -11,3 +31,16 @@
outline-offset: -2px;
}
}
+
+.detailsTarget {
+ width: 100%;
+ min-width: 0;
+ color: inherit;
+ text-align: left;
+ border-radius: var(--mantine-radius-xs);
+
+ &:focus-visible {
+ outline: 2px solid var(--mantine-primary-color-filled);
+ outline-offset: 2px;
+ }
+}
diff --git a/packages/widgets/src/media-server/component.spec.tsx b/packages/widgets/src/media-server/component.spec.tsx
new file mode 100644
index 000000000..ee941eac1
--- /dev/null
+++ b/packages/widgets/src/media-server/component.spec.tsx
@@ -0,0 +1,101 @@
+// @vitest-environment jsdom
+
+import { act, useState } from "react";
+import type { ReactNode } from "react";
+import { createRoot } from "react-dom/client";
+import type { Root } from "react-dom/client";
+import { MantineProvider } from "@mantine/core";
+import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
+
+import type { StreamSession } from "@homarr/integrations";
+
+import { SessionDetailsPopover } from "./component";
+
+vi.mock("@homarr/translation/client", () => ({
+ useScopedI18n: () => (key: string) => key,
+}));
+
+const session = {
+ sessionId: "session-1",
+ sessionName: "Living room",
+ user: { username: "Alex", profilePictureUrl: null },
+ currentlyPlaying: {
+ type: "movie",
+ name: "Example movie",
+ episodeName: null,
+ seasonName: null,
+ metadata: null,
+ },
+} as unknown as StreamSession;
+
+function ControlledPopover({ children }: { children: ReactNode }) {
+ const [opened, setOpened] = useState(false);
+ return (
+
+ {children}
+
+ );
+}
+
+describe("SessionDetailsPopover", () => {
+ let host: HTMLDivElement;
+ let root: Root;
+
+ beforeEach(() => {
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
+ vi.stubGlobal(
+ "ResizeObserver",
+ class {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+ },
+ );
+ vi.stubGlobal("matchMedia", () => ({
+ matches: false,
+ media: "",
+ onchange: null,
+ addListener: () => undefined,
+ removeListener: () => undefined,
+ addEventListener: () => undefined,
+ removeEventListener: () => undefined,
+ dispatchEvent: () => false,
+ }));
+
+ host = document.createElement("div");
+ document.body.append(host);
+ root = createRoot(host);
+ });
+
+ afterEach(async () => {
+ await act(() => root.unmount());
+ host.remove();
+ vi.unstubAllGlobals();
+ });
+
+ test("opens and closes anchored session details from the target", async () => {
+ await act(() =>
+ root.render(
+
+ Open session
+ ,
+ ),
+ );
+
+ const target = host.querySelector("button");
+ expect(target?.getAttribute("aria-expanded")).toBe("false");
+
+ await act(async () => {
+ target?.click();
+ await new Promise((resolve) => setTimeout(resolve, 0));
+ });
+ expect(target?.getAttribute("aria-expanded")).toBe("true");
+ expect(document.body.textContent).toContain("Example movie");
+
+ await act(async () => {
+ target?.click();
+ await new Promise((resolve) => setTimeout(resolve, 0));
+ });
+ expect(target?.getAttribute("aria-expanded")).toBe("false");
+ });
+});