use std::io::Cursor;
use boko::Book;
use boko::export::{EpubConfig, EpubExporter, Exporter, GlobalStylePool, normalize_book};
use boko::ir::{ComputedStyle, FontWeight, IRChapter, StyleId};
#[test]
fn test_global_style_pool_merge_deduplicates() {
let mut global = GlobalStylePool::new();
let mut chapter1 = IRChapter::new();
let bold = ComputedStyle {
font_weight: FontWeight::BOLD,
..Default::default()
};
let bold_id1 = chapter1.styles.intern(bold.clone());
let mut chapter2 = IRChapter::new();
let bold_id2 = chapter2.styles.intern(bold);
global.merge(0, &chapter1);
global.merge(1, &chapter2);
let global_id1 = global.remap(0, bold_id1);
let global_id2 = global.remap(1, bold_id2);
assert_eq!(global_id1, global_id2);
assert_eq!(global.pool().len(), 2);
}
#[test]
fn test_global_style_pool_different_styles_get_different_ids() {
let mut global = GlobalStylePool::new();
let mut chapter1 = IRChapter::new();
let bold = ComputedStyle {
font_weight: FontWeight::BOLD,
..Default::default()
};
let bold_id = chapter1.styles.intern(bold);
let mut chapter2 = IRChapter::new();
let italic = ComputedStyle {
font_style: boko::ir::FontStyle::Italic,
..Default::default()
};
let italic_id = chapter2.styles.intern(italic);
global.merge(0, &chapter1);
global.merge(1, &chapter2);
let global_bold = global.remap(0, bold_id);
let global_italic = global.remap(1, italic_id);
assert_ne!(global_bold, global_italic);
assert_eq!(global.pool().len(), 3);
}
#[test]
fn test_global_style_pool_remap_unknown_returns_default() {
let global = GlobalStylePool::new();
let result = global.remap(999, StyleId(999));
assert_eq!(result, StyleId::DEFAULT);
}
#[test]
fn test_global_style_pool_used_styles() {
let mut global = GlobalStylePool::new();
let mut chapter = IRChapter::new();
let bold = ComputedStyle {
font_weight: FontWeight::BOLD,
..Default::default()
};
chapter.styles.intern(bold);
global.merge(0, &chapter);
let used = global.used_styles();
assert!(!used.is_empty());
assert!(used.len() >= 2);
}
#[test]
fn test_normalized_epub_export_produces_valid_output() {
let mut book = Book::open("tests/fixtures/epictetus.epub").expect("Failed to open test book");
let mut output = Cursor::new(Vec::new());
let exporter = EpubExporter::new().with_config(EpubConfig {
normalize: true,
..Default::default()
});
exporter
.export(&mut book, &mut output)
.expect("Normalized export failed");
let data = output.into_inner();
assert!(!data.is_empty(), "Exported EPUB should not be empty");
assert!(
data.starts_with(b"PK"),
"Exported EPUB should be a valid ZIP file"
);
}
#[test]
fn test_normalize_book_produces_content() {
let mut book = Book::open("tests/fixtures/epictetus.epub").expect("Failed to open test book");
let content = normalize_book(&mut book).expect("normalize_book failed");
assert!(
!content.chapters.is_empty(),
"Should have normalized chapters"
);
for chapter in &content.chapters {
assert!(
!chapter.document.is_empty(),
"Chapter document should not be empty"
);
assert!(
chapter.document.contains("<!DOCTYPE"),
"Chapter should be valid XHTML"
);
assert!(
chapter.document.contains("<html"),
"Chapter should contain html element"
);
}
}
#[test]
fn test_normalized_export_includes_stylesheet_reference() {
let mut book = Book::open("tests/fixtures/epictetus.epub").expect("Failed to open test book");
let content = normalize_book(&mut book).expect("normalize_book failed");
for chapter in &content.chapters {
assert!(
chapter.document.contains("style.css"),
"Chapter should reference style.css"
);
}
}
#[test]
fn test_normalized_epub_contains_style_css() {
let mut book = Book::open("tests/fixtures/epictetus.epub").expect("Failed to open test book");
let mut output = Cursor::new(Vec::new());
let exporter = EpubExporter::new().with_config(EpubConfig {
normalize: true,
..Default::default()
});
exporter
.export(&mut book, &mut output)
.expect("Normalized export failed");
let data = output.into_inner();
let reader = Cursor::new(data);
let mut archive = zip::ZipArchive::new(reader).expect("Failed to read ZIP");
let mut found_style = false;
for i in 0..archive.len() {
let file = archive.by_index(i).expect("Failed to read ZIP entry");
if file.name().ends_with("style.css") {
found_style = true;
break;
}
}
assert!(found_style, "Normalized EPUB should contain style.css");
}
#[test]
fn test_normalized_export_has_numbered_chapters() {
let mut book = Book::open("tests/fixtures/epictetus.epub").expect("Failed to open test book");
let mut norm_output = Cursor::new(Vec::new());
let norm_exporter = EpubExporter::new().with_config(EpubConfig {
normalize: true,
..Default::default()
});
norm_exporter
.export(&mut book, &mut norm_output)
.expect("Normalized export failed");
let norm_data = norm_output.into_inner();
assert!(!norm_data.is_empty());
let norm_reader = Cursor::new(norm_data);
let mut norm_archive = zip::ZipArchive::new(norm_reader).expect("Failed to read norm ZIP");
let mut has_chapter_0 = false;
for i in 0..norm_archive.len() {
let file = norm_archive.by_index(i).expect("Failed to read ZIP entry");
if file.name().contains("chapter_0.xhtml") {
has_chapter_0 = true;
break;
}
}
assert!(
has_chapter_0,
"Normalized EPUB should have numbered chapter files"
);
}
#[test]
fn test_azw3_to_normalized_epub() {
let mut book = Book::open("tests/fixtures/epictetus.azw3").expect("Failed to open AZW3 book");
let mut output = Cursor::new(Vec::new());
let exporter = EpubExporter::new().with_config(EpubConfig {
normalize: true,
..Default::default()
});
exporter
.export(&mut book, &mut output)
.expect("AZW3 to normalized EPUB export failed");
let data = output.into_inner();
assert!(!data.is_empty(), "Exported EPUB should not be empty");
assert!(
data.starts_with(b"PK"),
"Exported EPUB should be a valid ZIP file"
);
}
#[test]
fn test_book_cache_works() {
let mut book = Book::open("tests/fixtures/epictetus.epub").expect("Failed to open test book");
let spine: Vec<_> = book.spine().to_vec();
assert!(!spine.is_empty(), "Book should have spine entries");
let chapter1 = book
.load_chapter_cached(spine[0].id)
.expect("First load failed");
let chapter2 = book
.load_chapter_cached(spine[0].id)
.expect("Second load failed");
assert_eq!(chapter1.node_count(), chapter2.node_count());
book.clear_cache();
}