use super::*;
fn build_xlsx_with_merges(sheet_name: &str, cells: &[(&str, &str)], merges: &[&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);
}
for &merge_range in merges {
sheet.add_merge_cells(merge_range);
}
}
let mut cursor = Cursor::new(Vec::new());
umya_spreadsheet::writer::xlsx::write_writer(&book, &mut cursor).unwrap();
cursor.into_inner()
}
#[test]
fn test_merge_colspan_basic() {
let data = build_xlsx_with_merges("Sheet1", &[("A1", "Merged")], &["A1:B1"]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(
tp.table.rows[0].cells.len(),
1,
"Merged cells should produce 1 cell"
);
assert_eq!(tp.table.rows[0].cells[0].col_span, 2);
assert_eq!(tp.table.rows[0].cells[0].row_span, 1);
assert_eq!(cell_text(&tp.table.rows[0].cells[0]), "Merged");
}
#[test]
fn test_merge_rowspan_basic() {
let data = build_xlsx_with_merges("Sheet1", &[("A1", "Tall")], &["A1:A2"]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(tp.table.rows[0].cells.len(), 1);
assert_eq!(tp.table.rows[0].cells[0].row_span, 2);
assert_eq!(tp.table.rows[0].cells[0].col_span, 1);
assert_eq!(cell_text(&tp.table.rows[0].cells[0]), "Tall");
assert_eq!(tp.table.rows[1].cells.len(), 0);
}
#[test]
fn test_merge_colspan_and_rowspan() {
let data = build_xlsx_with_merges(
"Sheet1",
&[("A1", "Big"), ("C1", "Right"), ("C2", "Below")],
&["A1:B2"],
);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(tp.table.rows[0].cells.len(), 2);
assert_eq!(tp.table.rows[0].cells[0].col_span, 2);
assert_eq!(tp.table.rows[0].cells[0].row_span, 2);
assert_eq!(cell_text(&tp.table.rows[0].cells[0]), "Big");
assert_eq!(cell_text(&tp.table.rows[0].cells[1]), "Right");
assert_eq!(tp.table.rows[1].cells.len(), 1);
assert_eq!(cell_text(&tp.table.rows[1].cells[0]), "Below");
}
#[test]
fn test_merge_content_in_top_left_only() {
let data = build_xlsx_with_merges(
"Sheet1",
&[("A1", "TopLeft"), ("B1", "should be ignored")],
&["A1:B1"],
);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(tp.table.rows[0].cells.len(), 1);
assert_eq!(cell_text(&tp.table.rows[0].cells[0]), "TopLeft");
}
#[test]
fn test_merge_multiple_ranges() {
let data = build_xlsx_with_merges(
"Sheet1",
&[("A1", "Wide"), ("A2", "Tall"), ("B2", "B2"), ("B3", "B3")],
&["A1:B1", "A2:A3"],
);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(tp.table.rows[0].cells.len(), 1);
assert_eq!(tp.table.rows[0].cells[0].col_span, 2);
assert_eq!(cell_text(&tp.table.rows[0].cells[0]), "Wide");
assert_eq!(tp.table.rows[1].cells.len(), 2);
assert_eq!(tp.table.rows[1].cells[0].row_span, 2);
assert_eq!(cell_text(&tp.table.rows[1].cells[0]), "Tall");
assert_eq!(cell_text(&tp.table.rows[1].cells[1]), "B2");
assert_eq!(tp.table.rows[2].cells.len(), 1);
assert_eq!(cell_text(&tp.table.rows[2].cells[0]), "B3");
}
#[test]
fn test_merge_no_merges_unchanged() {
let data = build_xlsx_bytes("Sheet1", &[("A1", "X"), ("B1", "Y")]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(tp.table.rows[0].cells.len(), 2);
for cell in &tp.table.rows[0].cells {
assert_eq!(cell.col_span, 1);
assert_eq!(cell.row_span, 1);
}
}
#[test]
fn test_merge_wide_colspan() {
let data = build_xlsx_with_merges("Sheet1", &[("A1", "Title")], &["A1:D1"]);
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
assert_eq!(tp.table.rows[0].cells.len(), 1);
assert_eq!(tp.table.rows[0].cells[0].col_span, 4);
assert_eq!(cell_text(&tp.table.rows[0].cells[0]), "Title");
}
fn build_xlsx_formatted(setup: impl FnOnce(&mut umya_spreadsheet::Worksheet)) -> Vec<u8> {
let mut book = umya_spreadsheet::new_file();
{
let sheet = book.get_sheet_mut(&0).unwrap();
sheet.set_name("Sheet1");
setup(sheet);
}
let mut cursor = Cursor::new(Vec::new());
umya_spreadsheet::writer::xlsx::write_writer(&book, &mut cursor).unwrap();
cursor.into_inner()
}
#[test]
fn test_cell_bold_text() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value("Bold");
cell.get_style_mut().get_font_mut().set_bold(true);
});
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let style = first_run_style(&tp.table.rows[0].cells[0]);
assert_eq!(style.bold, Some(true));
}
#[test]
fn test_cell_italic_text() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value("Italic");
cell.get_style_mut().get_font_mut().set_italic(true);
});
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let style = first_run_style(&tp.table.rows[0].cells[0]);
assert_eq!(style.italic, Some(true));
}
#[test]
fn test_cell_font_color() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value("Red");
cell.get_style_mut()
.get_font_mut()
.get_color_mut()
.set_argb("FFFF0000");
});
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let style = first_run_style(&tp.table.rows[0].cells[0]);
assert_eq!(style.color, Some(Color::new(255, 0, 0)));
}
#[test]
fn test_cell_font_name_and_size() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value("Styled");
let font = cell.get_style_mut().get_font_mut();
font.set_name("Arial");
font.set_size(14.0);
});
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let style = first_run_style(&tp.table.rows[0].cells[0]);
assert_eq!(style.font_family.as_deref(), Some("Arial"));
assert_eq!(style.font_size, Some(14.0));
}
#[test]
fn test_cell_background_fill() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value("Yellow BG");
cell.get_style_mut().set_background_color("FFFFFF00");
});
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.background, Some(Color::new(255, 255, 0)));
}
#[test]
fn test_cell_borders() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value("Bordered");
let borders = cell.get_style_mut().get_borders_mut();
borders
.get_bottom_mut()
.set_border_style(umya_spreadsheet::Border::BORDER_MEDIUM);
borders
.get_bottom_mut()
.get_color_mut()
.set_argb("FF000000");
borders
.get_top_mut()
.set_border_style(umya_spreadsheet::Border::BORDER_THIN);
borders.get_top_mut().get_color_mut().set_argb("FFFF0000");
});
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];
let border = cell.border.as_ref().expect("Expected border");
let bottom = border.bottom.as_ref().expect("Expected bottom border");
assert!((bottom.width - 1.0).abs() < 0.01);
assert_eq!(bottom.color, Color::new(0, 0, 0));
let top = border.top.as_ref().expect("Expected top border");
assert!((top.width - 0.5).abs() < 0.01);
assert_eq!(top.color, Color::new(255, 0, 0));
}
#[test]
fn test_cell_border_styles() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value("Styled borders");
let borders = cell.get_style_mut().get_borders_mut();
borders
.get_top_mut()
.set_border_style(umya_spreadsheet::Border::BORDER_DASHED);
borders.get_top_mut().get_color_mut().set_argb("FF000000");
borders
.get_bottom_mut()
.set_border_style(umya_spreadsheet::Border::BORDER_DOTTED);
borders
.get_bottom_mut()
.get_color_mut()
.set_argb("FF000000");
borders
.get_left_mut()
.set_border_style(umya_spreadsheet::Border::BORDER_DASHDOT);
borders.get_left_mut().get_color_mut().set_argb("FF000000");
borders
.get_right_mut()
.set_border_style(umya_spreadsheet::Border::BORDER_DOUBLE);
borders.get_right_mut().get_color_mut().set_argb("FF000000");
});
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];
let border = cell.border.as_ref().expect("Expected border");
let top = border.top.as_ref().expect("Expected top border");
assert_eq!(top.style, BorderLineStyle::Dashed, "Top should be dashed");
let bottom = border.bottom.as_ref().expect("Expected bottom border");
assert_eq!(
bottom.style,
BorderLineStyle::Dotted,
"Bottom should be dotted"
);
let left = border.left.as_ref().expect("Expected left border");
assert_eq!(
left.style,
BorderLineStyle::DashDot,
"Left should be dashDot"
);
let right = border.right.as_ref().expect("Expected right border");
assert_eq!(
right.style,
BorderLineStyle::Double,
"Right should be double"
);
}
#[test]
fn test_cell_border_medium_dashed() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value("MedDash");
let borders = cell.get_style_mut().get_borders_mut();
borders
.get_top_mut()
.set_border_style(umya_spreadsheet::Border::BORDER_MEDIUMDASHED);
borders.get_top_mut().get_color_mut().set_argb("FF000000");
});
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];
let border = cell.border.as_ref().expect("Expected border");
let top = border.top.as_ref().expect("Expected top border");
assert_eq!(top.style, BorderLineStyle::Dashed);
assert!((top.width - 1.0).abs() < 0.01);
}
#[test]
fn test_row_height() {
let data = build_xlsx_formatted(|sheet| {
sheet.get_cell_mut("A1").set_value("Tall row");
sheet.get_row_dimension_mut(&1).set_height(30.0);
});
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let row = &tp.table.rows[0];
assert_eq!(row.height, Some(30.0));
}
#[test]
fn test_cell_no_formatting_defaults() {
let data = build_xlsx_bytes("Sheet1", &[("A1", "Plain")]);
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];
let style = first_run_style(cell);
assert!(style.bold.is_none() || style.bold == Some(false));
assert!(style.italic.is_none() || style.italic == Some(false));
assert!(cell.border.is_none());
assert!(cell.background.is_none());
}
#[test]
fn test_number_format_currency() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value_number(1234.56f64);
cell.get_style_mut()
.get_number_format_mut()
.set_format_code(umya_spreadsheet::NumberingFormat::FORMAT_CURRENCY_USD_SIMPLE);
});
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let text = cell_text(&tp.table.rows[0].cells[0]);
assert!(
text.contains('$') && text.contains("1,234.56"),
"Expected currency format with $ and 1,234.56, got: {text}"
);
}
#[test]
fn test_number_format_percentage() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value_number(0.456f64);
cell.get_style_mut()
.get_number_format_mut()
.set_format_code(umya_spreadsheet::NumberingFormat::FORMAT_PERCENTAGE);
});
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let text = cell_text(&tp.table.rows[0].cells[0]);
assert!(
text.contains('%'),
"Expected percentage format with %, got: {text}"
);
}
#[test]
fn test_number_format_percentage_with_decimals() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value_number(0.5f64);
cell.get_style_mut()
.get_number_format_mut()
.set_format_code(umya_spreadsheet::NumberingFormat::FORMAT_PERCENTAGE_00);
});
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let text = cell_text(&tp.table.rows[0].cells[0]);
assert!(
text.contains('%') && text.contains("50.00"),
"Expected 50.00%, got: {text}"
);
}
#[test]
fn test_number_format_date() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value_number(45306f64);
cell.get_style_mut()
.get_number_format_mut()
.set_format_code(umya_spreadsheet::NumberingFormat::FORMAT_DATE_YYYYMMDD);
});
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let text = cell_text(&tp.table.rows[0].cells[0]);
assert!(
text.contains('-') && !text.contains("45306"),
"Expected date format yyyy-mm-dd, got: {text}"
);
}
#[test]
fn test_number_format_thousands_separator() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value_number(1234567f64);
cell.get_style_mut()
.get_number_format_mut()
.set_format_code("#,##0");
});
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let text = cell_text(&tp.table.rows[0].cells[0]);
assert_eq!(text, "1,234,567", "Expected thousands separator formatting");
}
#[test]
fn test_number_format_general_unchanged() {
let data = build_xlsx_formatted(|sheet| {
sheet.get_cell_mut("A1").set_value("42");
sheet.get_cell_mut("B1").set_value("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_number_format_builtin_id() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value_number(1234.5f64);
cell.get_style_mut()
.get_number_format_mut()
.set_number_format_id(4);
});
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let text = cell_text(&tp.table.rows[0].cells[0]);
assert!(
text.contains("1,234") && text.contains("50"),
"Expected #,##0.00 formatting via ID 4, got: {text}"
);
}
#[test]
fn test_number_format_custom_format_string() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value_number(std::f64::consts::PI);
cell.get_style_mut()
.get_number_format_mut()
.set_format_code("0.000");
});
let parser = XlsxParser;
let (doc, _warnings) = parser.parse(&data, &ConvertOptions::default()).unwrap();
let tp = get_sheet_page(&doc, 0);
let text = cell_text(&tp.table.rows[0].cells[0]);
assert_eq!(text, "3.142", "Expected 3 decimal places formatting");
}
#[test]
fn test_cell_combined_formatting() {
let data = build_xlsx_formatted(|sheet| {
let cell = sheet.get_cell_mut("A1");
cell.set_value("Full");
let style = cell.get_style_mut();
let font = style.get_font_mut();
font.set_bold(true);
font.set_size(16.0);
font.set_name("Helvetica");
font.get_color_mut().set_argb("FF0000FF");
style.set_background_color("FFFFCC00");
let borders = style.get_borders_mut();
borders
.get_left_mut()
.set_border_style(umya_spreadsheet::Border::BORDER_THICK);
borders.get_left_mut().get_color_mut().set_argb("FF00FF00");
});
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];
let style = first_run_style(cell);
assert_eq!(style.bold, Some(true));
assert_eq!(style.font_size, Some(16.0));
assert_eq!(style.font_family.as_deref(), Some("Helvetica"));
assert_eq!(style.color, Some(Color::new(0, 0, 255)));
assert_eq!(cell.background, Some(Color::new(255, 204, 0)));
let border = cell.border.as_ref().expect("Expected border");
let left = border.left.as_ref().expect("Expected left border");
assert!((left.width - 2.0).abs() < 0.01);
assert_eq!(left.color, Color::new(0, 255, 0));
}