mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-27 11:46:51 -06:00
Refuse table row moves across the header/footer boundary
Found while testing crates/doc/src/table.rs:882 (Table::move_row, called from crates/engine/src/cmd/table.rs:1437 and :1369) Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 5.5
parent
a7f3239cfb
commit
230b29be60
+16
-1
@@ -878,7 +878,8 @@ impl Table {
|
||||
}
|
||||
|
||||
/// Move row `from` to position `to` (indices before the move). Refused when merged cells span
|
||||
/// rows (the move would cut them).
|
||||
/// rows (the move would cut them) or when the move would take a row across a header/footer
|
||||
/// boundary (`from` and `to` must be the same kind of row).
|
||||
pub fn move_row(&mut self, from: usize, to: usize) -> Result<(), String> {
|
||||
let (nr, nc) = (self.nrows(), self.ncols());
|
||||
if from >= nr || to >= nr {
|
||||
@@ -887,6 +888,9 @@ impl Table {
|
||||
if self.regions().iter().any(|r| r.2 > 1) {
|
||||
return Err("unmerge cells that span rows first".into());
|
||||
}
|
||||
if self.rows[from].kind != self.rows[to].kind {
|
||||
return Err("header and footer rows must stay first and last".into());
|
||||
}
|
||||
let row = self.rows.remove(from);
|
||||
self.rows.insert(to, row);
|
||||
let cells: Vec<Cell> = self.cells.drain(from * nc..(from + 1) * nc).collect();
|
||||
@@ -1195,6 +1199,17 @@ mod tests {
|
||||
Story::with_text(StoryId(1), t, ParaFormat::default())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_row_keeps_header_and_footer_rows_in_place() {
|
||||
let mut t = Table::new(1, 3, 2, 1, 1, 120.0);
|
||||
for (from, to) in [(0, 2), (0, 4), (2, 0), (2, 4), (4, 2), (4, 0)] {
|
||||
assert!(t.move_row(from, to).is_err(), "move_row({from}, {to}) should be refused");
|
||||
t.check().unwrap();
|
||||
}
|
||||
t.move_row(1, 3).unwrap();
|
||||
t.check().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_owners_include_covered_cells_without_expanding_range() {
|
||||
for column in [true, false] {
|
||||
|
||||
@@ -1677,6 +1677,31 @@ mod sort_tests {
|
||||
assert_eq!((cell(&s, 2, 0), cell(&s, 2, 1)), ("x".to_string(), "a".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_row_cannot_move_a_header_row_out_of_place() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
let r = s.execute("frame.create", &json!({"rect": [72, 72, 500, 400], "content": "text", "text": ""})).unwrap();
|
||||
s.execute("text.select", &json!({"story": r["story"], "anchor": 0, "focus": 0})).unwrap();
|
||||
s.execute("table.insert", &json!({"rows": 3, "cols": 2, "headerRows": 1})).unwrap();
|
||||
assert!(s.execute("table.moveRow", &json!({"from": 0, "to": 2})).is_err());
|
||||
s.doc().unwrap().doc.check().unwrap();
|
||||
// Dragging the selected header row onto the last body row is refused too.
|
||||
let d = s.doc().unwrap().doc.clone();
|
||||
let (sid, tid) = d.stories.values().find_map(|st| st.tables.keys().next().map(|k| (st.id, *k))).unwrap();
|
||||
let fid = d.stories[&sid].frames[0];
|
||||
let cs = s.cache.get(&d, sid, None);
|
||||
let cells = &cs.frames[0].tables[0].cells;
|
||||
let centre = |r: usize| {
|
||||
let c = cells.iter().find(|c| c.row == r && c.col == 0).unwrap().rect.center();
|
||||
d.text_xf(d.item(fid).unwrap()) * c
|
||||
};
|
||||
let (from, to) = (centre(0), centre(2));
|
||||
s.execute("table.select", &json!({"story": sid.0, "table": tid, "rows": [0, 0], "what": "row"})).unwrap();
|
||||
assert!(s.execute("table.dropCells", &json!({"frame": fid.0, "from": [from.x, from.y], "to": [to.x, to.y]})).is_err());
|
||||
s.doc().unwrap().doc.check().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_options_limits_header_and_footer_rows() {
|
||||
let mut s = Session::new();
|
||||
|
||||
Reference in New Issue
Block a user