Files
gridcraft/crates/ui-egui/tests/it/committed_text_fields.rs
T
prxnvt e7591de2ec Objects: one selection for charts, pictures, shapes and slicers, with eight resize handles
The sheet tracked the selected object as a bare id in `selected_chart`, so
only charts and text boxes drew a selection frame, only the bottom-right
corner resized, and the kind of the selected object had to be guessed from
the id. The selection is now `SheetApp::selected_object: Option<ObjRef>`,
with `selected_chart()` kept for the Chart Design tab and the Format Chart
pane.

- Model: `ObjKind` (chart, image, shape, slicer) and `ObjRef { kind, id }`;
  `Sheet` helpers find, move and remove an object by `ObjRef` and walk every
  object's anchor in a fixed order. `next_object_id` saturates instead of
  overflowing on an id at `u32::MAX` read from a file.
- Commands: `object.delete`, `object.move` and `object.setAnchorMode` accept
  the four kinds through one parameter check, refuse an unknown kind or id
  instead of succeeding silently, and clamp hostile offsets. `object.delete`
  replies with the deleted object. Arrange and the Selection Pane include
  slicers, which stack above the other objects.
- Sheet: the selected object of any kind draws a frame with eight handles
  over every object. Each handle resizes only its own edges, which never
  cross; the body moves the object, and the first drag frame already applies
  the pointer's movement. The pointer shows the handle's resize cursor or a
  move cursor, with the diagonals swapped on a right-to-left sheet, where the
  frame moves with its object.
- Keys: Escape drops the object selection; Delete and Backspace keep
  deleting the selected object (#213). A selection whose object disappears
  (undo, redo, delete, sheet switch) is cleared.
- Double-click: `open_object` routes by kind (a text box edits in place, a
  chart opens Format Chart).
- Control protocol: `ui.inspect` reports `selectedObject {kind, id}` and
  keeps `selectedChart`; `ui.object.select` selects any object and replies
  with its rect. The snapshot example takes `--object <id>`.

Tests: engine tests for every kind by kind and id, the Selection Pane and
hostile parameters; UI tests for selecting each kind (a slicer over a chart
takes the click), all eight handles on four kinds, cursors, Delete,
Backspace and Escape, undo and redo, double-click routing, and resizing on a
right-to-left sheet. Existing tests use the new selection API.
2026-10-31 05:51:02 -04:00

74 lines
3.1 KiB
Rust

//! Text fields over workbook values (chart titles, table name) keep what the user types until they commit it.
use egui::{Event, Key, Modifiers, accesskit::Role};
use egui_kittest::kittest::Queryable as _;
use gridcraft_engine::Session;
use gridcraft_ui_egui::SheetApp;
use serde_json::json;
type Harness = egui_kittest::Harness<'static, SheetApp>;
fn harness(session: Session) -> Harness {
crate::support::harness(session, 4)
}
fn session() -> Session {
let mut session = Session::new();
session.new_workbook();
session.execute("range.setValues", json!({"range": "A1", "values": [["Name", "Qty"], ["a", 1], ["b", 2]]})).unwrap();
session
}
/// Clicks the field, replaces its text with `text` and presses Enter.
fn retype(h: &mut Harness, field: egui::Rect, text: &str) {
h.input_mut().events.push(Event::PointerMoved(field.center()));
h.run_steps(1);
h.get_all_by_role(Role::TextInput).find(|n| n.rect() == field).unwrap().click();
h.run_steps(2);
h.key_press_modifiers(Modifiers::COMMAND, Key::A);
h.run_steps(1);
// One keystroke per frame, as a person types: each must survive the next frame.
for c in text.chars() {
h.input_mut().events.push(Event::Text(c.to_string()));
h.run_steps(2);
}
h.key_press(Key::Enter);
h.run_steps(2);
}
#[test]
/// Editing the Format Chart title field sets the chart title.
fn typing_a_chart_title_in_format_chart_area_sets_it() {
let mut session = session();
session.execute("selection.set", json!({"range": "A1:B3"})).unwrap();
let chart = session.execute("insert.chart", json!({"type": "column", "at": "D3"})).unwrap();
let id = chart["chart"].as_u64().unwrap() as u32;
let mut h = harness(session);
h.state_mut().select_object_id(Some(id));
h.state_mut().grid.pane = Some("formatChart".into());
h.run_steps(2);
h.get_by_role_and_label(Role::DisclosureTriangle, "Chart Title").click();
h.run_steps(2);
// The pane sits on the right; the formula bar and name box are further left.
let field = h.get_all_by_role(Role::TextInput).map(|n| n.rect()).find(|r| r.left() > 850.0).unwrap();
retype(&mut h, field, "Sales");
let title = h.state().session.active().unwrap().wb.active().unwrap().charts.iter().find(|c| c.id == id).unwrap().title.clone();
assert_eq!(title.as_deref(), Some("Sales"));
}
#[test]
fn typing_a_table_name_on_the_table_design_tab_renames_the_table() {
let mut session = session();
session.execute("insert.table", json!({"range": "A1:B3", "header": true})).unwrap();
session.execute("selection.set", json!({"range": "A2"})).unwrap();
let mut h = harness(session);
h.state_mut().ui.ribbon_tab = "Table Design".into();
h.run_steps(2);
let label = h.get_by_label("Table Name:").rect();
let below_label = |r: &egui::Rect| r.top() >= label.bottom() - 2.0 && (r.left() - label.left()).abs() < 20.0;
let field = h.get_all_by_role(Role::TextInput).map(|n| n.rect()).find(below_label).unwrap();
retype(&mut h, field, "Sales");
let name = h.state().session.active().unwrap().wb.active().unwrap().tables[0].name.clone();
assert_eq!(name, "Sales");
}