test(ui): pin the zoom mark to the tab that is hiding panes (#752)

`tab_is_zoomed` is what the chip and the sidebar row actually ask, and
nothing asked it back. The one test the change carried covers
`Pane::zoom_hides_siblings` — the predicate — so the wiring above it,
which picks `Tty7App::maximized` for the active tab and `Tab::zoomed`
for every other one, went in untested in both directions.

Held against the real app rather than the pane tree, so the mark is
pinned to the state a reader sees:

- it appears on the tab the zoom happened in, and on no other;
- it goes again on un-zoom, which is the half that a mark keyed off a
  handle nobody clears would fail;
- it rides with its tab across a switch (#599), so an inactive tab
  holding a zoom still wears it, and two tabs wear one at once — each
  read off its own handle;
- a parked zoom naming a pane the tab no longer holds is not marked;
- neither is a zoom over the last pane standing, once its siblings
  have closed;
- and an index past the end answers false rather than panicking.
This commit is contained in:
l0ng-ai
2026-09-07 22:40:36 +08:00
parent 7595fcc946
commit bfd9b188c5
+80
View File
@@ -10170,6 +10170,86 @@ mod zoom_gpui_tests {
);
});
}
/// The mark the chrome wears (#752) has to go out again by every road the
/// zoom itself leaves by, and it has to name the right tab while several
/// tabs are each holding one.
#[gpui::test]
fn the_zoom_mark_is_on_whichever_tabs_are_hiding_panes(cx: &mut TestAppContext) {
use crate::terminal::view::quiet_test_pane;
use crate::ui::pane::{Pane, PaneSlot};
let (app, mut vcx, _streams) = harness_with_tabs(cx, 3);
app.update_in(&mut vcx, |app, window, cx| {
// A zoom only hides something where there is a sibling to hide, so
// tabs 0 and 1 get a second pane and tab 2 stays single.
let mut held = Vec::new();
for tab in 0..2 {
let (view, stream) = quiet_test_pane(90 + tab as u64, window, cx);
held.push(stream);
let first = app.tabs[tab].pane.first_leaf().expect("tab has a pane");
app.tabs[tab].pane = Pane::split_node(
gpui::Axis::Horizontal,
0.5,
Pane::leaf(first),
Pane::leaf(PaneSlot::Ready(view)),
);
}
for i in 0..app.tabs.len() {
assert!(!app.tab_is_zoomed(i), "nothing is zoomed yet");
}
// Zooming marks the tab it happened in, and only that one.
app.toggle_maximize(window, cx);
assert!(app.tab_is_zoomed(0), "the zoomed tab wears the mark");
assert!(!app.tab_is_zoomed(1));
assert!(!app.tab_is_zoomed(2));
// And un-zooming takes it away again.
app.toggle_maximize(window, cx);
assert!(!app.tab_is_zoomed(0), "un-zooming clears the mark");
// The mark rides with its tab across a switch (#599) — an inactive
// tab holding a zoom still wears it — and two tabs can wear one at
// the same time, each reading its own handle.
app.toggle_maximize(window, cx);
app.activate(1, window, cx);
assert!(app.tab_is_zoomed(0), "the parked zoom is still a zoom");
assert!(!app.tab_is_zoomed(1));
app.toggle_maximize(window, cx);
assert!(app.tab_is_zoomed(0) && app.tab_is_zoomed(1));
assert!(!app.tab_is_zoomed(2), "a single-pane tab hides nothing");
// A parked zoom naming a pane that has since left the tab is no
// zoom: it would not come back on a switch, so it is not marked.
let parked = app.tabs[0].zoomed.clone().expect("tab 0 parked a zoom");
let elsewhere = app.tabs[2]
.pane
.first_leaf()
.and_then(|slot| slot.terminal().cloned());
app.tabs[0].zoomed = elsewhere;
assert!(
!app.tab_is_zoomed(0),
"a zoom over a pane this tab does not hold is not marked"
);
app.tabs[0].zoomed = Some(parked.clone());
assert!(app.tab_is_zoomed(0));
// Nor is a zoom over the last pane standing: its siblings closed
// while the tab was away, and the tab now looks like — and draws
// as — an ordinary single pane.
app.tabs[0].pane = Pane::leaf(PaneSlot::Ready(parked));
assert!(
!app.tab_is_zoomed(0),
"a zoom that covers nothing stops being marked"
);
assert!(!app.tab_is_zoomed(9), "there is no tab 9 to mark");
drop(held);
});
}
}
// A test window has no daemon behind it — its socket path is under the pinned