use ppt_rs::api::Presentation;
use ppt_rs::generator::image_effects::{
generate_blip_fill_xml, generate_effect_list_xml, generate_effect_xml,
};
use ppt_rs::generator::images::{Crop, Image, ImageEffect};
use ppt_rs::generator::memory_profile::estimate_slide_payload;
use ppt_rs::generator::package_xml::{
create_content_types_xml, create_content_types_xml_with_notes_and_charts,
create_presentation_rels_xml, create_presentation_xml,
first_slide_rel_id, slide_id_value, slide_rel_id,
};
use ppt_rs::generator::table::{
generate_cell_xml, header_cell, table_from_string_rows, CellAlign, CellVAlign, IMPORT_HEADER_BG,
};
use ppt_rs::generator::{
create_pptx_with_content, create_pptx_with_settings, generate_image_xml, PresentationSettings,
PresentationTheme, SlideContent, Table, TableCell, ThemeColorScheme,
};
use ppt_rs::prelude::themes;
use std::io::Cursor;
fn read_zip_entry(pptx: &[u8], path: &str) -> String {
let cursor = Cursor::new(pptx);
let mut archive = zip::ZipArchive::new(cursor).expect("valid pptx zip");
let mut file = archive.by_name(path).expect("zip entry");
let mut content = String::new();
std::io::Read::read_to_string(&mut file, &mut content).unwrap();
content
}
fn theme_xml_from_pptx(data: &[u8]) -> String {
read_zip_entry(data, "ppt/theme/theme1.xml")
}
fn slide_xml_from_pptx(data: &[u8], slide_num: usize) -> String {
read_zip_entry(data, &format!("ppt/slides/slide{slide_num}.xml"))
}
#[test]
fn test_all_theme_presets_embed_in_pptx() {
let presets: Vec<(&str, PresentationTheme, &str)> = vec![
("Corporate", PresentationTheme::corporate(), "1565C0"),
("Modern", PresentationTheme::modern(), "212121"),
("Vibrant", PresentationTheme::vibrant(), "E91E63"),
("Dark", PresentationTheme::dark(), "BB86FC"),
("Nature", PresentationTheme::nature(), "2E7D32"),
("Tech", PresentationTheme::tech(), "0D47A1"),
("Carbon", PresentationTheme::carbon(), "0043CE"),
];
for (name, theme, accent) in presets {
let slides = vec![SlideContent::new("Themed")];
let settings = PresentationSettings::new().theme(theme);
let pptx = create_pptx_with_settings("Preset Test", &slides, Some(settings)).unwrap();
let xml = theme_xml_from_pptx(&pptx);
assert!(xml.contains(accent), "{name} theme should contain accent {accent}");
assert!(xml.contains(&format!(r#"name="{name}""#)), "{name} theme name missing");
}
}
#[test]
fn test_default_office_theme_without_settings() {
let slides = vec![SlideContent::new("Default")];
let pptx = create_pptx_with_content("Default Theme", slides).unwrap();
let xml = theme_xml_from_pptx(&pptx);
assert!(xml.contains(r#"name="Office Theme""#));
assert!(xml.contains("4F81BD"), "office default accent1");
}
#[test]
fn test_custom_theme_color_scheme() {
let colors = ThemeColorScheme::from_palette(
"AA0000", "BB0000", "CC0000", "FFFFFF", "111111", "EEEEEE", "000000",
)
.accent1("DD1122")
.hyperlink("00AAFF");
let theme = PresentationTheme::new("Brand").colors(colors);
let slides = vec![SlideContent::new("Brand")];
let pptx = create_pptx_with_settings(
"Brand",
&slides,
Some(PresentationSettings::new().theme(theme)),
)
.unwrap();
let xml = theme_xml_from_pptx(&pptx);
assert!(xml.contains("DD1122"));
assert!(xml.contains("00AAFF"));
assert!(xml.contains(r#"name="Brand""#));
}
#[test]
fn test_prelude_themes_bridge_to_presentation_theme() {
for theme in themes::all() {
let embedded = theme.to_presentation_theme();
assert_eq!(embedded.name, theme.name);
assert!(embedded.to_theme_xml().contains(theme.primary));
}
}
#[test]
fn test_presentation_into_bytes_with_theme() {
let pres = Presentation::with_title("Into Bytes")
.add_slide(SlideContent::new("Slide"))
.with_theme(PresentationTheme::tech());
let via_build = pres.build().unwrap();
let pres2 = Presentation::with_title("Into Bytes")
.add_slide(SlideContent::new("Slide"))
.with_theme(PresentationTheme::tech());
let via_into = pres2.into_bytes().unwrap();
assert_eq!(via_build.len(), via_into.len());
assert!(theme_xml_from_pptx(&via_into).contains("0D47A1"));
}
#[test]
fn test_cell_horizontal_alignment_in_xml() {
let cell = TableCell::new("Left").align(CellAlign::Left);
let xml = generate_cell_xml(&cell);
assert!(xml.contains(r#"algn="l""#));
}
#[test]
fn test_cell_vertical_alignment_in_xml() {
let cell = TableCell::new("Top").valign(CellVAlign::Top);
let xml = generate_cell_xml(&cell);
assert!(xml.contains(r#"anchor="t""#));
}
#[test]
fn test_cell_wrap_disabled_in_xml() {
let cell = TableCell::new("No wrap").wrap(false);
let xml = generate_cell_xml(&cell);
assert!(xml.contains("<a:bodyPr/>"));
assert!(!xml.contains(r#"wrap=""#));
}
#[test]
fn test_cell_merge_attributes_in_xml() {
let anchor = TableCell::new("Merged").grid_span(2);
let covered = TableCell::new("").h_merge();
let anchor_xml = generate_cell_xml(&anchor);
let covered_xml = generate_cell_xml(&covered);
assert!(anchor_xml.contains(r#"gridSpan="2""#));
assert!(covered_xml.contains(r#"hMerge="1""#));
assert!(covered_xml.contains("<a:endParaRPr"));
}
#[test]
fn test_header_cell_preset_colors() {
let cell = header_cell("Column");
let xml = generate_cell_xml(&cell);
assert!(xml.contains(IMPORT_HEADER_BG));
assert!(xml.contains("FFFFFF"));
assert!(xml.contains(r#"b="1""#));
}
#[test]
fn test_table_from_string_rows_embedded_in_pptx() {
let table = table_from_string_rows(
vec![
vec!["Name".into(), "Score".into()],
vec!["Alice".into(), "95".into()],
],
true,
);
let slide = SlideContent::new("Data").table(table);
let pptx = create_pptx_with_content("Table Deck", vec![slide]).unwrap();
let slide_xml = slide_xml_from_pptx(&pptx, 1);
assert!(slide_xml.contains(IMPORT_HEADER_BG));
assert!(slide_xml.contains("<a:t>Name</a:t>"));
assert!(slide_xml.contains("<a:t>Alice</a:t>"));
}
#[test]
fn test_table_position_defaults_from_style_module() {
let table: Table = table_from_string_rows(vec![vec!["A".into()]], false);
assert_eq!(table.x, 500_000);
assert_eq!(table.y, 1_800_000);
}
#[test]
fn test_all_image_effect_variants_generate_xml() {
let effects = [
(ImageEffect::Shadow, "outerShdw"),
(ImageEffect::Reflection, "reflection"),
(ImageEffect::Glow, "glow"),
(ImageEffect::SoftEdges, "softEdge"),
(ImageEffect::InnerShadow, "innerShdw"),
(ImageEffect::Blur, "blur"),
];
for (effect, token) in effects {
let xml = generate_effect_xml(&effect);
assert!(xml.contains(token), "{token} missing for effect variant");
}
}
#[test]
fn test_effect_list_module_matches_integrated_image_xml() {
let effects = vec![
ImageEffect::Glow,
ImageEffect::SoftEdges,
ImageEffect::Blur,
];
let list_xml = generate_effect_list_xml(&effects);
let img = Image::new("fx.png", 1000, 1000, "PNG")
.with_effect(ImageEffect::Glow)
.with_effect(ImageEffect::SoftEdges)
.with_effect(ImageEffect::Blur);
let integrated = generate_image_xml(&img, 1, 1);
for token in ["glow", "softEdge", "blur"] {
assert!(list_xml.contains(token));
assert!(integrated.contains(token));
}
}
#[test]
fn test_blip_fill_crop_module_matches_integrated_xml() {
let crop = Crop {
left: 0.05,
top: 0.10,
right: 0.15,
bottom: 0.20,
};
let module_xml = generate_blip_fill_xml("rId3", Some(&crop));
let img = Image::new("crop.png", 1000, 1000, "PNG").with_crop(0.05, 0.10, 0.15, 0.20);
let integrated = generate_image_xml(&img, 1, 3);
assert!(module_xml.contains(r#"l="5000""#));
assert!(integrated.contains(r#"l="5000""#));
}
#[test]
fn test_content_types_xml_slide_overrides() {
let xml = create_content_types_xml(3);
assert!(xml.contains("/ppt/slides/slide1.xml"));
assert!(xml.contains("/ppt/slides/slide2.xml"));
assert!(xml.contains("/ppt/slides/slide3.xml"));
assert!(!xml.contains("/ppt/slides/slide4.xml"));
}
#[test]
fn test_presentation_rels_xml_slide_relationships() {
let xml = create_presentation_rels_xml(2);
assert!(xml.contains(r#"Target="slides/slide1.xml""#));
assert!(xml.contains(r#"Target="slides/slide2.xml""#));
assert!(xml.contains(r#"Id="rId6""#));
assert!(xml.contains(r#"Id="rId7""#));
assert!(xml.contains(r#"Id="rId2""#));
assert!(xml.contains("presProps"));
assert!(xml.contains("tableStyles"));
}
#[test]
fn test_presentation_xml_slide_id_list() {
let xml = create_presentation_xml("Title", 2, false, false);
assert!(xml.contains(r#"id="256""#));
assert!(xml.contains(r#"id="257""#));
assert!(xml.contains(r#"r:id="rId2""#));
assert!(xml.contains(r#"r:id="rId3""#));
}
#[test]
fn test_content_types_with_notes_and_charts() {
let mut slide_with_notes = SlideContent::new("Notes");
slide_with_notes.notes = Some("Speaker note".into());
let slides = vec![SlideContent::new("Plain"), slide_with_notes];
let xml = create_content_types_xml_with_notes_and_charts(2, Some(&slides), 1, false, &[]);
assert!(xml.contains("/ppt/notesSlides/notesSlide1.xml"));
assert!(!xml.contains("/ppt/notesSlides/notesSlide2.xml"));
assert!(xml.contains("/ppt/charts/chart1.xml"));
assert!(xml.contains("Microsoft_Excel_Sheet1.xlsx"));
assert!(xml.contains(r#"Extension="xlsx""#));
assert!(xml.contains("notesMaster1.xml"));
assert!(xml.contains("/ppt/presProps.xml"));
assert!(xml.contains("/ppt/viewProps.xml"));
assert!(xml.contains("/ppt/tableStyles.xml"));
}
#[test]
fn test_slide_rel_id_helpers() {
assert_eq!(first_slide_rel_id(false, false), 2);
assert_eq!(slide_rel_id(1, false, false), 2);
assert_eq!(slide_id_value(1), 256);
}
#[test]
fn test_build_and_into_bytes_produce_same_pptx() {
let pres = Presentation::with_title("Compare APIs")
.add_slide(SlideContent::new("One").add_bullet("A"))
.add_slide(SlideContent::new("Two").add_bullet("B"));
let from_build = pres.build().unwrap();
let pres2 = Presentation::with_title("Compare APIs")
.add_slide(SlideContent::new("One").add_bullet("A"))
.add_slide(SlideContent::new("Two").add_bullet("B"));
let from_into = pres2.into_bytes().unwrap();
assert_eq!(from_build, from_into);
}
#[test]
fn test_estimate_slide_payload_counts_text() {
let slides = vec![
SlideContent::new("Hello").add_bullet("World"),
SlideContent::new("Second"),
];
let bytes = estimate_slide_payload(&slides);
assert!(bytes >= "Hello".len() + "World".len() + "Second".len());
}
#[test]
fn test_borrow_based_settings_api() {
let slides = vec![SlideContent::new("Borrowed").add_bullet("Point")];
let settings = PresentationSettings::new().theme(PresentationTheme::nature());
let pptx = create_pptx_with_settings("Borrow Test", &slides, Some(settings)).unwrap();
assert!(theme_xml_from_pptx(&pptx).contains("2E7D32"));
read_zip_entry(&pptx, "[Content_Types].xml");
read_zip_entry(&pptx, "ppt/slides/slide1.xml");
}