@assistant-ui/react
Version:
Open-source TypeScript/React library for building production-grade AI chat experiences
132 lines (121 loc) • 3.04 kB
text/typescript
import { describe, expect, it } from "vitest";
import {
computeTopAnchorReserve,
computeTopAnchorTargetScrollTop,
} from "./computeTopAnchorSlack";
const makeElement = ({
top = 0,
height = 0,
scrollTop = 0,
scrollHeight = 0,
clientHeight = 0,
offsetHeight = height,
offsetTop = top,
offsetParent = null,
}: {
top?: number;
height?: number;
scrollTop?: number;
scrollHeight?: number;
clientHeight?: number;
offsetHeight?: number;
offsetTop?: number;
offsetParent?: HTMLElement | null;
}) =>
({
scrollTop,
scrollHeight,
clientHeight,
offsetHeight,
offsetTop,
offsetParent,
getBoundingClientRect: () => ({
top,
height,
}),
}) as HTMLElement;
describe("computeTopAnchorTargetScrollTop", () => {
it("uses layout offset geometry instead of the anchor's transformed visual position", () => {
const viewport = makeElement({ offsetTop: 0 });
const anchor = makeElement({
top: 160,
height: 60,
offsetTop: 156,
offsetParent: viewport,
});
expect(
computeTopAnchorTargetScrollTop({
viewport,
anchor,
tallerThan: 160,
visibleHeight: 96,
}),
).toBe(156);
});
it("over-scrolls tall anchors so only visibleHeight is visible", () => {
const viewport = makeElement({ offsetTop: 0 });
const anchor = makeElement({ height: 240, offsetTop: 200 });
// 240 > 160 threshold => keep 96 visible => over-scroll by 240 - 96 = 144
expect(
computeTopAnchorTargetScrollTop({
viewport,
anchor,
tallerThan: 160,
visibleHeight: 96,
}),
).toBe(200 + 144);
});
});
describe("computeTopAnchorReserve", () => {
it("reserves only the extra height needed to make the anchor target reachable", () => {
const viewport = makeElement({
offsetTop: 0,
scrollTop: 0,
scrollHeight: 560,
clientHeight: 400,
});
const anchor = makeElement({ height: 64, offsetTop: 220 });
const reserve = makeElement({ offsetHeight: 0 });
expect(
computeTopAnchorReserve({
viewport,
anchor,
reserve,
tallerThan: 160,
visibleHeight: 96,
}),
).toBe(60);
});
it("shrinks as the response content grows", () => {
const anchor = makeElement({ height: 64, offsetTop: 220 });
const reserve = makeElement({ offsetHeight: 60 });
expect(
computeTopAnchorReserve({
viewport: makeElement({
offsetTop: 0,
scrollTop: 0,
scrollHeight: 620,
clientHeight: 400,
}),
anchor,
reserve,
tallerThan: 160,
visibleHeight: 96,
}),
).toBe(60);
expect(
computeTopAnchorReserve({
viewport: makeElement({
offsetTop: 0,
scrollTop: 0,
scrollHeight: 680,
clientHeight: 400,
}),
anchor,
reserve,
tallerThan: 160,
visibleHeight: 96,
}),
).toBe(0);
});
});