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"); + }); +});