diff --git a/crates/zx_document_core/src/epub.rs b/crates/zx_document_core/src/epub.rs
index 11bca9a..ec01ffb 100644
--- a/crates/zx_document_core/src/epub.rs
+++ b/crates/zx_document_core/src/epub.rs
@@ -655,4 +655,141 @@ mod tests {
assert_eq!(chapters[1].title, "Chapter 2");
assert_eq!(chapters[2].title, "Chapter 3");
}
+
+ // ── strip_html tests ──
+
+ #[test]
+ fn test_strip_html_removes_tags() {
+ assert_eq!(strip_html("
Hello World
"), "Hello World");
+ }
+
+ #[test]
+ fn test_strip_html_collapses_whitespace() {
+ assert_eq!(strip_html("Hello World
"), "Hello World");
+ assert_eq!(strip_html("\n Line 1\n Line 2\n
"), "Line 1 Line 2");
+ }
+
+ #[test]
+ fn test_strip_html_handles_nested_tags() {
+ assert_eq!(strip_html(""), "Bold text");
+ }
+
+ #[test]
+ fn test_strip_html_handles_self_closing_tags() {
+ assert_eq!(strip_html("Text
more
text"), "Textmoretext");
+ }
+
+ #[test]
+ fn test_strip_html_handles_attributes() {
+ assert_eq!(strip_html("Hello
"), "Hello");
+ }
+
+ #[test]
+ fn test_strip_html_empty_input() {
+ assert_eq!(strip_html(""), "");
+ }
+
+ #[test]
+ fn test_strip_html_plain_text_unchanged() {
+ assert_eq!(strip_html("Plain text, no HTML tags."), "Plain text, no HTML tags.");
+ }
+
+ // ── extract_epub_chapter_texts tests ──
+
+ fn epub_with_chapter_text(title: &str, chapters: &[(&str, &str)]) -> Vec {
+ let mut buf = std::io::Cursor::new(Vec::new());
+ {
+ let mut zip = zip::ZipWriter::new(&mut buf);
+ let opts = zip::write::SimpleFileOptions::default()
+ .compression_method(zip::CompressionMethod::Stored);
+
+ zip.start_file("mimetype", opts).unwrap();
+ zip.write_all(b"application/epub+zip").unwrap();
+
+ zip.start_file("META-INF/container.xml", opts).unwrap();
+ zip.write_all(
+ b"\n\n \n \n \n"
+ ).unwrap();
+
+ let mut opf = format!(
+ "\n\n\n {}\n\n\n - \n",
+ title
+ );
+ for (i, (href, _)) in chapters.iter().enumerate() {
+ opf.push_str(&format!("
- \n", i, href));
+ }
+ opf.push_str("
\n\n");
+ for i in 0..chapters.len() {
+ opf.push_str(&format!(" \n", i));
+ }
+ opf.push_str("\n");
+ zip.start_file("OEBPS/content.opf", opts).unwrap();
+ zip.write_all(opf.as_bytes()).unwrap();
+
+ let mut ncx = String::from("\n\n\n");
+ for (i, (href, _)) in chapters.iter().enumerate() {
+ ncx.push_str(&format!(
+ " \n Ch {}\n \n \n",
+ i, i + 1, i + 1, href
+ ));
+ }
+ ncx.push_str("\n");
+ zip.start_file("toc.ncx", opts).unwrap();
+ zip.write_all(ncx.as_bytes()).unwrap();
+
+ for (href, content) in chapters {
+ zip.start_file(*href, opts).unwrap();
+ zip.write_all(content.as_bytes()).unwrap();
+ }
+
+ zip.finish().unwrap();
+ }
+ buf.into_inner()
+ }
+
+ #[test]
+ fn test_extract_epub_chapter_texts_with_html() {
+ let data = epub_with_chapter_text("Test", &[
+ ("OEBPS/ch1.xhtml", "Chapter one content.
"),
+ ("OEBPS/ch2.xhtml", "Title
Chapter two.
"),
+ ]);
+ let path = write_temp(&data, "chapter_text.epub");
+ let texts = extract_epub_chapter_texts(&path).unwrap();
+ assert_eq!(texts.len(), 2);
+ assert_eq!(texts[0].text, "Chapter one content.");
+ assert_eq!(texts[1].text, "TitleChapter two.");
+ }
+
+ #[test]
+ fn test_extract_epub_chapter_texts_empty_chapter_filtered() {
+ let data = epub_with_chapter_text("Empty", &[
+ ("OEBPS/ch1.xhtml", ""),
+ ("OEBPS/ch2.xhtml", "Not empty
"),
+ ]);
+ let path = write_temp(&data, "empty_ch.epub");
+ let texts = extract_epub_chapter_texts(&path).unwrap();
+ assert_eq!(texts.len(), 1);
+ assert_eq!(texts[0].text, "Not empty");
+ }
+
+ #[test]
+ fn test_extract_epub_chapter_texts_all_empty() {
+ let data = epub_with_chapter_text("AllEmpty", &[
+ ("OEBPS/ch1.xhtml", ""),
+ ]);
+ let path = write_temp(&data, "all_empty.epub");
+ let texts = extract_epub_chapter_texts(&path).unwrap();
+ assert!(texts.is_empty());
+ }
+
+ #[test]
+ fn test_extract_epub_chapter_texts_plain_text_preserved() {
+ let data = epub_with_chapter_text("Plain", &[
+ ("OEBPS/ch1.xhtml", "No HTML tags, just plain text."),
+ ]);
+ let path = write_temp(&data, "plain.epub");
+ let texts = extract_epub_chapter_texts(&path).unwrap();
+ assert_eq!(texts.len(), 1);
+ assert_eq!(texts[0].text, "No HTML tags, just plain text.");
+ }
}