test(client): restore reconnect toast coverage
- commit
- c4381db480eff655d4b4452466fe9c3fbaf9b98e
- author
- brice · · 2026-07-14 19:45 UTC
- parent
- 1238e7d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +41 -1
M internal/usecase/client/reconnect_toast_test.go +41 -1
| @@ -8,6 +8,7 @@ import ( | |||
| 8 | 8 | "io" | |
| 9 | 9 | "log/slog" | |
| 10 | 10 | "strings" | |
| 11 | + | "sync" | |
| 11 | 12 | "testing" | |
| 12 | 13 | "time" | |
| 13 | 14 | ||
| @@ -71,6 +72,34 @@ func newReconnectToastTerminalHarnessWithOutput(t *testing.T, out io.Writer) *re | |||
| 71 | 72 | ||
| 72 | 73 | func (h *reconnectToastTerminalHarness) closeInput() { _ = h.inWriter.Close() } | |
| 73 | 74 | ||
| 75 | + | type reconnectToastOutputRecorder struct { | |
| 76 | + | mu sync.Mutex | |
| 77 | + | pending strings.Builder | |
| 78 | + | completed chan string | |
| 79 | + | } | |
| 80 | + | ||
| 81 | + | func newReconnectToastOutputRecorder() *reconnectToastOutputRecorder { | |
| 82 | + | return &reconnectToastOutputRecorder{completed: make(chan string, 3)} | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | func (r *reconnectToastOutputRecorder) Write(p []byte) (int, error) { | |
| 86 | + | const restoreCursor = "\x1b[0m\x1b[u" | |
| 87 | + | ||
| 88 | + | r.mu.Lock() | |
| 89 | + | r.pending.Write(p) | |
| 90 | + | var output string | |
| 91 | + | if string(p) == restoreCursor { | |
| 92 | + | output = r.pending.String() | |
| 93 | + | r.pending.Reset() | |
| 94 | + | } | |
| 95 | + | r.mu.Unlock() | |
| 96 | + | ||
| 97 | + | if output != "" { | |
| 98 | + | r.completed <- output | |
| 99 | + | } | |
| 100 | + | return len(p), nil | |
| 101 | + | } | |
| 102 | + | ||
| 74 | 103 | func reconnectToastWelcome(token uint64) ports.Frame { | |
| 75 | 104 | return reconnectToastWelcomeNamed("", token) | |
| 76 | 105 | } | |
| @@ -225,7 +254,8 @@ func newReconnectAttachAttempt(term ports.Terminal, transport ports.Transport, c | |||
| 225 | 254 | } | |
| 226 | 255 | ||
| 227 | 256 | func TestReconnectToastDegradedClearsModalAndProbingIsVisible(t *testing.T) { | |
| 228 | - | term := newReconnectToastTerminalHarness(t) | |
| 257 | + | out := newReconnectToastOutputRecorder() | |
| 258 | + | term := newReconnectToastTerminalHarnessWithOutput(t, out) | |
| 229 | 259 | defer term.closeInput() | |
| 230 | 260 | tr := newReconnectToastLinkTransport() | |
| 231 | 261 | tr.recvCh <- reconnectToastRecv{frame: reconnectToastWelcome(44)} | |
| @@ -235,8 +265,18 @@ func TestReconnectToastDegradedClearsModalAndProbingIsVisible(t *testing.T) { | |||
| 235 | 265 | attempt := newReconnectAttachAttempt(term.term, tr, newReconnectHandshakeClock(t), AttachRequest{Intent: ports.IntentAttach, SessionName: "main", Remote: true}, 0, &terminalThemeState{}, tr.LinkEvents(), &ms) | |
| 236 | 266 | go func() { resultCh <- attempt.run(context.Background()) }() | |
| 237 | 267 | ||
| 268 | + | tr.events <- ports.LinkEvent{State: ports.LinkStateProbing} | |
| 269 | + | probed := <-out.completed | |
| 270 | + | require.Contains(t, probed, reconnectStageMessage(reconnectStageProbingUDP)) | |
| 271 | + | ||
| 238 | 272 | tr.events <- ports.LinkEvent{State: ports.LinkStateDegraded} | |
| 273 | + | cleared := <-out.completed | |
| 274 | + | assertReconnectToastClearCoversBounds(t, cleared, reconnectToastBoundsFor(term.size, reconnectStageMessage(reconnectStageProbingUDP))) | |
| 275 | + | ||
| 239 | 276 | tr.events <- ports.LinkEvent{State: ports.LinkStateProbing} | |
| 277 | + | probed = <-out.completed | |
| 278 | + | require.Contains(t, probed, reconnectStageMessage(reconnectStageProbingUDP)) | |
| 279 | + | ||
| 240 | 280 | tr.recvCh <- reconnectToastRecv{frame: reconnectToastDetach(ports.ReasonDetach)} | |
| 241 | 281 | result := <-resultCh | |
| 242 | 282 | require.NoError(t, result.err) | |
| @@ -8,6 +8,7 @@ import ( | |||||
| 8 | "io" | 8 | "io" | ||
| 9 | "log/slog" | 9 | "log/slog" | ||
| 10 | "strings" | 10 | "strings" | ||
| 11 | + | "sync" | |||
| 11 | "testing" | 12 | "testing" | ||
| 12 | "time" | 13 | "time" | ||
| 13 | 14 | ||||
| @@ -71,6 +72,34 @@ func newReconnectToastTerminalHarnessWithOutput(t *testing.T, out io.Writer) *re | |||||
| 71 | 72 | ||||
| 72 | func (h *reconnectToastTerminalHarness) closeInput() { _ = h.inWriter.Close() } | 73 | func (h *reconnectToastTerminalHarness) closeInput() { _ = h.inWriter.Close() } | ||
| 73 | 74 | ||||
| 75 | + | type reconnectToastOutputRecorder struct { | |||
| 76 | + | mu sync.Mutex | |||
| 77 | + | pending strings.Builder | |||
| 78 | + | completed chan string | |||
| 79 | + | } | |||
| 80 | + | ||||
| 81 | + | func newReconnectToastOutputRecorder() *reconnectToastOutputRecorder { | |||
| 82 | + | return &reconnectToastOutputRecorder{completed: make(chan string, 3)} | |||
| 83 | + | } | |||
| 84 | + | ||||
| 85 | + | func (r *reconnectToastOutputRecorder) Write(p []byte) (int, error) { | |||
| 86 | + | const restoreCursor = "\x1b[0m\x1b[u" | |||
| 87 | + | ||||
| 88 | + | r.mu.Lock() | |||
| 89 | + | r.pending.Write(p) | |||
| 90 | + | var output string | |||
| 91 | + | if string(p) == restoreCursor { | |||
| 92 | + | output = r.pending.String() | |||
| 93 | + | r.pending.Reset() | |||
| 94 | + | } | |||
| 95 | + | r.mu.Unlock() | |||
| 96 | + | ||||
| 97 | + | if output != "" { | |||
| 98 | + | r.completed <- output | |||
| 99 | + | } | |||
| 100 | + | return len(p), nil | |||
| 101 | + | } | |||
| 102 | + | ||||
| 74 | func reconnectToastWelcome(token uint64) ports.Frame { | 103 | func reconnectToastWelcome(token uint64) ports.Frame { | ||
| 75 | return reconnectToastWelcomeNamed("", token) | 104 | return reconnectToastWelcomeNamed("", token) | ||
| 76 | } | 105 | } | ||
| @@ -225,7 +254,8 @@ func newReconnectAttachAttempt(term ports.Terminal, transport ports.Transport, c | |||||
| 225 | } | 254 | } | ||
| 226 | 255 | ||||
| 227 | func TestReconnectToastDegradedClearsModalAndProbingIsVisible(t *testing.T) { | 256 | func TestReconnectToastDegradedClearsModalAndProbingIsVisible(t *testing.T) { | ||
| 228 | - | term := newReconnectToastTerminalHarness(t) | 257 | + | out := newReconnectToastOutputRecorder() |
| 258 | + | term := newReconnectToastTerminalHarnessWithOutput(t, out) | |||
| 229 | defer term.closeInput() | 259 | defer term.closeInput() | ||
| 230 | tr := newReconnectToastLinkTransport() | 260 | tr := newReconnectToastLinkTransport() | ||
| 231 | tr.recvCh <- reconnectToastRecv{frame: reconnectToastWelcome(44)} | 261 | tr.recvCh <- reconnectToastRecv{frame: reconnectToastWelcome(44)} | ||
| @@ -235,8 +265,18 @@ func TestReconnectToastDegradedClearsModalAndProbingIsVisible(t *testing.T) { | |||||
| 235 | attempt := newReconnectAttachAttempt(term.term, tr, newReconnectHandshakeClock(t), AttachRequest{Intent: ports.IntentAttach, SessionName: "main", Remote: true}, 0, &terminalThemeState{}, tr.LinkEvents(), &ms) | 265 | attempt := newReconnectAttachAttempt(term.term, tr, newReconnectHandshakeClock(t), AttachRequest{Intent: ports.IntentAttach, SessionName: "main", Remote: true}, 0, &terminalThemeState{}, tr.LinkEvents(), &ms) | ||
| 236 | go func() { resultCh <- attempt.run(context.Background()) }() | 266 | go func() { resultCh <- attempt.run(context.Background()) }() | ||
| 237 | 267 | ||||
| 268 | + | tr.events <- ports.LinkEvent{State: ports.LinkStateProbing} | |||
| 269 | + | probed := <-out.completed | |||
| 270 | + | require.Contains(t, probed, reconnectStageMessage(reconnectStageProbingUDP)) | |||
| 271 | + | ||||
| 238 | tr.events <- ports.LinkEvent{State: ports.LinkStateDegraded} | 272 | tr.events <- ports.LinkEvent{State: ports.LinkStateDegraded} | ||
| 273 | + | cleared := <-out.completed | |||
| 274 | + | assertReconnectToastClearCoversBounds(t, cleared, reconnectToastBoundsFor(term.size, reconnectStageMessage(reconnectStageProbingUDP))) | |||
| 275 | + | ||||
| 239 | tr.events <- ports.LinkEvent{State: ports.LinkStateProbing} | 276 | tr.events <- ports.LinkEvent{State: ports.LinkStateProbing} | ||
| 277 | + | probed = <-out.completed | |||
| 278 | + | require.Contains(t, probed, reconnectStageMessage(reconnectStageProbingUDP)) | |||
| 279 | + | ||||
| 240 | tr.recvCh <- reconnectToastRecv{frame: reconnectToastDetach(ports.ReasonDetach)} | 280 | tr.recvCh <- reconnectToastRecv{frame: reconnectToastDetach(ports.ReasonDetach)} | ||
| 241 | result := <-resultCh | 281 | result := <-resultCh | ||
| 242 | require.NoError(t, result.err) | 282 | require.NoError(t, result.err) | ||