Files
element-call-Github/src/useRootSize.test.tsx
T
Timo K.andClaude Fable 5.1 779ee7a008 Finish measuring the root rather than the viewport
The move from media queries to `@container element-call` queries left a
few places still asking the viewport: the room header's compact size
and the lobby's footer placement went through `useMediaQuery`, the
lobby's video preview was `50vh` tall, the reaction picker was capped at
`100vw`, and the content insets and the gradient background were sized
from `100vw`/`100vh`. Embedded in a corner of a host's page, each of
those answered for the page rather than the corner.

`useRootSizeMatches` is `useMediaQuery` for the root element, built on
the same `observeElementSize$` the layout uses; the lengths become
container units. Container units resolve against the nearest query
container, and there are others in the tree (the spotlight layouts, the
media tiles), so base.css says when they may be used.

jsdom gives the body no size at all, which would have every such query
read as a tiny window; the test setup now gives it a desktop's, matching
what the media query mock already answered.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 15:38:59 +02:00

116 lines
3.3 KiB
TypeScript

/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { act, renderHook } from "@testing-library/react";
import { createElement, type FC, type PropsWithChildren } from "react";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { useRootSizeMatches } from "./useRootSize";
import { RootElementProvider } from "./RootElementContext";
/** A ResizeObserver the test fires itself. jsdom does not ship one. */
class MockResizeObserver {
public static instances: MockResizeObserver[] = [];
public constructor(private readonly callback: ResizeObserverCallback) {
MockResizeObserver.instances.push(this);
}
public observe(): void {
this.fire();
}
public unobserve(): void {}
public disconnect(): void {}
public fire(): void {
this.callback([], this as unknown as ResizeObserver);
}
}
describe("useRootSizeMatches", () => {
const originalResizeObserver = window.ResizeObserver;
let root: HTMLDivElement;
let size: { width: number; height: number };
let Wrapper: FC<PropsWithChildren>;
beforeEach(() => {
MockResizeObserver.instances = [];
window.ResizeObserver =
MockResizeObserver as unknown as typeof ResizeObserver;
size = { width: 800, height: 600 };
root = document.createElement("div");
Object.defineProperty(root, "clientWidth", { get: () => size.width });
Object.defineProperty(root, "clientHeight", { get: () => size.height });
Wrapper = ({ children }) =>
createElement(RootElementProvider, { value: root }, children);
});
afterEach(() => {
window.ResizeObserver = originalResizeObserver;
});
const resize = (to: { width: number; height: number }): void => {
size = to;
act(() => {
for (const observer of MockResizeObserver.instances) observer.fire();
});
};
test("answers for the root's current size straight away", () => {
const { result } = renderHook(
() => useRootSizeMatches(({ width }) => width <= 550),
{ wrapper: Wrapper },
);
expect(result.current).toBe(false);
});
test("follows the root as it is resized", () => {
const { result } = renderHook(
() => useRootSizeMatches(({ width }) => width <= 550),
{ wrapper: Wrapper },
);
resize({ width: 300, height: 600 });
expect(result.current).toBe(true);
resize({ width: 900, height: 600 });
expect(result.current).toBe(false);
});
test("only re-renders when the answer changes", () => {
const renders = vi.fn();
renderHook(
() => {
renders();
return useRootSizeMatches(({ height }) => height <= 500);
},
{ wrapper: Wrapper },
);
const before = renders.mock.calls.length;
// Still tall enough either way
resize({ width: 800, height: 700 });
resize({ width: 500, height: 650 });
expect(renders.mock.calls.length).toBe(before);
resize({ width: 500, height: 400 });
expect(renders.mock.calls.length).toBe(before + 1);
});
test("keeps one subscription across renders with an inline predicate", () => {
const { rerender } = renderHook(
() => useRootSizeMatches(({ width }) => width <= 550),
{ wrapper: Wrapper },
);
rerender();
rerender();
expect(MockResizeObserver.instances).toHaveLength(1);
});
});