1package cef
2
3import (
4 "context"
5 "testing"
6
7 purecef "github.com/bnema/purego-cef/cef"
8 cefmocks "github.com/bnema/purego-cef/cef/mocks"
9 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/mock"
11 "github.com/stretchr/testify/require"
12
13 "github.com/bnema/dumber/internal/application/dto"
14)
15
16func TestWebViewNavigateSemantic_HeadingExecutesStrictNavigationScript(t *testing.T) {
17 browser := cefmocks.NewMockBrowser(t)
18 frame := cefmocks.NewMockFrame(t)
19 browser.EXPECT().GetMainFrame().Return(frame).Once()
20 frame.EXPECT().ExecuteJavaScript(mock.MatchedBy(mockStringContaining(t,
21 "document.querySelectorAll(\"h1,h2,h3,h4,h5,h6\")",
22 "heading.closest(\"[hidden],[inert],[aria-hidden=\\\"true\\\"]\")",
23 "window.getComputedStyle(heading)",
24 "firstAtOrAfterViewportTop",
25 "if (next < 0 || next >= headings.length) return;",
26 "previous && previous !== target",
27 "scrollIntoView",
28 "window[stateKey] = target",
29 "const highlightColor = \"#22c55e\"",
30 "const targetAttribute = \"data-dumber-vim-heading-",
31 "let style = document.querySelector(\"style[\" + styleAttribute + \"]\")",
32 "style.textContent = \"[\" + targetAttribute + \"]\"",
33 "1 * 3",
34 )), "", int32(0)).Once()
35
36 wv := &WebView{browser: browser}
37 err := wv.NavigateSemantic(context.Background(), dto.SemanticNavigationRequest{
38 Target: dto.SemanticNavigationTargetHeading,
39 Direction: dto.SemanticNavigationForward,
40 Count: 3,
41 HighlightColor: "#22c55e",
42 })
43 require.NoError(t, err)
44}
45
46func TestWebViewActivateSemanticNavigationTargetClicksSelectedHeadingLink(t *testing.T) {
47 browser := cefmocks.NewMockBrowser(t)
48 frame := cefmocks.NewMockFrame(t)
49 browser.EXPECT().GetMainFrame().Return(frame).Once()
50 frame.EXPECT().ExecuteJavaScript(mock.MatchedBy(mockStringContaining(t,
51 "const target = window[stateKey]",
52 "target.closest(\"a[href]\")",
53 "target.querySelector(\"a[href]\")",
54 "link.click()",
55 )), "", int32(0)).Once()
56
57 wv := &WebView{browser: browser}
58 require.NoError(t, wv.ActivateSemanticNavigationTarget(context.Background()))
59}
60
61func TestWebViewClearSemanticNavigationHighlightRemovesTargetArtifacts(t *testing.T) {
62 browser := cefmocks.NewMockBrowser(t)
63 frame := cefmocks.NewMockFrame(t)
64 browser.EXPECT().GetMainFrame().Return(frame).Once()
65 frame.EXPECT().ExecuteJavaScript(mock.MatchedBy(mockStringContaining(t,
66 "const targetAttribute = \"data-dumber-vim-heading-",
67 "document.querySelectorAll(\"[\" + targetAttribute + \"]\")",
68 "target.removeAttribute(targetAttribute)",
69 "style[\" + styleAttribute + \"]",
70 "delete window[stateKey]",
71 )), "", int32(0)).Once()
72
73 wv := &WebView{browser: browser}
74 require.NoError(t, wv.ClearSemanticNavigationHighlight(context.Background()))
75}
76
77func TestWebViewClearSemanticNavigationHighlightReportsSchedulingFailure(t *testing.T) {
78 oldNewTask, oldPostTask := cefNewTask, cefPostTask
79 defer func() { cefNewTask, cefPostTask = oldNewTask, oldPostTask }()
80 cefNewTask = func(task purecef.Task) purecef.Task { return task }
81 cefPostTask = func(purecef.ThreadID, purecef.Task) int32 { return 0 }
82
83 wv := &WebView{engine: &Engine{}, browser: cefmocks.NewMockBrowser(t)}
84 require.Error(t, wv.ClearSemanticNavigationHighlight(context.Background()))
85}
86
87func TestWebViewNavigateSemantic_NormalizesCountAndRejectsUnsupportedRequests(t *testing.T) {
88 forward := headingNavigationScript(1, 1, "", "test")
89 backward := headingNavigationScript(-1, 1, "#4ade80", "test")
90 assert.Contains(t, forward, "index = firstAtOrAfterViewportTop - 1")
91 assert.Contains(t, forward, "if (index < -1) index = -1;")
92 assert.Contains(t, backward, "index = firstAtOrAfterViewportTop >= 0 ? firstAtOrAfterViewportTop : headings.length")
93 assert.Contains(t, backward, "-1 * 1")
94 assert.NotContains(t, backward, "window.innerHeight")
95 assert.Contains(t, forward, "const highlightColor = \"#fbbf24\"")
96 assert.Contains(t, backward, "const highlightColor = \"#4ade80\"")
97
98 wv := &WebView{}
99 err := wv.NavigateSemantic(context.Background(), dto.SemanticNavigationRequest{
100 Target: dto.SemanticNavigationTargetHeading,
101 Direction: dto.SemanticNavigationForward,
102 Count: 0,
103 })
104 require.NoError(t, err)
105
106 err = wv.NavigateSemantic(context.Background(), dto.SemanticNavigationRequest{
107 Target: dto.SemanticNavigationTargetHeading + 1,
108 Direction: dto.SemanticNavigationForward,
109 })
110 require.Error(t, err)
111
112 err = wv.NavigateSemantic(context.Background(), dto.SemanticNavigationRequest{
113 Target: dto.SemanticNavigationTargetHeading,
114 Direction: 0,
115 })
116 require.Error(t, err)
117}
118
119func mockStringContaining(t *testing.T, fragments ...string) func(string) bool {
120 t.Helper()
121 return func(script string) bool {
122 for _, fragment := range fragments {
123 if !assert.Contains(t, script, fragment) {
124 return false
125 }
126 }
127 return true
128 }
129}