T24f Agent: control and cost
HumanInputRequest asks a question with answers to pick and a line to write one; CostBreakdown totals a session and splits it by part. InterruptButton, TokenUsageChart, TraceViewer and EvalResultTable point to chat::stop_button, a stacked BarChart, TimelineProfiler and DataTable. UsageBar shows amounts as its owner writes them. MemoryPanel and HumanInputRequest share agent::line::send_line; the chapter's bool callbacks take forms::OnFlag. DataTable scrolls columns wider than its box sideways, pinned or not, and leaves a plain wheel to the page; sortable headers are Tab stops. TimelineProfiler fits its ruler to its lane, its spans are Tab stops, and a picked span takes accent.
This commit is contained in:
20 files changed
+934
-180
No files matched your search
@@ -0,0 +1,95 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use gpui::{
|
||||
Context, IntoElement, KeyUpEvent, Keystroke, Modifiers, Render, Styled, TestAppContext, Window,
|
||||
point, px,
|
||||
};
|
||||
|
||||
use super::{edit_at, settle};
|
||||
use crate::{
|
||||
tables::{Column, DataTable, Row},
|
||||
theme::Theme,
|
||||
};
|
||||
|
||||
/// Three editable columns 200 wide in a table 280 wide, the first pinned or not; it keeps the column of each edit.
|
||||
struct Wide(Rc<RefCell<Vec<String>>>, bool);
|
||||
|
||||
impl Render for Wide {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let heard = self.0.clone();
|
||||
let column = |key: &'static str| Column::new(key, key).width(gpui::rems(12.5)).editable();
|
||||
let first = if self.1 {
|
||||
column("a").pinned()
|
||||
} else {
|
||||
column("a")
|
||||
};
|
||||
DataTable::new("wide", [first, column("b"), column("c")])
|
||||
.rows(vec![Row::new("r", ["A".into(), "B".into(), "C".into()])])
|
||||
.on_edit(move |_, column, _, _, _| heard.borrow_mut().push(column.to_string()))
|
||||
.w(px(280.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn columns_past_the_table_scroll_sideways_and_a_plain_wheel_leaves_them(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
crate::forms::bind_keys(cx);
|
||||
});
|
||||
for pinned in [false, true] {
|
||||
let heard = Rc::new(RefCell::new(Vec::new()));
|
||||
let seen = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Wide(seen, pinned));
|
||||
settle(cx);
|
||||
let cell = point(px(250.0), px(54.0));
|
||||
for delta in [point(px(0.0), px(-2000.0)), point(px(-2000.0), px(0.0))] {
|
||||
cx.simulate_event(gpui::ScrollWheelEvent {
|
||||
position: cell,
|
||||
delta: gpui::ScrollDelta::Pixels(delta),
|
||||
modifiers: Modifiers::none(),
|
||||
touch_phase: gpui::TouchPhase::Moved,
|
||||
});
|
||||
settle(cx);
|
||||
edit_at(cell, "x", cx);
|
||||
}
|
||||
assert_eq!(*heard.borrow(), ["b", "c"], "pinned {pinned}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Two rows under a sortable, editable name; it keeps the row of each edit.
|
||||
struct Sorting(Rc<RefCell<Vec<String>>>);
|
||||
|
||||
impl Render for Sorting {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let heard = self.0.clone();
|
||||
DataTable::new("sorting", [Column::new("name", "Name").editable()])
|
||||
.rows(vec![
|
||||
Row::new("bea", ["Bea".into()]),
|
||||
Row::new("ada", ["Ada".into()]),
|
||||
])
|
||||
.on_edit(move |row, _, _, _, _| heard.borrow_mut().push(row.to_string()))
|
||||
.w(px(400.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn tab_reaches_a_header_and_enter_sorts_by_it(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
crate::forms::bind_keys(cx);
|
||||
});
|
||||
let heard = Rc::new(RefCell::new(Vec::new()));
|
||||
let seen = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Sorting(seen));
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.simulate_keystrokes("enter");
|
||||
settle(cx);
|
||||
cx.simulate_event(KeyUpEvent {
|
||||
keystroke: Keystroke::parse("enter").expect("a key"),
|
||||
});
|
||||
settle(cx);
|
||||
edit_at(point(px(120.0), px(54.0)), "x", cx);
|
||||
assert_eq!(*heard.borrow(), ["ada"], "Ada rises to the top");
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
use std::{
|
||||
cell::{Cell as Count, RefCell},
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use gpui::{
|
||||
Context, FocusHandle, IntoElement, KeyBinding, Modifiers, ParentElement, Render, SharedString,
|
||||
Styled, TestAppContext, VisualTestContext, Window, div, point, px,
|
||||
};
|
||||
|
||||
mod columns;
|
||||
|
||||
use super::{Column, DataTable, FilterBuilder, FilterRule, Row};
|
||||
use crate::{
|
||||
primitives::{FocusNext, FocusPrev, FocusScope},
|
||||
theme::Theme,
|
||||
};
|
||||
|
||||
/// Five selectable rows, 36 tall under a 36 tall header; it keeps what the table reports.
|
||||
struct Picks(Rc<RefCell<Vec<SharedString>>>);
|
||||
|
||||
impl Render for Picks {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let (now, store) = (self.0.borrow().clone(), self.0.clone());
|
||||
DataTable::new("picks", [Column::new("name", "Name")])
|
||||
.rows(
|
||||
(0..5)
|
||||
.map(|ix| Row::new(format!("r{ix}"), [format!("Row {ix}").into()]))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.selected(now)
|
||||
.on_select(move |keys, _, _| *store.borrow_mut() = keys.to_vec())
|
||||
.w(px(400.0))
|
||||
}
|
||||
}
|
||||
|
||||
fn settle(cx: &mut VisualTestContext) {
|
||||
cx.run_until_parked();
|
||||
cx.update(|window, _| window.refresh());
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
/// Edits the cell at `at`: a double press, then `text` and Enter.
|
||||
fn edit_at(at: gpui::Point<gpui::Pixels>, text: &str, cx: &mut VisualTestContext) {
|
||||
for count in [1, 2] {
|
||||
cx.simulate_event(gpui::MouseDownEvent {
|
||||
button: gpui::MouseButton::Left,
|
||||
position: at,
|
||||
modifiers: Modifiers::none(),
|
||||
click_count: count,
|
||||
first_mouse: false,
|
||||
});
|
||||
cx.simulate_event(gpui::MouseUpEvent {
|
||||
button: gpui::MouseButton::Left,
|
||||
position: at,
|
||||
modifiers: Modifiers::none(),
|
||||
click_count: count,
|
||||
});
|
||||
}
|
||||
settle(cx);
|
||||
cx.simulate_input(text);
|
||||
cx.simulate_keystrokes("enter");
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
fn picks(seen: &Rc<RefCell<Vec<SharedString>>>) -> Vec<String> {
|
||||
seen.borrow().iter().map(|key| key.to_string()).collect()
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn boxes_toggle_shift_takes_a_range_and_the_header_takes_all(cx: &mut TestAppContext) {
|
||||
cx.update(Theme::init);
|
||||
let seen = Rc::new(RefCell::new(Vec::new()));
|
||||
let store = seen.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Picks(store));
|
||||
settle(cx);
|
||||
let row = |ix: usize, x: f32| point(px(x), px(54.0 + 36.0 * ix as f32));
|
||||
cx.simulate_click(row(0, 14.0), Modifiers::none());
|
||||
settle(cx);
|
||||
assert_eq!(picks(&seen), ["r0"]);
|
||||
cx.simulate_click(
|
||||
row(3, 200.0),
|
||||
Modifiers {
|
||||
shift: true,
|
||||
..Modifiers::none()
|
||||
},
|
||||
);
|
||||
settle(cx);
|
||||
assert_eq!(picks(&seen), ["r0", "r1", "r2", "r3"]);
|
||||
cx.simulate_click(point(px(14.0), px(18.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
assert_eq!(
|
||||
picks(&seen),
|
||||
["r0", "r1", "r2", "r3", "r4"],
|
||||
"a mixed header box takes all"
|
||||
);
|
||||
cx.simulate_click(point(px(14.0), px(18.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
assert!(picks(&seen).is_empty(), "a full one clears");
|
||||
}
|
||||
|
||||
/// Six rows in two groups, measured as they draw.
|
||||
struct Grouped(Rc<std::cell::Cell<gpui::Pixels>>);
|
||||
|
||||
impl Render for Grouped {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let tall = self.0.clone();
|
||||
let rows: Vec<Row> = (0..6)
|
||||
.map(|ix| {
|
||||
Row::new(
|
||||
format!("r{ix}"),
|
||||
[
|
||||
if ix < 4 { "Europe" } else { "Asia" }.into(),
|
||||
format!("Row {ix}").into(),
|
||||
],
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
crate::primitives::Measure::new("measure", move |bounds, _, _| tall.set(bounds.size.height))
|
||||
.w(px(400.0))
|
||||
.child(
|
||||
DataTable::new(
|
||||
"grouped",
|
||||
[Column::new("region", "Region"), Column::new("name", "Name")],
|
||||
)
|
||||
.rows(rows)
|
||||
.group_by("region"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_press_on_a_group_folds_its_rows(cx: &mut TestAppContext) {
|
||||
cx.update(Theme::init);
|
||||
let tall = Rc::new(std::cell::Cell::new(gpui::Pixels::ZERO));
|
||||
let seen = tall.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Grouped(seen));
|
||||
settle(cx);
|
||||
let open = tall.get();
|
||||
cx.simulate_click(point(px(60.0), px(54.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
assert_eq!(
|
||||
open - tall.get(),
|
||||
px(36.0 * 4.0),
|
||||
"Europe's four rows fold away"
|
||||
);
|
||||
}
|
||||
|
||||
/// Two rows whose names edit in place and whose detail opens; it keeps what it hears.
|
||||
struct Editable(Rc<RefCell<Vec<String>>>);
|
||||
|
||||
impl Render for Editable {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let (heard, opened) = (self.0.clone(), self.0.clone());
|
||||
DataTable::new("editable", [Column::new("name", "Name").editable()])
|
||||
.rows(vec![
|
||||
Row::new("a", ["Ada".into()]),
|
||||
Row::new("b", ["Alan".into()]),
|
||||
])
|
||||
.detail(move |key, _, _| {
|
||||
opened.borrow_mut().push(format!("detail {key}"));
|
||||
gpui::div().h(px(40.0)).into_any_element()
|
||||
})
|
||||
.on_edit(move |row, column, text, _, _| {
|
||||
heard
|
||||
.borrow_mut()
|
||||
.push(format!("edit {row} {column} {text}"))
|
||||
})
|
||||
.w(px(400.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_row_opens_its_detail_and_a_double_press_edits_a_cell(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
crate::forms::bind_keys(cx);
|
||||
});
|
||||
let heard = Rc::new(RefCell::new(Vec::new()));
|
||||
let seen = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Editable(seen));
|
||||
settle(cx);
|
||||
cx.simulate_click(point(px(14.0), px(54.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
assert_eq!(heard.borrow().last().map(String::as_str), Some("detail a"));
|
||||
edit_at(
|
||||
point(px(120.0), px(54.0 + 36.0 + 40.0 + 24.0)),
|
||||
"Alan Kay",
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
heard
|
||||
.borrow()
|
||||
.iter()
|
||||
.filter(|line| line.starts_with("edit"))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
["edit b name Alan Kay"]
|
||||
);
|
||||
}
|
||||
|
||||
/// A two-column grid of three rows that keeps every edit it hears.
|
||||
struct Grid(Rc<RefCell<Vec<(usize, usize, String)>>>);
|
||||
|
||||
impl Render for Grid {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
self.render_grid()
|
||||
}
|
||||
}
|
||||
|
||||
impl Grid {
|
||||
fn render_grid(&self) -> impl IntoElement + use<> {
|
||||
let heard = self.0.clone();
|
||||
let rows: Vec<Vec<SharedString>> = (0..3)
|
||||
.map(|row| vec![format!("a{row}").into(), format!("b{row}").into()])
|
||||
.collect();
|
||||
super::DataGrid::new("grid", ["A", "B"], rows)
|
||||
.on_change(move |edits, _, _| {
|
||||
heard.borrow_mut().extend(
|
||||
edits
|
||||
.iter()
|
||||
.map(|(row, col, text)| (*row, *col, text.to_string())),
|
||||
)
|
||||
})
|
||||
.w(px(300.0))
|
||||
.h(px(200.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn typing_edits_enter_keeps_and_backspace_clears_a_range(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
crate::forms::bind_keys(cx);
|
||||
});
|
||||
let heard = Rc::new(RefCell::new(Vec::new()));
|
||||
let seen = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Grid(seen));
|
||||
settle(cx);
|
||||
cx.simulate_click(point(px(48.0), px(42.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
cx.simulate_keystrokes("7");
|
||||
settle(cx);
|
||||
cx.simulate_keystrokes("enter");
|
||||
settle(cx);
|
||||
assert_eq!(*heard.borrow(), [(0, 0, "7".to_string())]);
|
||||
cx.simulate_keystrokes("shift-down backspace");
|
||||
settle(cx);
|
||||
assert_eq!(
|
||||
heard.borrow()[1..],
|
||||
[(1, 0, String::new()), (2, 0, String::new())],
|
||||
"enter moved down; the range clears"
|
||||
);
|
||||
}
|
||||
|
||||
/// Filter rules the owner keeps, 600 across, rebuilt from what the builder reports.
|
||||
struct Filters(Rc<RefCell<Vec<FilterRule>>>);
|
||||
|
||||
impl Render for Filters {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (rules, store, view) = (self.0.borrow().clone(), self.0.clone(), cx.entity());
|
||||
div().w(px(600.0)).child(
|
||||
FilterBuilder::new("filters", [("name", "Name"), ("region", "Region")])
|
||||
.rules(rules, false)
|
||||
.on_change(move |rules, _, _, cx| {
|
||||
*store.borrow_mut() = rules.to_vec();
|
||||
view.update(cx, |_, cx| cx.notify());
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn removing_the_rule_being_typed_leaves_the_next_rule_its_own_value(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
crate::forms::bind_keys(cx);
|
||||
});
|
||||
let rule = |column: &'static str, value: &'static str| FilterRule {
|
||||
column: column.into(),
|
||||
value: value.into(),
|
||||
..FilterRule::default()
|
||||
};
|
||||
let rules = Rc::new(RefCell::new(vec![
|
||||
rule("name", "one"),
|
||||
rule("region", "keep"),
|
||||
]));
|
||||
let seen = rules.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Filters(seen));
|
||||
settle(cx);
|
||||
cx.simulate_click(point(px(330.0), px(12.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
cx.simulate_input("typed");
|
||||
settle(cx);
|
||||
assert_eq!(
|
||||
rules.borrow()[0].value.as_ref(),
|
||||
"onetyped",
|
||||
"the first field took the typing"
|
||||
);
|
||||
cx.simulate_click(point(px(588.0), px(12.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
cx.simulate_input("X");
|
||||
settle(cx);
|
||||
assert_eq!(
|
||||
*rules.borrow(),
|
||||
[rule("region", "keep")],
|
||||
"the removed rule's draft went with it"
|
||||
);
|
||||
}
|
||||
|
||||
/// A two-column grid whose row count the test sets.
|
||||
struct Shrinking(Rc<Count<usize>>);
|
||||
|
||||
impl Render for Shrinking {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let rows: Vec<Vec<SharedString>> = (0..self.0.get())
|
||||
.map(|row| vec![format!("a{row}").into(), format!("b{row}").into()])
|
||||
.collect();
|
||||
super::DataGrid::new("shrink", ["A", "B"], rows)
|
||||
.w(px(300.0))
|
||||
.h(px(200.0))
|
||||
}
|
||||
}
|
||||
|
||||
fn grid_keys(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
crate::forms::bind_keys(cx);
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("tab", FocusNext, None),
|
||||
KeyBinding::new("shift-tab", FocusPrev, None),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_grid_that_shrinks_keeps_its_cursor_inside(cx: &mut TestAppContext) {
|
||||
grid_keys(cx);
|
||||
let count = Rc::new(Count::new(3));
|
||||
let (view, cx) = cx.add_window_view(|_, _| Shrinking(count.clone()));
|
||||
settle(cx);
|
||||
cx.simulate_click(point(px(146.0), px(98.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
count.set(1);
|
||||
view.update(cx, |_, cx| cx.notify());
|
||||
settle(cx);
|
||||
cx.simulate_keystrokes("cmd-c");
|
||||
settle(cx);
|
||||
let copied = cx.update(|_, cx| cx.read_from_clipboard().and_then(|item| item.text()));
|
||||
assert_eq!(
|
||||
copied.as_deref(),
|
||||
Some("b0"),
|
||||
"the cursor moved to the last row left"
|
||||
);
|
||||
}
|
||||
|
||||
/// A sheet with A1:C1 merged, 4 across, that keeps every edit it hears.
|
||||
struct Merged(Rc<RefCell<Vec<(usize, usize, String)>>>);
|
||||
|
||||
impl Render for Merged {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let heard = self.0.clone();
|
||||
let cells: HashMap<(usize, usize), SharedString> =
|
||||
[((0, 0), "Title".into())].into_iter().collect();
|
||||
super::Spreadsheet::new("merged", 3, 4)
|
||||
.cells(cells)
|
||||
.merge((0, 0), (0, 2))
|
||||
.on_change(move |edits, _, _| {
|
||||
heard.borrow_mut().extend(
|
||||
edits
|
||||
.iter()
|
||||
.map(|(row, col, text)| (*row, *col, text.to_string())),
|
||||
)
|
||||
})
|
||||
.w(px(500.0))
|
||||
.h(px(200.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_merge_answers_for_every_cell_it_covers(cx: &mut TestAppContext) {
|
||||
grid_keys(cx);
|
||||
let heard = Rc::new(RefCell::new(Vec::new()));
|
||||
let seen = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Merged(seen));
|
||||
settle(cx);
|
||||
cx.simulate_click(point(px(146.0), px(42.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
cx.simulate_keystrokes("7 enter");
|
||||
settle(cx);
|
||||
cx.simulate_keystrokes("up right 8 enter");
|
||||
settle(cx);
|
||||
assert_eq!(
|
||||
*heard.borrow(),
|
||||
[(0, 0, "7".to_string()), (0, 3, "8".to_string())],
|
||||
"a press inside lands on the merge; right steps past it"
|
||||
);
|
||||
}
|
||||
|
||||
/// A grid inside a focus scope, as apps hold it, that keeps every edit it hears.
|
||||
struct Scoped(FocusHandle, Rc<RefCell<Vec<(usize, usize, String)>>>);
|
||||
|
||||
impl Render for Scoped {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
FocusScope::new(&self.0)
|
||||
.size_full()
|
||||
.child(Grid(self.1.clone()).render_grid())
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn tab_steps_across_the_grid_and_keeps_an_edit(cx: &mut TestAppContext) {
|
||||
grid_keys(cx);
|
||||
let heard = Rc::new(RefCell::new(Vec::new()));
|
||||
let seen = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, cx| Scoped(cx.focus_handle(), seen));
|
||||
settle(cx);
|
||||
cx.simulate_click(point(px(48.0), px(42.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
cx.simulate_keystrokes("tab 7 enter");
|
||||
settle(cx);
|
||||
cx.simulate_click(point(px(48.0), px(70.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
cx.simulate_keystrokes("5 tab");
|
||||
settle(cx);
|
||||
cx.simulate_keystrokes("6 enter shift-tab");
|
||||
settle(cx);
|
||||
assert_eq!(
|
||||
*heard.borrow(),
|
||||
[
|
||||
(0, 1, "7".to_string()),
|
||||
(1, 0, "5".to_string()),
|
||||
(1, 1, "6".to_string())
|
||||
],
|
||||
"tab moves right, and keeps an open edit first"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn tab_steps_past_a_merge_like_the_arrows(cx: &mut TestAppContext) {
|
||||
grid_keys(cx);
|
||||
let heard = Rc::new(RefCell::new(Vec::new()));
|
||||
let seen = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Merged(seen));
|
||||
settle(cx);
|
||||
cx.simulate_click(point(px(146.0), px(42.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
cx.simulate_keystrokes("tab 9 enter");
|
||||
settle(cx);
|
||||
assert_eq!(
|
||||
*heard.borrow(),
|
||||
[(0, 3, "9".to_string())],
|
||||
"tab leaves the merge by its far side"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user