147 lines
4.2 KiB
TypeScript
147 lines
4.2 KiB
TypeScript
import { act, render, waitFor } from "@testing-library/react";
|
|
import createGlobe from "cobe";
|
|
import { ControlRoomGlobe } from "./ControlRoomGlobe";
|
|
|
|
const mockDestroy = jest.fn();
|
|
|
|
jest.mock("cobe", () => ({
|
|
__esModule: true,
|
|
default: jest.fn(() => ({ destroy: mockDestroy })),
|
|
}));
|
|
|
|
jest.mock("motion/react", () => ({
|
|
useMotionValue: (initial: number) => ({
|
|
get: () => initial,
|
|
set: jest.fn(),
|
|
}),
|
|
useSpring: (value: unknown) => value,
|
|
}));
|
|
|
|
let intersectionCallback: IntersectionObserverCallback | undefined;
|
|
let resizeCallback: ResizeObserverCallback | undefined;
|
|
let measuredWidth = 400;
|
|
|
|
function setDocumentHidden(hidden: boolean) {
|
|
Object.defineProperty(document, "hidden", {
|
|
configurable: true,
|
|
value: hidden,
|
|
});
|
|
}
|
|
|
|
function setGlobeIntersects(isIntersecting: boolean) {
|
|
const entry = { isIntersecting } as IntersectionObserverEntry;
|
|
act(() => intersectionCallback?.([entry], {} as IntersectionObserver));
|
|
}
|
|
|
|
function renderCallback() {
|
|
expect(createGlobe).toHaveBeenCalledTimes(1);
|
|
const call = jest.mocked(createGlobe).mock.calls[0];
|
|
if (!call) throw new Error("Globe was not initialized");
|
|
return call[1].onRender;
|
|
}
|
|
|
|
describe("ControlRoomGlobe lifecycle", () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
setDocumentHidden(false);
|
|
intersectionCallback = undefined;
|
|
resizeCallback = undefined;
|
|
measuredWidth = 400;
|
|
jest
|
|
.spyOn(HTMLElement.prototype, "offsetWidth", "get")
|
|
.mockImplementation(() => measuredWidth);
|
|
jest
|
|
.spyOn(HTMLCanvasElement.prototype, "getContext")
|
|
.mockReturnValue({} as unknown as GPUCanvasContext);
|
|
|
|
class MockIntersectionObserver {
|
|
readonly root = null;
|
|
readonly rootMargin = "";
|
|
readonly thresholds = [];
|
|
|
|
constructor(callback: IntersectionObserverCallback) {
|
|
intersectionCallback = callback;
|
|
}
|
|
|
|
disconnect = jest.fn();
|
|
observe = jest.fn();
|
|
takeRecords = () => [];
|
|
unobserve = jest.fn();
|
|
}
|
|
global.IntersectionObserver =
|
|
MockIntersectionObserver as unknown as typeof IntersectionObserver;
|
|
|
|
class MockResizeObserver {
|
|
constructor(callback: ResizeObserverCallback) {
|
|
resizeCallback = callback;
|
|
}
|
|
|
|
disconnect = jest.fn();
|
|
observe = jest.fn();
|
|
unobserve = jest.fn();
|
|
}
|
|
global.ResizeObserver =
|
|
MockResizeObserver as unknown as typeof ResizeObserver;
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
it("pauses for a hidden tab without losing the intersection flag", async () => {
|
|
render(<ControlRoomGlobe />);
|
|
await waitFor(() => expect(createGlobe).toHaveBeenCalledTimes(1));
|
|
const renderFrame = renderCallback();
|
|
const state = { phi: 0, theta: 0, width: 0, height: 0 };
|
|
|
|
setGlobeIntersects(true);
|
|
renderFrame(state);
|
|
expect(state.phi).not.toBe(0);
|
|
|
|
setDocumentHidden(true);
|
|
const pausedPhi = state.phi;
|
|
renderFrame(state);
|
|
expect(state.phi).toBe(pausedPhi);
|
|
|
|
setDocumentHidden(false);
|
|
renderFrame(state);
|
|
expect(state.phi).not.toBe(pausedPhi);
|
|
|
|
setGlobeIntersects(false);
|
|
const offscreenPhi = state.phi;
|
|
setDocumentHidden(true);
|
|
setDocumentHidden(false);
|
|
renderFrame(state);
|
|
expect(state.phi).toBe(offscreenPhi);
|
|
});
|
|
|
|
it("keeps one Cobe instance while ResizeObserver updates render dimensions", async () => {
|
|
render(<ControlRoomGlobe />);
|
|
await waitFor(() => expect(createGlobe).toHaveBeenCalledTimes(1));
|
|
|
|
measuredWidth = 520;
|
|
const entry = {
|
|
contentRect: { width: measuredWidth },
|
|
} as ResizeObserverEntry;
|
|
act(() => resizeCallback?.([entry], {} as ResizeObserver));
|
|
setGlobeIntersects(true);
|
|
const state = { phi: 0, theta: 0, width: 0, height: 0 };
|
|
renderCallback()(state);
|
|
|
|
expect(createGlobe).toHaveBeenCalledTimes(1);
|
|
expect(state.width).toBe(1040);
|
|
expect(state.height).toBe(1040);
|
|
});
|
|
|
|
it("keeps the reduced-motion globe static while tracking dimensions", async () => {
|
|
render(<ControlRoomGlobe autoRotate={false} />);
|
|
await waitFor(() => expect(createGlobe).toHaveBeenCalled());
|
|
setGlobeIntersects(true);
|
|
const state = { phi: 0, theta: 0, width: 0, height: 0 };
|
|
|
|
renderCallback()(state);
|
|
|
|
expect(state).toEqual({ phi: 0, theta: 0, width: 800, height: 800 });
|
|
});
|
|
});
|