A sweep that read every render found 28 roots still on w_full, reached by 64 components: the field button under Select and every picker, the checked and mention fields, the slider track, NumberInput, Steps, the state views, the agent previews, and every chart and finance frame. All drop it. LivePreview's size_full becomes h_full, since its card no longer fills. Every host of those roots was listed and read, 231 in all. Five tests that mounted a picker as the window root sit in a size_full box, the index manager gives its method select a flex_1 box, and the notification center and the gallery's state frames stack their state views in a column so they stretch. Each dropped root carries a debug selector, and six chapter tests mount 43 components in a padded card whose column is measured by content, through layout::tests::narrow_width: each root must take the full 240px. With w_full back on the roots, all six fail. AGENTS.md names the roots that keep w_full and why: picture frames that keep a ratio, and rows that hand a text field its share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
226 lines
6.7 KiB
Rust
226 lines
6.7 KiB
Rust
use std::time::{Duration, Instant};
|
|
|
|
use anyhow::anyhow;
|
|
use gpui::{
|
|
Context, Entity, FocusHandle, IntoElement, ParentElement, Pixels, Render, Styled,
|
|
TestAppContext, VisualTestContext, Window, div, px,
|
|
};
|
|
|
|
use super::{press, settle, setup, wait};
|
|
use crate::{
|
|
buttons::Button,
|
|
feedback::{
|
|
ConfirmationCard, Countdown, EmptyState, ErrorBoundary, ErrorView, ResultView, SaveState,
|
|
SavingIndicator,
|
|
},
|
|
layout::tests::narrow_width,
|
|
primitives::{FocusScope, IconName, Measure},
|
|
};
|
|
|
|
/// A boundary, a confirmation card, a saving line and a countdown, recording what they do.
|
|
struct Shelf {
|
|
root: FocusHandle,
|
|
broken: bool,
|
|
save: SaveState,
|
|
until: Instant,
|
|
log: Vec<String>,
|
|
}
|
|
|
|
impl Shelf {
|
|
fn note(
|
|
&self,
|
|
cx: &mut Context<Self>,
|
|
what: &'static str,
|
|
) -> impl Fn(&mut Window, &mut gpui::App) + 'static + use<> {
|
|
let view = cx.entity();
|
|
move |_, cx| view.update(cx, |shelf, _| shelf.log.push(what.into()))
|
|
}
|
|
}
|
|
|
|
impl Render for Shelf {
|
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
let content = if self.broken {
|
|
Err(anyhow!("the service answered 503"))
|
|
} else {
|
|
Ok(div().child("fine"))
|
|
};
|
|
FocusScope::new(&self.root)
|
|
.root()
|
|
.size_full()
|
|
.child(ErrorBoundary::new("boundary", content).on_retry(self.note(cx, "retry")))
|
|
.child(
|
|
ConfirmationCard::new("card", "Sure?")
|
|
.on_cancel(self.note(cx, "cancel"))
|
|
.on_confirm(self.note(cx, "confirm")),
|
|
)
|
|
.child(SavingIndicator::new("saving", self.save).on_retry(self.note(cx, "save again")))
|
|
.child(Countdown::new("countdown", self.until).on_done(self.note(cx, "done")))
|
|
}
|
|
}
|
|
|
|
fn shelf(
|
|
broken: bool,
|
|
save: SaveState,
|
|
cx: &mut TestAppContext,
|
|
) -> (Entity<Shelf>, &mut VisualTestContext) {
|
|
setup(cx);
|
|
let (view, cx) = cx.add_window_view(|_, cx| Shelf {
|
|
root: cx.focus_handle(),
|
|
broken,
|
|
save,
|
|
until: cx.background_executor().now() + Duration::from_secs(3),
|
|
log: Vec::new(),
|
|
});
|
|
settle(cx);
|
|
let root = view.read_with(cx, |shelf, _| shelf.root.clone());
|
|
cx.update(|window, _| window.focus(&root));
|
|
(view, cx)
|
|
}
|
|
|
|
fn log(view: &Entity<Shelf>, cx: &mut VisualTestContext) -> Vec<String> {
|
|
view.read_with(cx, |shelf, _| shelf.log.clone())
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn a_broken_boundary_offers_to_try_again(cx: &mut TestAppContext) {
|
|
let (view, cx) = shelf(true, SaveState::Saved, cx);
|
|
press("tab", cx);
|
|
press("enter", cx);
|
|
assert_eq!(log(&view, cx), ["retry"]);
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn the_card_cancels_and_confirms(cx: &mut TestAppContext) {
|
|
let (view, cx) = shelf(false, SaveState::Saved, cx);
|
|
press("tab", cx);
|
|
press("enter", cx);
|
|
press("tab", cx);
|
|
press("enter", cx);
|
|
assert_eq!(log(&view, cx), ["cancel", "confirm"]);
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn retry_shows_only_once_saving_failed(cx: &mut TestAppContext) {
|
|
let (view, cx) = shelf(false, SaveState::Failed, cx);
|
|
for _ in 0..3 {
|
|
press("tab", cx);
|
|
}
|
|
press("enter", cx);
|
|
assert_eq!(log(&view, cx), ["save again"]);
|
|
view.update(cx, |shelf, cx| {
|
|
shelf.save = SaveState::Saved;
|
|
cx.notify();
|
|
});
|
|
settle(cx);
|
|
let root = view.read_with(cx, |shelf, _| shelf.root.clone());
|
|
cx.update(|window, _| window.focus(&root));
|
|
for _ in 0..3 {
|
|
press("tab", cx);
|
|
}
|
|
press("enter", cx);
|
|
assert_eq!(
|
|
log(&view, cx),
|
|
["save again", "cancel"],
|
|
"Tab wrapped past a saved line"
|
|
);
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn the_countdown_runs_on_done_once_at_zero(cx: &mut TestAppContext) {
|
|
let (view, cx) = shelf(false, SaveState::Saved, cx);
|
|
wait(Duration::from_millis(2_900), cx);
|
|
assert!(log(&view, cx).is_empty());
|
|
wait(Duration::from_millis(200), cx);
|
|
assert_eq!(log(&view, cx), ["done"]);
|
|
wait(Duration::from_secs(5), cx);
|
|
assert_eq!(log(&view, cx), ["done"]);
|
|
}
|
|
|
|
/// An empty state in a narrow column, and the height it took.
|
|
struct Column {
|
|
body: &'static str,
|
|
height: Pixels,
|
|
}
|
|
|
|
impl Render for Column {
|
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
let view = cx.entity();
|
|
div().w(px(420.0)).child(
|
|
Measure::new("column", move |bounds, _, cx| {
|
|
view.update(cx, |column, _| column.height = bounds.size.height)
|
|
})
|
|
.child(
|
|
EmptyState::new("empty", IconName::Inbox, "Nothing yet")
|
|
.body(self.body)
|
|
.action(Button::new("act", "Act")),
|
|
),
|
|
)
|
|
}
|
|
}
|
|
|
|
fn column_height(body: &'static str, cx: &mut TestAppContext) -> Pixels {
|
|
setup(cx);
|
|
let (view, cx) = cx.add_window_view(|_, _| Column {
|
|
body,
|
|
height: Pixels::ZERO,
|
|
});
|
|
settle(cx);
|
|
view.read_with(cx, |column, _| column.height)
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn a_wrapped_body_pushes_the_actions_down(cx: &mut TestAppContext) {
|
|
let short = column_height("One line.", cx);
|
|
let long = column_height(
|
|
"Many words that cannot fit on one line of a narrow column, so they wrap onto a second, a third and a fourth line.",
|
|
cx,
|
|
);
|
|
assert!(long > short + px(24.0), "{short:?} then {long:?}");
|
|
}
|
|
|
|
/// A message too long for its box.
|
|
struct Worded;
|
|
|
|
impl Render for Worded {
|
|
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
|
div()
|
|
.w(px(200.0))
|
|
.child(crate::feedback::InlineMessage::new(
|
|
crate::primitives::Severity::Danger,
|
|
"This file does not read as CSV: line 2: a quote inside a field it did not open.",
|
|
))
|
|
}
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn a_long_inline_message_wraps_inside_its_box(cx: &mut TestAppContext) {
|
|
setup(cx);
|
|
let (_, cx) = cx.add_window_view(|_, _| Worded);
|
|
cx.run_until_parked();
|
|
let text = cx
|
|
.debug_bounds("inline-message-text")
|
|
.expect("the words draw");
|
|
assert!(text.right() <= px(200.0), "{text:?}");
|
|
}
|
|
|
|
#[gpui::test]
|
|
fn state_views_fill_a_column_their_block_measures_by_content(cx: &mut TestAppContext) {
|
|
setup(cx);
|
|
let widths = [
|
|
narrow_width(cx, "state-root", |_, _| {
|
|
EmptyState::new("empty", IconName::Search, "Nothing here").into_any_element()
|
|
}),
|
|
narrow_width(cx, "state-root", |_, _| {
|
|
ErrorView::not_found("missing").into_any_element()
|
|
}),
|
|
narrow_width(cx, "state-root", |_, _| {
|
|
ResultView::success("done", "Saved").into_any_element()
|
|
}),
|
|
];
|
|
assert_eq!(
|
|
widths,
|
|
[px(240.0); 3],
|
|
"each view spans the card inside its padding"
|
|
);
|
|
}
|