1package content
2
3import (
4 "context"
5 "sync"
6 "testing"
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)
13
14// TestPendingReveal tests the pending reveal state management.
15// These are internal package tests that verify the state transitions
16// without needing to mock WebKit dependencies.
17
18func TestMarkPendingReveal_SetsState(t *testing.T) {
19 c := &Coordinator{
20 pendingReveal: make(map[entity.PaneID]bool),
21 }
22 paneID := entity.PaneID("pane-1")
23
24 c.markPendingReveal(paneID)
25
26 c.revealMu.Lock()
27 defer c.revealMu.Unlock()
28 assert.True(t, c.pendingReveal[paneID])
29}
30
31func TestClearPendingReveal_RemovesState(t *testing.T) {
32 c := &Coordinator{
33 pendingReveal: make(map[entity.PaneID]bool),
34 }
35 paneID := entity.PaneID("pane-1")
36
37 // First mark as pending
38 c.markPendingReveal(paneID)
39
40 // Then clear
41 c.clearPendingReveal(paneID)
42
43 c.revealMu.Lock()
44 defer c.revealMu.Unlock()
45 assert.False(t, c.pendingReveal[paneID])
46}
47
48func TestRevealIfPending_NotPending_DoesNothing(t *testing.T) {
49 callbackCalled := false
50 c := &Coordinator{
51 pendingReveal: make(map[entity.PaneID]bool),
52 webViews: make(map[entity.PaneID]port.WebView),
53 onWebViewShown: func(paneID entity.PaneID) {
54 callbackCalled = true
55 },
56 }
57 paneID := entity.PaneID("pane-1")
58
59 // Call reveal without marking pending first
60 c.revealIfPending(context.Background(), paneID, "http://example.com", "test")
61
62 assert.False(t, callbackCalled, "callback should not be called when not pending")
63}
64
65func TestRevealIfPending_ClearsStateOnReveal(t *testing.T) {
66 c := &Coordinator{
67 pendingReveal: make(map[entity.PaneID]bool),
68 webViews: make(map[entity.PaneID]port.WebView),
69 }
70 paneID := entity.PaneID("pane-1")
71
72 // Mark as pending
73 c.markPendingReveal(paneID)
74
75 // Reveal (will return early since webview is nil, but should still clear state)
76 c.revealIfPending(context.Background(), paneID, "http://example.com", "test")
77
78 c.revealMu.Lock()
79 defer c.revealMu.Unlock()
80 assert.False(t, c.pendingReveal[paneID], "pending state should be cleared after reveal attempt")
81}
82
83func TestRevealIfPending_OnlyRevealsOnce(t *testing.T) {
84 revealCount := 0
85 c := &Coordinator{
86 pendingReveal: make(map[entity.PaneID]bool),
87 webViews: make(map[entity.PaneID]port.WebView),
88 onWebViewShown: func(paneID entity.PaneID) {
89 revealCount++
90 },
91 }
92 paneID := entity.PaneID("pane-1")
93
94 // Mark as pending
95 c.markPendingReveal(paneID)
96
97 // Call reveal multiple times
98 c.revealIfPending(context.Background(), paneID, "", "first")
99 c.revealIfPending(context.Background(), paneID, "", "second")
100 c.revealIfPending(context.Background(), paneID, "", "third")
101
102 // Callback should not be called since webview is nil, but state should be cleared
103 assert.Equal(t, 0, revealCount, "callback not called when webview is nil")
104
105 // Verify state is cleared
106 c.revealMu.Lock()
107 defer c.revealMu.Unlock()
108 assert.False(t, c.pendingReveal[paneID])
109}
110
111func TestPendingReveal_ConcurrentAccess(t *testing.T) {
112 c := &Coordinator{
113 pendingReveal: make(map[entity.PaneID]bool),
114 webViews: make(map[entity.PaneID]port.WebView),
115 }
116
117 var wg sync.WaitGroup
118 paneIDs := []entity.PaneID{"pane-1", "pane-2", "pane-3", "pane-4", "pane-5"}
119
120 // Concurrently mark and clear panes
121 for i := 0; i < 100; i++ {
122 for _, paneID := range paneIDs {
123 wg.Add(3)
124
125 go func(id entity.PaneID) {
126 defer wg.Done()
127 c.markPendingReveal(id)
128 }(paneID)
129
130 go func(id entity.PaneID) {
131 defer wg.Done()
132 c.clearPendingReveal(id)
133 }(paneID)
134
135 go func(id entity.PaneID) {
136 defer wg.Done()
137 c.revealIfPending(context.Background(), id, "", "concurrent")
138 }(paneID)
139 }
140 }
141
142 wg.Wait()
143 // Test passes if no race conditions occur
144}
145
146func TestMarkPendingReveal_MultiplePanes(t *testing.T) {
147 c := &Coordinator{
148 pendingReveal: make(map[entity.PaneID]bool),
149 }
150
151 pane1 := entity.PaneID("pane-1")
152 pane2 := entity.PaneID("pane-2")
153
154 c.markPendingReveal(pane1)
155 c.markPendingReveal(pane2)
156
157 c.revealMu.Lock()
158 defer c.revealMu.Unlock()
159 assert.True(t, c.pendingReveal[pane1])
160 assert.True(t, c.pendingReveal[pane2])
161}
162
163func TestClearPendingReveal_OnlyAffectsTargetPane(t *testing.T) {
164 c := &Coordinator{
165 pendingReveal: make(map[entity.PaneID]bool),
166 }
167
168 pane1 := entity.PaneID("pane-1")
169 pane2 := entity.PaneID("pane-2")
170
171 c.markPendingReveal(pane1)
172 c.markPendingReveal(pane2)
173 c.clearPendingReveal(pane1)
174
175 c.revealMu.Lock()
176 defer c.revealMu.Unlock()
177 assert.False(t, c.pendingReveal[pane1], "pane1 should be cleared")
178 assert.True(t, c.pendingReveal[pane2], "pane2 should still be pending")
179}