Files
Ely-GPUI-Components/src/lists/tree/tests.rs
T
ZacharyZhang-NYandClaude Opus 5.5 a0984e80a3 DB & Dev Tools · Data formats: YAML lists at their key's indent, XML entities
YAML reads a list written at its key's own indent, as manifests do, and
a list at a map's indent ends at the next key. A `#` inside a quoted
scalar is no comment. XML reads entity references in text and
attributes, failing on one it does not know, and a text's path counts
text nodes from one. A tree's note takes at most half its row, cut with
an ellipsis, so a long value or attribute leaves the key its name.
Format and Minify fail loud if pressed while the JSON does not read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:42:03 -04:00

223 lines
6.9 KiB
Rust

use gpui::{
Context, Entity, IntoElement, Modifiers, MouseButton, Pixels, Point, Render, SharedString,
Styled, TestAppContext, VisualTestContext, Window, point, px,
};
use super::{DropAt, Tree, TreeNode};
use crate::theme::Theme;
/// A small project tree that keeps what its tree reports.
#[derive(Default)]
struct Explorer {
docs: Vec<TreeNode>,
selected: Vec<SharedString>,
checked: Option<Vec<SharedString>>,
heard: Vec<String>,
}
impl Render for Explorer {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
let heard = |view: &Entity<Explorer>| {
let view = view.clone();
move |line: String, cx: &mut gpui::App| {
view.update(cx, |explorer, _| explorer.heard.push(line))
}
};
let docs = match self.docs.is_empty() {
true => TreeNode::new("docs", "docs").pending(),
false => TreeNode::new("docs", "docs").children(self.docs.clone()),
};
let all = [
TreeNode::new("src", "src").children([
TreeNode::new("lib", "lib.rs"),
TreeNode::new("ui", "ui").child(TreeNode::new("button", "button.rs")),
]),
docs,
TreeNode::new("readme", "README.md")
.note("a note long enough to take the whole row and more"),
];
let (select, check, load, rename, moved) = (
view.clone(),
view,
heard(&cx.entity()),
heard(&cx.entity()),
heard(&cx.entity()),
);
let tree = Tree::new("tree", all)
.open(["src"])
.selected(self.selected.clone())
.on_select(move |keys, _, cx| {
select.update(cx, |explorer, _| explorer.selected = keys.to_vec())
})
.on_load(move |key, _, cx| load(format!("load {key}"), cx))
.on_rename(move |key, name, _, cx| rename(format!("rename {key} {name}"), cx))
.on_move(move |key, target, at, _, cx| moved(format!("move {key} {at:?} {target}"), cx))
.w(px(300.0))
.h(px(400.0));
match self.checked.clone() {
Some(checked) => tree.checked(checked).on_check(move |keys, _, cx| {
check.update(cx, |explorer, _| explorer.checked = Some(keys.to_vec()))
}),
None => tree,
}
}
}
fn explorer(checked: bool, cx: &mut TestAppContext) -> (Entity<Explorer>, &mut VisualTestContext) {
cx.update(|cx| {
Theme::init(cx);
Theme::update(cx, |theme| theme.reduced_motion = true);
crate::forms::bind_keys(cx);
});
let (view, cx) = cx.add_window_view(move |_, _| Explorer {
checked: checked.then(Vec::new),
..Explorer::default()
});
settle(cx);
(view, cx)
}
/// Frames 2ms apart, past reduced motion's 1ms glides.
fn settle(cx: &mut VisualTestContext) {
for _ in 0..3 {
std::thread::sleep(std::time::Duration::from_millis(2));
cx.update(|window, _| window.refresh());
cx.run_until_parked();
}
}
/// Row `ix`'s left padding: rows are 28 tall.
fn row(ix: usize) -> Point<Pixels> {
point(px(4.0), px(14.0 + 28.0 * ix as f32))
}
fn read<T>(view: &Entity<Explorer>, cx: &mut VisualTestContext, f: impl Fn(&Explorer) -> T) -> T {
view.read_with(cx, |explorer, _| f(explorer))
}
#[gpui::test]
fn a_long_note_leaves_the_label_half_the_row(cx: &mut TestAppContext) {
let (_, cx) = explorer(false, cx);
let note = cx.debug_bounds("tree-note").expect("the readme's note");
assert!(
note.size.width <= px(150.0),
"the note takes at most half the row: {note:?}"
);
assert!(
note.size.width > px(100.0),
"the note keeps its half: {note:?}"
);
}
#[gpui::test]
fn keys_walk_open_climb_and_close(cx: &mut TestAppContext) {
let (view, cx) = explorer(false, cx);
cx.simulate_click(row(0), Modifiers::none());
settle(cx);
let mut seen = Vec::new();
for keys in ["right", "down", "right", "right", "left", "left"] {
cx.simulate_keystrokes(keys);
settle(cx);
seen.push(read(&view, cx, |explorer| explorer.selected.join(",")));
}
assert_eq!(
seen,
["lib", "ui", "ui", "button", "ui", "ui"],
"right enters, left climbs, then closes"
);
cx.simulate_keystrokes("down");
settle(cx);
assert_eq!(
read(&view, cx, |explorer| explorer.selected.join(",")),
"docs",
"ui closed again"
);
}
#[gpui::test]
fn a_pending_node_asks_for_its_children_once(cx: &mut TestAppContext) {
let (view, cx) = explorer(false, cx);
cx.simulate_click(row(3), Modifiers::none());
settle(cx);
cx.simulate_keystrokes("right");
settle(cx);
settle(cx);
assert_eq!(
read(&view, cx, |explorer| explorer.heard.clone()),
["load docs"],
"asked once while it waits"
);
view.update(cx, |explorer, cx| {
explorer.docs = vec![TreeNode::new("guide", "guide.md")];
cx.notify();
});
settle(cx);
cx.simulate_keystrokes("left right down");
settle(cx);
assert_eq!(
read(&view, cx, |explorer| explorer.heard.clone()),
["load docs"]
);
assert_eq!(
read(&view, cx, |explorer| explorer.selected.join(",")),
"guide",
"the children came"
);
}
#[gpui::test]
fn space_checks_a_whole_branch_and_again_clears_it(cx: &mut TestAppContext) {
let (view, cx) = explorer(true, cx);
cx.simulate_click(row(0), Modifiers::none());
settle(cx);
cx.simulate_keystrokes("space");
settle(cx);
assert_eq!(
read(&view, cx, |explorer| explorer.checked.clone()),
Some(vec!["lib".into(), "button".into()])
);
cx.simulate_keystrokes("space");
settle(cx);
assert_eq!(
read(&view, cx, |explorer| explorer.checked.clone()),
Some(Vec::new())
);
}
#[gpui::test]
fn f2_renames_a_node(cx: &mut TestAppContext) {
let (view, cx) = explorer(false, cx);
cx.simulate_click(row(1), Modifiers::none());
settle(cx);
cx.simulate_keystrokes("f2");
settle(cx);
cx.simulate_input("main.rs");
cx.simulate_keystrokes("enter");
settle(cx);
assert_eq!(
read(&view, cx, |explorer| explorer.heard.clone()),
["rename lib main.rs"]
);
}
#[gpui::test]
fn a_drop_in_a_folders_middle_moves_the_node_inside(cx: &mut TestAppContext) {
let (view, cx) = explorer(false, cx);
cx.simulate_mouse_down(row(4), MouseButton::Left, Modifiers::none());
for y in [120.0, 100.0, 80.0, 70.0] {
cx.simulate_mouse_move(point(px(40.0), px(y)), MouseButton::Left, Modifiers::none());
settle(cx);
}
cx.simulate_mouse_up(
point(px(40.0), px(70.0)),
MouseButton::Left,
Modifiers::none(),
);
settle(cx);
assert_eq!(
read(&view, cx, |explorer| explorer.heard.clone()),
[format!("move readme {:?} ui", DropAt::Inside)]
);
}