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}