detached 80d4e6a

test(navigation): cover URI callbacks after pool reuse of the same WebView

commit
80d4e6aeb396a1dbe56fd3d53bcff896ebee71e8
author
brice · · 2026-10-15 07:35 UTC
parent
13aa777
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +31 -0
M internal/ui/coordinator/content/navigation_security_test.go +31 -0
@@ -6,6 +6,8 @@ import (
66 "testing"
77
88 "github.com/stretchr/testify/assert"
9+ "github.com/stretchr/testify/mock"
10+ "github.com/stretchr/testify/require"
911
1012 "github.com/bnema/dumber/internal/application/port"
1113 "github.com/bnema/dumber/internal/application/port/mocks"
@@ -289,3 +291,32 @@ func TestHandleURIChanged_NonHTTPDocumentClearsOrigin(t *testing.T) {
289291 assert.Equal(t, []string{""}, rec.origins, doc)
290292 }
291293 }
294+
295+func TestURIChangedCallback_IgnoredAfterSameWebViewIsReusedFromPool(t *testing.T) {
296+ ctx := context.Background()
297+ c := newMinimalCoordinator()
298+ rec := &externalLaunchRecorder{}
299+ rec.install(c)
300+ spaCalls := 0
301+ c.onPaneURIUpdated = func(entity.PaneID, string) { spaCalls++ }
302+
303+ generation := uint64(1)
304+ wv := mocks.NewMockWebView(t)
305+ wv.EXPECT().ID().Return(port.WebViewID(1)).Maybe()
306+ wv.EXPECT().Generation().RunAndReturn(func() uint64 { return generation }).Maybe()
307+ var callbacks *port.WebViewCallbacks
308+ wv.EXPECT().SetCallbacks(mock.Anything).Run(func(cb *port.WebViewCallbacks) { callbacks = cb }).Once()
309+
310+ c.setWebViewLocked(testPaneID, wv)
311+ c.setupWebViewCallbacks(ctx, testPaneID, wv)
312+ require.NotNil(t, callbacks)
313+
314+ // The same object is reused from the pool for a new binding.
315+ generation = 2
316+ callbacks.OnURIChanged("https://site.example/")
317+ callbacks.OnURIChanged("claude://login")
318+
319+ assert.Empty(t, rec.prompts)
320+ assert.Empty(t, c.externalPromptOrigins)
321+ assert.Zero(t, spaCalls)
322+}
@@ -6,6 +6,8 @@ import (
6 "testing"6 "testing"
7 7
8 "github.com/stretchr/testify/assert"8 "github.com/stretchr/testify/assert"
9+ "github.com/stretchr/testify/mock"
10+ "github.com/stretchr/testify/require"
9 11
10 "github.com/bnema/dumber/internal/application/port"12 "github.com/bnema/dumber/internal/application/port"
11 "github.com/bnema/dumber/internal/application/port/mocks"13 "github.com/bnema/dumber/internal/application/port/mocks"
@@ -289,3 +291,32 @@ func TestHandleURIChanged_NonHTTPDocumentClearsOrigin(t *testing.T) {
289 assert.Equal(t, []string{""}, rec.origins, doc)291 assert.Equal(t, []string{""}, rec.origins, doc)
290 }292 }
291 }293 }
294+
295+func TestURIChangedCallback_IgnoredAfterSameWebViewIsReusedFromPool(t *testing.T) {
296+ ctx := context.Background()
297+ c := newMinimalCoordinator()
298+ rec := &externalLaunchRecorder{}
299+ rec.install(c)
300+ spaCalls := 0
301+ c.onPaneURIUpdated = func(entity.PaneID, string) { spaCalls++ }
302+
303+ generation := uint64(1)
304+ wv := mocks.NewMockWebView(t)
305+ wv.EXPECT().ID().Return(port.WebViewID(1)).Maybe()
306+ wv.EXPECT().Generation().RunAndReturn(func() uint64 { return generation }).Maybe()
307+ var callbacks *port.WebViewCallbacks
308+ wv.EXPECT().SetCallbacks(mock.Anything).Run(func(cb *port.WebViewCallbacks) { callbacks = cb }).Once()
309+
310+ c.setWebViewLocked(testPaneID, wv)
311+ c.setupWebViewCallbacks(ctx, testPaneID, wv)
312+ require.NotNil(t, callbacks)
313+
314+ // The same object is reused from the pool for a new binding.
315+ generation = 2
316+ callbacks.OnURIChanged("https://site.example/")
317+ callbacks.OnURIChanged("claude://login")
318+
319+ assert.Empty(t, rec.prompts)
320+ assert.Empty(t, c.externalPromptOrigins)
321+ assert.Zero(t, spaCalls)
322+}

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