pagescroll_test.go RawHistoryFolder

1package webutil
2
3import (
4	"strings"
5	"testing"
6)
7
8func TestBuildPageScrollByJS_TargetsViewportCenterInsteadOfActiveElement(t *testing.T) {
9	js := BuildPageScrollByJS(0, 80)
10
11	if !strings.Contains(js, "doc.elementFromPoint(window.innerWidth/2,window.innerHeight/2)") {
12		t.Fatalf("page scroll must resolve its initial target from the viewport center, got: %s", js)
13	}
14	if strings.Contains(js, "activeElement") {
15		t.Fatalf("page scroll must not use the legacy active-element target, got: %s", js)
16	}
17}
18
19func TestBuildPageScrollByJS_ContainsRequiredOperations(t *testing.T) {
20	tests := []struct {
21		name string
22		dx   int
23		dy   int
24	}{
25		{"down", 0, 80},
26		{"up", 0, -80},
27		{"left", -80, 0},
28		{"right", 80, 0},
29		{"upFast", 0, -320},
30		{"downFast", 0, 320},
31	}
32	required := []struct {
33		message  string
34		snippets []string
35	}{
36		{"JS must resolve the viewport-center element", []string{"elementFromPoint", "window.innerWidth/2", "window.innerHeight/2"}},
37		{"JS must walk parentElement in a loop", []string{"parentElement"}},
38		{"JS must reference document.scrollingElement", []string{"scrollingElement"}},
39		{"JS must fall back to window.scrollBy", []string{"window.scrollBy"}},
40		{"JS must use getComputedStyle", []string{"getComputedStyle"}},
41		{"JS must check overflowY", []string{"overflowY"}},
42		{"JS must check overflowX", []string{"overflowX"}},
43		{"JS must use direct element scroll offsets", []string{"scrollLeft=beforeLeft+dx", "scrollTop=beforeTop+dy"}},
44		{"JS must check vertical direction-specific remaining scroll", []string{"dy<0&&el.scrollTop>0", "dy>0&&el.scrollTop<maxTop"}},
45		{"JS must check horizontal direction-specific remaining scroll", []string{"dx<0&&el.scrollLeft>0", "dx>0&&el.scrollLeft<maxLeft"}},
46		{"JS must check scrollable overflow values", []string{"'scroll'", "'auto'", "'overlay'"}},
47		{"JS must wrap in try/catch", []string{"try{", "catch(e)"}},
48		{"JS must support immediate window scrolling", []string{"window.scrollBy(dx,dy)"}},
49	}
50
51	for _, tt := range tests {
52		t.Run(tt.name, func(t *testing.T) {
53			js := BuildPageScrollByJS(tt.dx, tt.dy)
54			for _, check := range required {
55				for _, snippet := range check.snippets {
56					if !strings.Contains(js, snippet) {
57						t.Error(check.message)
58						break
59					}
60				}
61			}
62			if !strings.HasPrefix(js, "(function(){") || !strings.HasSuffix(js, "})()") {
63				t.Error("JS must be wrapped in an IIFE")
64			}
65		})
66	}
67}
68
69func TestBuildPageScrollByJS_SpecificDelta(t *testing.T) {
70	js := BuildPageScrollByJS(0, 80)
71	if !strings.Contains(js, "var dx=0,dy=80") {
72		t.Errorf("expected dy=80 in JS variables, got: %s", js)
73	}
74	if strings.Contains(js, "requestAnimationFrame") {
75		t.Errorf("expected immediate fallback scroll step without requestAnimationFrame, got: %s", js)
76	}
77
78	js = BuildPageScrollByJS(-80, 0)
79	if !strings.Contains(js, "var dx=-80,dy=0") {
80		t.Errorf("expected dx=-80 in JS variables, got: %s", js)
81	}
82}
83
84func TestBuildPageScrollByJS_HorizontalAxisCheck(t *testing.T) {
85	js := BuildPageScrollByJS(-80, 0)
86	if !strings.Contains(js, "clientWidth") || !strings.Contains(js, "scrollWidth") {
87		t.Error("horizontal scroll must check scrollWidth/clientWidth")
88	}
89}
90
91func TestBuildPageScrollByJS_VerticalAxisCheck(t *testing.T) {
92	js := BuildPageScrollByJS(0, 80)
93	if !strings.Contains(js, "clientHeight") || !strings.Contains(js, "scrollHeight") {
94		t.Error("vertical scroll must check scrollHeight/clientHeight")
95	}
96}
97
98func TestBuildPageScrollByJS_NegativeDeltaMagnitude(t *testing.T) {
99	js := BuildPageScrollByJS(0, -320)
100	if !strings.Contains(js, "var dx=0,dy=-320") {
101		t.Errorf("expected dy=-320 in JS variables, got: %s", js)
102	}
103}
104
105func TestBuildPageScrollByJS_SyntacticallyValid(t *testing.T) {
106	tests := []struct{ dx, dy int }{{0, 80}, {0, -320}, {-80, 0}, {80, 0}, {0, 0}}
107
108	for _, tt := range tests {
109		js := BuildPageScrollByJS(tt.dx, tt.dy)
110
111		depth := 0
112		for _, ch := range js {
113			switch ch {
114			case '(':
115				depth++
116			case ')':
117				depth--
118			}
119		}
120		if depth != 0 {
121			t.Errorf("dx=%d,dy=%d: unbalanced parentheses (depth=%d)", tt.dx, tt.dy, depth)
122		}
123
124		depth = 0
125		for _, ch := range js {
126			switch ch {
127			case '{':
128				depth++
129			case '}':
130				depth--
131			}
132		}
133		if depth != 0 {
134			t.Errorf("dx=%d,dy=%d: unbalanced braces (depth=%d)", tt.dx, tt.dy, depth)
135		}
136	}
137}
138
139func TestBuildPageScrollByJS_ZeroDeltaSafe(t *testing.T) {
140	js := BuildPageScrollByJS(0, 0)
141	if !strings.Contains(js, "var dx=0,dy=0") {
142		t.Errorf("expected zero deltas in JS variables, got: %s", js)
143	}
144	if !strings.Contains(js, "clientHeight") || !strings.Contains(js, "scrollHeight") {
145		t.Error("zero-delta script must still include vertical scroll checks")
146	}
147}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close