mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-10 23:25:52 +00:00
scaling based on component size instead webview size
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
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 { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { observeElementSize$ } from "./elementSize";
|
||||
|
||||
/**
|
||||
* A ResizeObserver whose notifications the test triggers itself, and which
|
||||
* keeps track of what it is watching. jsdom does not ship one.
|
||||
*/
|
||||
class MockResizeObserver {
|
||||
public static instances: MockResizeObserver[] = [];
|
||||
public observed: Element[] = [];
|
||||
public disconnected = false;
|
||||
|
||||
public constructor(private readonly callback: ResizeObserverCallback) {
|
||||
MockResizeObserver.instances.push(this);
|
||||
}
|
||||
|
||||
public observe(element: Element): void {
|
||||
this.observed.push(element);
|
||||
// Real observers report the initial size once observation starts
|
||||
this.fire();
|
||||
}
|
||||
|
||||
public unobserve(): void {}
|
||||
|
||||
public disconnect(): void {
|
||||
this.disconnected = true;
|
||||
}
|
||||
|
||||
public fire(): void {
|
||||
this.callback([], this as unknown as ResizeObserver);
|
||||
}
|
||||
}
|
||||
|
||||
describe("observeElementSize$", () => {
|
||||
const originalResizeObserver = window.ResizeObserver;
|
||||
let element: HTMLDivElement;
|
||||
let size: { width: number; height: number };
|
||||
|
||||
beforeEach(() => {
|
||||
MockResizeObserver.instances = [];
|
||||
window.ResizeObserver =
|
||||
MockResizeObserver as unknown as typeof ResizeObserver;
|
||||
// jsdom does no layout, so the element reports whatever we say it does
|
||||
size = { width: 800, height: 600 };
|
||||
element = document.createElement("div");
|
||||
Object.defineProperty(element, "clientWidth", { get: () => size.width });
|
||||
Object.defineProperty(element, "clientHeight", { get: () => size.height });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.ResizeObserver = originalResizeObserver;
|
||||
});
|
||||
|
||||
it("emits the current size synchronously on subscription", () => {
|
||||
const next = vi.fn();
|
||||
observeElementSize$(element).subscribe(next);
|
||||
expect(next).toHaveBeenCalledTimes(1);
|
||||
expect(next).toHaveBeenCalledWith({ width: 800, height: 600 });
|
||||
expect(MockResizeObserver.instances[0].observed).toEqual([element]);
|
||||
});
|
||||
|
||||
it("emits again whenever the element is resized", () => {
|
||||
const next = vi.fn();
|
||||
observeElementSize$(element).subscribe(next);
|
||||
const [observer] = MockResizeObserver.instances;
|
||||
|
||||
size = { width: 300, height: 300 };
|
||||
observer.fire();
|
||||
size = { width: 1000, height: 700 };
|
||||
observer.fire();
|
||||
|
||||
expect(next.mock.calls.map(([s]) => s)).toEqual([
|
||||
{ width: 800, height: 600 },
|
||||
{ width: 300, height: 300 },
|
||||
{ width: 1000, height: 700 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not repeat a size that has not changed", () => {
|
||||
const next = vi.fn();
|
||||
observeElementSize$(element).subscribe(next);
|
||||
// The initial report the observer makes on observe() carried the same size
|
||||
// as the synchronous measurement, and so did not count
|
||||
expect(next).toHaveBeenCalledTimes(1);
|
||||
MockResizeObserver.instances[0].fire();
|
||||
expect(next).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("stops observing when unsubscribed", () => {
|
||||
const subscription = observeElementSize$(element).subscribe();
|
||||
const [observer] = MockResizeObserver.instances;
|
||||
expect(observer.disconnected).toBe(false);
|
||||
subscription.unsubscribe();
|
||||
expect(observer.disconnected).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
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 { Observable, distinctUntilChanged } from "rxjs";
|
||||
|
||||
export interface ElementSize {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Observes the size of an element, starting with the size it currently has and
|
||||
* following it through every change for as long as the subscription lasts.
|
||||
*
|
||||
* Measured with `clientWidth`/`clientHeight`, so borders, scrollbars and any
|
||||
* transforms a host might animate the element with are left out: this is the
|
||||
* space there is to draw in, not where the element happens to be painted.
|
||||
*/
|
||||
export function observeElementSize$(element: Element): Observable<ElementSize> {
|
||||
return new Observable<ElementSize>((subscriber) => {
|
||||
const measure = (): void =>
|
||||
subscriber.next({
|
||||
width: element.clientWidth,
|
||||
height: element.clientHeight,
|
||||
});
|
||||
// Synchronously, so that a Behavior built on this has an initial value
|
||||
measure();
|
||||
const observer = new ResizeObserver(measure);
|
||||
observer.observe(element);
|
||||
return (): void => observer.disconnect();
|
||||
}).pipe(
|
||||
// A ResizeObserver reports once on observe(), which repeats the first
|
||||
// measurement above, and again for changes to dimensions we do not read
|
||||
distinctUntilChanged(
|
||||
(a, b) => a.width === b.width && a.height === b.height,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -176,6 +176,7 @@ export function getBasicCallViewModelEnvironment(
|
||||
}),
|
||||
connectionState$: constant(ConnectionState.Connected),
|
||||
matrixRTCMode$: constant(MatrixRTCMode.Compatibility),
|
||||
windowSize$: constant({ width: 1000, height: 800 }),
|
||||
...callViewModelOptions,
|
||||
},
|
||||
handRaisedSubject$,
|
||||
|
||||
Reference in New Issue
Block a user