mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 02:36:48 -06:00
* CI: dev builds keep line tables only * CI: one integration-test binary per crate * CI: xtask runs tests with nextest, doctests apart, --keep-going on clippy and wasm cargo xtask test runs the workspace tests (--lib --bins --tests) with cargo-nextest when it is installed, else cargo test, then the doctests. cargo xtask ci uses the same two steps. * CI: docs name cargo xtask test * CI: agent guides name the moved fuzz test * CI: tests added on main join their crate's test binary
163 lines
6.7 KiB
Rust
163 lines
6.7 KiB
Rust
//! Formula helpers must stay in the worksheet while its editor scrolls.
|
|
use egui::{Context, Id, LayerId, Order, RawInput, Rect, pos2, vec2};
|
|
use gridcraft_engine::Session;
|
|
use gridcraft_ui_egui::SheetApp;
|
|
|
|
fn frame(ctx: &Context, app: &mut SheetApp) -> egui::FullOutput {
|
|
let mut output =
|
|
ctx.run_ui(RawInput { screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(1200.0, 800.0))), ..Default::default() }, |ui| {
|
|
let ctx = ui.ctx().clone();
|
|
app.logic(&ctx);
|
|
app.ui(ui);
|
|
});
|
|
output.textures_delta.clear();
|
|
output
|
|
}
|
|
|
|
fn frames(ctx: &Context, app: &mut SheetApp) {
|
|
for _ in 0..4 {
|
|
frame(ctx, app);
|
|
}
|
|
}
|
|
|
|
fn visible(ctx: &Context, editor: &str) -> bool {
|
|
ctx.memory(|m| m.areas().is_visible(&LayerId::new(Order::Tooltip, Id::new(editor).with("ac"))))
|
|
}
|
|
|
|
#[test]
|
|
fn cell_formula_helpers_hide_when_scrolled_out_and_return_with_the_editor() {
|
|
for text in ["=SU", "=SUM("] {
|
|
let ctx = Context::default();
|
|
let mut session = Session::new();
|
|
session.new_workbook();
|
|
let mut app = SheetApp::new(session, Default::default());
|
|
frames(&ctx, &mut app);
|
|
app.begin_edit(Some(text.into()), false);
|
|
if text == "=SU" {
|
|
app.editor.as_mut().unwrap().update_autocomplete(&["SUM".into(), "SUMIF".into()]);
|
|
}
|
|
frames(&ctx, &mut app);
|
|
assert!(visible(&ctx, "gridcraft.cell_editor"), "visible cell shows {text} help");
|
|
app.view_mut().unwrap().scroll.y = 400.0;
|
|
frames(&ctx, &mut app);
|
|
assert!(!visible(&ctx, "gridcraft.cell_editor"), "scrolled-out cell must hide {text} help");
|
|
assert_eq!(app.editor.as_ref().unwrap().text, text, "scrolling preserves the edit");
|
|
app.view_mut().unwrap().scroll.y = 0.0;
|
|
frames(&ctx, &mut app);
|
|
assert!(visible(&ctx, "gridcraft.cell_editor"), "scrolling back restores {text} help");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn formula_bar_hint_remains_visible_when_the_worksheet_scrolls() {
|
|
let ctx = Context::default();
|
|
let mut session = Session::new();
|
|
session.new_workbook();
|
|
let mut app = SheetApp::new(session, Default::default());
|
|
frames(&ctx, &mut app);
|
|
app.begin_edit(Some("=SUM(".into()), true);
|
|
frames(&ctx, &mut app);
|
|
assert!(visible(&ctx, "gridcraft.formula_bar"));
|
|
app.view_mut().unwrap().scroll.y = 400.0;
|
|
frames(&ctx, &mut app);
|
|
assert!(visible(&ctx, "gridcraft.formula_bar"));
|
|
}
|
|
|
|
#[test]
|
|
fn cell_argument_hint_is_clipped_to_the_worksheet() {
|
|
let ctx = Context::default();
|
|
let mut session = Session::new();
|
|
session.new_workbook();
|
|
let mut app = SheetApp::new(session, Default::default());
|
|
frames(&ctx, &mut app);
|
|
app.begin_edit(Some("=SUM(".into()), false);
|
|
frames(&ctx, &mut app);
|
|
app.view_mut().unwrap().scroll.y = 10.0;
|
|
frames(&ctx, &mut app);
|
|
let output = frame(&ctx, &mut app);
|
|
let cells = app.grid.cells_rect.unwrap();
|
|
let hint = output.shapes.iter().find(|s| matches!(&s.shape, egui::Shape::Text(t) if t.galley.text() == "SUM(")).expect("argument hint painted");
|
|
assert!(cells.contains_rect(hint.clip_rect), "helper must not paint over the headers: {:?}", hint.clip_rect);
|
|
}
|
|
|
|
#[test]
|
|
fn scrolled_out_cell_editor_still_handles_escape() {
|
|
let ctx = Context::default();
|
|
let mut session = Session::new();
|
|
session.new_workbook();
|
|
let mut app = SheetApp::new(session, Default::default());
|
|
frames(&ctx, &mut app);
|
|
app.begin_edit(Some("=SUM(".into()), false);
|
|
frames(&ctx, &mut app);
|
|
app.view_mut().unwrap().scroll.y = 400.0;
|
|
frames(&ctx, &mut app);
|
|
let mut output = ctx.run_ui(
|
|
RawInput {
|
|
screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(1200.0, 800.0))),
|
|
events: vec![egui::Event::Key {
|
|
key: egui::Key::Escape,
|
|
physical_key: None,
|
|
pressed: true,
|
|
repeat: false,
|
|
modifiers: egui::Modifiers::NONE,
|
|
}],
|
|
..Default::default()
|
|
},
|
|
|ui| app.ui(ui),
|
|
);
|
|
output.textures_delta.clear();
|
|
assert!(app.editor.is_none(), "scrolling must not disable editor keys");
|
|
}
|
|
|
|
#[test]
|
|
fn cell_editor_and_helper_stay_below_the_frozen_top_row() {
|
|
let ctx = Context::default();
|
|
let mut session = Session::new();
|
|
session.new_workbook();
|
|
session.run("view.freezeTopRow", serde_json::json!({})).unwrap();
|
|
session.run("selection.set", serde_json::json!({"range": "B5"})).unwrap();
|
|
let mut app = SheetApp::new(session, Default::default());
|
|
frames(&ctx, &mut app);
|
|
let sh = app.session.active().unwrap().wb.active().unwrap();
|
|
let frozen_h = sh.row_height(0);
|
|
let scroll = sh.row_top(4) as f32;
|
|
app.begin_edit(Some("=SUM(".into()), false);
|
|
frames(&ctx, &mut app);
|
|
assert!(visible(&ctx, "gridcraft.cell_editor"));
|
|
app.view_mut().unwrap().scroll.y = scroll - frozen_h / 2.0;
|
|
frames(&ctx, &mut app);
|
|
let output = frame(&ctx, &mut app);
|
|
let frozen_bottom = app.grid.cells_rect.unwrap().top() + frozen_h * gridcraft_ui_egui::grid::DISPLAY_SCALE;
|
|
let editor = output
|
|
.shapes
|
|
.iter()
|
|
.find(|s| s.clip_rect.bottom() > frozen_bottom && matches!(&s.shape, egui::Shape::Text(t) if t.galley.text() == "=SUM("))
|
|
.expect("editor painted");
|
|
assert!(editor.clip_rect.top() >= frozen_bottom, "editor must be clipped below the frozen row: {:?}", editor.clip_rect);
|
|
app.view_mut().unwrap().scroll.y = scroll;
|
|
frames(&ctx, &mut app);
|
|
assert!(!visible(&ctx, "gridcraft.cell_editor"), "cell hidden under the frozen row must hide its helper");
|
|
assert_eq!(app.editor.as_ref().unwrap().text, "=SUM(");
|
|
app.view_mut().unwrap().scroll.y = 0.0;
|
|
frames(&ctx, &mut app);
|
|
assert!(visible(&ctx, "gridcraft.cell_editor"), "scrolling back restores the helper");
|
|
}
|
|
|
|
#[test]
|
|
fn argument_hint_shows_the_function_description() {
|
|
// #363: inside DATEDIF( the hint lists the units the third argument takes.
|
|
let ctx = Context::default();
|
|
let mut session = Session::new();
|
|
session.new_workbook();
|
|
let mut app = SheetApp::new(session, Default::default());
|
|
frames(&ctx, &mut app);
|
|
app.begin_edit(Some("=DATEDIF(A1,B1,".into()), false);
|
|
frames(&ctx, &mut app);
|
|
let output = frame(&ctx, &mut app);
|
|
let texts: Vec<&egui::epaint::TextShape> =
|
|
output.shapes.iter().filter_map(|s| if let egui::Shape::Text(t) = &s.shape { Some(t) } else { None }).collect();
|
|
assert!(texts.iter().any(|t| t.galley.text() == "DATEDIF("), "signature painted");
|
|
let desc = texts.iter().find(|t| t.galley.text().contains("YM or YD")).expect("description with the units painted");
|
|
assert!(desc.galley.rect.width() <= 400.0, "description wraps instead of widening the hint: {}", desc.galley.rect.width());
|
|
}
|