Files
AgentHub/client/src/hooks/__tests__/useOnScreen.test.tsx
T
nntrivi2001 c3f83d3922 perf(chat): hidden panes stop polling, rows stop re-rendering, a covered terminal stops drawing
The Workspace keeps panes of other workspaces and tabs mounted (display:none),
and every one of them kept following the live stream as if shown.

- useOnScreen (IntersectionObserver): ConversationView, the outline and the
  700 ms menu scan skip while their pane is hidden and catch up once on show.
- A new_event refetches the tail at most every 1.5 s (leading + trailing).
- MessageList: WorkGroup/MessageRow/EditedFiles compare only their own blocks
  and results, so a tail append no longer re-renders every row.
- A terminal under the chat is moved off the viewport with a transform, so
  xterm pauses drawing without a resize.
- PaneChat and TerminalView are memoized; LaneConsolePane passes a stable onExit.
- RunningStep's clock and the outline poll skip unchanged or hidden updates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:26:57 +07:00

57 lines
1.9 KiB
TypeScript

/**
* @file useOnScreen.test.tsx
* @description useOnScreen follows the IntersectionObserver: on screen by
* default (and always without an observer, as in jsdom), off once the element
* stops intersecting, and `onShow` runs only on the way back on screen.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { act, render } from "@testing-library/react";
import { useRef } from "react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { useOnScreen, isShown } from "../useOnScreen";
const original = globalThis.IntersectionObserver;
afterEach(() => {
globalThis.IntersectionObserver = original;
});
let seen: { current: boolean | null } = { current: null };
function Probe({ onShow }: { onShow?: () => void }) {
const ref = useRef<HTMLDivElement>(null);
seen = useOnScreen(ref, onShow);
return <div ref={ref} />;
}
describe("useOnScreen", () => {
it("counts as on screen without an IntersectionObserver", () => {
// @ts-expect-error removed for the test
delete globalThis.IntersectionObserver;
render(<Probe />);
expect(seen.current).toBe(true);
expect(isShown(seen as never)).toBe(true);
});
it("follows the observer and calls onShow only when it comes back", () => {
let fire: (v: boolean) => void = () => {};
// @ts-expect-error test stub
globalThis.IntersectionObserver = class {
constructor(cb: (e: { isIntersecting: boolean }[]) => void) {
fire = (v) => cb([{ isIntersecting: v }]);
}
observe() {}
disconnect() {}
};
const onShow = vi.fn();
render(<Probe onShow={onShow} />);
act(() => fire(true));
expect(onShow).not.toHaveBeenCalled();
act(() => fire(false));
expect(seen.current).toBe(false);
expect(isShown(seen as never)).toBe(false);
act(() => fire(true));
expect(seen.current).toBe(true);
expect(onShow).toHaveBeenCalledTimes(1);
});
});