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 TestWebViewClearSemanticNavigationHighlightRemovesTargetArtifacts(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 targetAttribute = \"data-dumber-vim-heading-",
52 "document.querySelectorAll(\"[\" + targetAttribute + \"]\")",
53 "target.removeAttribute(targetAttribute)",
54 "style[\" + styleAttribute + \"]",
55 "delete window[stateKey]",
56 )), "", int32(0)).Once()
57
58 wv := &WebView{browser: browser}
59 require.NoError(t, wv.ClearSemanticNavigationHighlight(context.Background()))
60}
61
62func TestWebViewClearSemanticNavigationHighlightReportsSchedulingFailure(t *testing.T) {
63 oldNewTask, oldPostTask := cefNewTask, cefPostTask
64 defer func() { cefNewTask, cefPostTask = oldNewTask, oldPostTask }()
65 cefNewTask = func(task purecef.Task) purecef.Task { return task }
66 cefPostTask = func(purecef.ThreadID, purecef.Task) int32 { return 0 }
67
68 wv := &WebView{engine: &Engine{}, browser: cefmocks.NewMockBrowser(t)}
69 require.Error(t, wv.ClearSemanticNavigationHighlight(context.Background()))
70}
71
72func TestWebViewNavigateSemantic_NormalizesCountAndRejectsUnsupportedRequests(t *testing.T) {
73 forward := headingNavigationScript(1, 1, "", "test")
74 backward := headingNavigationScript(-1, 1, "#4ade80", "test")
75 assert.Contains(t, forward, "index = firstAtOrAfterViewportTop - 1")
76 assert.Contains(t, forward, "if (index < -1) index = -1;")
77 assert.Contains(t, backward, "index = firstAtOrAfterViewportTop >= 0 ? firstAtOrAfterViewportTop : headings.length")
78 assert.Contains(t, backward, "-1 * 1")
79 assert.NotContains(t, backward, "window.innerHeight")
80 assert.Contains(t, forward, "const highlightColor = \"#fbbf24\"")
81 assert.Contains(t, backward, "const highlightColor = \"#4ade80\"")
82
83 wv := &WebView{}
84 err := wv.NavigateSemantic(context.Background(), dto.SemanticNavigationRequest{
85 Target: dto.SemanticNavigationTargetHeading,
86 Direction: dto.SemanticNavigationForward,
87 Count: 0,
88 })
89 require.NoError(t, err)
90
91 err = wv.NavigateSemantic(context.Background(), dto.SemanticNavigationRequest{
92 Target: dto.SemanticNavigationTargetHeading + 1,
93 Direction: dto.SemanticNavigationForward,
94 })
95 require.Error(t, err)
96
97 err = wv.NavigateSemantic(context.Background(), dto.SemanticNavigationRequest{
98 Target: dto.SemanticNavigationTargetHeading,
99 Direction: 0,
100 })
101 require.Error(t, err)
102}
103
104func mockStringContaining(t *testing.T, fragments ...string) func(string) bool {
105 t.Helper()
106 return func(script string) bool {
107 for _, fragment := range fragments {
108 if !assert.Contains(t, script, fragment) {
109 return false
110 }
111 }
112 return true
113 }
114}