extern crate text_document_editing as document_editing;
use anyhow::Result;
use document_editing::document_editing_controller;
use document_editing::{
InsertTableColumnDto, InsertTableDto, InsertTableRowDto, MergeTableCellsDto,
RemoveTableColumnDto, RemoveTableDto, RemoveTableRowDto, SplitTableCellDto,
};
use test_harness::{
FrameRelationshipField, block_controller, export_text, frame_controller, get_all_block_ids,
get_document_stats, get_sorted_cells, get_table_ids, setup_with_text, table_cell_controller,
table_controller,
};
#[test]
fn test_insert_table_basic() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 3,
},
)?;
assert!(result.table_id > 0);
let table = table_controller::get(&db, &(result.table_id as u64))?.unwrap();
assert_eq!(table.rows, 2);
assert_eq!(table.columns, 3);
let cells = get_sorted_cells(&db, &(result.table_id as u64))?;
assert_eq!(cells.len(), 6);
assert_eq!((cells[0].row, cells[0].column), (0, 0));
assert_eq!((cells[1].row, cells[1].column), (0, 1));
assert_eq!((cells[2].row, cells[2].column), (0, 2));
assert_eq!((cells[3].row, cells[3].column), (1, 0));
assert_eq!((cells[4].row, cells[4].column), (1, 1));
assert_eq!((cells[5].row, cells[5].column), (1, 2));
for cell in &cells {
assert!(
cell.cell_frame.is_some(),
"Cell ({},{}) has no frame",
cell.row,
cell.column
);
let frame = frame_controller::get(&db, &cell.cell_frame.unwrap())?.unwrap();
let block_ids =
frame_controller::get_relationship(&db, &frame.id, &FrameRelationshipField::Blocks)?;
assert!(
!block_ids.is_empty(),
"Cell ({},{}) frame has no blocks",
cell.row,
cell.column
);
}
let table_ids = get_table_ids(&db)?;
assert!(table_ids.contains(&(result.table_id as u64)));
let stats = get_document_stats(&db)?;
assert_eq!(stats.block_count, 7);
Ok(())
}
#[test]
fn test_insert_table_mid_document() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("Hello\nWorld")?;
let _result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 6,
anchor: 6,
rows: 2,
columns: 2,
},
)?;
let all_block_ids = get_all_block_ids(&db)?;
let mut all_blocks: Vec<_> = all_block_ids
.iter()
.filter_map(|id| block_controller::get(&db, id).ok().flatten())
.collect();
all_blocks.sort_by_key(|b| b.document_position);
let hello_block = all_blocks.iter().find(|b| b.plain_text == "Hello").unwrap();
assert_eq!(hello_block.document_position, 0);
let world_block = all_blocks.iter().find(|b| b.plain_text == "World").unwrap();
assert_eq!(world_block.document_position, 10);
Ok(())
}
#[test]
fn test_insert_table_undo_redo() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 2,
},
)?;
let table_id = result.table_id as u64;
assert!(table_controller::get(&db, &table_id)?.is_some());
let tables_before = get_table_ids(&db)?;
assert_eq!(tables_before.len(), 1);
urm.undo(None)?;
let tables_after_undo = get_table_ids(&db)?;
assert_eq!(tables_after_undo.len(), 0);
let stats = get_document_stats(&db)?;
assert_eq!(stats.block_count, 1);
urm.redo(None)?;
let tables_after_redo = get_table_ids(&db)?;
assert_eq!(tables_after_redo.len(), 1);
Ok(())
}
#[test]
fn test_insert_table_validation() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 0,
columns: 3,
},
);
assert!(result.is_err());
let result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 0,
},
);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_insert_table_updates_document_stats() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("Hello")?;
let stats_before = get_document_stats(&db)?;
assert_eq!(stats_before.block_count, 1);
document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 5,
anchor: 5,
rows: 3,
columns: 2,
},
)?;
let stats_after = get_document_stats(&db)?;
assert_eq!(stats_after.block_count, 7);
Ok(())
}
#[test]
fn test_remove_table() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 3,
},
)?;
let table_id = insert_result.table_id;
assert_eq!(get_table_ids(&db)?.len(), 1);
document_editing_controller::remove_table(
&db,
&hub,
&mut urm,
None,
&RemoveTableDto { table_id },
)?;
assert_eq!(get_table_ids(&db)?.len(), 0);
let stats = get_document_stats(&db)?;
assert_eq!(stats.block_count, 1);
Ok(())
}
#[test]
fn test_remove_table_undo() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 2,
},
)?;
document_editing_controller::remove_table(
&db,
&hub,
&mut urm,
None,
&RemoveTableDto {
table_id: insert_result.table_id,
},
)?;
assert_eq!(get_table_ids(&db)?.len(), 0);
urm.undo(None)?;
assert_eq!(get_table_ids(&db)?.len(), 1);
let stats = get_document_stats(&db)?;
assert_eq!(stats.block_count, 5);
Ok(())
}
#[test]
fn test_remove_table_shifts_positions() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("Hello\nWorld")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 6,
anchor: 6,
rows: 2,
columns: 2,
},
)?;
document_editing_controller::remove_table(
&db,
&hub,
&mut urm,
None,
&RemoveTableDto {
table_id: insert_result.table_id,
},
)?;
let text = export_text(&db, &hub)?;
assert_eq!(text, "Hello\nWorld");
Ok(())
}
#[test]
fn test_insert_row_at_end() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 3,
},
)?;
let table_id = insert_result.table_id;
let row_result = document_editing_controller::insert_table_row(
&db,
&hub,
&mut urm,
None,
&InsertTableRowDto {
table_id,
row_index: 2,
},
)?;
assert_eq!(row_result.new_row_count, 3);
let table = table_controller::get(&db, &(table_id as u64))?.unwrap();
assert_eq!(table.rows, 3);
let cells = get_sorted_cells(&db, &(table_id as u64))?;
assert_eq!(cells.len(), 9);
let new_row_cells: Vec<_> = cells.iter().filter(|c| c.row == 2).collect();
assert_eq!(new_row_cells.len(), 3);
Ok(())
}
#[test]
fn test_insert_row_at_beginning() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 2,
},
)?;
let table_id = insert_result.table_id;
document_editing_controller::insert_table_row(
&db,
&hub,
&mut urm,
None,
&InsertTableRowDto {
table_id,
row_index: 0,
},
)?;
let cells = get_sorted_cells(&db, &(table_id as u64))?;
assert_eq!(cells.len(), 6);
let row0_cells: Vec<_> = cells.iter().filter(|c| c.row == 0).collect();
assert_eq!(row0_cells.len(), 2);
let row1_cells: Vec<_> = cells.iter().filter(|c| c.row == 1).collect();
assert_eq!(row1_cells.len(), 2);
Ok(())
}
#[test]
fn test_insert_row_undo() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 2,
},
)?;
let table_id = insert_result.table_id;
document_editing_controller::insert_table_row(
&db,
&hub,
&mut urm,
None,
&InsertTableRowDto {
table_id,
row_index: 1,
},
)?;
let table = table_controller::get(&db, &(table_id as u64))?.unwrap();
assert_eq!(table.rows, 3);
urm.undo(None)?;
let table = table_controller::get(&db, &(table_id as u64))?.unwrap();
assert_eq!(table.rows, 2);
let cells = get_sorted_cells(&db, &(table_id as u64))?;
assert_eq!(cells.len(), 4);
Ok(())
}
#[test]
fn test_insert_column_at_end() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 3,
},
)?;
let table_id = insert_result.table_id;
let col_result = document_editing_controller::insert_table_column(
&db,
&hub,
&mut urm,
None,
&InsertTableColumnDto {
table_id,
column_index: 3,
},
)?;
assert_eq!(col_result.new_column_count, 4);
let cells = get_sorted_cells(&db, &(table_id as u64))?;
assert_eq!(cells.len(), 8);
Ok(())
}
#[test]
fn test_insert_column_undo() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 2,
},
)?;
let table_id = insert_result.table_id;
document_editing_controller::insert_table_column(
&db,
&hub,
&mut urm,
None,
&InsertTableColumnDto {
table_id,
column_index: 1,
},
)?;
assert_eq!(
table_controller::get(&db, &(table_id as u64))?
.unwrap()
.columns,
3
);
urm.undo(None)?;
assert_eq!(
table_controller::get(&db, &(table_id as u64))?
.unwrap()
.columns,
2
);
Ok(())
}
#[test]
fn test_remove_row() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 3,
columns: 2,
},
)?;
let table_id = insert_result.table_id;
let row_result = document_editing_controller::remove_table_row(
&db,
&hub,
&mut urm,
None,
&RemoveTableRowDto {
table_id,
row_index: 1,
},
)?;
assert_eq!(row_result.new_row_count, 2);
let cells = get_sorted_cells(&db, &(table_id as u64))?;
assert_eq!(cells.len(), 4);
let row0_cells: Vec<_> = cells.iter().filter(|c| c.row == 0).collect();
assert_eq!(row0_cells.len(), 2);
let row1_cells: Vec<_> = cells.iter().filter(|c| c.row == 1).collect();
assert_eq!(row1_cells.len(), 2);
Ok(())
}
#[test]
fn test_remove_last_row_fails() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 1,
columns: 2,
},
)?;
let result = document_editing_controller::remove_table_row(
&db,
&hub,
&mut urm,
None,
&RemoveTableRowDto {
table_id: insert_result.table_id,
row_index: 0,
},
);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_remove_row_undo() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 3,
columns: 2,
},
)?;
let table_id = insert_result.table_id;
document_editing_controller::remove_table_row(
&db,
&hub,
&mut urm,
None,
&RemoveTableRowDto {
table_id,
row_index: 0,
},
)?;
assert_eq!(
table_controller::get(&db, &(table_id as u64))?
.unwrap()
.rows,
2
);
urm.undo(None)?;
assert_eq!(
table_controller::get(&db, &(table_id as u64))?
.unwrap()
.rows,
3
);
assert_eq!(get_sorted_cells(&db, &(table_id as u64))?.len(), 6);
Ok(())
}
#[test]
fn test_remove_column() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 3,
},
)?;
let table_id = insert_result.table_id;
let col_result = document_editing_controller::remove_table_column(
&db,
&hub,
&mut urm,
None,
&RemoveTableColumnDto {
table_id,
column_index: 1,
},
)?;
assert_eq!(col_result.new_column_count, 2);
let cells = get_sorted_cells(&db, &(table_id as u64))?;
assert_eq!(cells.len(), 4);
for cell in &cells {
assert!(cell.column < 2);
}
Ok(())
}
#[test]
fn test_remove_last_column_fails() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 1,
},
)?;
let result = document_editing_controller::remove_table_column(
&db,
&hub,
&mut urm,
None,
&RemoveTableColumnDto {
table_id: insert_result.table_id,
column_index: 0,
},
);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_merge_2x2_cells() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 3,
columns: 3,
},
)?;
let table_id = insert_result.table_id;
let merge_result = document_editing_controller::merge_table_cells(
&db,
&hub,
&mut urm,
None,
&MergeTableCellsDto {
table_id,
start_row: 0,
start_column: 0,
end_row: 1,
end_column: 1,
},
)?;
let surviving =
table_cell_controller::get(&db, &(merge_result.merged_cell_id as u64))?.unwrap();
assert_eq!(surviving.row_span, 2);
assert_eq!(surviving.column_span, 2);
let cells = get_sorted_cells(&db, &(table_id as u64))?;
assert_eq!(cells.len(), 6);
Ok(())
}
#[test]
fn test_merge_already_merged_fails() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 3,
columns: 3,
},
)?;
let table_id = insert_result.table_id;
document_editing_controller::merge_table_cells(
&db,
&hub,
&mut urm,
None,
&MergeTableCellsDto {
table_id,
start_row: 0,
start_column: 0,
end_row: 1,
end_column: 1,
},
)?;
let result = document_editing_controller::merge_table_cells(
&db,
&hub,
&mut urm,
None,
&MergeTableCellsDto {
table_id,
start_row: 0,
start_column: 0,
end_row: 2,
end_column: 0,
},
);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_merge_undo() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 2,
},
)?;
let table_id = insert_result.table_id;
document_editing_controller::merge_table_cells(
&db,
&hub,
&mut urm,
None,
&MergeTableCellsDto {
table_id,
start_row: 0,
start_column: 0,
end_row: 1,
end_column: 1,
},
)?;
assert_eq!(get_sorted_cells(&db, &(table_id as u64))?.len(), 1);
urm.undo(None)?;
assert_eq!(get_sorted_cells(&db, &(table_id as u64))?.len(), 4);
Ok(())
}
#[test]
fn test_split_merged_cell() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 3,
columns: 3,
},
)?;
let table_id = insert_result.table_id;
let merge_result = document_editing_controller::merge_table_cells(
&db,
&hub,
&mut urm,
None,
&MergeTableCellsDto {
table_id,
start_row: 0,
start_column: 0,
end_row: 1,
end_column: 1,
},
)?;
let cells_after_merge = get_sorted_cells(&db, &(table_id as u64))?;
assert_eq!(cells_after_merge.len(), 6);
let split_result = document_editing_controller::split_table_cell(
&db,
&hub,
&mut urm,
None,
&SplitTableCellDto {
cell_id: merge_result.merged_cell_id,
split_rows: 2,
split_columns: 2,
},
)?;
assert_eq!(split_result.new_cell_ids.len(), 4);
let cells_after_split = get_sorted_cells(&db, &(table_id as u64))?;
assert_eq!(cells_after_split.len(), 9);
Ok(())
}
#[test]
fn test_split_unmerged_cell_fails() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 2,
},
)?;
let cells = get_sorted_cells(&db, &(insert_result.table_id as u64))?;
let cell_id = cells[0].id as i64;
let result = document_editing_controller::split_table_cell(
&db,
&hub,
&mut urm,
None,
&SplitTableCellDto {
cell_id,
split_rows: 1,
split_columns: 1,
},
);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_split_undo() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 0,
anchor: 0,
rows: 2,
columns: 2,
},
)?;
let table_id = insert_result.table_id;
let merge_result = document_editing_controller::merge_table_cells(
&db,
&hub,
&mut urm,
None,
&MergeTableCellsDto {
table_id,
start_row: 0,
start_column: 0,
end_row: 1,
end_column: 1,
},
)?;
document_editing_controller::split_table_cell(
&db,
&hub,
&mut urm,
None,
&SplitTableCellDto {
cell_id: merge_result.merged_cell_id,
split_rows: 2,
split_columns: 2,
},
)?;
assert_eq!(get_sorted_cells(&db, &(table_id as u64))?.len(), 4);
urm.undo(None)?;
let cells = get_sorted_cells(&db, &(table_id as u64))?;
assert_eq!(cells.len(), 1);
assert_eq!(cells[0].row_span, 2);
assert_eq!(cells[0].column_span, 2);
Ok(())
}
#[test]
fn test_positions_sequential_after_insert_table() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("First\nSecond\nThird")?;
document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 6,
anchor: 6,
rows: 2,
columns: 2,
},
)?;
let all_block_ids = get_all_block_ids(&db)?;
let mut positions: Vec<i64> = all_block_ids
.iter()
.filter_map(|id| block_controller::get(&db, id).ok().flatten())
.map(|b| b.document_position)
.collect();
positions.sort();
let unique: std::collections::HashSet<i64> = positions.iter().copied().collect();
assert_eq!(
unique.len(),
positions.len(),
"Duplicate positions found: {:?}",
positions
);
for pos in &positions {
assert!(*pos >= 0, "Negative position found: {}", pos);
}
Ok(())
}
#[test]
fn test_positions_consistent_after_row_insert_and_remove() -> Result<()> {
let (db, hub, mut urm) = setup_with_text("Before\nAfter")?;
let insert_result = document_editing_controller::insert_table(
&db,
&hub,
&mut urm,
None,
&InsertTableDto {
position: 7,
anchor: 7,
rows: 2,
columns: 2,
},
)?;
let table_id = insert_result.table_id;
document_editing_controller::insert_table_row(
&db,
&hub,
&mut urm,
None,
&InsertTableRowDto {
table_id,
row_index: 1,
},
)?;
document_editing_controller::remove_table_row(
&db,
&hub,
&mut urm,
None,
&RemoveTableRowDto {
table_id,
row_index: 1,
},
)?;
let table = table_controller::get(&db, &(table_id as u64))?.unwrap();
assert_eq!(table.rows, 2);
assert_eq!(table.columns, 2);
Ok(())
}