Open History to see the last commit
1import { describe, expect, it } from "vite-plus/test";
2
3import {
4 FOLLOWING,
5 initialTimelineFollow,
6 navigationReleasesFollow,
7 nextListOpening,
8 transitionTimelineFollow,
9 type TimelineFollow,
10 type TimelineListOpening,
11} from "./timelineViewport";
12
13describe("nextListOpening", () => {
14 const unmounted: TimelineListOpening = { mounted: false };
15
16 it("records the thread a list mounts on", () => {
17 expect(nextListOpening(unmounted, true, "a")).toEqual({ mounted: true, identity: "a" });
18 });
19
20 it("drops the opening once the list shows another thread, even after returning", () => {
21 const switched = nextListOpening({ mounted: true, identity: "a" }, true, "b");
22 expect(switched).toEqual({ mounted: true, identity: null });
23 expect(nextListOpening(switched, true, "a")).toBe(switched);
24 });
25
26 it("records a fresh opening after the list unmounts and remounts", () => {
27 const gone = nextListOpening({ mounted: true, identity: null }, false, "b");
28 expect(gone).toEqual(unmounted);
29 expect(nextListOpening(gone, true, "b")).toEqual({ mounted: true, identity: "b" });
30 });
31
32 it("keeps the same state object when nothing changes", () => {
33 const opened: TimelineListOpening = { mounted: true, identity: "a" };
34 expect(nextListOpening(opened, true, "a")).toBe(opened);
35 expect(nextListOpening(unmounted, false, "a")).toBe(unmounted);
36 });
37});
38
39describe("transitionTimelineFollow", () => {
40 const releasedAtEnd: TimelineFollow = { mode: "released", atEnd: true };
41 const releasedAway: TimelineFollow = { mode: "released", atEnd: false };
42
43 it("releases on navigation and stays released", () => {
44 expect(transitionTimelineFollow(FOLLOWING, { type: "navigated" })).toEqual(releasedAtEnd);
45 expect(transitionTimelineFollow(releasedAway, { type: "navigated" })).toBe(releasedAway);
46 });
47
48 it("ignores the end band flicker while following", () => {
49 expect(transitionTimelineFollow(FOLLOWING, { type: "leftEnd" })).toBe(FOLLOWING);
50 });
51
52 it("re-arms only after the released view leaves the end and comes back", () => {
53 expect(transitionTimelineFollow(releasedAtEnd, { type: "reachedEnd" })).toBe(releasedAtEnd);
54 const away = transitionTimelineFollow(releasedAtEnd, { type: "leftEnd" });
55 expect(away).toEqual(releasedAway);
56 expect(transitionTimelineFollow(away, { type: "reachedEnd" })).toBe(FOLLOWING);
57 });
58
59 it("follows the end on request from any state", () => {
60 expect(transitionTimelineFollow(releasedAway, { type: "followEnd" })).toBe(FOLLOWING);
61 });
62
63 it("starts a thread visit from its remembered position", () => {
64 expect(initialTimelineFollow(true)).toBe(FOLLOWING);
65 expect(initialTimelineFollow(false)).toEqual(releasedAway);
66 });
67});
68
69describe("navigationReleasesFollow", () => {
70 const viewport = (contentScrollsUp: boolean, awayFromEnd: boolean) => ({
71 contentScrollsUp: () => contentScrollsUp,
72 awayFromEnd: () => awayFromEnd,
73 });
74
75 it("releases on upward input only when the content can scroll up", () => {
76 const up = { source: "wheel", direction: "up" } as const;
77 expect(navigationReleasesFollow(up, viewport(true, false))).toBe(true);
78 expect(navigationReleasesFollow(up, viewport(false, true))).toBe(false);
79 expect(
80 navigationReleasesFollow({ source: "wheel", direction: "down" }, viewport(true, true)),
81 ).toBe(false);
82 });
83
84 it("releases on downward keys, touch and row clicks only away from the end", () => {
85 const down = { source: "key", key: "PageDown", direction: "down" } as const;
86 expect(navigationReleasesFollow(down, viewport(true, false))).toBe(false);
87 expect(navigationReleasesFollow(down, viewport(false, true))).toBe(true);
88 expect(navigationReleasesFollow({ source: "touch" }, viewport(true, false))).toBe(false);
89 expect(
90 navigationReleasesFollow({ source: "pointer", onScrollbar: false }, viewport(true, false)),
91 ).toBe(false);
92 expect(
93 navigationReleasesFollow({ source: "pointer", onScrollbar: true }, viewport(true, false)),
94 ).toBe(true);
95 });
96
97 it("lets commands decide for themselves", () => {
98 expect(
99 navigationReleasesFollow({ source: "command", releasesFollow: false }, viewport(true, true)),
100 ).toBe(false);
101 expect(navigationReleasesFollow({ source: "scroll" }, viewport(false, false))).toBe(true);
102 });
103});