use pptx_rs::presentation::CustomPropertyValue;
use pptx_rs::{Inches, Presentation};
use std::io::Read;
fn read_zip_part(bytes: &[u8], part_path: &str) -> Option<String> {
let cursor = std::io::Cursor::new(bytes.to_vec());
let mut zip = zip::ZipArchive::new(cursor).ok()?;
for i in 0..zip.len() {
let mut file = zip.by_index(i).ok()?;
if file.name() == part_path {
let mut buf = String::new();
file.read_to_string(&mut buf).ok()?;
return Some(buf);
}
}
None
}
#[allow(dead_code)]
fn list_zip_parts(bytes: &[u8]) -> Vec<String> {
let cursor = std::io::Cursor::new(bytes.to_vec());
let mut zip = match zip::ZipArchive::new(cursor) {
Ok(z) => z,
Err(_) => return vec![],
};
let mut parts = Vec::new();
for i in 0..zip.len() {
if let Ok(file) = zip.by_index(i) {
parts.push(file.name().to_string());
}
}
parts
}
#[test]
fn slide_notes_text_round_trip() {
let mut prs = Presentation::new().expect("Presentation::new failed");
let counter = prs.id_counter();
let slide = prs
.slides_mut()
.add_slide(counter)
.expect("add_slide failed");
slide.set_notes_text(Some("这是备注内容\n第二行备注"));
let bytes = prs.to_bytes().expect("to_bytes failed");
let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
assert_eq!(prs2.slides().len(), 1);
let notes_xml =
read_zip_part(&bytes, "ppt/notesSlides/notesSlide1.xml").expect("notesSlide1.xml exists");
assert!(
notes_xml.contains("这是备注内容"),
"notes XML should contain the notes text, got: {}",
notes_xml
);
}
#[test]
fn slide_notes_clear_round_trip() {
let mut prs = Presentation::new().expect("Presentation::new failed");
let counter = prs.id_counter();
let slide = prs
.slides_mut()
.add_slide(counter)
.expect("add_slide failed");
slide.set_notes_text(Some("临时备注"));
slide.set_notes_text(None);
let bytes = prs.to_bytes().expect("to_bytes failed");
let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
assert_eq!(prs2.slides().len(), 1);
assert!(
read_zip_part(&bytes, "ppt/notesSlides/notesSlide1.xml").is_none(),
"notes should be cleared"
);
}
#[test]
fn add_single_comment_round_trip() {
let mut prs = Presentation::new().expect("Presentation::new failed");
let author_id = prs.comment_authors_mut().get_or_insert_id("张三", "ZS");
let counter = prs.id_counter();
let slide = prs
.slides_mut()
.add_slide(counter)
.expect("add_slide failed");
let idx = slide.add_comment(author_id, Inches(1.0), Inches(1.0), "评论内容");
assert_eq!(idx, 1, "first comment idx should be 1");
let bytes = prs.to_bytes().expect("to_bytes failed");
let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
assert_eq!(prs2.slides().len(), 1);
assert_eq!(prs2.comment_authors().authors.len(), 1, "author preserved");
let authors_xml =
read_zip_part(&bytes, "ppt/commentAuthors.xml").expect("commentAuthors.xml exists");
assert!(
authors_xml.contains("张三"),
"commentAuthors should contain author name"
);
}
#[test]
fn add_multiple_comments_round_trip() {
let mut prs = Presentation::new().expect("Presentation::new failed");
let author_id = prs.comment_authors_mut().get_or_insert_id("李四", "LS");
let counter = prs.id_counter();
let slide = prs
.slides_mut()
.add_slide(counter)
.expect("add_slide failed");
let idx1 = slide.add_comment(author_id, Inches(1.0), Inches(1.0), "第一条");
let idx2 = slide.add_comment(author_id, Inches(2.0), Inches(2.0), "第二条");
assert_eq!(idx1, 1);
assert_eq!(idx2, 2);
let bytes = prs.to_bytes().expect("to_bytes failed");
let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
assert_eq!(prs2.slides().len(), 1);
}
#[test]
fn clear_comments_round_trip() {
let mut prs = Presentation::new().expect("Presentation::new failed");
let author_id = prs.comment_authors_mut().get_or_insert_id("王五", "WW");
let counter = prs.id_counter();
let slide = prs
.slides_mut()
.add_slide(counter)
.expect("add_slide failed");
slide.add_comment(author_id, Inches(1.0), Inches(1.0), "将被清除");
slide.clear_comments();
let bytes = prs.to_bytes().expect("to_bytes failed");
let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
assert_eq!(prs2.slides().len(), 1);
assert!(
read_zip_part(&bytes, "ppt/comments/comment1.xml").is_none(),
"comments should be cleared"
);
}
#[test]
fn custom_properties_all_value_types_round_trip() {
let mut prs = Presentation::new().expect("Presentation::new failed");
prs.custom_properties_mut()
.set("Project", CustomPropertyValue::Text("Demo".to_string()));
prs.custom_properties_mut()
.set("Version", CustomPropertyValue::Int(42));
prs.custom_properties_mut()
.set("Score", CustomPropertyValue::Float(3.15));
prs.custom_properties_mut()
.set("Approved", CustomPropertyValue::Bool(true));
prs.custom_properties_mut().set(
"CreatedAt",
CustomPropertyValue::DateTime("2026-06-28T00:00:00Z".to_string()),
);
let bytes = prs.to_bytes().expect("to_bytes failed");
let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
let props = prs2.custom_properties();
assert_eq!(
props.get("Project"),
Some(&CustomPropertyValue::Text("Demo".to_string())),
"Text property should round-trip"
);
assert_eq!(
props.get("Version"),
Some(&CustomPropertyValue::Int(42)),
"Int property should round-trip"
);
assert_eq!(
props.get("Approved"),
Some(&CustomPropertyValue::Bool(true)),
"Bool property should round-trip"
);
}
#[test]
fn custom_properties_remove_round_trip() {
let mut prs = Presentation::new().expect("Presentation::new failed");
prs.custom_properties_mut()
.set("Temp", CustomPropertyValue::Int(1));
let removed = prs.custom_properties_mut().remove("Temp");
assert_eq!(removed, Some(CustomPropertyValue::Int(1)));
let bytes = prs.to_bytes().expect("to_bytes failed");
let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
assert!(
prs2.custom_properties().get("Temp").is_none(),
"removed property should not exist"
);
}
#[test]
fn core_properties_round_trip() {
let mut prs = Presentation::new().expect("Presentation::new failed");
{
prs.set_metadata(
Some("测试标题"),
Some("测试作者"),
Some("测试主题"),
Some("k1,k2"),
);
let core = prs.core_properties_mut();
core.last_modified_by = Some("LM".to_string());
core.application = Some("pptx-rs-test".to_string());
core.category = Some("测试分类".to_string());
core.description = Some("描述内容".to_string());
core.revision = Some("3".to_string());
core.created = Some("2026-07-01T00:00:00Z".to_string());
core.modified = Some("2026-07-01T01:00:00Z".to_string());
}
let bytes = prs.to_bytes().expect("to_bytes failed");
let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
let core_xml = read_zip_part(&bytes, "docProps/core.xml").expect("core.xml exists");
assert!(
core_xml.contains("coreProperties"),
"core.xml should be serialized, got: {}",
core_xml
);
let app_xml = read_zip_part(&bytes, "docProps/app.xml").expect("app.xml exists");
assert!(
app_xml.contains("<Application>pptx-rs-test</Application>"),
"app.xml should contain Application, got: {}",
app_xml
);
let cp = prs2.core_properties();
assert_eq!(cp.title.as_deref(), Some("测试标题"));
assert_eq!(cp.creator.as_deref(), Some("测试作者"));
assert_eq!(cp.subject.as_deref(), Some("测试主题"));
assert_eq!(cp.keywords.as_deref(), Some("k1,k2"));
assert_eq!(cp.last_modified_by.as_deref(), Some("LM"));
assert_eq!(cp.application.as_deref(), Some("pptx-rs-test"));
assert_eq!(cp.category.as_deref(), Some("测试分类"));
assert_eq!(cp.description.as_deref(), Some("描述内容"));
assert_eq!(cp.revision.as_deref(), Some("3"));
assert_eq!(cp.created.as_deref(), Some("2026-07-01T00:00:00Z"));
assert_eq!(cp.modified.as_deref(), Some("2026-07-01T01:00:00Z"));
}
#[test]
fn multiple_slides_notes_and_comments_round_trip() {
let mut prs = Presentation::new().expect("Presentation::new failed");
let author_id = prs.comment_authors_mut().get_or_insert_id("赵六", "ZL");
let counter = prs.id_counter();
let slide1 = prs
.slides_mut()
.add_slide(counter)
.expect("add_slide 1 failed");
slide1.set_notes_text(Some("第 1 张备注"));
let counter = prs.id_counter();
let slide2 = prs
.slides_mut()
.add_slide(counter)
.expect("add_slide 2 failed");
slide2.add_comment(author_id, Inches(1.0), Inches(1.0), "第 2 张批注");
assert_eq!(prs.slides().len(), 2);
let bytes = prs.to_bytes().expect("to_bytes failed");
let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
assert_eq!(prs2.slides().len(), 2, "slide count preserved");
assert!(
read_zip_part(&bytes, "ppt/notesSlides/notesSlide1.xml").is_some(),
"slide 1 notes preserved"
);
assert!(
read_zip_part(&bytes, "ppt/comments/comment1.xml").is_some(),
"slide 2 comments preserved"
);
}