test(daemon): cover floating activation paths
- commit
- 116924109462c4cecc13292b163c376e5f3649ec
- author
- brice · · 2026-07-10 10:58 UTC
- parent
- bc8142c
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +126 -0
M internal/usecase/daemon/attention_test.go +5 -0
| @@ -440,6 +440,10 @@ func TestCloseRingingTabRefreshesOtherSessionBottomBar(t *testing.T) { | |||
| 440 | 440 | ||
| 441 | 441 | func TestAltAJumpAttentionSelectsOldestLocalTab(t *testing.T) { | |
| 442 | 442 | d, sess, ac, sends, releases := newManualTabSession(t, 3) | |
| 443 | + | sess.mu.Lock() | |
| 444 | + | sess.client = ac | |
| 445 | + | sess.mu.Unlock() | |
| 446 | + | d.ptys = newBlockingOpenFactory(t, d) | |
| 443 | 447 | defer releases[0]() | |
| 444 | 448 | defer releases[1]() | |
| 445 | 449 | defer releases[2]() | |
| @@ -454,6 +458,7 @@ func TestAltAJumpAttentionSelectsOldestLocalTab(t *testing.T) { | |||
| 454 | 458 | d.handleInput(sess, ac, []byte("\x1ba")) | |
| 455 | 459 | ||
| 456 | 460 | require.Equal(t, 2, activeTabIndex(sess)) | |
| 461 | + | requireFloatingInitialized(t, sess.activeTab()) | |
| 457 | 462 | _ = mustOutputData(t, sends) | |
| 458 | 463 | } | |
| 459 | 464 | ||
| @@ -440,6 +440,10 @@ func TestCloseRingingTabRefreshesOtherSessionBottomBar(t *testing.T) { | |||||
| 440 | 440 | ||||
| 441 | func TestAltAJumpAttentionSelectsOldestLocalTab(t *testing.T) { | 441 | func TestAltAJumpAttentionSelectsOldestLocalTab(t *testing.T) { | ||
| 442 | d, sess, ac, sends, releases := newManualTabSession(t, 3) | 442 | d, sess, ac, sends, releases := newManualTabSession(t, 3) | ||
| 443 | + | sess.mu.Lock() | |||
| 444 | + | sess.client = ac | |||
| 445 | + | sess.mu.Unlock() | |||
| 446 | + | d.ptys = newBlockingOpenFactory(t, d) | |||
| 443 | defer releases[0]() | 447 | defer releases[0]() | ||
| 444 | defer releases[1]() | 448 | defer releases[1]() | ||
| 445 | defer releases[2]() | 449 | defer releases[2]() | ||
| @@ -454,6 +458,7 @@ func TestAltAJumpAttentionSelectsOldestLocalTab(t *testing.T) { | |||||
| 454 | d.handleInput(sess, ac, []byte("\x1ba")) | 458 | d.handleInput(sess, ac, []byte("\x1ba")) | ||
| 455 | 459 | ||||
| 456 | require.Equal(t, 2, activeTabIndex(sess)) | 460 | require.Equal(t, 2, activeTabIndex(sess)) | ||
| 461 | + | requireFloatingInitialized(t, sess.activeTab()) | |||
| 457 | _ = mustOutputData(t, sends) | 462 | _ = mustOutputData(t, sends) | ||
| 458 | } | 463 | } | ||
| 459 | 464 | ||||
M internal/usecase/daemon/daemon_test.go +22 -0
| @@ -125,6 +125,28 @@ func (*quietPTY) Resize(domain.Size) error { return nil } | |||
| 125 | 125 | func (*quietPTY) Pid() int { return 0 } | |
| 126 | 126 | func (*quietPTY) ForegroundPgid() (int, error) { return 0, nil } | |
| 127 | 127 | ||
| 128 | + | // blockingOpenFactory keeps activation tests deterministically in Warming | |
| 129 | + | // without starting pane reader/scheduler goroutines. | |
| 130 | + | type blockingOpenFactory struct { | |
| 131 | + | release chan struct{} | |
| 132 | + | once sync.Once | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | func newBlockingOpenFactory(t *testing.T, d *Daemon) *blockingOpenFactory { | |
| 136 | + | t.Helper() | |
| 137 | + | f := &blockingOpenFactory{release: make(chan struct{})} | |
| 138 | + | t.Cleanup(func() { | |
| 139 | + | f.once.Do(func() { close(f.release) }) | |
| 140 | + | d.sessWg.Wait() | |
| 141 | + | }) | |
| 142 | + | return f | |
| 143 | + | } | |
| 144 | + | ||
| 145 | + | func (f *blockingOpenFactory) Open(string, []string, []string, string, domain.Size) (ports.PTY, error) { | |
| 146 | + | <-f.release | |
| 147 | + | return nil, io.ErrClosedPipe | |
| 148 | + | } | |
| 149 | + | ||
| 128 | 150 | // newFactory keeps the supplied fixture for normal panes. Floating prewarms | |
| 129 | 151 | // have a distinct (smaller) geometry and receive independent quiet PTYs, so | |
| 130 | 152 | // they cannot consume test output or add post-test mock calls. | |
| @@ -125,6 +125,28 @@ func (*quietPTY) Resize(domain.Size) error { return nil } | |||||
| 125 | func (*quietPTY) Pid() int { return 0 } | 125 | func (*quietPTY) Pid() int { return 0 } | ||
| 126 | func (*quietPTY) ForegroundPgid() (int, error) { return 0, nil } | 126 | func (*quietPTY) ForegroundPgid() (int, error) { return 0, nil } | ||
| 127 | 127 | ||||
| 128 | + | // blockingOpenFactory keeps activation tests deterministically in Warming | |||
| 129 | + | // without starting pane reader/scheduler goroutines. | |||
| 130 | + | type blockingOpenFactory struct { | |||
| 131 | + | release chan struct{} | |||
| 132 | + | once sync.Once | |||
| 133 | + | } | |||
| 134 | + | ||||
| 135 | + | func newBlockingOpenFactory(t *testing.T, d *Daemon) *blockingOpenFactory { | |||
| 136 | + | t.Helper() | |||
| 137 | + | f := &blockingOpenFactory{release: make(chan struct{})} | |||
| 138 | + | t.Cleanup(func() { | |||
| 139 | + | f.once.Do(func() { close(f.release) }) | |||
| 140 | + | d.sessWg.Wait() | |||
| 141 | + | }) | |||
| 142 | + | return f | |||
| 143 | + | } | |||
| 144 | + | ||||
| 145 | + | func (f *blockingOpenFactory) Open(string, []string, []string, string, domain.Size) (ports.PTY, error) { | |||
| 146 | + | <-f.release | |||
| 147 | + | return nil, io.ErrClosedPipe | |||
| 148 | + | } | |||
| 149 | + | ||||
| 128 | // newFactory keeps the supplied fixture for normal panes. Floating prewarms | 150 | // newFactory keeps the supplied fixture for normal panes. Floating prewarms | ||
| 129 | // have a distinct (smaller) geometry and receive independent quiet PTYs, so | 151 | // have a distinct (smaller) geometry and receive independent quiet PTYs, so | ||
| 130 | // they cannot consume test output or add post-test mock calls. | 152 | // they cannot consume test output or add post-test mock calls. | ||
M internal/usecase/daemon/input_test.go +44 -0
| @@ -62,6 +62,22 @@ func TestAltDigitSwitchesBetweenThreeTabs(t *testing.T) { | |||
| 62 | 62 | require.Eventually(t, func() bool { return len(writes3) == 1 }, 2*time.Second, 5*time.Millisecond) | |
| 63 | 63 | require.Equal(t, []byte("C"), <-writes3) | |
| 64 | 64 | ||
| 65 | + | d.mu.Lock() | |
| 66 | + | var sess *session | |
| 67 | + | for _, candidate := range d.sessions { | |
| 68 | + | sess = candidate | |
| 69 | + | break | |
| 70 | + | } | |
| 71 | + | d.mu.Unlock() | |
| 72 | + | require.NotNil(t, sess) | |
| 73 | + | sess.mu.Lock() | |
| 74 | + | tabs := append([]*tab(nil), sess.tabs...) | |
| 75 | + | sess.mu.Unlock() | |
| 76 | + | require.Len(t, tabs, 3) | |
| 77 | + | for _, tb := range tabs { | |
| 78 | + | requireFloatingInitialized(t, tb) | |
| 79 | + | } | |
| 80 | + | ||
| 65 | 81 | releaseConn() | |
| 66 | 82 | releasePTY1() | |
| 67 | 83 | releasePTY2() | |
| @@ -81,6 +97,13 @@ func TestAltCForwardsToPTY(t *testing.T) { | |||
| 81 | 97 | releasePTY() | |
| 82 | 98 | } | |
| 83 | 99 | ||
| 100 | + | func requireFloatingInitialized(t *testing.T, tb *tab) { | |
| 101 | + | t.Helper() | |
| 102 | + | tb.mu.Lock() | |
| 103 | + | defer tb.mu.Unlock() | |
| 104 | + | require.NotEqual(t, floatingUninitialized, tb.floating.state) | |
| 105 | + | } | |
| 106 | + | ||
| 84 | 107 | func installTestFloating(tb *tab, p *pane, visible bool) { | |
| 85 | 108 | tb.mu.Lock() | |
| 86 | 109 | defer tb.mu.Unlock() | |
| @@ -167,6 +190,27 @@ func TestFloatingStaysTerminalTargetAfterDirectionalFocus(t *testing.T) { | |||
| 167 | 190 | requirePTYWrite(t, floatingWrites, []byte("x")) | |
| 168 | 191 | } | |
| 169 | 192 | ||
| 193 | + | func TestFloatingVisibilityRemainsIndependentAcrossTabSwitches(t *testing.T) { | |
| 194 | + | d, sess, ac, _ := newManualSessionWithPTYs(t, nil) | |
| 195 | + | first := sess.activeTab() | |
| 196 | + | second := newTab(nil, first.size) | |
| 197 | + | installTestFloating(first, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), true) | |
| 198 | + | installTestFloating(second, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), false) | |
| 199 | + | sess.mu.Lock() | |
| 200 | + | sess.tabs = append(sess.tabs, second) | |
| 201 | + | sess.mu.Unlock() | |
| 202 | + | ||
| 203 | + | daemonKeyHandler{d: d, ac: ac}.Action(keys.ActionSwitchTab2) | |
| 204 | + | second.mu.Lock() | |
| 205 | + | require.Equal(t, floatingHidden, second.floating.state) | |
| 206 | + | second.mu.Unlock() | |
| 207 | + | ||
| 208 | + | daemonKeyHandler{d: d, ac: ac}.Action(keys.ActionSwitchTab1) | |
| 209 | + | first.mu.Lock() | |
| 210 | + | require.Equal(t, floatingVisible, first.floating.state) | |
| 211 | + | first.mu.Unlock() | |
| 212 | + | } | |
| 213 | + | ||
| 170 | 214 | func TestFloatingMouseTranslatesSGRToInnerCoordinates(t *testing.T) { | |
| 171 | 215 | normal := portsmocks.NewMockPTY(t) | |
| 172 | 216 | floatingPTY := portsmocks.NewMockPTY(t) | |
| @@ -62,6 +62,22 @@ func TestAltDigitSwitchesBetweenThreeTabs(t *testing.T) { | |||||
| 62 | require.Eventually(t, func() bool { return len(writes3) == 1 }, 2*time.Second, 5*time.Millisecond) | 62 | require.Eventually(t, func() bool { return len(writes3) == 1 }, 2*time.Second, 5*time.Millisecond) | ||
| 63 | require.Equal(t, []byte("C"), <-writes3) | 63 | require.Equal(t, []byte("C"), <-writes3) | ||
| 64 | 64 | ||||
| 65 | + | d.mu.Lock() | |||
| 66 | + | var sess *session | |||
| 67 | + | for _, candidate := range d.sessions { | |||
| 68 | + | sess = candidate | |||
| 69 | + | break | |||
| 70 | + | } | |||
| 71 | + | d.mu.Unlock() | |||
| 72 | + | require.NotNil(t, sess) | |||
| 73 | + | sess.mu.Lock() | |||
| 74 | + | tabs := append([]*tab(nil), sess.tabs...) | |||
| 75 | + | sess.mu.Unlock() | |||
| 76 | + | require.Len(t, tabs, 3) | |||
| 77 | + | for _, tb := range tabs { | |||
| 78 | + | requireFloatingInitialized(t, tb) | |||
| 79 | + | } | |||
| 80 | + | ||||
| 65 | releaseConn() | 81 | releaseConn() | ||
| 66 | releasePTY1() | 82 | releasePTY1() | ||
| 67 | releasePTY2() | 83 | releasePTY2() | ||
| @@ -81,6 +97,13 @@ func TestAltCForwardsToPTY(t *testing.T) { | |||||
| 81 | releasePTY() | 97 | releasePTY() | ||
| 82 | } | 98 | } | ||
| 83 | 99 | ||||
| 100 | + | func requireFloatingInitialized(t *testing.T, tb *tab) { | |||
| 101 | + | t.Helper() | |||
| 102 | + | tb.mu.Lock() | |||
| 103 | + | defer tb.mu.Unlock() | |||
| 104 | + | require.NotEqual(t, floatingUninitialized, tb.floating.state) | |||
| 105 | + | } | |||
| 106 | + | ||||
| 84 | func installTestFloating(tb *tab, p *pane, visible bool) { | 107 | func installTestFloating(tb *tab, p *pane, visible bool) { | ||
| 85 | tb.mu.Lock() | 108 | tb.mu.Lock() | ||
| 86 | defer tb.mu.Unlock() | 109 | defer tb.mu.Unlock() | ||
| @@ -167,6 +190,27 @@ func TestFloatingStaysTerminalTargetAfterDirectionalFocus(t *testing.T) { | |||||
| 167 | requirePTYWrite(t, floatingWrites, []byte("x")) | 190 | requirePTYWrite(t, floatingWrites, []byte("x")) | ||
| 168 | } | 191 | } | ||
| 169 | 192 | ||||
| 193 | + | func TestFloatingVisibilityRemainsIndependentAcrossTabSwitches(t *testing.T) { | |||
| 194 | + | d, sess, ac, _ := newManualSessionWithPTYs(t, nil) | |||
| 195 | + | first := sess.activeTab() | |||
| 196 | + | second := newTab(nil, first.size) | |||
| 197 | + | installTestFloating(first, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), true) | |||
| 198 | + | installTestFloating(second, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), false) | |||
| 199 | + | sess.mu.Lock() | |||
| 200 | + | sess.tabs = append(sess.tabs, second) | |||
| 201 | + | sess.mu.Unlock() | |||
| 202 | + | ||||
| 203 | + | daemonKeyHandler{d: d, ac: ac}.Action(keys.ActionSwitchTab2) | |||
| 204 | + | second.mu.Lock() | |||
| 205 | + | require.Equal(t, floatingHidden, second.floating.state) | |||
| 206 | + | second.mu.Unlock() | |||
| 207 | + | ||||
| 208 | + | daemonKeyHandler{d: d, ac: ac}.Action(keys.ActionSwitchTab1) | |||
| 209 | + | first.mu.Lock() | |||
| 210 | + | require.Equal(t, floatingVisible, first.floating.state) | |||
| 211 | + | first.mu.Unlock() | |||
| 212 | + | } | |||
| 213 | + | ||||
| 170 | func TestFloatingMouseTranslatesSGRToInnerCoordinates(t *testing.T) { | 214 | func TestFloatingMouseTranslatesSGRToInnerCoordinates(t *testing.T) { | ||
| 171 | normal := portsmocks.NewMockPTY(t) | 215 | normal := portsmocks.NewMockPTY(t) | ||
| 172 | floatingPTY := portsmocks.NewMockPTY(t) | 216 | floatingPTY := portsmocks.NewMockPTY(t) | ||
M internal/usecase/daemon/palette_test.go +26 -0
| @@ -18,6 +18,10 @@ func TestPaletteOpenTypeEnterRunAndEscClose(t *testing.T) { | |||
| 18 | 18 | p1, release1 := newBlockingPTY(t) | |
| 19 | 19 | p2, release2 := newBlockingPTY(t) | |
| 20 | 20 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1, p2) | |
| 21 | + | sess.mu.Lock() | |
| 22 | + | sess.client = ac | |
| 23 | + | sess.mu.Unlock() | |
| 24 | + | d.ptys = newBlockingOpenFactory(t, d) | |
| 21 | 25 | defer release1() | |
| 22 | 26 | defer release2() | |
| 23 | 27 | ||
| @@ -31,6 +35,7 @@ func TestPaletteOpenTypeEnterRunAndEscClose(t *testing.T) { | |||
| 31 | 35 | d.handleInput(sess, ac, []byte("NXT\r")) | |
| 32 | 36 | require.False(t, ac.overlays.paletteActive()) | |
| 33 | 37 | require.Equal(t, 1, activeTabIndex(sess)) | |
| 38 | + | requireFloatingInitialized(t, sess.activeTab()) | |
| 34 | 39 | awaitFrame(t, sends, ports.MsgOutput) | |
| 35 | 40 | ||
| 36 | 41 | d.handleInput(sess, ac, []byte("\x1b ")) | |
| @@ -40,6 +45,24 @@ func TestPaletteOpenTypeEnterRunAndEscClose(t *testing.T) { | |||
| 40 | 45 | awaitFrame(t, sends, ports.MsgOutput) | |
| 41 | 46 | } | |
| 42 | 47 | ||
| 48 | + | func TestPaletteFLTExecutesFloatingToggle(t *testing.T) { | |
| 49 | + | p, release := newBlockingPTY(t) | |
| 50 | + | defer release() | |
| 51 | + | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 52 | + | tb := sess.activeTab() | |
| 53 | + | installTestFloating(tb, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), false) | |
| 54 | + | ||
| 55 | + | d.handleInput(sess, ac, []byte("\x1b ")) | |
| 56 | + | awaitFrame(t, sends, ports.MsgOutput) | |
| 57 | + | d.handleInput(sess, ac, []byte("FLT\r")) | |
| 58 | + | ||
| 59 | + | require.False(t, ac.overlays.paletteActive()) | |
| 60 | + | tb.mu.Lock() | |
| 61 | + | require.Equal(t, floatingVisible, tb.floating.state) | |
| 62 | + | tb.mu.Unlock() | |
| 63 | + | awaitFrame(t, sends, ports.MsgOutput) | |
| 64 | + | } | |
| 65 | + | ||
| 43 | 66 | func TestPaletteCNSPromptsForSessionNameThenCreatesAndSwitches(t *testing.T) { | |
| 44 | 67 | p1, release1 := newBlockingPTY(t) | |
| 45 | 68 | p2, release2 := newBlockingPTY(t) | |
| @@ -81,6 +104,9 @@ func TestPaletteCNSPromptsForSessionNameThenCreatesAndSwitches(t *testing.T) { | |||
| 81 | 104 | ||
| 82 | 105 | func TestPaletteReopensWithSuccessfulCommandFirst(t *testing.T) { | |
| 83 | 106 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | |
| 107 | + | sess.mu.Lock() | |
| 108 | + | sess.client = ac | |
| 109 | + | sess.mu.Unlock() | |
| 84 | 110 | defer releases[0]() | |
| 85 | 111 | defer releases[1]() | |
| 86 | 112 | ||
| @@ -18,6 +18,10 @@ func TestPaletteOpenTypeEnterRunAndEscClose(t *testing.T) { | |||||
| 18 | p1, release1 := newBlockingPTY(t) | 18 | p1, release1 := newBlockingPTY(t) | ||
| 19 | p2, release2 := newBlockingPTY(t) | 19 | p2, release2 := newBlockingPTY(t) | ||
| 20 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1, p2) | 20 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1, p2) | ||
| 21 | + | sess.mu.Lock() | |||
| 22 | + | sess.client = ac | |||
| 23 | + | sess.mu.Unlock() | |||
| 24 | + | d.ptys = newBlockingOpenFactory(t, d) | |||
| 21 | defer release1() | 25 | defer release1() | ||
| 22 | defer release2() | 26 | defer release2() | ||
| 23 | 27 | ||||
| @@ -31,6 +35,7 @@ func TestPaletteOpenTypeEnterRunAndEscClose(t *testing.T) { | |||||
| 31 | d.handleInput(sess, ac, []byte("NXT\r")) | 35 | d.handleInput(sess, ac, []byte("NXT\r")) | ||
| 32 | require.False(t, ac.overlays.paletteActive()) | 36 | require.False(t, ac.overlays.paletteActive()) | ||
| 33 | require.Equal(t, 1, activeTabIndex(sess)) | 37 | require.Equal(t, 1, activeTabIndex(sess)) | ||
| 38 | + | requireFloatingInitialized(t, sess.activeTab()) | |||
| 34 | awaitFrame(t, sends, ports.MsgOutput) | 39 | awaitFrame(t, sends, ports.MsgOutput) | ||
| 35 | 40 | ||||
| 36 | d.handleInput(sess, ac, []byte("\x1b ")) | 41 | d.handleInput(sess, ac, []byte("\x1b ")) | ||
| @@ -40,6 +45,24 @@ func TestPaletteOpenTypeEnterRunAndEscClose(t *testing.T) { | |||||
| 40 | awaitFrame(t, sends, ports.MsgOutput) | 45 | awaitFrame(t, sends, ports.MsgOutput) | ||
| 41 | } | 46 | } | ||
| 42 | 47 | ||||
| 48 | + | func TestPaletteFLTExecutesFloatingToggle(t *testing.T) { | |||
| 49 | + | p, release := newBlockingPTY(t) | |||
| 50 | + | defer release() | |||
| 51 | + | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |||
| 52 | + | tb := sess.activeTab() | |||
| 53 | + | installTestFloating(tb, newPane("floating", nil, domain.Size{Cols: 20, Rows: 5}), false) | |||
| 54 | + | ||||
| 55 | + | d.handleInput(sess, ac, []byte("\x1b ")) | |||
| 56 | + | awaitFrame(t, sends, ports.MsgOutput) | |||
| 57 | + | d.handleInput(sess, ac, []byte("FLT\r")) | |||
| 58 | + | ||||
| 59 | + | require.False(t, ac.overlays.paletteActive()) | |||
| 60 | + | tb.mu.Lock() | |||
| 61 | + | require.Equal(t, floatingVisible, tb.floating.state) | |||
| 62 | + | tb.mu.Unlock() | |||
| 63 | + | awaitFrame(t, sends, ports.MsgOutput) | |||
| 64 | + | } | |||
| 65 | + | ||||
| 43 | func TestPaletteCNSPromptsForSessionNameThenCreatesAndSwitches(t *testing.T) { | 66 | func TestPaletteCNSPromptsForSessionNameThenCreatesAndSwitches(t *testing.T) { | ||
| 44 | p1, release1 := newBlockingPTY(t) | 67 | p1, release1 := newBlockingPTY(t) | ||
| 45 | p2, release2 := newBlockingPTY(t) | 68 | p2, release2 := newBlockingPTY(t) | ||
| @@ -81,6 +104,9 @@ func TestPaletteCNSPromptsForSessionNameThenCreatesAndSwitches(t *testing.T) { | |||||
| 81 | 104 | ||||
| 82 | func TestPaletteReopensWithSuccessfulCommandFirst(t *testing.T) { | 105 | func TestPaletteReopensWithSuccessfulCommandFirst(t *testing.T) { | ||
| 83 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | 106 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | ||
| 107 | + | sess.mu.Lock() | |||
| 108 | + | sess.client = ac | |||
| 109 | + | sess.mu.Unlock() | |||
| 84 | defer releases[0]() | 110 | defer releases[0]() | ||
| 85 | defer releases[1]() | 111 | defer releases[1]() | ||
| 86 | 112 | ||||
M internal/usecase/daemon/picker_test.go +5 -0
| @@ -90,6 +90,10 @@ func TestPickerResumesStoppedSessionWithPersistedTabNames(t *testing.T) { | |||
| 90 | 90 | ||
| 91 | 91 | func TestPickerSameSessionNavigationSwitchAndEscClose(t *testing.T) { | |
| 92 | 92 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | |
| 93 | + | sess.mu.Lock() | |
| 94 | + | sess.client = ac | |
| 95 | + | sess.mu.Unlock() | |
| 96 | + | d.ptys = newBlockingOpenFactory(t, d) | |
| 93 | 97 | defer func() { | |
| 94 | 98 | for _, release := range releases { | |
| 95 | 99 | release() | |
| @@ -103,6 +107,7 @@ func TestPickerSameSessionNavigationSwitchAndEscClose(t *testing.T) { | |||
| 103 | 107 | d.handleInput(sess, ac, []byte("\r")) | |
| 104 | 108 | ||
| 105 | 109 | require.Equal(t, 1, activeTabIndex(sess)) | |
| 110 | + | requireFloatingInitialized(t, sess.activeTab()) | |
| 106 | 111 | awaitFrame(t, sends, ports.MsgOutput) | |
| 107 | 112 | d.enterPicker(sess, ac) | |
| 108 | 113 | awaitFrame(t, sends, ports.MsgOutput) | |
| @@ -90,6 +90,10 @@ func TestPickerResumesStoppedSessionWithPersistedTabNames(t *testing.T) { | |||||
| 90 | 90 | ||||
| 91 | func TestPickerSameSessionNavigationSwitchAndEscClose(t *testing.T) { | 91 | func TestPickerSameSessionNavigationSwitchAndEscClose(t *testing.T) { | ||
| 92 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | 92 | d, sess, ac, sends, releases := newManualTabSession(t, 2) | ||
| 93 | + | sess.mu.Lock() | |||
| 94 | + | sess.client = ac | |||
| 95 | + | sess.mu.Unlock() | |||
| 96 | + | d.ptys = newBlockingOpenFactory(t, d) | |||
| 93 | defer func() { | 97 | defer func() { | ||
| 94 | for _, release := range releases { | 98 | for _, release := range releases { | ||
| 95 | release() | 99 | release() | ||
| @@ -103,6 +107,7 @@ func TestPickerSameSessionNavigationSwitchAndEscClose(t *testing.T) { | |||||
| 103 | d.handleInput(sess, ac, []byte("\r")) | 107 | d.handleInput(sess, ac, []byte("\r")) | ||
| 104 | 108 | ||||
| 105 | require.Equal(t, 1, activeTabIndex(sess)) | 109 | require.Equal(t, 1, activeTabIndex(sess)) | ||
| 110 | + | requireFloatingInitialized(t, sess.activeTab()) | |||
| 106 | awaitFrame(t, sends, ports.MsgOutput) | 111 | awaitFrame(t, sends, ports.MsgOutput) | ||
| 107 | d.enterPicker(sess, ac) | 112 | d.enterPicker(sess, ac) | ||
| 108 | awaitFrame(t, sends, ports.MsgOutput) | 113 | awaitFrame(t, sends, ports.MsgOutput) | ||
M internal/usecase/daemon/session_test.go +24 -0
| @@ -67,6 +67,7 @@ func TestRoutePropagatesHelloCwdAndTabsInheritIt(t *testing.T) { | |||
| 67 | 67 | ||
| 68 | 68 | require.NoError(t, d.createTab(sess, sz)) | |
| 69 | 69 | require.Equal(t, []string{"/tmp/work", "/tmp/work"}, dirs) | |
| 70 | + | requireFloatingInitialized(t, sess.activeTab()) | |
| 70 | 71 | _ = d.killSession(sess, ports.ReasonSessionKilled, false) | |
| 71 | 72 | releaseFirst() | |
| 72 | 73 | releaseSecond() | |
| @@ -909,6 +910,29 @@ func TestTabNamePersistenceTracksTabIndexShifts(t *testing.T) { | |||
| 909 | 910 | } | |
| 910 | 911 | } | |
| 911 | 912 | ||
| 913 | + | func TestCloseActiveTabActivatesDestinationFloatingPane(t *testing.T) { | |
| 914 | + | d, sess, ac, _, releases := newManualTabSession(t, 2) | |
| 915 | + | sess.mu.Lock() | |
| 916 | + | sess.client = ac | |
| 917 | + | sess.mu.Unlock() | |
| 918 | + | d.ptys = newBlockingOpenFactory(t, d) | |
| 919 | + | defer releases[0]() | |
| 920 | + | defer releases[1]() | |
| 921 | + | sess.mu.Lock() | |
| 922 | + | first, closing := sess.tabs[0], sess.tabs[1] | |
| 923 | + | sess.active = 1 | |
| 924 | + | sess.mu.Unlock() | |
| 925 | + | first.mu.Lock() | |
| 926 | + | stale := first.takeFloatingLocked() | |
| 927 | + | first.mu.Unlock() | |
| 928 | + | closeFloatingPane(stale) | |
| 929 | + | ||
| 930 | + | d.closeTab(sess, closing, false) | |
| 931 | + | ||
| 932 | + | require.Same(t, first, sess.activeTab()) | |
| 933 | + | requireFloatingInitialized(t, first) | |
| 934 | + | } | |
| 935 | + | ||
| 912 | 936 | func TestRenameTabDoesNotPersistForEphemeralSession(t *testing.T) { | |
| 913 | 937 | sz := domain.Size{Cols: 80, Rows: 24} | |
| 914 | 938 | p, release := newBlockingPTY(t) | |
| @@ -67,6 +67,7 @@ func TestRoutePropagatesHelloCwdAndTabsInheritIt(t *testing.T) { | |||||
| 67 | 67 | ||||
| 68 | require.NoError(t, d.createTab(sess, sz)) | 68 | require.NoError(t, d.createTab(sess, sz)) | ||
| 69 | require.Equal(t, []string{"/tmp/work", "/tmp/work"}, dirs) | 69 | require.Equal(t, []string{"/tmp/work", "/tmp/work"}, dirs) | ||
| 70 | + | requireFloatingInitialized(t, sess.activeTab()) | |||
| 70 | _ = d.killSession(sess, ports.ReasonSessionKilled, false) | 71 | _ = d.killSession(sess, ports.ReasonSessionKilled, false) | ||
| 71 | releaseFirst() | 72 | releaseFirst() | ||
| 72 | releaseSecond() | 73 | releaseSecond() | ||
| @@ -909,6 +910,29 @@ func TestTabNamePersistenceTracksTabIndexShifts(t *testing.T) { | |||||
| 909 | } | 910 | } | ||
| 910 | } | 911 | } | ||
| 911 | 912 | ||||
| 913 | + | func TestCloseActiveTabActivatesDestinationFloatingPane(t *testing.T) { | |||
| 914 | + | d, sess, ac, _, releases := newManualTabSession(t, 2) | |||
| 915 | + | sess.mu.Lock() | |||
| 916 | + | sess.client = ac | |||
| 917 | + | sess.mu.Unlock() | |||
| 918 | + | d.ptys = newBlockingOpenFactory(t, d) | |||
| 919 | + | defer releases[0]() | |||
| 920 | + | defer releases[1]() | |||
| 921 | + | sess.mu.Lock() | |||
| 922 | + | first, closing := sess.tabs[0], sess.tabs[1] | |||
| 923 | + | sess.active = 1 | |||
| 924 | + | sess.mu.Unlock() | |||
| 925 | + | first.mu.Lock() | |||
| 926 | + | stale := first.takeFloatingLocked() | |||
| 927 | + | first.mu.Unlock() | |||
| 928 | + | closeFloatingPane(stale) | |||
| 929 | + | ||||
| 930 | + | d.closeTab(sess, closing, false) | |||
| 931 | + | ||||
| 932 | + | require.Same(t, first, sess.activeTab()) | |||
| 933 | + | requireFloatingInitialized(t, first) | |||
| 934 | + | } | |||
| 935 | + | ||||
| 912 | func TestRenameTabDoesNotPersistForEphemeralSession(t *testing.T) { | 936 | func TestRenameTabDoesNotPersistForEphemeralSession(t *testing.T) { | ||
| 913 | sz := domain.Size{Cols: 80, Rows: 24} | 937 | sz := domain.Size{Cols: 80, Rows: 24} | ||
| 914 | p, release := newBlockingPTY(t) | 938 | p, release := newBlockingPTY(t) | ||