1use std::{collections::HashMap, path::PathBuf};
18
19#[cfg(feature = "builder")]
20use crate::{
21 error::{EpubBuilderError, EpubError},
22 utils::ELEMENT_IN_DC_NAMESPACE,
23};
24
25#[derive(Debug, PartialEq, Eq)]
29pub enum EpubVersion {
30 Version2_0,
31 Version3_0,
32}
33
34#[derive(Debug, Clone)]
59pub struct MetadataItem {
60 pub id: Option<String>,
65
66 pub property: String,
72
73 pub value: String,
75
76 pub lang: Option<String>,
78
79 pub refined: Vec<MetadataRefinement>,
87}
88
89#[cfg(feature = "builder")]
90impl MetadataItem {
91 pub fn new(property: &str, value: &str) -> Self {
99 Self {
100 id: None,
101 property: property.to_string(),
102 value: value.to_string(),
103 lang: None,
104 refined: vec![],
105 }
106 }
107
108 pub fn with_id(&mut self, id: &str) -> &mut Self {
115 self.id = Some(id.to_string());
116 self
117 }
118
119 pub fn with_lang(&mut self, lang: &str) -> &mut Self {
126 self.lang = Some(lang.to_string());
127 self
128 }
129
130 pub fn append_refinement(&mut self, refine: MetadataRefinement) -> &mut Self {
140 if self.id.is_some() {
141 self.refined.push(refine);
142 } else {
143 }
145
146 self
147 }
148
149 pub fn build(&self) -> Self {
153 Self { ..self.clone() }
154 }
155
156 pub(crate) fn attributes(&self) -> Vec<(&str, &str)> {
158 let mut attributes = Vec::new();
159
160 if !ELEMENT_IN_DC_NAMESPACE.contains(&self.property.as_str()) {
161 attributes.push(("property", self.property.as_str()));
162 }
163
164 if let Some(id) = &self.id {
165 attributes.push(("id", id.as_str()));
166 };
167
168 if let Some(lang) = &self.lang {
169 attributes.push(("lang", lang.as_str()));
170 };
171
172 attributes
173 }
174}
175
176#[derive(Debug, Clone)]
200pub struct MetadataRefinement {
201 pub refines: String,
202
203 pub property: String,
208
209 pub value: String,
211
212 pub lang: Option<String>,
214
215 pub scheme: Option<String>,
220}
221
222#[cfg(feature = "builder")]
223impl MetadataRefinement {
224 pub fn new(refines: &str, property: &str, value: &str) -> Self {
233 Self {
234 refines: refines.to_string(),
235 property: property.to_string(),
236 value: value.to_string(),
237 lang: None,
238 scheme: None,
239 }
240 }
241
242 pub fn with_lang(&mut self, lang: &str) -> &mut Self {
249 self.lang = Some(lang.to_string());
250 self
251 }
252
253 pub fn with_scheme(&mut self, scheme: &str) -> &mut Self {
260 self.scheme = Some(scheme.to_string());
261 self
262 }
263
264 pub fn build(&self) -> Self {
268 Self { ..self.clone() }
269 }
270
271 pub(crate) fn attributes(&self) -> Vec<(&str, &str)> {
273 let mut attributes = Vec::new();
274
275 attributes.push(("refines", self.refines.as_str()));
276 attributes.push(("property", self.property.as_str()));
277
278 if let Some(lang) = &self.lang {
279 attributes.push(("lang", lang.as_str()));
280 };
281
282 if let Some(scheme) = &self.scheme {
283 attributes.push(("scheme", scheme.as_str()));
284 };
285
286 attributes
287 }
288}
289
290#[derive(Debug)]
299pub struct MetadataLinkItem {
300 pub href: String,
302
303 pub rel: String,
305
306 pub hreflang: Option<String>,
308
309 pub id: Option<String>,
313
314 pub mime: Option<String>,
316
317 pub properties: Option<String>,
322
323 pub refines: Option<String>,
328}
329
330#[derive(Debug, Default)]
336pub struct MetadataSheet {
337 pub contributor: Vec<String>,
339 pub creator: Vec<String>,
341 pub date: HashMap<String, String>,
343 pub identifier: HashMap<String, String>,
345 pub language: Vec<String>,
347 pub relation: Vec<String>,
349 pub subject: Vec<String>,
351 pub title: Vec<String>,
353
354 pub coverage: String,
356 pub description: String,
358 pub format: String,
360 pub publisher: String,
362 pub rights: String,
364 pub source: String,
366 pub epub_type: String,
368}
369
370impl MetadataSheet {
371 pub fn new() -> Self {
373 Self {
374 contributor: Vec::new(),
375 creator: Vec::new(),
376 date: HashMap::new(),
377 identifier: HashMap::new(),
378 language: Vec::new(),
379 relation: Vec::new(),
380 subject: Vec::new(),
381 title: Vec::new(),
382
383 coverage: String::new(),
384 description: String::new(),
385 format: String::new(),
386 publisher: String::new(),
387 rights: String::new(),
388 source: String::new(),
389 epub_type: String::new(),
390 }
391 }
392}
393
394#[cfg(feature = "builder")]
395impl MetadataSheet {
396 pub fn append_contributor(&mut self, contributor: impl Into<String>) -> &mut Self {
398 self.contributor.push(contributor.into());
399 self
400 }
401
402 pub fn append_creator(&mut self, creator: impl Into<String>) -> &mut Self {
404 self.creator.push(creator.into());
405 self
406 }
407
408 pub fn append_language(&mut self, language: impl Into<String>) -> &mut Self {
410 self.language.push(language.into());
411 self
412 }
413
414 pub fn append_relation(&mut self, relation: impl Into<String>) -> &mut Self {
416 self.relation.push(relation.into());
417 self
418 }
419
420 pub fn append_subject(&mut self, subject: impl Into<String>) -> &mut Self {
422 self.subject.push(subject.into());
423 self
424 }
425
426 pub fn append_title(&mut self, title: impl Into<String>) -> &mut Self {
428 self.title.push(title.into());
429 self
430 }
431
432 pub fn append_date(&mut self, date: impl Into<String>, event: impl Into<String>) -> &mut Self {
441 self.date.insert(date.into(), event.into());
442 self
443 }
444
445 pub fn append_identifier(
447 &mut self,
448 id: impl Into<String>,
449 value: impl Into<String>,
450 ) -> &mut Self {
451 self.identifier.insert(id.into(), value.into());
452 self
453 }
454
455 pub fn with_coverage(&mut self, coverage: impl Into<String>) -> &mut Self {
457 self.coverage = coverage.into();
458 self
459 }
460
461 pub fn with_description(&mut self, description: impl Into<String>) -> &mut Self {
463 self.description = description.into();
464 self
465 }
466
467 pub fn with_format(&mut self, format: impl Into<String>) -> &mut Self {
469 self.format = format.into();
470 self
471 }
472
473 pub fn with_publisher(&mut self, publisher: impl Into<String>) -> &mut Self {
475 self.publisher = publisher.into();
476 self
477 }
478
479 pub fn with_rights(&mut self, rights: impl Into<String>) -> &mut Self {
481 self.rights = rights.into();
482 self
483 }
484
485 pub fn with_source(&mut self, source: impl Into<String>) -> &mut Self {
487 self.source = source.into();
488 self
489 }
490
491 pub fn with_epub_type(&mut self, epub_type: impl Into<String>) -> &mut Self {
493 self.epub_type = epub_type.into();
494 self
495 }
496
497 pub fn build(&self) -> MetadataSheet {
499 MetadataSheet {
500 contributor: self.contributor.clone(),
501 creator: self.creator.clone(),
502 date: self.date.clone(),
503 identifier: self.identifier.clone(),
504 language: self.language.clone(),
505 relation: self.relation.clone(),
506 subject: self.subject.clone(),
507 title: self.title.clone(),
508 coverage: self.coverage.clone(),
509 description: self.description.clone(),
510 format: self.format.clone(),
511 publisher: self.publisher.clone(),
512 rights: self.rights.clone(),
513 source: self.source.clone(),
514 epub_type: self.epub_type.clone(),
515 }
516 }
517}
518
519#[cfg(feature = "builder")]
520impl From<MetadataSheet> for Vec<MetadataItem> {
521 fn from(sheet: MetadataSheet) -> Vec<MetadataItem> {
527 let mut items = Vec::new();
528
529 for title in &sheet.title {
532 items.push(MetadataItem::new("title", title));
533 }
534
535 for creator in &sheet.creator {
536 items.push(MetadataItem::new("creator", creator));
537 }
538
539 for contributor in &sheet.contributor {
540 items.push(MetadataItem::new("contributor", contributor));
541 }
542
543 for subject in &sheet.subject {
544 items.push(MetadataItem::new("subject", subject));
545 }
546
547 for language in &sheet.language {
548 items.push(MetadataItem::new("language", language));
549 }
550
551 for relation in &sheet.relation {
552 items.push(MetadataItem::new("relation", relation));
553 }
554
555 for (date, event) in &sheet.date {
559 let mut item = MetadataItem::new("date", date);
560 if !event.is_empty() {
561 let refinement_id = format!("date-{}", items.len());
562 item.id = Some(refinement_id.clone());
563 item.refined
564 .push(MetadataRefinement::new(&refinement_id, "event", event));
565 }
566 items.push(item);
567 }
568
569 for (id, value) in &sheet.identifier {
570 let mut item = MetadataItem::new("identifier", value);
571 if !id.is_empty() {
572 item.id = Some(id.clone());
573 }
574 items.push(item);
575 }
576
577 if !sheet.description.is_empty() {
580 items.push(MetadataItem::new("description", &sheet.description));
581 }
582
583 if !sheet.format.is_empty() {
584 items.push(MetadataItem::new("format", &sheet.format));
585 }
586
587 if !sheet.publisher.is_empty() {
588 items.push(MetadataItem::new("publisher", &sheet.publisher));
589 }
590
591 if !sheet.rights.is_empty() {
592 items.push(MetadataItem::new("rights", &sheet.rights));
593 }
594
595 if !sheet.source.is_empty() {
596 items.push(MetadataItem::new("source", &sheet.source));
597 }
598
599 if !sheet.coverage.is_empty() {
600 items.push(MetadataItem::new("coverage", &sheet.coverage));
601 }
602
603 if !sheet.epub_type.is_empty() {
604 items.push(MetadataItem::new("type", &sheet.epub_type));
605 }
606
607 items
608 }
609}
610
611#[derive(Debug, Clone)]
642pub struct ManifestItem {
643 pub id: String,
645
646 pub path: PathBuf,
652
653 pub mime: String,
655
656 pub properties: Option<String>,
662
663 pub fallback: Option<String>,
674}
675
676#[cfg(feature = "builder")]
677impl ManifestItem {
678 pub fn new(id: &str, path: &str) -> Result<Self, EpubError> {
689 if path.starts_with("../") {
690 return Err(
691 EpubBuilderError::IllegalManifestPath { manifest_id: id.to_string() }.into(),
692 );
693 }
694
695 Ok(Self {
696 id: id.to_string(),
697 path: PathBuf::from(path),
698 mime: String::new(),
699 properties: None,
700 fallback: None,
701 })
702 }
703
704 pub(crate) fn set_mime(self, mime: &str) -> Self {
706 Self {
707 id: self.id,
708 path: self.path,
709 mime: mime.to_string(),
710 properties: self.properties,
711 fallback: self.fallback,
712 }
713 }
714
715 pub fn append_property(&mut self, property: &str) -> &mut Self {
722 let new_properties = if let Some(properties) = &self.properties {
723 format!("{} {}", properties, property)
724 } else {
725 property.to_string()
726 };
727
728 self.properties = Some(new_properties);
729 self
730 }
731
732 pub fn with_fallback(&mut self, fallback: &str) -> &mut Self {
739 self.fallback = Some(fallback.to_string());
740 self
741 }
742
743 pub fn build(&self) -> Self {
747 Self { ..self.clone() }
748 }
749
750 pub fn attributes(&self) -> Vec<(&str, &str)> {
752 let mut attributes = Vec::new();
753
754 attributes.push(("id", self.id.as_str()));
755 attributes.push(("href", self.path.to_str().unwrap()));
756 attributes.push(("media-type", self.mime.as_str()));
757
758 if let Some(properties) = &self.properties {
759 attributes.push(("properties", properties.as_str()));
760 }
761
762 if let Some(fallback) = &self.fallback {
763 attributes.push(("fallback", fallback.as_str()));
764 }
765
766 attributes
767 }
768}
769
770#[derive(Debug, Clone)]
797pub struct SpineItem {
798 pub idref: String,
804
805 pub id: Option<String>,
807
808 pub properties: Option<String>,
814
815 pub linear: bool,
825}
826
827#[cfg(feature = "builder")]
828impl SpineItem {
829 pub fn new(idref: &str) -> Self {
838 Self {
839 idref: idref.to_string(),
840 id: None,
841 properties: None,
842 linear: true,
843 }
844 }
845
846 pub fn with_id(&mut self, id: &str) -> &mut Self {
853 self.id = Some(id.to_string());
854 self
855 }
856
857 pub fn append_property(&mut self, property: &str) -> &mut Self {
864 let new_properties = if let Some(properties) = &self.properties {
865 format!("{} {}", properties, property)
866 } else {
867 property.to_string()
868 };
869
870 self.properties = Some(new_properties);
871 self
872 }
873
874 pub fn set_linear(&mut self, linear: bool) -> &mut Self {
881 self.linear = linear;
882 self
883 }
884
885 pub fn build(&self) -> Self {
889 Self { ..self.clone() }
890 }
891
892 pub(crate) fn attributes(&self) -> Vec<(&str, &str)> {
894 let mut attributes = Vec::new();
895
896 attributes.push(("idref", self.idref.as_str()));
897 attributes.push(("linear", if self.linear { "yes" } else { "no" }));
898
899 if let Some(id) = &self.id {
900 attributes.push(("id", id.as_str()));
901 }
902
903 if let Some(properties) = &self.properties {
904 attributes.push(("properties", properties.as_str()));
905 }
906
907 attributes
908 }
909}
910
911#[derive(Debug, Clone)]
917pub struct EncryptionData {
918 pub method: String,
925
926 pub data: String,
931}
932
933#[derive(Debug, Eq, Clone)]
958pub struct NavPoint {
959 pub label: String,
963
964 pub content: Option<PathBuf>,
969
970 pub children: Vec<NavPoint>,
972
973 pub play_order: Option<usize>,
978}
979
980#[cfg(feature = "builder")]
981impl NavPoint {
982 pub fn new(label: &str) -> Self {
989 Self {
990 label: label.to_string(),
991 content: None,
992 children: vec![],
993 play_order: None,
994 }
995 }
996
997 pub fn with_content(&mut self, content: &str) -> &mut Self {
1004 self.content = Some(PathBuf::from(content));
1005 self
1006 }
1007
1008 pub fn append_child(&mut self, child: NavPoint) -> &mut Self {
1015 self.children.push(child);
1016 self
1017 }
1018
1019 pub fn set_children(&mut self, children: Vec<NavPoint>) -> &mut Self {
1026 self.children = children;
1027 self
1028 }
1029
1030 pub fn build(&self) -> Self {
1034 Self { ..self.clone() }
1035 }
1036}
1037
1038impl Ord for NavPoint {
1039 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1040 self.play_order.cmp(&other.play_order)
1041 }
1042}
1043
1044impl PartialOrd for NavPoint {
1045 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1046 Some(self.cmp(other))
1047 }
1048}
1049
1050impl PartialEq for NavPoint {
1051 fn eq(&self, other: &Self) -> bool {
1052 self.play_order == other.play_order
1053 }
1054}
1055
1056#[cfg(feature = "content-builder")]
1061#[derive(Debug, Clone, Eq, PartialEq)]
1062pub struct Footnote {
1063 pub locate: usize,
1065
1066 pub content: String,
1068}
1069
1070#[cfg(feature = "content-builder")]
1071impl Ord for Footnote {
1072 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1073 self.locate.cmp(&other.locate)
1074 }
1075}
1076
1077#[cfg(feature = "content-builder")]
1078impl PartialOrd for Footnote {
1079 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1080 Some(self.cmp(other))
1081 }
1082}
1083
1084#[cfg(feature = "content-builder")]
1086#[derive(Debug, Copy, Clone)]
1087pub enum BlockType {
1088 Text,
1092
1093 Quote,
1098
1099 Title,
1103
1104 Image,
1108
1109 Audio,
1113
1114 Video,
1118
1119 MathML,
1124}
1125
1126#[cfg(feature = "content-builder")]
1127impl std::fmt::Display for BlockType {
1128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1129 match self {
1130 BlockType::Text => write!(f, "Text"),
1131 BlockType::Quote => write!(f, "Quote"),
1132 BlockType::Title => write!(f, "Title"),
1133 BlockType::Image => write!(f, "Image"),
1134 BlockType::Audio => write!(f, "Audio"),
1135 BlockType::Video => write!(f, "Video"),
1136 BlockType::MathML => write!(f, "MathML"),
1137 }
1138 }
1139}
1140
1141#[cfg(feature = "content-builder")]
1146#[derive(Debug, Default, Clone)]
1147pub struct StyleOptions {
1148 pub text: TextStyle,
1150
1151 pub color_scheme: ColorScheme,
1155
1156 pub layout: PageLayout,
1160}
1161
1162#[cfg(feature = "content-builder")]
1163#[cfg(feature = "builder")]
1164impl StyleOptions {
1165 pub fn new() -> Self {
1167 Self::default()
1168 }
1169
1170 pub fn with_text(&mut self, text: TextStyle) -> &mut Self {
1172 self.text = text;
1173 self
1174 }
1175
1176 pub fn with_color_scheme(&mut self, color_scheme: ColorScheme) -> &mut Self {
1178 self.color_scheme = color_scheme;
1179 self
1180 }
1181
1182 pub fn with_layout(&mut self, layout: PageLayout) -> &mut Self {
1184 self.layout = layout;
1185 self
1186 }
1187
1188 pub fn build(&self) -> Self {
1190 Self { ..self.clone() }
1191 }
1192}
1193
1194#[cfg(feature = "content-builder")]
1199#[derive(Debug, Clone)]
1200pub struct TextStyle {
1201 pub font_size: f32,
1206
1207 pub line_height: f32,
1213
1214 pub font_family: String,
1219
1220 pub font_weight: String,
1225
1226 pub font_style: String,
1231
1232 pub letter_spacing: String,
1237
1238 pub text_indent: f32,
1243}
1244
1245#[cfg(feature = "content-builder")]
1246impl Default for TextStyle {
1247 fn default() -> Self {
1248 Self {
1249 font_size: 1.0,
1250 line_height: 1.6,
1251 font_family: "-apple-system, Roboto, sans-serif".to_string(),
1252 font_weight: "normal".to_string(),
1253 font_style: "normal".to_string(),
1254 letter_spacing: "normal".to_string(),
1255 text_indent: 2.0,
1256 }
1257 }
1258}
1259
1260#[cfg(feature = "content-builder")]
1261impl TextStyle {
1262 pub fn new() -> Self {
1264 Self::default()
1265 }
1266
1267 pub fn with_font_size(&mut self, font_size: f32) -> &mut Self {
1269 self.font_size = font_size;
1270 self
1271 }
1272
1273 pub fn with_line_height(&mut self, line_height: f32) -> &mut Self {
1275 self.line_height = line_height;
1276 self
1277 }
1278
1279 pub fn with_font_family(&mut self, font_family: &str) -> &mut Self {
1281 self.font_family = font_family.to_string();
1282 self
1283 }
1284
1285 pub fn with_font_weight(&mut self, font_weight: &str) -> &mut Self {
1287 self.font_weight = font_weight.to_string();
1288 self
1289 }
1290
1291 pub fn with_font_style(&mut self, font_style: &str) -> &mut Self {
1293 self.font_style = font_style.to_string();
1294 self
1295 }
1296
1297 pub fn with_letter_spacing(&mut self, letter_spacing: &str) -> &mut Self {
1299 self.letter_spacing = letter_spacing.to_string();
1300 self
1301 }
1302
1303 pub fn with_text_indent(&mut self, text_indent: f32) -> &mut Self {
1305 self.text_indent = text_indent;
1306 self
1307 }
1308
1309 pub fn build(&self) -> Self {
1311 Self { ..self.clone() }
1312 }
1313}
1314
1315#[cfg(feature = "content-builder")]
1320#[derive(Debug, Clone)]
1321pub struct ColorScheme {
1322 pub background: String,
1327
1328 pub text: String,
1333
1334 pub link: String,
1339}
1340
1341#[cfg(feature = "content-builder")]
1342impl Default for ColorScheme {
1343 fn default() -> Self {
1344 Self {
1345 background: "#FFFFFF".to_string(),
1346 text: "#000000".to_string(),
1347 link: "#6f6f6f".to_string(),
1348 }
1349 }
1350}
1351
1352#[cfg(feature = "content-builder")]
1353impl ColorScheme {
1354 pub fn new() -> Self {
1356 Self::default()
1357 }
1358
1359 pub fn with_background(&mut self, background: &str) -> &mut Self {
1361 self.background = background.to_string();
1362 self
1363 }
1364
1365 pub fn with_text(&mut self, text: &str) -> &mut Self {
1367 self.text = text.to_string();
1368 self
1369 }
1370
1371 pub fn with_link(&mut self, link: &str) -> &mut Self {
1373 self.link = link.to_string();
1374 self
1375 }
1376
1377 pub fn build(&self) -> Self {
1379 Self { ..self.clone() }
1380 }
1381}
1382
1383#[cfg(feature = "content-builder")]
1388#[derive(Debug, Clone)]
1389pub struct PageLayout {
1390 pub margin: usize,
1394
1395 pub text_align: TextAlign,
1399
1400 pub paragraph_spacing: usize,
1404}
1405
1406#[cfg(feature = "content-builder")]
1407impl Default for PageLayout {
1408 fn default() -> Self {
1409 Self {
1410 margin: 20,
1411 text_align: Default::default(),
1412 paragraph_spacing: 16,
1413 }
1414 }
1415}
1416
1417#[cfg(feature = "content-builder")]
1418impl PageLayout {
1419 pub fn new() -> Self {
1421 Self::default()
1422 }
1423
1424 pub fn with_margin(&mut self, margin: usize) -> &mut Self {
1426 self.margin = margin;
1427 self
1428 }
1429
1430 pub fn with_text_align(&mut self, text_align: TextAlign) -> &mut Self {
1432 self.text_align = text_align;
1433 self
1434 }
1435
1436 pub fn with_paragraph_spacing(&mut self, paragraph_spacing: usize) -> &mut Self {
1438 self.paragraph_spacing = paragraph_spacing;
1439 self
1440 }
1441
1442 pub fn build(&self) -> Self {
1444 Self { ..self.clone() }
1445 }
1446}
1447
1448#[cfg(feature = "content-builder")]
1452#[derive(Debug, Default, Clone, Copy, PartialEq)]
1453pub enum TextAlign {
1454 #[default]
1458 Left,
1459
1460 Right,
1464
1465 Justify,
1470
1471 Center,
1475}
1476
1477#[cfg(feature = "content-builder")]
1478impl std::fmt::Display for TextAlign {
1479 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1480 match self {
1481 TextAlign::Left => write!(f, "left"),
1482 TextAlign::Right => write!(f, "right"),
1483 TextAlign::Justify => write!(f, "justify"),
1484 TextAlign::Center => write!(f, "center"),
1485 }
1486 }
1487}
1488
1489#[cfg(test)]
1490mod tests {
1491 mod navpoint_tests {
1492 use std::path::PathBuf;
1493
1494 use crate::types::NavPoint;
1495
1496 #[test]
1498 fn test_navpoint_partial_eq() {
1499 let nav1 = NavPoint {
1500 label: "Chapter 1".to_string(),
1501 content: Some(PathBuf::from("chapter1.html")),
1502 children: vec![],
1503 play_order: Some(1),
1504 };
1505
1506 let nav2 = NavPoint {
1507 label: "Chapter 1".to_string(),
1508 content: Some(PathBuf::from("chapter2.html")),
1509 children: vec![],
1510 play_order: Some(1),
1511 };
1512
1513 let nav3 = NavPoint {
1514 label: "Chapter 2".to_string(),
1515 content: Some(PathBuf::from("chapter1.html")),
1516 children: vec![],
1517 play_order: Some(2),
1518 };
1519
1520 assert_eq!(nav1, nav2); assert_ne!(nav1, nav3); }
1523
1524 #[test]
1526 fn test_navpoint_ord() {
1527 let nav1 = NavPoint {
1528 label: "Chapter 1".to_string(),
1529 content: Some(PathBuf::from("chapter1.html")),
1530 children: vec![],
1531 play_order: Some(1),
1532 };
1533
1534 let nav2 = NavPoint {
1535 label: "Chapter 2".to_string(),
1536 content: Some(PathBuf::from("chapter2.html")),
1537 children: vec![],
1538 play_order: Some(2),
1539 };
1540
1541 let nav3 = NavPoint {
1542 label: "Chapter 3".to_string(),
1543 content: Some(PathBuf::from("chapter3.html")),
1544 children: vec![],
1545 play_order: Some(3),
1546 };
1547
1548 assert!(nav1 < nav2);
1550 assert!(nav2 > nav1);
1551 assert!(nav1 == nav1);
1552
1553 assert_eq!(nav1.partial_cmp(&nav2), Some(std::cmp::Ordering::Less));
1555 assert_eq!(nav2.partial_cmp(&nav1), Some(std::cmp::Ordering::Greater));
1556 assert_eq!(nav1.partial_cmp(&nav1), Some(std::cmp::Ordering::Equal));
1557
1558 let mut nav_points = vec![nav2.clone(), nav3.clone(), nav1.clone()];
1560 nav_points.sort();
1561 assert_eq!(nav_points, vec![nav1, nav2, nav3]);
1562 }
1563
1564 #[test]
1566 fn test_navpoint_ord_with_none_play_order() {
1567 let nav_with_order = NavPoint {
1568 label: "Chapter 1".to_string(),
1569 content: Some(PathBuf::from("chapter1.html")),
1570 children: vec![],
1571 play_order: Some(1),
1572 };
1573
1574 let nav_without_order = NavPoint {
1575 label: "Preface".to_string(),
1576 content: Some(PathBuf::from("preface.html")),
1577 children: vec![],
1578 play_order: None,
1579 };
1580
1581 assert!(nav_without_order < nav_with_order);
1582 assert!(nav_with_order > nav_without_order);
1583
1584 let nav_without_order2 = NavPoint {
1585 label: "Introduction".to_string(),
1586 content: Some(PathBuf::from("intro.html")),
1587 children: vec![],
1588 play_order: None,
1589 };
1590
1591 assert!(nav_without_order == nav_without_order2);
1592 }
1593
1594 #[test]
1596 fn test_navpoint_with_children() {
1597 let child1 = NavPoint {
1598 label: "Section 1.1".to_string(),
1599 content: Some(PathBuf::from("section1_1.html")),
1600 children: vec![],
1601 play_order: Some(1),
1602 };
1603
1604 let child2 = NavPoint {
1605 label: "Section 1.2".to_string(),
1606 content: Some(PathBuf::from("section1_2.html")),
1607 children: vec![],
1608 play_order: Some(2),
1609 };
1610
1611 let parent1 = NavPoint {
1612 label: "Chapter 1".to_string(),
1613 content: Some(PathBuf::from("chapter1.html")),
1614 children: vec![child1.clone(), child2.clone()],
1615 play_order: Some(1),
1616 };
1617
1618 let parent2 = NavPoint {
1619 label: "Chapter 1".to_string(),
1620 content: Some(PathBuf::from("chapter1.html")),
1621 children: vec![child1.clone(), child2.clone()],
1622 play_order: Some(1),
1623 };
1624
1625 assert!(parent1 == parent2);
1626
1627 let parent3 = NavPoint {
1628 label: "Chapter 2".to_string(),
1629 content: Some(PathBuf::from("chapter2.html")),
1630 children: vec![child1.clone(), child2.clone()],
1631 play_order: Some(2),
1632 };
1633
1634 assert!(parent1 != parent3);
1635 assert!(parent1 < parent3);
1636 }
1637
1638 #[test]
1640 fn test_navpoint_with_none_content() {
1641 let nav1 = NavPoint {
1642 label: "Chapter 1".to_string(),
1643 content: None,
1644 children: vec![],
1645 play_order: Some(1),
1646 };
1647
1648 let nav2 = NavPoint {
1649 label: "Chapter 1".to_string(),
1650 content: None,
1651 children: vec![],
1652 play_order: Some(1),
1653 };
1654
1655 assert!(nav1 == nav2);
1656 }
1657 }
1658
1659 #[cfg(feature = "builder")]
1660 mod builder_tests {
1661 mod metadata_item {
1662 use crate::types::{MetadataItem, MetadataRefinement};
1663
1664 #[test]
1665 fn test_metadata_item_new() {
1666 let metadata_item = MetadataItem::new("title", "EPUB Test Book");
1667
1668 assert_eq!(metadata_item.property, "title");
1669 assert_eq!(metadata_item.value, "EPUB Test Book");
1670 assert_eq!(metadata_item.id, None);
1671 assert_eq!(metadata_item.lang, None);
1672 assert_eq!(metadata_item.refined.len(), 0);
1673 }
1674
1675 #[test]
1676 fn test_metadata_item_with_id() {
1677 let mut metadata_item = MetadataItem::new("creator", "John Doe");
1678 metadata_item.with_id("creator-1");
1679
1680 assert_eq!(metadata_item.property, "creator");
1681 assert_eq!(metadata_item.value, "John Doe");
1682 assert_eq!(metadata_item.id, Some("creator-1".to_string()));
1683 assert_eq!(metadata_item.lang, None);
1684 assert_eq!(metadata_item.refined.len(), 0);
1685 }
1686
1687 #[test]
1688 fn test_metadata_item_with_lang() {
1689 let mut metadata_item = MetadataItem::new("title", "测试书籍");
1690 metadata_item.with_lang("zh-CN");
1691
1692 assert_eq!(metadata_item.property, "title");
1693 assert_eq!(metadata_item.value, "测试书籍");
1694 assert_eq!(metadata_item.id, None);
1695 assert_eq!(metadata_item.lang, Some("zh-CN".to_string()));
1696 assert_eq!(metadata_item.refined.len(), 0);
1697 }
1698
1699 #[test]
1700 fn test_metadata_item_append_refinement() {
1701 let mut metadata_item = MetadataItem::new("creator", "John Doe");
1702 metadata_item.with_id("creator-1"); let refinement = MetadataRefinement::new("creator-1", "role", "author");
1705 metadata_item.append_refinement(refinement);
1706
1707 assert_eq!(metadata_item.refined.len(), 1);
1708 assert_eq!(metadata_item.refined[0].refines, "creator-1");
1709 assert_eq!(metadata_item.refined[0].property, "role");
1710 assert_eq!(metadata_item.refined[0].value, "author");
1711 }
1712
1713 #[test]
1714 fn test_metadata_item_append_refinement_without_id() {
1715 let mut metadata_item = MetadataItem::new("title", "Test Book");
1716 let refinement = MetadataRefinement::new("title", "title-type", "main");
1719 metadata_item.append_refinement(refinement);
1720
1721 assert_eq!(metadata_item.refined.len(), 0);
1723 }
1724
1725 #[test]
1726 fn test_metadata_item_build() {
1727 let mut metadata_item = MetadataItem::new("identifier", "urn:isbn:1234567890");
1728 metadata_item.with_id("pub-id").with_lang("en");
1729
1730 let built = metadata_item.build();
1731
1732 assert_eq!(built.property, "identifier");
1733 assert_eq!(built.value, "urn:isbn:1234567890");
1734 assert_eq!(built.id, Some("pub-id".to_string()));
1735 assert_eq!(built.lang, Some("en".to_string()));
1736 assert_eq!(built.refined.len(), 0);
1737 }
1738
1739 #[test]
1740 fn test_metadata_item_builder_chaining() {
1741 let mut metadata_item = MetadataItem::new("title", "EPUB 3.3 Guide");
1742 metadata_item.with_id("title").with_lang("en");
1743
1744 let refinement = MetadataRefinement::new("title", "title-type", "main");
1745 metadata_item.append_refinement(refinement);
1746
1747 let built = metadata_item.build();
1748
1749 assert_eq!(built.property, "title");
1750 assert_eq!(built.value, "EPUB 3.3 Guide");
1751 assert_eq!(built.id, Some("title".to_string()));
1752 assert_eq!(built.lang, Some("en".to_string()));
1753 assert_eq!(built.refined.len(), 1);
1754 }
1755
1756 #[test]
1757 fn test_metadata_item_attributes_dc_namespace() {
1758 let mut metadata_item = MetadataItem::new("title", "Test Book");
1759 metadata_item.with_id("title-id");
1760
1761 let attributes = metadata_item.attributes();
1762
1763 assert!(!attributes.iter().any(|(k, _)| k == &"property"));
1765 assert!(
1766 attributes
1767 .iter()
1768 .any(|(k, v)| k == &"id" && v == &"title-id")
1769 );
1770 }
1771
1772 #[test]
1773 fn test_metadata_item_attributes_non_dc_namespace() {
1774 let mut metadata_item = MetadataItem::new("meta", "value");
1775 metadata_item.with_id("meta-id");
1776
1777 let attributes = metadata_item.attributes();
1778
1779 assert!(attributes.iter().any(|(k, _)| k == &"property"));
1781 assert!(
1782 attributes
1783 .iter()
1784 .any(|(k, v)| k == &"id" && v == &"meta-id")
1785 );
1786 }
1787
1788 #[test]
1789 fn test_metadata_item_attributes_with_lang() {
1790 let mut metadata_item = MetadataItem::new("title", "Test Book");
1791 metadata_item.with_id("title-id").with_lang("en");
1792
1793 let attributes = metadata_item.attributes();
1794
1795 assert!(
1796 attributes
1797 .iter()
1798 .any(|(k, v)| k == &"id" && v == &"title-id")
1799 );
1800 assert!(attributes.iter().any(|(k, v)| k == &"lang" && v == &"en"));
1801 }
1802 }
1803
1804 mod metadata_refinement {
1805 use crate::types::MetadataRefinement;
1806
1807 #[test]
1808 fn test_metadata_refinement_new() {
1809 let refinement = MetadataRefinement::new("title", "title-type", "main");
1810
1811 assert_eq!(refinement.refines, "title");
1812 assert_eq!(refinement.property, "title-type");
1813 assert_eq!(refinement.value, "main");
1814 assert_eq!(refinement.lang, None);
1815 assert_eq!(refinement.scheme, None);
1816 }
1817
1818 #[test]
1819 fn test_metadata_refinement_with_lang() {
1820 let mut refinement = MetadataRefinement::new("creator", "role", "author");
1821 refinement.with_lang("en");
1822
1823 assert_eq!(refinement.refines, "creator");
1824 assert_eq!(refinement.property, "role");
1825 assert_eq!(refinement.value, "author");
1826 assert_eq!(refinement.lang, Some("en".to_string()));
1827 assert_eq!(refinement.scheme, None);
1828 }
1829
1830 #[test]
1831 fn test_metadata_refinement_with_scheme() {
1832 let mut refinement = MetadataRefinement::new("creator", "role", "author");
1833 refinement.with_scheme("marc:relators");
1834
1835 assert_eq!(refinement.refines, "creator");
1836 assert_eq!(refinement.property, "role");
1837 assert_eq!(refinement.value, "author");
1838 assert_eq!(refinement.lang, None);
1839 assert_eq!(refinement.scheme, Some("marc:relators".to_string()));
1840 }
1841
1842 #[test]
1843 fn test_metadata_refinement_build() {
1844 let mut refinement = MetadataRefinement::new("title", "alternate-script", "テスト");
1845 refinement.with_lang("ja").with_scheme("iso-15924");
1846
1847 let built = refinement.build();
1848
1849 assert_eq!(built.refines, "title");
1850 assert_eq!(built.property, "alternate-script");
1851 assert_eq!(built.value, "テスト");
1852 assert_eq!(built.lang, Some("ja".to_string()));
1853 assert_eq!(built.scheme, Some("iso-15924".to_string()));
1854 }
1855
1856 #[test]
1857 fn test_metadata_refinement_builder_chaining() {
1858 let mut refinement = MetadataRefinement::new("creator", "file-as", "Doe, John");
1859 refinement.with_lang("en").with_scheme("dcterms");
1860
1861 let built = refinement.build();
1862
1863 assert_eq!(built.refines, "creator");
1864 assert_eq!(built.property, "file-as");
1865 assert_eq!(built.value, "Doe, John");
1866 assert_eq!(built.lang, Some("en".to_string()));
1867 assert_eq!(built.scheme, Some("dcterms".to_string()));
1868 }
1869
1870 #[test]
1871 fn test_metadata_refinement_attributes() {
1872 let mut refinement = MetadataRefinement::new("title", "title-type", "main");
1873 refinement.with_lang("en").with_scheme("onix:codelist5");
1874
1875 let attributes = refinement.attributes();
1876
1877 assert!(
1878 attributes
1879 .iter()
1880 .any(|(k, v)| k == &"refines" && v == &"title")
1881 );
1882 assert!(
1883 attributes
1884 .iter()
1885 .any(|(k, v)| k == &"property" && v == &"title-type")
1886 );
1887 assert!(attributes.iter().any(|(k, v)| k == &"lang" && v == &"en"));
1888 assert!(
1889 attributes
1890 .iter()
1891 .any(|(k, v)| k == &"scheme" && v == &"onix:codelist5")
1892 );
1893 }
1894
1895 #[test]
1896 fn test_metadata_refinement_attributes_optional_fields() {
1897 let refinement = MetadataRefinement::new("creator", "role", "author");
1898 let attributes = refinement.attributes();
1899
1900 assert!(
1901 attributes
1902 .iter()
1903 .any(|(k, v)| k == &"refines" && v == &"creator")
1904 );
1905 assert!(
1906 attributes
1907 .iter()
1908 .any(|(k, v)| k == &"property" && v == &"role")
1909 );
1910
1911 assert!(!attributes.iter().any(|(k, _)| k == &"lang"));
1913 assert!(!attributes.iter().any(|(k, _)| k == &"scheme"));
1914 }
1915 }
1916
1917 mod manifest_item {
1918 use std::path::PathBuf;
1919
1920 use crate::types::ManifestItem;
1921
1922 #[test]
1923 fn test_manifest_item_new() {
1924 let manifest_item = ManifestItem::new("cover", "images/cover.jpg");
1925 assert!(manifest_item.is_ok());
1926
1927 let manifest_item = manifest_item.unwrap();
1928 assert_eq!(manifest_item.id, "cover");
1929 assert_eq!(manifest_item.path, PathBuf::from("images/cover.jpg"));
1930 assert_eq!(manifest_item.mime, "");
1931 assert_eq!(manifest_item.properties, None);
1932 assert_eq!(manifest_item.fallback, None);
1933 }
1934
1935 #[test]
1936 fn test_manifest_item_append_property() {
1937 let manifest_item = ManifestItem::new("nav", "nav.xhtml");
1938 assert!(manifest_item.is_ok());
1939
1940 let mut manifest_item = manifest_item.unwrap();
1941 manifest_item.append_property("nav");
1942
1943 assert_eq!(manifest_item.id, "nav");
1944 assert_eq!(manifest_item.path, PathBuf::from("nav.xhtml"));
1945 assert_eq!(manifest_item.mime, "");
1946 assert_eq!(manifest_item.properties, Some("nav".to_string()));
1947 assert_eq!(manifest_item.fallback, None);
1948 }
1949
1950 #[test]
1951 fn test_manifest_item_append_multiple_properties() {
1952 let manifest_item = ManifestItem::new("content", "content.xhtml");
1953 assert!(manifest_item.is_ok());
1954
1955 let mut manifest_item = manifest_item.unwrap();
1956 manifest_item
1957 .append_property("nav")
1958 .append_property("scripted")
1959 .append_property("svg");
1960
1961 assert_eq!(
1962 manifest_item.properties,
1963 Some("nav scripted svg".to_string())
1964 );
1965 }
1966
1967 #[test]
1968 fn test_manifest_item_with_fallback() {
1969 let manifest_item = ManifestItem::new("image", "image.tiff");
1970 assert!(manifest_item.is_ok());
1971
1972 let mut manifest_item = manifest_item.unwrap();
1973 manifest_item.with_fallback("image-fallback");
1974
1975 assert_eq!(manifest_item.id, "image");
1976 assert_eq!(manifest_item.path, PathBuf::from("image.tiff"));
1977 assert_eq!(manifest_item.mime, "");
1978 assert_eq!(manifest_item.properties, None);
1979 assert_eq!(manifest_item.fallback, Some("image-fallback".to_string()));
1980 }
1981
1982 #[test]
1983 fn test_manifest_item_set_mime() {
1984 let manifest_item = ManifestItem::new("style", "style.css");
1985 assert!(manifest_item.is_ok());
1986
1987 let manifest_item = manifest_item.unwrap();
1988 let updated_item = manifest_item.set_mime("text/css");
1989
1990 assert_eq!(updated_item.id, "style");
1991 assert_eq!(updated_item.path, PathBuf::from("style.css"));
1992 assert_eq!(updated_item.mime, "text/css");
1993 assert_eq!(updated_item.properties, None);
1994 assert_eq!(updated_item.fallback, None);
1995 }
1996
1997 #[test]
1998 fn test_manifest_item_build() {
1999 let manifest_item = ManifestItem::new("cover", "images/cover.jpg");
2000 assert!(manifest_item.is_ok());
2001
2002 let mut manifest_item = manifest_item.unwrap();
2003 manifest_item
2004 .append_property("cover-image")
2005 .with_fallback("cover-fallback");
2006
2007 let built = manifest_item.build();
2008
2009 assert_eq!(built.id, "cover");
2010 assert_eq!(built.path, PathBuf::from("images/cover.jpg"));
2011 assert_eq!(built.mime, "");
2012 assert_eq!(built.properties, Some("cover-image".to_string()));
2013 assert_eq!(built.fallback, Some("cover-fallback".to_string()));
2014 }
2015
2016 #[test]
2017 fn test_manifest_item_builder_chaining() {
2018 let manifest_item = ManifestItem::new("content", "content.xhtml");
2019 assert!(manifest_item.is_ok());
2020
2021 let mut manifest_item = manifest_item.unwrap();
2022 manifest_item
2023 .append_property("scripted")
2024 .append_property("svg")
2025 .with_fallback("fallback-content");
2026
2027 let built = manifest_item.build();
2028
2029 assert_eq!(built.id, "content");
2030 assert_eq!(built.path, PathBuf::from("content.xhtml"));
2031 assert_eq!(built.mime, "");
2032 assert_eq!(built.properties, Some("scripted svg".to_string()));
2033 assert_eq!(built.fallback, Some("fallback-content".to_string()));
2034 }
2035
2036 #[test]
2037 fn test_manifest_item_attributes() {
2038 let manifest_item = ManifestItem::new("nav", "nav.xhtml");
2039 assert!(manifest_item.is_ok());
2040
2041 let mut manifest_item = manifest_item.unwrap();
2042 manifest_item
2043 .append_property("nav")
2044 .with_fallback("fallback-nav");
2045
2046 let manifest_item = manifest_item.set_mime("application/xhtml+xml");
2048 let attributes = manifest_item.attributes();
2049
2050 assert!(attributes.contains(&("id", "nav")));
2052 assert!(attributes.contains(&("href", "nav.xhtml")));
2053 assert!(attributes.contains(&("media-type", "application/xhtml+xml")));
2054 assert!(attributes.contains(&("properties", "nav")));
2055 assert!(attributes.contains(&("fallback", "fallback-nav")));
2056 }
2057
2058 #[test]
2059 fn test_manifest_item_attributes_optional_fields() {
2060 let manifest_item = ManifestItem::new("simple", "simple.xhtml");
2061 assert!(manifest_item.is_ok());
2062
2063 let manifest_item = manifest_item.unwrap();
2064 let manifest_item = manifest_item.set_mime("application/xhtml+xml");
2065 let attributes = manifest_item.attributes();
2066
2067 assert!(attributes.contains(&("id", "simple")));
2069 assert!(attributes.contains(&("href", "simple.xhtml")));
2070 assert!(attributes.contains(&("media-type", "application/xhtml+xml")));
2071
2072 assert!(!attributes.iter().any(|(k, _)| k == &"properties"));
2074 assert!(!attributes.iter().any(|(k, _)| k == &"fallback"));
2075 }
2076
2077 #[test]
2078 fn test_manifest_item_path_handling() {
2079 let manifest_item = ManifestItem::new("test", "../images/test.png");
2080 assert!(manifest_item.is_err());
2081
2082 let err = manifest_item.unwrap_err();
2083 assert_eq!(
2084 err.to_string(),
2085 "Epub builder error: A manifest with id 'test' should not use a relative path starting with '../'."
2086 );
2087 }
2088 }
2089
2090 mod spine_item {
2091 use crate::types::SpineItem;
2092
2093 #[test]
2094 fn test_spine_item_new() {
2095 let spine_item = SpineItem::new("content_001");
2096
2097 assert_eq!(spine_item.idref, "content_001");
2098 assert_eq!(spine_item.id, None);
2099 assert_eq!(spine_item.properties, None);
2100 assert_eq!(spine_item.linear, true);
2101 }
2102
2103 #[test]
2104 fn test_spine_item_with_id() {
2105 let mut spine_item = SpineItem::new("content_001");
2106 spine_item.with_id("spine1");
2107
2108 assert_eq!(spine_item.idref, "content_001");
2109 assert_eq!(spine_item.id, Some("spine1".to_string()));
2110 assert_eq!(spine_item.properties, None);
2111 assert_eq!(spine_item.linear, true);
2112 }
2113
2114 #[test]
2115 fn test_spine_item_append_property() {
2116 let mut spine_item = SpineItem::new("content_001");
2117 spine_item.append_property("page-spread-left");
2118
2119 assert_eq!(spine_item.idref, "content_001");
2120 assert_eq!(spine_item.id, None);
2121 assert_eq!(spine_item.properties, Some("page-spread-left".to_string()));
2122 assert_eq!(spine_item.linear, true);
2123 }
2124
2125 #[test]
2126 fn test_spine_item_append_multiple_properties() {
2127 let mut spine_item = SpineItem::new("content_001");
2128 spine_item
2129 .append_property("page-spread-left")
2130 .append_property("rendition:layout-pre-paginated");
2131
2132 assert_eq!(
2133 spine_item.properties,
2134 Some("page-spread-left rendition:layout-pre-paginated".to_string())
2135 );
2136 }
2137
2138 #[test]
2139 fn test_spine_item_set_linear() {
2140 let mut spine_item = SpineItem::new("content_001");
2141 spine_item.set_linear(false);
2142
2143 assert_eq!(spine_item.idref, "content_001");
2144 assert_eq!(spine_item.id, None);
2145 assert_eq!(spine_item.properties, None);
2146 assert_eq!(spine_item.linear, false);
2147 }
2148
2149 #[test]
2150 fn test_spine_item_build() {
2151 let mut spine_item = SpineItem::new("content_001");
2152 spine_item
2153 .with_id("spine1")
2154 .append_property("page-spread-left")
2155 .set_linear(false);
2156
2157 let built = spine_item.build();
2158
2159 assert_eq!(built.idref, "content_001");
2160 assert_eq!(built.id, Some("spine1".to_string()));
2161 assert_eq!(built.properties, Some("page-spread-left".to_string()));
2162 assert_eq!(built.linear, false);
2163 }
2164
2165 #[test]
2166 fn test_spine_item_builder_chaining() {
2167 let mut spine_item = SpineItem::new("content_001");
2168 spine_item
2169 .with_id("spine1")
2170 .append_property("page-spread-left")
2171 .set_linear(false);
2172
2173 let built = spine_item.build();
2174
2175 assert_eq!(built.idref, "content_001");
2176 assert_eq!(built.id, Some("spine1".to_string()));
2177 assert_eq!(built.properties, Some("page-spread-left".to_string()));
2178 assert_eq!(built.linear, false);
2179 }
2180
2181 #[test]
2182 fn test_spine_item_attributes() {
2183 let mut spine_item = SpineItem::new("content_001");
2184 spine_item
2185 .with_id("spine1")
2186 .append_property("page-spread-left")
2187 .set_linear(false);
2188
2189 let attributes = spine_item.attributes();
2190
2191 assert!(attributes.contains(&("idref", "content_001")));
2193 assert!(attributes.contains(&("id", "spine1")));
2194 assert!(attributes.contains(&("properties", "page-spread-left")));
2195 assert!(attributes.contains(&("linear", "no"))); }
2197
2198 #[test]
2199 fn test_spine_item_attributes_linear_yes() {
2200 let spine_item = SpineItem::new("content_001");
2201 let attributes = spine_item.attributes();
2202
2203 assert!(attributes.contains(&("linear", "yes")));
2205 }
2206
2207 #[test]
2208 fn test_spine_item_attributes_optional_fields() {
2209 let spine_item = SpineItem::new("content_001");
2210 let attributes = spine_item.attributes();
2211
2212 assert!(attributes.contains(&("idref", "content_001")));
2214 assert!(attributes.contains(&("linear", "yes")));
2215
2216 assert!(!attributes.iter().any(|(k, _)| k == &"id"));
2218 assert!(!attributes.iter().any(|(k, _)| k == &"properties"));
2219 }
2220 }
2221
2222 mod metadata_sheet {
2223 use crate::types::{MetadataItem, MetadataSheet};
2224
2225 #[test]
2226 fn test_metadata_sheet_new() {
2227 let sheet = MetadataSheet::new();
2228
2229 assert!(sheet.contributor.is_empty());
2230 assert!(sheet.creator.is_empty());
2231 assert!(sheet.date.is_empty());
2232 assert!(sheet.identifier.is_empty());
2233 assert!(sheet.language.is_empty());
2234 assert!(sheet.relation.is_empty());
2235 assert!(sheet.subject.is_empty());
2236 assert!(sheet.title.is_empty());
2237
2238 assert!(sheet.coverage.is_empty());
2239 assert!(sheet.description.is_empty());
2240 assert!(sheet.format.is_empty());
2241 assert!(sheet.publisher.is_empty());
2242 assert!(sheet.rights.is_empty());
2243 assert!(sheet.source.is_empty());
2244 assert!(sheet.epub_type.is_empty());
2245 }
2246
2247 #[test]
2248 fn test_metadata_sheet_append_vec_fields() {
2249 let mut sheet = MetadataSheet::new();
2250
2251 sheet
2252 .append_title("Test Book")
2253 .append_creator("John Doe")
2254 .append_creator("Jane Smith")
2255 .append_contributor("Editor One")
2256 .append_language("en")
2257 .append_language("zh-CN")
2258 .append_subject("Fiction")
2259 .append_subject("Drama")
2260 .append_relation("prequel");
2261
2262 assert_eq!(sheet.title.len(), 1);
2263 assert_eq!(sheet.title[0], "Test Book");
2264
2265 assert_eq!(sheet.creator.len(), 2);
2266 assert_eq!(sheet.creator[0], "John Doe");
2267 assert_eq!(sheet.creator[1], "Jane Smith");
2268
2269 assert_eq!(sheet.contributor.len(), 1);
2270 assert_eq!(sheet.contributor[0], "Editor One");
2271
2272 assert_eq!(sheet.language.len(), 2);
2273 assert_eq!(sheet.language[0], "en");
2274 assert_eq!(sheet.language[1], "zh-CN");
2275
2276 assert_eq!(sheet.subject.len(), 2);
2277 assert_eq!(sheet.subject[0], "Fiction");
2278 assert_eq!(sheet.subject[1], "Drama");
2279
2280 assert_eq!(sheet.relation.len(), 1);
2281 assert_eq!(sheet.relation[0], "prequel");
2282 }
2283
2284 #[test]
2285 fn test_metadata_sheet_append_date_and_identifier() {
2286 let mut sheet = MetadataSheet::new();
2287
2288 sheet
2289 .append_date("2024-01-15", "publication")
2290 .append_date("2024-01-10", "creation")
2291 .append_identifier("book-id", "urn:isbn:1234567890")
2292 .append_identifier("uuid-id", "urn:uuid:12345678-1234-1234-1234-123456789012");
2293
2294 assert_eq!(sheet.date.len(), 2);
2295 assert_eq!(
2296 sheet.date.get("2024-01-15"),
2297 Some(&"publication".to_string())
2298 );
2299 assert_eq!(sheet.date.get("2024-01-10"), Some(&"creation".to_string()));
2300
2301 assert_eq!(sheet.identifier.len(), 2);
2302 assert_eq!(
2303 sheet.identifier.get("book-id"),
2304 Some(&"urn:isbn:1234567890".to_string())
2305 );
2306 assert_eq!(
2307 sheet.identifier.get("uuid-id"),
2308 Some(&"urn:uuid:12345678-1234-1234-1234-123456789012".to_string())
2309 );
2310 }
2311
2312 #[test]
2313 fn test_metadata_sheet_with_string_fields() {
2314 let mut sheet = MetadataSheet::new();
2315
2316 sheet
2317 .with_coverage("Spatial coverage")
2318 .with_description("A test book description")
2319 .with_format("application/epub+zip")
2320 .with_publisher("Test Publisher")
2321 .with_rights("Copyright 2024")
2322 .with_source("Original source")
2323 .with_epub_type("buku");
2324
2325 assert_eq!(sheet.coverage, "Spatial coverage");
2326 assert_eq!(sheet.description, "A test book description");
2327 assert_eq!(sheet.format, "application/epub+zip");
2328 assert_eq!(sheet.publisher, "Test Publisher");
2329 assert_eq!(sheet.rights, "Copyright 2024");
2330 assert_eq!(sheet.source, "Original source");
2331 assert_eq!(sheet.epub_type, "buku");
2332 }
2333
2334 #[test]
2335 fn test_metadata_sheet_builder_chaining() {
2336 let mut sheet = MetadataSheet::new();
2337
2338 sheet
2339 .append_title("Chained Book")
2340 .append_creator("Chained Author")
2341 .append_date("2024-01-01", "")
2342 .append_identifier("id-1", "test-id")
2343 .with_publisher("Chained Publisher")
2344 .with_description("Chained description");
2345
2346 assert_eq!(sheet.title.len(), 1);
2347 assert_eq!(sheet.title[0], "Chained Book");
2348
2349 assert_eq!(sheet.creator.len(), 1);
2350 assert_eq!(sheet.creator[0], "Chained Author");
2351
2352 assert_eq!(sheet.date.len(), 1);
2353 assert_eq!(sheet.identifier.len(), 1);
2354 assert_eq!(sheet.publisher, "Chained Publisher");
2355 assert_eq!(sheet.description, "Chained description");
2356 }
2357
2358 #[test]
2359 fn test_metadata_sheet_build() {
2360 let mut sheet = MetadataSheet::new();
2361 sheet
2362 .append_title("Original Title")
2363 .with_publisher("Original Publisher");
2364
2365 let built = sheet.build();
2366
2367 assert_eq!(built.title.len(), 1);
2368 assert_eq!(built.title[0], "Original Title");
2369 assert_eq!(built.publisher, "Original Publisher");
2370
2371 sheet.append_title("New Title");
2372 sheet.with_publisher("New Publisher");
2373
2374 assert_eq!(sheet.title.len(), 2);
2375 assert_eq!(built.title.len(), 1);
2376 assert_eq!(built.publisher, "Original Publisher");
2377 }
2378
2379 #[test]
2380 fn test_metadata_sheet_into_metadata_items() {
2381 let mut sheet = MetadataSheet::new();
2382 sheet
2383 .append_title("Test Title")
2384 .append_creator("Test Creator")
2385 .with_description("Test Description")
2386 .with_publisher("Test Publisher");
2387
2388 let items: Vec<MetadataItem> = sheet.into();
2389
2390 assert_eq!(items.len(), 4);
2391
2392 assert!(
2393 items
2394 .iter()
2395 .any(|i| i.property == "title" && i.value == "Test Title")
2396 );
2397
2398 assert!(
2399 items
2400 .iter()
2401 .any(|i| i.property == "creator" && i.value == "Test Creator")
2402 );
2403
2404 assert!(
2405 items
2406 .iter()
2407 .any(|i| i.property == "description" && i.value == "Test Description")
2408 );
2409
2410 assert!(
2411 items
2412 .iter()
2413 .any(|i| i.property == "publisher" && i.value == "Test Publisher")
2414 );
2415 }
2416
2417 #[test]
2418 fn test_metadata_sheet_into_metadata_items_with_date_and_identifier() {
2419 let mut sheet = MetadataSheet::new();
2420 sheet
2421 .append_date("2024-01-15", "publication")
2422 .append_identifier("book-id", "urn:isbn:9876543210");
2423
2424 let items: Vec<MetadataItem> = sheet.into();
2425
2426 assert_eq!(items.len(), 2);
2427
2428 let date_item = items.iter().find(|i| i.property == "date").unwrap();
2429
2430 assert_eq!(date_item.value, "2024-01-15");
2431 assert!(date_item.id.is_some());
2432 assert_eq!(date_item.refined.len(), 1);
2433 assert_eq!(date_item.refined[0].property, "event");
2434 assert_eq!(date_item.refined[0].value, "publication");
2435
2436 let id_item = items.iter().find(|i| i.property == "identifier").unwrap();
2437
2438 assert_eq!(id_item.value, "urn:isbn:9876543210");
2439 assert_eq!(id_item.id, Some("book-id".to_string()));
2440 }
2441
2442 #[test]
2443 fn test_metadata_sheet_into_metadata_items_ignores_empty_fields() {
2444 let mut sheet = MetadataSheet::new();
2445 sheet.append_title("Valid Title").with_description(""); let items: Vec<MetadataItem> = sheet.into();
2448
2449 assert_eq!(items.len(), 1);
2450 assert_eq!(items[0].property, "title");
2451 }
2452 }
2453
2454 mod navpoint {
2455
2456 use std::path::PathBuf;
2457
2458 use crate::types::NavPoint;
2459
2460 #[test]
2461 fn test_navpoint_new() {
2462 let navpoint = NavPoint::new("Test Chapter");
2463
2464 assert_eq!(navpoint.label, "Test Chapter");
2465 assert_eq!(navpoint.content, None);
2466 assert_eq!(navpoint.children.len(), 0);
2467 }
2468
2469 #[test]
2470 fn test_navpoint_with_content() {
2471 let mut navpoint = NavPoint::new("Test Chapter");
2472 navpoint.with_content("chapter1.html");
2473
2474 assert_eq!(navpoint.label, "Test Chapter");
2475 assert_eq!(navpoint.content, Some(PathBuf::from("chapter1.html")));
2476 assert_eq!(navpoint.children.len(), 0);
2477 }
2478
2479 #[test]
2480 fn test_navpoint_append_child() {
2481 let mut parent = NavPoint::new("Parent Chapter");
2482
2483 let mut child1 = NavPoint::new("Child Section 1");
2484 child1.with_content("section1.html");
2485
2486 let mut child2 = NavPoint::new("Child Section 2");
2487 child2.with_content("section2.html");
2488
2489 parent.append_child(child1.build());
2490 parent.append_child(child2.build());
2491
2492 assert_eq!(parent.children.len(), 2);
2493 assert_eq!(parent.children[0].label, "Child Section 1");
2494 assert_eq!(parent.children[1].label, "Child Section 2");
2495 }
2496
2497 #[test]
2498 fn test_navpoint_set_children() {
2499 let mut navpoint = NavPoint::new("Main Chapter");
2500 let children = vec![NavPoint::new("Section 1"), NavPoint::new("Section 2")];
2501
2502 navpoint.set_children(children);
2503
2504 assert_eq!(navpoint.children.len(), 2);
2505 assert_eq!(navpoint.children[0].label, "Section 1");
2506 assert_eq!(navpoint.children[1].label, "Section 2");
2507 }
2508
2509 #[test]
2510 fn test_navpoint_build() {
2511 let mut navpoint = NavPoint::new("Complete Chapter");
2512 navpoint.with_content("complete.html");
2513
2514 let child = NavPoint::new("Sub Section");
2515 navpoint.append_child(child.build());
2516
2517 let built = navpoint.build();
2518
2519 assert_eq!(built.label, "Complete Chapter");
2520 assert_eq!(built.content, Some(PathBuf::from("complete.html")));
2521 assert_eq!(built.children.len(), 1);
2522 assert_eq!(built.children[0].label, "Sub Section");
2523 }
2524
2525 #[test]
2526 fn test_navpoint_builder_chaining() {
2527 let mut navpoint = NavPoint::new("Chained Chapter");
2528
2529 navpoint
2530 .with_content("chained.html")
2531 .append_child(NavPoint::new("Child 1").build())
2532 .append_child(NavPoint::new("Child 2").build());
2533
2534 let built = navpoint.build();
2535
2536 assert_eq!(built.label, "Chained Chapter");
2537 assert_eq!(built.content, Some(PathBuf::from("chained.html")));
2538 assert_eq!(built.children.len(), 2);
2539 }
2540
2541 #[test]
2542 fn test_navpoint_empty_children() {
2543 let navpoint = NavPoint::new("No Children Chapter");
2544 let built = navpoint.build();
2545
2546 assert_eq!(built.children.len(), 0);
2547 }
2548
2549 #[test]
2550 fn test_navpoint_complex_hierarchy() {
2551 let mut root = NavPoint::new("Book");
2552
2553 let mut chapter1 = NavPoint::new("Chapter 1");
2554 chapter1
2555 .with_content("chapter1.html")
2556 .append_child(
2557 NavPoint::new("Section 1.1")
2558 .with_content("sec1_1.html")
2559 .build(),
2560 )
2561 .append_child(
2562 NavPoint::new("Section 1.2")
2563 .with_content("sec1_2.html")
2564 .build(),
2565 );
2566
2567 let mut chapter2 = NavPoint::new("Chapter 2");
2568 chapter2.with_content("chapter2.html").append_child(
2569 NavPoint::new("Section 2.1")
2570 .with_content("sec2_1.html")
2571 .build(),
2572 );
2573
2574 root.append_child(chapter1.build())
2575 .append_child(chapter2.build());
2576
2577 let book = root.build();
2578
2579 assert_eq!(book.label, "Book");
2580 assert_eq!(book.children.len(), 2);
2581
2582 let ch1 = &book.children[0];
2583 assert_eq!(ch1.label, "Chapter 1");
2584 assert_eq!(ch1.children.len(), 2);
2585
2586 let ch2 = &book.children[1];
2587 assert_eq!(ch2.label, "Chapter 2");
2588 assert_eq!(ch2.children.len(), 1);
2589 }
2590 }
2591 }
2592
2593 #[cfg(feature = "content-builder")]
2594 mod footnote_tests {
2595 use crate::types::Footnote;
2596
2597 #[test]
2598 fn test_footnote_basic_creation() {
2599 let footnote = Footnote {
2600 locate: 100,
2601 content: "Sample footnote".to_string(),
2602 };
2603
2604 assert_eq!(footnote.locate, 100);
2605 assert_eq!(footnote.content, "Sample footnote");
2606 }
2607
2608 #[test]
2609 fn test_footnote_equality() {
2610 let footnote1 = Footnote {
2611 locate: 100,
2612 content: "First note".to_string(),
2613 };
2614
2615 let footnote2 = Footnote {
2616 locate: 100,
2617 content: "First note".to_string(),
2618 };
2619
2620 let footnote3 = Footnote {
2621 locate: 100,
2622 content: "Different note".to_string(),
2623 };
2624
2625 let footnote4 = Footnote {
2626 locate: 200,
2627 content: "First note".to_string(),
2628 };
2629
2630 assert_eq!(footnote1, footnote2);
2631 assert_ne!(footnote1, footnote3);
2632 assert_ne!(footnote1, footnote4);
2633 }
2634
2635 #[test]
2636 fn test_footnote_ordering() {
2637 let footnote1 = Footnote {
2638 locate: 100,
2639 content: "First".to_string(),
2640 };
2641
2642 let footnote2 = Footnote {
2643 locate: 200,
2644 content: "Second".to_string(),
2645 };
2646
2647 let footnote3 = Footnote {
2648 locate: 150,
2649 content: "Middle".to_string(),
2650 };
2651
2652 assert!(footnote1 < footnote2);
2653 assert!(footnote2 > footnote1);
2654 assert!(footnote1 < footnote3);
2655 assert!(footnote3 < footnote2);
2656 assert_eq!(footnote1.cmp(&footnote1), std::cmp::Ordering::Equal);
2657 }
2658
2659 #[test]
2660 fn test_footnote_sorting() {
2661 let mut footnotes = vec![
2662 Footnote {
2663 locate: 300,
2664 content: "Third note".to_string(),
2665 },
2666 Footnote {
2667 locate: 100,
2668 content: "First note".to_string(),
2669 },
2670 Footnote {
2671 locate: 200,
2672 content: "Second note".to_string(),
2673 },
2674 ];
2675
2676 footnotes.sort();
2677
2678 assert_eq!(footnotes[0].locate, 100);
2679 assert_eq!(footnotes[1].locate, 200);
2680 assert_eq!(footnotes[2].locate, 300);
2681
2682 assert_eq!(footnotes[0].content, "First note");
2683 assert_eq!(footnotes[1].content, "Second note");
2684 assert_eq!(footnotes[2].content, "Third note");
2685 }
2686 }
2687
2688 #[cfg(feature = "content-builder")]
2689 mod block_type_tests {
2690 use crate::types::BlockType;
2691
2692 #[test]
2693 fn test_block_type_variants() {
2694 let _ = BlockType::Text;
2695 let _ = BlockType::Quote;
2696 let _ = BlockType::Title;
2697 let _ = BlockType::Image;
2698 let _ = BlockType::Audio;
2699 let _ = BlockType::Video;
2700 let _ = BlockType::MathML;
2701 }
2702
2703 #[test]
2704 fn test_block_type_debug() {
2705 let text = format!("{:?}", BlockType::Text);
2706 assert_eq!(text, "Text");
2707
2708 let quote = format!("{:?}", BlockType::Quote);
2709 assert_eq!(quote, "Quote");
2710
2711 let image = format!("{:?}", BlockType::Image);
2712 assert_eq!(image, "Image");
2713 }
2714 }
2715
2716 #[cfg(feature = "content-builder")]
2717 mod style_options_tests {
2718 use crate::types::{ColorScheme, PageLayout, StyleOptions, TextAlign, TextStyle};
2719
2720 #[test]
2721 fn test_style_options_default() {
2722 let options = StyleOptions::default();
2723
2724 assert_eq!(options.text.font_size, 1.0);
2725 assert_eq!(options.text.line_height, 1.6);
2726 assert_eq!(
2727 options.text.font_family,
2728 "-apple-system, Roboto, sans-serif"
2729 );
2730 assert_eq!(options.text.font_weight, "normal");
2731 assert_eq!(options.text.font_style, "normal");
2732 assert_eq!(options.text.letter_spacing, "normal");
2733 assert_eq!(options.text.text_indent, 2.0);
2734
2735 assert_eq!(options.color_scheme.background, "#FFFFFF");
2736 assert_eq!(options.color_scheme.text, "#000000");
2737 assert_eq!(options.color_scheme.link, "#6f6f6f");
2738
2739 assert_eq!(options.layout.margin, 20);
2740 assert_eq!(options.layout.text_align, TextAlign::Left);
2741 assert_eq!(options.layout.paragraph_spacing, 16);
2742 }
2743
2744 #[test]
2745 fn test_style_options_custom_values() {
2746 let text = TextStyle {
2747 font_size: 1.5,
2748 line_height: 2.0,
2749 font_family: "Georgia, serif".to_string(),
2750 font_weight: "bold".to_string(),
2751 font_style: "italic".to_string(),
2752 letter_spacing: "0.1em".to_string(),
2753 text_indent: 3.0,
2754 };
2755
2756 let color_scheme = ColorScheme {
2757 background: "#F0F0F0".to_string(),
2758 text: "#333333".to_string(),
2759 link: "#0066CC".to_string(),
2760 };
2761
2762 let layout = PageLayout {
2763 margin: 30,
2764 text_align: TextAlign::Center,
2765 paragraph_spacing: 20,
2766 };
2767
2768 let options = StyleOptions { text, color_scheme, layout };
2769
2770 assert_eq!(options.text.font_size, 1.5);
2771 assert_eq!(options.text.font_weight, "bold");
2772 assert_eq!(options.color_scheme.background, "#F0F0F0");
2773 assert_eq!(options.layout.text_align, TextAlign::Center);
2774 }
2775
2776 #[test]
2777 fn test_text_style_default() {
2778 let style = TextStyle::default();
2779
2780 assert_eq!(style.font_size, 1.0);
2781 assert_eq!(style.line_height, 1.6);
2782 assert_eq!(style.font_family, "-apple-system, Roboto, sans-serif");
2783 assert_eq!(style.font_weight, "normal");
2784 assert_eq!(style.font_style, "normal");
2785 assert_eq!(style.letter_spacing, "normal");
2786 assert_eq!(style.text_indent, 2.0);
2787 }
2788
2789 #[test]
2790 fn test_text_style_custom_values() {
2791 let style = TextStyle {
2792 font_size: 2.0,
2793 line_height: 1.8,
2794 font_family: "Times New Roman".to_string(),
2795 font_weight: "bold".to_string(),
2796 font_style: "italic".to_string(),
2797 letter_spacing: "0.05em".to_string(),
2798 text_indent: 0.0,
2799 };
2800
2801 assert_eq!(style.font_size, 2.0);
2802 assert_eq!(style.line_height, 1.8);
2803 assert_eq!(style.font_family, "Times New Roman");
2804 assert_eq!(style.font_weight, "bold");
2805 assert_eq!(style.font_style, "italic");
2806 assert_eq!(style.letter_spacing, "0.05em");
2807 assert_eq!(style.text_indent, 0.0);
2808 }
2809
2810 #[test]
2811 fn test_text_style_debug() {
2812 let style = TextStyle::default();
2813 let debug_str = format!("{:?}", style);
2814 assert!(debug_str.contains("TextStyle"));
2815 assert!(debug_str.contains("font_size"));
2816 }
2817
2818 #[test]
2819 fn test_color_scheme_default() {
2820 let scheme = ColorScheme::default();
2821
2822 assert_eq!(scheme.background, "#FFFFFF");
2823 assert_eq!(scheme.text, "#000000");
2824 assert_eq!(scheme.link, "#6f6f6f");
2825 }
2826
2827 #[test]
2828 fn test_color_scheme_custom_values() {
2829 let scheme = ColorScheme {
2830 background: "#000000".to_string(),
2831 text: "#FFFFFF".to_string(),
2832 link: "#00FF00".to_string(),
2833 };
2834
2835 assert_eq!(scheme.background, "#000000");
2836 assert_eq!(scheme.text, "#FFFFFF");
2837 assert_eq!(scheme.link, "#00FF00");
2838 }
2839
2840 #[test]
2841 fn test_color_scheme_debug() {
2842 let scheme = ColorScheme::default();
2843 let debug_str = format!("{:?}", scheme);
2844 assert!(debug_str.contains("ColorScheme"));
2845 assert!(debug_str.contains("background"));
2846 }
2847
2848 #[test]
2849 fn test_page_layout_default() {
2850 let layout = PageLayout::default();
2851
2852 assert_eq!(layout.margin, 20);
2853 assert_eq!(layout.text_align, TextAlign::Left);
2854 assert_eq!(layout.paragraph_spacing, 16);
2855 }
2856
2857 #[test]
2858 fn test_page_layout_custom_values() {
2859 let layout = PageLayout {
2860 margin: 40,
2861 text_align: TextAlign::Justify,
2862 paragraph_spacing: 24,
2863 };
2864
2865 assert_eq!(layout.margin, 40);
2866 assert_eq!(layout.text_align, TextAlign::Justify);
2867 assert_eq!(layout.paragraph_spacing, 24);
2868 }
2869
2870 #[test]
2871 fn test_page_layout_debug() {
2872 let layout = PageLayout::default();
2873 let debug_str = format!("{:?}", layout);
2874 assert!(debug_str.contains("PageLayout"));
2875 assert!(debug_str.contains("margin"));
2876 }
2877
2878 #[test]
2879 fn test_text_align_default() {
2880 let align = TextAlign::default();
2881 assert_eq!(align, TextAlign::Left);
2882 }
2883
2884 #[test]
2885 fn test_text_align_display() {
2886 assert_eq!(TextAlign::Left.to_string(), "left");
2887 assert_eq!(TextAlign::Right.to_string(), "right");
2888 assert_eq!(TextAlign::Justify.to_string(), "justify");
2889 assert_eq!(TextAlign::Center.to_string(), "center");
2890 }
2891
2892 #[test]
2893 fn test_text_align_all_variants() {
2894 let left = TextAlign::Left;
2895 let right = TextAlign::Right;
2896 let justify = TextAlign::Justify;
2897 let center = TextAlign::Center;
2898
2899 assert!(matches!(left, TextAlign::Left));
2900 assert!(matches!(right, TextAlign::Right));
2901 assert!(matches!(justify, TextAlign::Justify));
2902 assert!(matches!(center, TextAlign::Center));
2903 }
2904
2905 #[test]
2906 fn test_text_align_debug() {
2907 assert_eq!(format!("{:?}", TextAlign::Left), "Left");
2908 assert_eq!(format!("{:?}", TextAlign::Right), "Right");
2909 assert_eq!(format!("{:?}", TextAlign::Justify), "Justify");
2910 assert_eq!(format!("{:?}", TextAlign::Center), "Center");
2911 }
2912
2913 #[test]
2914 fn test_style_options_builder_new() {
2915 let options = StyleOptions::new();
2916 assert_eq!(options.text.font_size, 1.0);
2917 assert_eq!(options.color_scheme.background, "#FFFFFF");
2918 assert_eq!(options.layout.margin, 20);
2919 }
2920
2921 #[test]
2922 fn test_style_options_builder_with_text() {
2923 let mut options = StyleOptions::new();
2924 let text_style = TextStyle::new()
2925 .with_font_size(2.0)
2926 .with_font_weight("bold")
2927 .build();
2928 options.with_text(text_style);
2929
2930 assert_eq!(options.text.font_size, 2.0);
2931 assert_eq!(options.text.font_weight, "bold");
2932 }
2933
2934 #[test]
2935 fn test_style_options_builder_with_color_scheme() {
2936 let mut options = StyleOptions::new();
2937 let color = ColorScheme::new()
2938 .with_background("#000000")
2939 .with_text("#FFFFFF")
2940 .build();
2941 options.with_color_scheme(color);
2942
2943 assert_eq!(options.color_scheme.background, "#000000");
2944 assert_eq!(options.color_scheme.text, "#FFFFFF");
2945 }
2946
2947 #[test]
2948 fn test_style_options_builder_with_layout() {
2949 let mut options = StyleOptions::new();
2950 let layout = PageLayout::new()
2951 .with_margin(40)
2952 .with_text_align(TextAlign::Justify)
2953 .with_paragraph_spacing(24)
2954 .build();
2955 options.with_layout(layout);
2956
2957 assert_eq!(options.layout.margin, 40);
2958 assert_eq!(options.layout.text_align, TextAlign::Justify);
2959 assert_eq!(options.layout.paragraph_spacing, 24);
2960 }
2961
2962 #[test]
2963 fn test_style_options_builder_build() {
2964 let options = StyleOptions::new()
2965 .with_text(TextStyle::new().with_font_size(1.5).build())
2966 .with_color_scheme(ColorScheme::new().with_link("#FF0000").build())
2967 .with_layout(PageLayout::new().with_margin(30).build())
2968 .build();
2969
2970 assert_eq!(options.text.font_size, 1.5);
2971 assert_eq!(options.color_scheme.link, "#FF0000");
2972 assert_eq!(options.layout.margin, 30);
2973 }
2974
2975 #[test]
2976 fn test_style_options_builder_chaining() {
2977 let options = StyleOptions::new()
2978 .with_text(
2979 TextStyle::new()
2980 .with_font_size(1.5)
2981 .with_line_height(2.0)
2982 .with_font_family("Arial")
2983 .with_font_weight("bold")
2984 .with_font_style("italic")
2985 .with_letter_spacing("0.1em")
2986 .with_text_indent(1.5)
2987 .build(),
2988 )
2989 .with_color_scheme(
2990 ColorScheme::new()
2991 .with_background("#CCCCCC")
2992 .with_text("#111111")
2993 .with_link("#0000FF")
2994 .build(),
2995 )
2996 .with_layout(
2997 PageLayout::new()
2998 .with_margin(25)
2999 .with_text_align(TextAlign::Right)
3000 .with_paragraph_spacing(20)
3001 .build(),
3002 )
3003 .build();
3004
3005 assert_eq!(options.text.font_size, 1.5);
3006 assert_eq!(options.text.line_height, 2.0);
3007 assert_eq!(options.text.font_family, "Arial");
3008 assert_eq!(options.text.font_weight, "bold");
3009 assert_eq!(options.text.font_style, "italic");
3010 assert_eq!(options.text.letter_spacing, "0.1em");
3011 assert_eq!(options.text.text_indent, 1.5);
3012
3013 assert_eq!(options.color_scheme.background, "#CCCCCC");
3014 assert_eq!(options.color_scheme.text, "#111111");
3015 assert_eq!(options.color_scheme.link, "#0000FF");
3016
3017 assert_eq!(options.layout.margin, 25);
3018 assert_eq!(options.layout.text_align, TextAlign::Right);
3019 assert_eq!(options.layout.paragraph_spacing, 20);
3020 }
3021
3022 #[test]
3023 fn test_text_style_builder_new() {
3024 let style = TextStyle::new();
3025 assert_eq!(style.font_size, 1.0);
3026 assert_eq!(style.line_height, 1.6);
3027 }
3028
3029 #[test]
3030 fn test_text_style_builder_with_font_size() {
3031 let mut style = TextStyle::new();
3032 style.with_font_size(2.5);
3033 assert_eq!(style.font_size, 2.5);
3034 }
3035
3036 #[test]
3037 fn test_text_style_builder_with_line_height() {
3038 let mut style = TextStyle::new();
3039 style.with_line_height(2.0);
3040 assert_eq!(style.line_height, 2.0);
3041 }
3042
3043 #[test]
3044 fn test_text_style_builder_with_font_family() {
3045 let mut style = TextStyle::new();
3046 style.with_font_family("Helvetica, Arial");
3047 assert_eq!(style.font_family, "Helvetica, Arial");
3048 }
3049
3050 #[test]
3051 fn test_text_style_builder_with_font_weight() {
3052 let mut style = TextStyle::new();
3053 style.with_font_weight("bold");
3054 assert_eq!(style.font_weight, "bold");
3055 }
3056
3057 #[test]
3058 fn test_text_style_builder_with_font_style() {
3059 let mut style = TextStyle::new();
3060 style.with_font_style("italic");
3061 assert_eq!(style.font_style, "italic");
3062 }
3063
3064 #[test]
3065 fn test_text_style_builder_with_letter_spacing() {
3066 let mut style = TextStyle::new();
3067 style.with_letter_spacing("0.05em");
3068 assert_eq!(style.letter_spacing, "0.05em");
3069 }
3070
3071 #[test]
3072 fn test_text_style_builder_with_text_indent() {
3073 let mut style = TextStyle::new();
3074 style.with_text_indent(3.0);
3075 assert_eq!(style.text_indent, 3.0);
3076 }
3077
3078 #[test]
3079 fn test_text_style_builder_build() {
3080 let style = TextStyle::new()
3081 .with_font_size(1.8)
3082 .with_line_height(1.9)
3083 .build();
3084
3085 assert_eq!(style.font_size, 1.8);
3086 assert_eq!(style.line_height, 1.9);
3087 }
3088
3089 #[test]
3090 fn test_text_style_builder_chaining() {
3091 let style = TextStyle::new()
3092 .with_font_size(2.0)
3093 .with_line_height(1.8)
3094 .with_font_family("Georgia")
3095 .with_font_weight("bold")
3096 .with_font_style("italic")
3097 .with_letter_spacing("0.1em")
3098 .with_text_indent(0.5)
3099 .build();
3100
3101 assert_eq!(style.font_size, 2.0);
3102 assert_eq!(style.line_height, 1.8);
3103 assert_eq!(style.font_family, "Georgia");
3104 assert_eq!(style.font_weight, "bold");
3105 assert_eq!(style.font_style, "italic");
3106 assert_eq!(style.letter_spacing, "0.1em");
3107 assert_eq!(style.text_indent, 0.5);
3108 }
3109
3110 #[test]
3111 fn test_color_scheme_builder_new() {
3112 let scheme = ColorScheme::new();
3113 assert_eq!(scheme.background, "#FFFFFF");
3114 assert_eq!(scheme.text, "#000000");
3115 }
3116
3117 #[test]
3118 fn test_color_scheme_builder_with_background() {
3119 let mut scheme = ColorScheme::new();
3120 scheme.with_background("#FF0000");
3121 assert_eq!(scheme.background, "#FF0000");
3122 }
3123
3124 #[test]
3125 fn test_color_scheme_builder_with_text() {
3126 let mut scheme = ColorScheme::new();
3127 scheme.with_text("#333333");
3128 assert_eq!(scheme.text, "#333333");
3129 }
3130
3131 #[test]
3132 fn test_color_scheme_builder_with_link() {
3133 let mut scheme = ColorScheme::new();
3134 scheme.with_link("#0000FF");
3135 assert_eq!(scheme.link, "#0000FF");
3136 }
3137
3138 #[test]
3139 fn test_color_scheme_builder_build() {
3140 let scheme = ColorScheme::new().with_background("#123456").build();
3141
3142 assert_eq!(scheme.background, "#123456");
3143 assert_eq!(scheme.text, "#000000");
3144 }
3145
3146 #[test]
3147 fn test_color_scheme_builder_chaining() {
3148 let scheme = ColorScheme::new()
3149 .with_background("#AABBCC")
3150 .with_text("#DDEEFF")
3151 .with_link("#112233")
3152 .build();
3153
3154 assert_eq!(scheme.background, "#AABBCC");
3155 assert_eq!(scheme.text, "#DDEEFF");
3156 assert_eq!(scheme.link, "#112233");
3157 }
3158
3159 #[test]
3160 fn test_page_layout_builder_new() {
3161 let layout = PageLayout::new();
3162 assert_eq!(layout.margin, 20);
3163 assert_eq!(layout.text_align, TextAlign::Left);
3164 assert_eq!(layout.paragraph_spacing, 16);
3165 }
3166
3167 #[test]
3168 fn test_page_layout_builder_with_margin() {
3169 let mut layout = PageLayout::new();
3170 layout.with_margin(50);
3171 assert_eq!(layout.margin, 50);
3172 }
3173
3174 #[test]
3175 fn test_page_layout_builder_with_text_align() {
3176 let mut layout = PageLayout::new();
3177 layout.with_text_align(TextAlign::Center);
3178 assert_eq!(layout.text_align, TextAlign::Center);
3179 }
3180
3181 #[test]
3182 fn test_page_layout_builder_with_paragraph_spacing() {
3183 let mut layout = PageLayout::new();
3184 layout.with_paragraph_spacing(30);
3185 assert_eq!(layout.paragraph_spacing, 30);
3186 }
3187
3188 #[test]
3189 fn test_page_layout_builder_build() {
3190 let layout = PageLayout::new().with_margin(35).build();
3191
3192 assert_eq!(layout.margin, 35);
3193 assert_eq!(layout.text_align, TextAlign::Left);
3194 }
3195
3196 #[test]
3197 fn test_page_layout_builder_chaining() {
3198 let layout = PageLayout::new()
3199 .with_margin(45)
3200 .with_text_align(TextAlign::Justify)
3201 .with_paragraph_spacing(28)
3202 .build();
3203
3204 assert_eq!(layout.margin, 45);
3205 assert_eq!(layout.text_align, TextAlign::Justify);
3206 assert_eq!(layout.paragraph_spacing, 28);
3207 }
3208
3209 #[test]
3210 fn test_page_layout_builder_all_text_align_variants() {
3211 let left = PageLayout::new().with_text_align(TextAlign::Left).build();
3212 assert_eq!(left.text_align, TextAlign::Left);
3213
3214 let right = PageLayout::new().with_text_align(TextAlign::Right).build();
3215 assert_eq!(right.text_align, TextAlign::Right);
3216
3217 let center = PageLayout::new().with_text_align(TextAlign::Center).build();
3218 assert_eq!(center.text_align, TextAlign::Center);
3219
3220 let justify = PageLayout::new()
3221 .with_text_align(TextAlign::Justify)
3222 .build();
3223 assert_eq!(justify.text_align, TextAlign::Justify);
3224 }
3225 }
3226}