Compare commits

...
Author SHA1 Message Date
skylar taylor-barrick 0ee8c0d15f Merge remote-tracking branch 'origin/main' into test/backfill-regression-tests 2026-10-23 11:03:45 -06:00
skylar taylor-barrick bc0cd89225 #1485: test that the PivotTable field-list scrollbar sits at the pane edge
#1485 let the field list take the pane's width so its scrollbar sits at the pane edge, without a test. With 14 short field names the scrollbar otherwise sits beside them, about 250 px from the edge.
2026-10-22 11:50:34 -06:00
skylar taylor-barrick 524ce912e5 #360: test that Watch Window stays inside the Formulas ribbon
#360 (#342) moved Watch Window out of a three-row column it overflowed, without a test. The test checks Watch Window's painted label ends no lower than the ribbon's last label row; as the fourth small button it ends 15 px below.
2026-10-22 11:50:34 -06:00
skylar taylor-barrick ebc986fcc9 #223: test that the title bar's Save includes the cell being typed
#223 made the title bar's Save button (and the Save Changes dialog's) commit the cell edit before saving, without a test for the button. The test types into A1, clicks Save, and reads the saved file back; with the commit_edit call removed it fails because the file lacks the value.
2026-10-22 11:50:34 -06:00
3 changed files with 116 additions and 0 deletions
+48
View File
@@ -108,6 +108,54 @@ fn save_actions_include_the_cell_draft_and_close_keeps_the_unsaved_prompt() {
assert_eq!(h.state().session.active().unwrap().wb.active().unwrap().value(CellRef::new(0, 0)), Value::Number(123.0)); assert_eq!(h.state().session.active().unwrap().wb.active().unwrap().value(CellRef::new(0, 0)), Value::Number(123.0));
} }
/// Where the title bar's icon button for `name` is. Its icon has no text, so it is found by
/// hovering along the title bar until its tooltip ("Save (⌘S)") shows.
fn title_bar_button(h: &mut egui_kittest::Harness<'static, SheetApp>, name: &str) -> egui::Pos2 {
let autosave = h.get_by_label("AutoSave").rect();
let tip = format!("{name} (");
let mut x = autosave.right();
while x < autosave.right() + 300.0 {
let at = egui::pos2(x, autosave.center().y);
h.hover_at(at);
h.run_steps(3);
if h.query_by_label_contains(&tip).is_some() {
return at;
}
x += 6.0;
}
panic!("no title bar button shows a {tip:?} tooltip");
}
#[test]
fn the_title_bar_save_button_saves_the_cell_being_typed() {
// Regression test for #223: the title bar's Save button saved the file without the cell the
// user was still typing in.
let dir = std::env::temp_dir().join(format!("gridcraft-title-bar-save-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("Book.xlsx").to_string_lossy().into_owned();
let mut h = harness(Services::default());
h.state_mut().run("file.save", serde_json::json!({"path": &path})).unwrap();
h.run_steps(2);
h.input_mut().events.push(egui::Event::Text("123".into()));
h.run_steps(2);
assert!(h.state().editor.is_some());
let save = title_bar_button(&mut h, "Save");
for pressed in [true, false] {
h.input_mut().events.push(egui::Event::PointerButton {
pos: save,
button: egui::PointerButton::Primary,
pressed,
modifiers: egui::Modifiers::NONE,
});
h.run_steps(1);
}
h.run_steps(2);
assert!(h.state().editor.is_none(), "Save commits the cell draft");
let (wb, _) = gridcraft_engine::io::open_bytes("Book.xlsx", &std::fs::read(&path).unwrap()).unwrap();
assert_eq!(wb.active().unwrap().value(CellRef::new(0, 0)), Value::Number(123.0), "the saved file has the typed value");
let _ = std::fs::remove_dir_all(&dir);
}
#[test] #[test]
fn file_options_opens_the_options_page_and_pages_are_reachable_by_command() { fn file_options_opens_the_options_page_and_pages_are_reachable_by_command() {
let mut h = harness(Services::default()); let mut h = harness(Services::default());
+24
View File
@@ -26,6 +26,30 @@ fn blank() -> Session {
s s
} }
#[test]
fn the_formulas_ribbon_keeps_watch_window_inside_the_ribbon() {
// Regression test for #360 (#342): Watch Window was a fourth small button in a column of
// three, so it hung below the ribbon and was cut off.
fn texts(shape: &egui::Shape, out: &mut Vec<(String, egui::Rect)>) {
match shape {
egui::Shape::Vec(shapes) => shapes.iter().for_each(|s| texts(s, out)),
egui::Shape::Text(t) => out.push((t.galley.text().to_string(), t.galley.rect.translate(t.pos.to_vec2()))),
_ => {}
}
}
let mut h = harness(blank());
h.state_mut().ui.ribbon_tab = "Formulas".into();
h.run_steps(3);
// Ribbon button labels are painted text: find them among the frame's shapes.
let mut painted = Vec::new();
h.output().shapes.iter().for_each(|c| texts(&c.shape, &mut painted));
let bottom_of = |word: &str| painted.iter().filter(|(s, _)| s.contains(word)).map(|(_, r)| r.bottom()).fold(f32::NEG_INFINITY, f32::max);
// "Calculation Options" is a big button whose second line sits at the foot of the ribbon.
let (watch, foot) = (bottom_of("Window"), bottom_of("Options"));
assert!(watch.is_finite() && foot.is_finite(), "the Formulas ribbon paints Watch Window and Calculation Options");
assert!(watch <= foot + 0.5, "Watch Window's label ends at y={watch}, below the ribbon's last label row (y={foot})");
}
#[test] #[test]
fn vertical_scrollbar_pages_and_drags_without_changing_selection() { fn vertical_scrollbar_pages_and_drags_without_changing_selection() {
let mut h = harness(blank()); let mut h = harness(blank());
+44
View File
@@ -56,3 +56,47 @@ fn pivot_field_button_surfaces_follow_the_active_theme() {
assert_eq!(field_button_fill(&shapes, "Region ▾"), expected, "dark mode: {dark}"); assert_eq!(field_button_fill(&shapes, "Region ▾"), expected, "dark mode: {dark}");
} }
} }
fn flatten(shape: Shape, out: &mut Vec<Shape>) {
match shape {
Shape::Vec(shapes) => shapes.into_iter().for_each(|s| flatten(s, out)),
s => out.push(s),
}
}
#[test]
fn the_field_list_scrollbar_sits_at_the_pane_edge() {
// Regression test for #1485: with enough source fields to scroll, the field list's scrollbar
// sat just right of the longest (short) field name instead of at the pane's right edge.
use egui_kittest::kittest::Queryable as _;
let names: Vec<String> = (1..=14).map(|i| format!("F{i}")).collect();
let mut session = Session::new();
session.new_workbook();
session.execute("range.setValues", json!({"range": "A1", "values": [names.clone(), (1..=14).collect::<Vec<i32>>()]})).unwrap();
session.execute("insert.pivotTable", json!({"source": "Sheet1!A1:N2", "destination": "Sheet1!A5", "rows": ["F1"], "values": ["F2"]})).unwrap();
session.execute("selection.set", json!({"cell": "A5"})).unwrap();
let mut h = egui_kittest::Harness::builder().with_size(vec2(1200.0, 800.0)).build_ui_state(
|ui, app: &mut SheetApp| {
let ctx = ui.ctx().clone();
app.logic(&ctx);
app.ui(ui);
},
SheetApp::new(session, Default::default()),
);
h.run_steps(4);
let first = h.get_by_label("F1").rect();
// A floating scrollbar is drawn while the pointer is over its area.
h.get_by_label("F1").hover();
h.run_steps(3);
let mut shapes = Vec::new();
h.output().shapes.iter().for_each(|c| flatten(c.shape.clone(), &mut shapes));
let bar = shapes
.iter()
.filter_map(|s| match s {
Shape::Rect(r) if r.fill.a() > 0 && r.rect.width() <= 12.0 && r.rect.height() >= 20.0 && r.rect.left() > first.left() => Some(r.rect),
_ => None,
})
.max_by(|a, b| a.right().total_cmp(&b.right()))
.expect("the field list draws a scrollbar");
assert!(bar.right() > 1200.0 - 20.0, "the scrollbar {bar:?} sits at the pane's right edge, not beside the field names ({first:?})");
}