use super::*;
use crate::ir::*;
fn build_xlsx_bytes(sheet_name: &str, cells: &[(&str, &str)]) -> Vec<u8> {
let mut book = umya_spreadsheet::new_file();
{
let sheet = book.get_sheet_mut(&0).unwrap();
sheet.set_name(sheet_name);
for &(coord, value) in cells {
sheet.get_cell_mut(coord).set_value(value);
}
}
let mut cursor = Cursor::new(Vec::new());
umya_spreadsheet::writer::xlsx::write_writer(&book, &mut cursor).unwrap();
cursor.into_inner()
}
fn build_xlsx_multi_sheet(sheets: &[(&str, &[(&str, &str)])]) -> Vec<u8> {
let mut book = umya_spreadsheet::new_file();
for (i, &(name, cells)) in sheets.iter().enumerate() {
if i == 0 {
let sheet = book.get_sheet_mut(&0).unwrap();
sheet.set_name(name);
for &(coord, value) in cells {
sheet.get_cell_mut(coord).set_value(value);
}
} else {
let mut sheet = umya_spreadsheet::Worksheet::default();
sheet.set_name(name);
for &(coord, value) in cells {
sheet.get_cell_mut(coord).set_value(value);
}
book.add_sheet(sheet).unwrap();
}
}
let mut cursor = Cursor::new(Vec::new());
umya_spreadsheet::writer::xlsx::write_writer(&book, &mut cursor).unwrap();
cursor.into_inner()
}
fn get_sheet_page(doc: &Document, idx: usize) -> &SheetPage {
match &doc.pages[idx] {
Page::Sheet(sp) => sp,
_ => panic!("Expected SheetPage at index {idx}"),
}
}
fn cell_text(cell: &TableCell) -> String {
cell.content
.iter()
.filter_map(|b| match b {
Block::Paragraph(p) => Some(p.runs.iter().map(|r| r.text.as_str()).collect::<String>()),
_ => None,
})
.collect::<Vec<_>>()
.join("")
}
fn first_run_style(cell: &TableCell) -> &TextStyle {
match &cell.content[0] {
Block::Paragraph(p) => &p.runs[0].style,
_ => panic!("Expected Paragraph"),
}
}
#[test]
fn test_parse_single_cell() {
let data = build_xlsx_bytes("Sheet1", &[("A1", "Hello")]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
assert_eq!(doc.pages.len(), 1);
let tp = get_sheet_page(&doc, 0);
assert_eq!(tp.name, "Sheet1");
assert_eq!(tp.table.rows.len(), 1);
assert_eq!(tp.table.rows[0].cells.len(), 1);
assert_eq!(cell_text(&tp.table.rows[0].cells[0]), "Hello");
}
#[test]
fn test_parse_multiple_cells() {
let data = build_xlsx_bytes(
"Data",
&[("A1", "Name"), ("B1", "Age"), ("A2", "Alice"), ("B2", "30")],
);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(tp.table.rows.len(), 2);
assert_eq!(tp.table.rows[0].cells.len(), 2);
assert_eq!(cell_text(&tp.table.rows[0].cells[0]), "Name");
assert_eq!(cell_text(&tp.table.rows[0].cells[1]), "Age");
assert_eq!(cell_text(&tp.table.rows[1].cells[0]), "Alice");
assert_eq!(cell_text(&tp.table.rows[1].cells[1]), "30");
}
#[test]
fn test_parse_empty_cells_in_grid() {
let data = build_xlsx_bytes("Sheet1", &[("A1", "Top-Left"), ("B2", "Bottom-Right")]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(tp.table.rows.len(), 2);
assert_eq!(tp.table.rows[0].cells.len(), 2);
assert_eq!(cell_text(&tp.table.rows[0].cells[0]), "Top-Left");
assert_eq!(cell_text(&tp.table.rows[0].cells[1]), "");
assert_eq!(cell_text(&tp.table.rows[1].cells[0]), "");
assert_eq!(cell_text(&tp.table.rows[1].cells[1]), "Bottom-Right");
}
#[test]
fn test_parse_numbers() {
let data = build_xlsx_bytes("Numbers", &[("A1", "42"), ("B1", "3.14")]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(cell_text(&tp.table.rows[0].cells[0]), "42");
assert_eq!(cell_text(&tp.table.rows[0].cells[1]), "3.14");
}
#[test]
fn test_parse_dates_as_text() {
let data = build_xlsx_bytes("Dates", &[("A1", "2024-01-15"), ("A2", "December 25")]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(cell_text(&tp.table.rows[0].cells[0]), "2024-01-15");
assert_eq!(cell_text(&tp.table.rows[1].cells[0]), "December 25");
}
#[test]
fn test_sheet_name_preserved() {
let data = build_xlsx_bytes("Financial Report", &[("A1", "Revenue")]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(tp.name, "Financial Report");
}
#[test]
fn test_parse_multiple_sheets() {
let data = build_xlsx_multi_sheet(&[
("Sheet1", &[("A1", "Data1")]),
("Sheet2", &[("A1", "Data2")]),
]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
assert_eq!(doc.pages.len(), 2);
let tp1 = get_sheet_page(&doc, 0);
let tp2 = get_sheet_page(&doc, 1);
assert_eq!(tp1.name, "Sheet1");
assert_eq!(tp2.name, "Sheet2");
assert_eq!(cell_text(&tp1.table.rows[0].cells[0]), "Data1");
assert_eq!(cell_text(&tp2.table.rows[0].cells[0]), "Data2");
}
#[test]
fn test_column_widths_default() {
let data = build_xlsx_bytes("Sheet1", &[("A1", "Hello"), ("B1", "World")]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(tp.table.column_widths.len(), 2);
for w in &tp.table.column_widths {
assert!(
*w > 50.0 && *w < 70.0,
"Expected default width in 50-70pt range, got {w}"
);
}
}
#[test]
fn test_page_size_defaults() {
let data = build_xlsx_bytes("Sheet1", &[("A1", "Test")]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let default_size = PageSize::default();
assert!((tp.size.width - default_size.width).abs() < 0.01);
assert!((tp.size.height - default_size.height).abs() < 0.01);
}
#[test]
fn test_table_row_column_consistency() {
let data = build_xlsx_bytes(
"Grid",
&[("A1", "1"), ("C1", "3"), ("B2", "5"), ("C3", "9")],
);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(tp.table.rows.len(), 3, "Expected 3 rows");
for row in &tp.table.rows {
assert_eq!(row.cells.len(), 3, "Expected 3 columns per row");
}
}
#[test]
fn test_parse_invalid_data_returns_error() {
let parser = XlsxParser;
let result = parser.parse(b"not an xlsx file", &ConvertOptions::default());
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, ConvertError::Parse(_)),
"Expected Parse error, got {err:?}"
);
}
#[test]
fn test_parse_error_includes_library_name() {
let parser = XlsxParser;
let result = parser.parse(b"not an xlsx file", &ConvertOptions::default());
let err = result.unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("umya-spreadsheet"),
"Parse error should include upstream library name 'umya-spreadsheet', got: {msg}"
);
}
#[test]
fn test_empty_cells_have_no_content() {
let data = build_xlsx_bytes("Sheet1", &[("A1", "Only A1"), ("C1", "Only C1")]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert!(
tp.table.rows[0].cells[1].content.is_empty(),
"Expected empty cell content for B1"
);
}
#[test]
fn test_cell_default_span_values() {
let data = build_xlsx_bytes("Sheet1", &[("A1", "Test")]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let cell = &tp.table.rows[0].cells[0];
assert_eq!(cell.col_span, 1);
assert_eq!(cell.row_span, 1);
assert!(cell.border.is_none());
assert!(cell.background.is_none());
}
#[path = "xlsx_cell_format_tests.rs"]
mod cell_format_tests;
#[path = "xlsx_page_feature_tests.rs"]
mod page_feature_tests;
#[path = "xlsx_condfmt_tests.rs"]
mod condfmt_tests;
#[path = "xlsx_chart_tests.rs"]
mod chart_tests;
#[path = "xlsx_streaming_tests.rs"]
mod streaming_tests;