1package coordinator
2
3import (
4 "context"
5 "testing"
6 "time"
7
8 "github.com/stretchr/testify/assert"
9
10 "github.com/bnema/dumber/internal/application/port"
11 "github.com/bnema/dumber/internal/domain/entity"
12 "github.com/bnema/dumber/internal/infrastructure/webkit"
13)
14
15func TestComposeOnClose_Order(t *testing.T) {
16 var calls []string
17
18 composed := composeOnClose(
19 func() { calls = append(calls, "existing") },
20 func() { calls = append(calls, "next") },
21 )
22 composed()
23
24 assert.Equal(t, []string{"existing", "next"}, calls)
25}
26
27func TestComposeOnURIChanged_Order(t *testing.T) {
28 var calls []string
29
30 composed := composeOnURIChanged(
31 func(_ string) { calls = append(calls, "existing") },
32 func(_ string) { calls = append(calls, "next") },
33 )
34 composed("https://example.com")
35
36 assert.Equal(t, []string{"existing", "next"}, calls)
37}
38
39func TestComposeOnLoadChanged_Order(t *testing.T) {
40 var calls []string
41
42 composed := composeOnLoadChanged(
43 func(_ webkit.LoadEvent) { calls = append(calls, "existing") },
44 func(_ webkit.LoadEvent) { calls = append(calls, "next") },
45 )
46 composed(webkit.LoadCommitted)
47
48 assert.Equal(t, []string{"existing", "next"}, calls)
49}
50
51func TestHandlePopupOAuthClose_SuccessSchedulesParentRefresh(t *testing.T) {
52 parentPaneID := entity.PaneID("parent-pane")
53 popupID := port.WebViewID(101)
54
55 c := &ContentCoordinator{
56 webViews: make(map[entity.PaneID]*webkit.WebView),
57 popupOAuth: make(map[port.WebViewID]*popupOAuthState),
58 popupRefresh: make(map[entity.PaneID]*time.Timer),
59 }
60
61 c.trackOAuthPopup(popupID, parentPaneID)
62 c.capturePopupOAuthState(popupID, "https://app/callback?code=123")
63 c.handlePopupOAuthClose(context.Background(), popupID)
64
65 c.popupMu.RLock()
66 _, exists := c.popupOAuth[popupID]
67 refreshTimer := c.popupRefresh[parentPaneID]
68 c.popupMu.RUnlock()
69
70 assert.False(t, exists, "oauth state should be removed after close handling")
71 assert.NotNil(t, refreshTimer, "successful oauth close should schedule parent refresh")
72
73 waitFor(t, time.Second, func() bool {
74 c.popupMu.RLock()
75 defer c.popupMu.RUnlock()
76 return c.popupRefresh[parentPaneID] == nil
77 })
78}
79
80func TestHandlePopupOAuthClose_ErrorDoesNotScheduleParentRefresh(t *testing.T) {
81 parentPaneID := entity.PaneID("parent-pane")
82 popupID := port.WebViewID(102)
83
84 c := &ContentCoordinator{
85 webViews: make(map[entity.PaneID]*webkit.WebView),
86 popupOAuth: make(map[port.WebViewID]*popupOAuthState),
87 popupRefresh: make(map[entity.PaneID]*time.Timer),
88 }
89
90 c.trackOAuthPopup(popupID, parentPaneID)
91 c.capturePopupOAuthState(popupID, "https://app/callback?error=access_denied")
92 c.handlePopupOAuthClose(context.Background(), popupID)
93
94 c.popupMu.RLock()
95 defer c.popupMu.RUnlock()
96 assert.Nil(t, c.popupRefresh[parentPaneID], "oauth errors should not refresh parent pane")
97}
98
99func waitFor(t *testing.T, timeout time.Duration, condition func() bool) {
100 t.Helper()
101
102 deadline := time.Now().Add(timeout)
103 for time.Now().Before(deadline) {
104 if condition() {
105 return
106 }
107 time.Sleep(20 * time.Millisecond)
108 }
109 t.Fatalf("condition not met before timeout (%s)", timeout)
110}