Files
kandev/apps/web/components/task/chat/use-chat-scroll-motion.test.tsx
T

56 lines
1.7 KiB
TypeScript

import { act, cleanup, renderHook } from "@testing-library/react";
import { afterEach, expect, it, vi } from "vitest";
import { useChatScrollMotion } from "./use-chat-scroll-motion";
afterEach(() => {
cleanup();
vi.unstubAllGlobals();
});
it("animates allowed requests, settles on disable, and stops when hidden", () => {
let id = 0;
let time = 0;
const frames = new Map<number, FrameRequestCallback>();
vi.stubGlobal("requestAnimationFrame", (cb: FrameRequestCallback) => {
frames.set(++id, cb);
return id;
});
vi.stubGlobal("cancelAnimationFrame", (key: number) => frames.delete(key));
const el = document.createElement("div");
Object.defineProperties(el, { scrollHeight: { value: 1000 }, clientHeight: { value: 200 } });
const options = {
scrollRef: { current: el },
motionEnabled: true,
enabled: true,
isVisible: true,
sessionId: "s",
isNearBottomRef: { current: true },
isBlocked: () => false,
instant: (element: HTMLElement) => {
element.scrollTop = 800;
},
};
const hook = renderHook(useChatScrollMotion, { initialProps: options });
act(() => hook.result.current.followBottom());
const step = () =>
act(() => {
time += 16;
const pending = [...frames.values()];
frames.clear();
pending.forEach((cb) => cb(time));
});
step();
step();
expect(el.scrollTop).toBeGreaterThan(0);
expect(el.scrollTop).toBeLessThan(800);
hook.rerender({ ...options, motionEnabled: false });
expect(el.scrollTop).toBe(800);
expect(frames.size).toBe(0);
el.scrollTop = 0;
hook.rerender(options);
act(() => hook.result.current.followBottom());
step();
hook.rerender({ ...options, isVisible: false });
step();
expect(frames.size).toBe(0);
hook.unmount();
});