feat: harden portfolio and add SEO foundations
This commit is contained in:
1 parent
b079afdd73
commit
62bcfbd8fa
121 files changed
+7769
-10469
No files matched your search
@@ -0,0 +1,146 @@
|
||||
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", 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: 0, height: 0 });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user