feat: spreadsheet v2 (#13347)

This commit is contained in:
jif-oai
2026-03-03 12:38:27 +00:00
committed by GitHub
parent 8c5e50ef39
commit 875eaac0d1
4 changed files with 664 additions and 3 deletions

View File

@@ -1,9 +1,16 @@
use std::collections::BTreeMap;
use pretty_assertions::assert_eq;
use crate::CellAddress;
use crate::CellRange;
use crate::SpreadsheetArtifact;
use crate::SpreadsheetArtifactManager;
use crate::SpreadsheetArtifactRequest;
use crate::SpreadsheetCell;
use crate::SpreadsheetCellValue;
use crate::SpreadsheetFileType;
use crate::SpreadsheetSheet;
#[test]
fn manager_can_create_edit_recalculate_and_export() -> Result<(), Box<dyn std::error::Error>> {
@@ -300,3 +307,250 @@ fn manager_supports_single_value_formula_and_cite_cell_actions()
);
Ok(())
}
#[test]
fn artifact_file_type_helpers_and_source_files_work() -> Result<(), Box<dyn std::error::Error>> {
let temp_dir = tempfile::tempdir()?;
let mut artifact = SpreadsheetArtifact::new(Some("Files".to_string()));
artifact.artifact_id = "spreadsheet_fixed".to_string();
artifact.create_sheet("Sheet1".to_string())?.set_value(
CellAddress::parse("A1")?,
Some(SpreadsheetCellValue::String("hello".to_string())),
)?;
assert_eq!(
SpreadsheetArtifact::allowed_file_extensions(),
&["xlsx", "json", "bin"]
);
assert_eq!(
SpreadsheetArtifact::allowed_file_mime_types(),
&[
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/json",
"application/octet-stream",
]
);
assert_eq!(
SpreadsheetArtifact::allowed_file_types().to_vec(),
vec![
SpreadsheetFileType::Xlsx,
SpreadsheetFileType::Json,
SpreadsheetFileType::Binary,
]
);
assert_eq!(
artifact.get_output_file_name(Some("preview"), SpreadsheetFileType::Json),
"spreadsheet_fixed_preview.json".to_string()
);
let json_path = temp_dir
.path()
.join(artifact.get_output_file_name(None, SpreadsheetFileType::Json));
artifact.save(&json_path, Some("json"))?;
let restored_json = SpreadsheetArtifact::load(&json_path, None)?;
assert_eq!(restored_json.to_dict()?, artifact.to_dict()?);
let bytes_path = temp_dir
.path()
.join(artifact.get_output_file_name(Some("bytes"), SpreadsheetFileType::Binary));
artifact.save(&bytes_path, Some("bin"))?;
let restored_bytes = SpreadsheetArtifact::read(&bytes_path, None)?;
assert_eq!(restored_bytes.to_dict()?, artifact.to_dict()?);
Ok(())
}
#[test]
fn sheet_cleanup_and_row_sizing_helpers_work() -> Result<(), Box<dyn std::error::Error>> {
let mut sheet = SpreadsheetSheet::new("Sheet1".to_string());
sheet.default_row_height = Some(15.0);
sheet.set_column_widths_bulk(&BTreeMap::from([
("A".to_string(), 12.0),
("C:D".to_string(), 20.0),
]))?;
sheet.set_row_height(2, Some(18.0))?;
sheet.set_row_heights(3, 4, Some(22.0))?;
sheet.set_row_heights_bulk(&BTreeMap::from([(4, None), (5, Some(30.0))]))?;
assert_eq!(sheet.get_column_width("A")?, Some(12.0));
assert_eq!(sheet.get_column_width("B")?, None);
assert_eq!(sheet.get_column_width("D")?, Some(20.0));
assert_eq!(sheet.get_row_height(2), Some(18.0));
assert_eq!(sheet.get_row_height(3), Some(22.0));
assert_eq!(sheet.get_row_height(4), Some(15.0));
assert_eq!(sheet.get_row_height(5), Some(30.0));
sheet.cells.insert(
CellAddress::parse("A1")?,
SpreadsheetCell {
value: None,
formula: None,
style_index: 0,
citations: Vec::new(),
},
);
let merged = CellRange::parse("B2:C3")?;
sheet.merged_ranges = vec![merged.clone(), merged.clone()];
sheet.cleanup_and_validate_sheet()?;
assert_eq!(sheet.cells, BTreeMap::new());
assert_eq!(sheet.merged_ranges, vec![merged]);
Ok(())
}
#[test]
fn xlsx_roundtrip_preserves_row_and_column_sizes() -> Result<(), Box<dyn std::error::Error>> {
let temp_dir = tempfile::tempdir()?;
let path = temp_dir.path().join("sizing.xlsx");
let mut artifact = SpreadsheetArtifact::new(Some("Sizing".to_string()));
let (expected_column_widths, expected_row_heights, expected_show_grid_lines) = {
let sheet = artifact.create_sheet("Sheet1".to_string())?;
sheet.show_grid_lines = false;
sheet.set_value(
CellAddress::parse("A1")?,
Some(SpreadsheetCellValue::Integer(42)),
)?;
sheet.set_column_widths_bulk(&BTreeMap::from([
("A:B".to_string(), 12.5),
("D".to_string(), 18.0),
]))?;
sheet.set_row_heights_bulk(&BTreeMap::from([(2, Some(24.0)), (6, Some(19.5))]))?;
(
sheet.column_widths.clone(),
sheet.row_heights.clone(),
sheet.show_grid_lines,
)
};
artifact.export(&path)?;
let restored = SpreadsheetArtifact::from_source_file(&path, None)?;
let restored_sheet = restored.get_sheet(Some("Sheet1"), None).expect("sheet");
assert_eq!(restored_sheet.column_widths, expected_column_widths);
assert_eq!(restored_sheet.row_heights, expected_row_heights);
assert_eq!(restored_sheet.show_grid_lines, expected_show_grid_lines);
Ok(())
}
#[test]
fn manager_supports_bulk_sizes_and_row_heights() -> Result<(), Box<dyn std::error::Error>> {
let temp_dir = tempfile::tempdir()?;
let mut manager = SpreadsheetArtifactManager::default();
let created = manager.execute(
SpreadsheetArtifactRequest {
artifact_id: None,
action: "create".to_string(),
args: serde_json::json!({ "name": "Sizing" }),
},
temp_dir.path(),
)?;
let artifact_id = created.artifact_id;
manager.execute(
SpreadsheetArtifactRequest {
artifact_id: Some(artifact_id.clone()),
action: "create_sheet".to_string(),
args: serde_json::json!({ "name": "Sheet1" }),
},
temp_dir.path(),
)?;
manager.execute(
SpreadsheetArtifactRequest {
artifact_id: Some(artifact_id.clone()),
action: "set_column_widths_bulk".to_string(),
args: serde_json::json!({
"sheet_name": "Sheet1",
"widths": {
"A:B": 12.0,
"D": 20.0
}
}),
},
temp_dir.path(),
)?;
manager.execute(
SpreadsheetArtifactRequest {
artifact_id: Some(artifact_id.clone()),
action: "set_row_height".to_string(),
args: serde_json::json!({
"sheet_name": "Sheet1",
"row_index": 2,
"height": 18.0
}),
},
temp_dir.path(),
)?;
manager.execute(
SpreadsheetArtifactRequest {
artifact_id: Some(artifact_id.clone()),
action: "set_row_heights".to_string(),
args: serde_json::json!({
"sheet_name": "Sheet1",
"start_row_index": 3,
"end_row_index": 4,
"height": 21.0
}),
},
temp_dir.path(),
)?;
manager.execute(
SpreadsheetArtifactRequest {
artifact_id: Some(artifact_id.clone()),
action: "set_row_heights_bulk".to_string(),
args: serde_json::json!({
"sheet_name": "Sheet1",
"heights": {
"4": null,
"5": 25.0
}
}),
},
temp_dir.path(),
)?;
let row_height = manager.execute(
SpreadsheetArtifactRequest {
artifact_id: Some(artifact_id.clone()),
action: "get_row_height".to_string(),
args: serde_json::json!({
"sheet_name": "Sheet1",
"row_index": 5
}),
},
temp_dir.path(),
)?;
assert_eq!(row_height.row_height, Some(25.0));
manager.execute(
SpreadsheetArtifactRequest {
artifact_id: Some(artifact_id.clone()),
action: "cleanup_and_validate_sheet".to_string(),
args: serde_json::json!({
"sheet_name": "Sheet1"
}),
},
temp_dir.path(),
)?;
let sheet = manager.execute(
SpreadsheetArtifactRequest {
artifact_id: Some(artifact_id),
action: "get_sheet".to_string(),
args: serde_json::json!({
"sheet_name": "Sheet1"
}),
},
temp_dir.path(),
)?;
let restored: SpreadsheetSheet =
serde_json::from_value(sheet.serialized_dict.expect("sheet dict"))?;
assert_eq!(
restored.column_widths,
BTreeMap::from([(1, 12.0), (2, 12.0), (4, 20.0)])
);
assert_eq!(
restored.row_heights,
BTreeMap::from([(2, 18.0), (3, 21.0), (5, 25.0)])
);
Ok(())
}