| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | |
| 3 | import { resolveWorkGroupScrollAnchor } from "./scrollAnchor.js"; |
| 4 | |
| 5 | describe("tool-group scroll anchors", () => { |
| 6 | const data = Array.from({ length: 18 }, (_, index) => ({ id: `call-${index}` })); |
| 7 | const state = { data, positionAtIndex: (index: number) => index * 33 }; |
| 8 | |
| 9 | it("returns to the top after a fast flick even when the cached start is five rows behind", () => { |
| 10 | const snapshot = { ...state, scroll: 0, start: 5 }; |
| 11 | expect(resolveWorkGroupScrollAnchor(snapshot)).toEqual({ |
| 12 | rowId: "call-0", |
| 13 | offsetWithinRow: 0, |
| 14 | scrollOffset: 0, |
| 15 | }); |
| 16 | }); |
| 17 | |
| 18 | it.each([ |
| 19 | { scroll: 302, start: 0, rowId: "call-9", offsetWithinRow: 5 }, |
| 20 | { scroll: 40, start: 12, rowId: "call-1", offsetWithinRow: 7 }, |
| 21 | { scroll: 165, start: 4, rowId: "call-5", offsetWithinRow: 0 }, |
| 22 | ])("uses the measured position at $scroll rather than cached row $start", (example) => { |
| 23 | expect(resolveWorkGroupScrollAnchor({ ...state, ...example })).toEqual({ |
| 24 | rowId: example.rowId, |
| 25 | offsetWithinRow: example.offsetWithinRow, |
| 26 | scrollOffset: example.scroll, |
| 27 | }); |
| 28 | }); |
| 29 | |
| 30 | it("preserves an offset within expanded output", () => { |
| 31 | const positions = [0, 33, 600]; |
| 32 | expect( |
| 33 | resolveWorkGroupScrollAnchor({ |
| 34 | data: [{ id: "first" }, { id: "output" }, { id: "last" }], |
| 35 | scroll: 153, |
| 36 | positionAtIndex: (index) => positions[index], |
| 37 | }), |
| 38 | ).toEqual({ rowId: "output", offsetWithinRow: 120, scrollOffset: 153 }); |
| 39 | }); |
| 40 | |
| 41 | it("does not preserve overscroll before the first row", () => { |
| 42 | expect(resolveWorkGroupScrollAnchor({ ...state, scroll: -20 })).toEqual({ |
| 43 | rowId: "call-0", |
| 44 | offsetWithinRow: 0, |
| 45 | scrollOffset: 0, |
| 46 | }); |
| 47 | }); |
| 48 | |
| 49 | it("skips incomplete layout snapshots instead of saving an invalid anchor", () => { |
| 50 | expect(resolveWorkGroupScrollAnchor({ ...state, data: [], scroll: 0 })).toBeUndefined(); |
| 51 | expect(resolveWorkGroupScrollAnchor({ ...state, scroll: Number.NaN })).toBeUndefined(); |
| 52 | expect( |
| 53 | resolveWorkGroupScrollAnchor({ ...state, scroll: 165, positionAtIndex: () => undefined }), |
| 54 | ).toBeUndefined(); |
| 55 | expect( |
| 56 | resolveWorkGroupScrollAnchor({ |
| 57 | ...state, |
| 58 | scroll: 165, |
| 59 | positionAtIndex: (index) => (index === 0 ? 0 : Number.NaN), |
| 60 | }), |
| 61 | ).toBeUndefined(); |
| 62 | }); |
| 63 | }); |