use super::*;
#[test]
fn test_generate_flow_page_with_text_header() {
use crate::ir::{HFInline, HeaderFooter, HeaderFooterParagraph};
let doc = make_doc(vec![Page::Flow(FlowPage {
size: PageSize::default(),
margins: Margins::default(),
content: vec![make_paragraph("Body text")],
header: Some(HeaderFooter {
paragraphs: vec![HeaderFooterParagraph {
style: ParagraphStyle::default(),
elements: vec![HFInline::Run(Run {
text: "Document Title".to_string(),
style: TextStyle::default(),
href: None,
footnote: None,
})],
}],
}),
footer: None,
columns: None,
})]);
let output = generate_typst(&doc).unwrap();
assert!(output.source.contains("header:"));
assert!(output.source.contains("Document Title"));
}
#[test]
fn test_generate_flow_page_with_page_number_footer() {
use crate::ir::{HFInline, HeaderFooter, HeaderFooterParagraph};
let doc = make_doc(vec![Page::Flow(FlowPage {
size: PageSize::default(),
margins: Margins::default(),
content: vec![make_paragraph("Body text")],
header: None,
footer: Some(HeaderFooter {
paragraphs: vec![HeaderFooterParagraph {
style: ParagraphStyle::default(),
elements: vec![
HFInline::Run(Run {
text: "Page ".to_string(),
style: TextStyle::default(),
href: None,
footnote: None,
}),
HFInline::PageNumber,
],
}],
}),
columns: None,
})]);
let output = generate_typst(&doc).unwrap();
assert!(output.source.contains("footer:"));
assert!(output.source.contains("counter(page).display()"));
assert!(output.source.contains("Page "));
}
#[test]
fn test_generate_flow_page_with_header_and_footer() {
use crate::ir::{HFInline, HeaderFooter, HeaderFooterParagraph};
let doc = make_doc(vec![Page::Flow(FlowPage {
size: PageSize::default(),
margins: Margins::default(),
content: vec![make_paragraph("Body")],
header: Some(HeaderFooter {
paragraphs: vec![HeaderFooterParagraph {
style: ParagraphStyle::default(),
elements: vec![HFInline::Run(Run {
text: "Header".to_string(),
style: TextStyle::default(),
href: None,
footnote: None,
})],
}],
}),
footer: Some(HeaderFooter {
paragraphs: vec![HeaderFooterParagraph {
style: ParagraphStyle::default(),
elements: vec![HFInline::PageNumber],
}],
}),
columns: None,
})]);
let output = generate_typst(&doc).unwrap();
assert!(output.source.contains("header:") && output.source.contains("footer:"));
}
#[test]
fn test_generate_flow_page_without_header_footer() {
let doc = make_doc(vec![make_flow_page(vec![make_paragraph("Body")])]);
let output = generate_typst(&doc).unwrap();
assert!(!output.source.contains("header:"));
assert!(!output.source.contains("footer:"));
}
#[test]
fn test_generate_typst_inserts_pagebreak_between_flow_pages() {
let first = Page::Flow(FlowPage {
size: PageSize::default(),
margins: Margins::default(),
content: vec![make_paragraph("First section")],
header: None,
footer: None,
columns: None,
});
let second = Page::Flow(FlowPage {
size: PageSize::default(),
margins: Margins::default(),
content: vec![make_paragraph("Second section")],
header: None,
footer: None,
columns: None,
});
let output = generate_typst(&make_doc(vec![first, second])).unwrap();
let pagebreak_count = output.source.matches("#pagebreak()").count();
assert_eq!(pagebreak_count, 1);
}
#[test]
fn test_fixed_page_with_background_color() {
let page = Page::Fixed(FixedPage {
size: PageSize {
width: 720.0,
height: 540.0,
},
elements: vec![],
background_color: Some(Color::new(255, 0, 0)),
background_gradient: None,
});
let doc = make_doc(vec![page]);
let output = generate_typst(&doc).unwrap();
assert!(output.source.contains("fill: rgb(255, 0, 0)"));
}
#[test]
fn test_fixed_page_without_background_color() {
let page = Page::Fixed(FixedPage {
size: PageSize {
width: 720.0,
height: 540.0,
},
elements: vec![],
background_color: None,
background_gradient: None,
});
let doc = make_doc(vec![page]);
let output = generate_typst(&doc).unwrap();
assert!(!output.source.contains("fill:"));
}
#[test]
fn test_fixed_page_table_element() {
let table = Table {
rows: vec![TableRow {
cells: vec![
TableCell {
content: vec![Block::Paragraph(Paragraph {
style: ParagraphStyle::default(),
runs: vec![Run {
text: "A1".to_string(),
style: TextStyle::default(),
href: None,
footnote: None,
}],
})],
..TableCell::default()
},
TableCell {
content: vec![Block::Paragraph(Paragraph {
style: ParagraphStyle::default(),
runs: vec![Run {
text: "B1".to_string(),
style: TextStyle::default(),
href: None,
footnote: None,
}],
})],
..TableCell::default()
},
],
height: None,
}],
column_widths: vec![100.0, 100.0],
..Table::default()
};
let page = Page::Fixed(FixedPage {
size: PageSize {
width: 720.0,
height: 540.0,
},
elements: vec![FixedElement {
x: 50.0,
y: 100.0,
width: 200.0,
height: 50.0,
kind: FixedElementKind::Table(table),
}],
background_color: None,
background_gradient: None,
});
let doc = make_doc(vec![page]);
let output = generate_typst(&doc).unwrap();
assert!(
output
.source
.contains("#place(top + left, dx: 50pt, dy: 100pt)")
);
assert!(output.source.contains("#table("));
assert!(output.source.contains("columns: (100pt, 100pt)"));
assert!(output.source.contains("A1"));
assert!(output.source.contains("B1"));
}
#[test]
fn test_hyperlink_generates_typst_link() {
let doc = make_doc(vec![make_flow_page(vec![Block::Paragraph(Paragraph {
style: ParagraphStyle::default(),
runs: vec![Run {
text: "Click me".to_string(),
style: TextStyle::default(),
href: Some("https://example.com".to_string()),
footnote: None,
}],
})])]);
let output = generate_typst(&doc).unwrap();
assert!(
output
.source
.contains(r#"#link("https://example.com")[Click me]"#)
);
}
#[test]
fn test_hyperlink_with_styled_text() {
let doc = make_doc(vec![make_flow_page(vec![Block::Paragraph(Paragraph {
style: ParagraphStyle::default(),
runs: vec![Run {
text: "Bold link".to_string(),
style: TextStyle {
bold: Some(true),
..TextStyle::default()
},
href: Some("https://example.com".to_string()),
footnote: None,
}],
})])]);
let output = generate_typst(&doc).unwrap();
assert!(output.source.contains(r#"#link("https://example.com")["#));
assert!(output.source.contains("#text(weight: \"bold\")"));
}
#[test]
fn test_hyperlink_mixed_with_plain_text() {
let doc = make_doc(vec![make_flow_page(vec![Block::Paragraph(Paragraph {
style: ParagraphStyle::default(),
runs: vec![
Run {
text: "Visit ".to_string(),
style: TextStyle::default(),
href: None,
footnote: None,
},
Run {
text: "Rust".to_string(),
style: TextStyle::default(),
href: Some("https://rust-lang.org".to_string()),
footnote: None,
},
Run {
text: " for more.".to_string(),
style: TextStyle::default(),
href: None,
footnote: None,
},
],
})])]);
let output = generate_typst(&doc).unwrap();
assert!(output.source.contains("Visit "));
assert!(
output
.source
.contains(r#"#link("https://rust-lang.org")[Rust]"#)
);
assert!(output.source.contains(" for more."));
}
#[test]
fn test_hyperlink_url_with_special_chars_escaped() {
let doc = make_doc(vec![make_flow_page(vec![Block::Paragraph(Paragraph {
style: ParagraphStyle::default(),
runs: vec![Run {
text: "Link".to_string(),
style: TextStyle::default(),
href: Some("https://example.com/path?q=1&r=2".to_string()),
footnote: None,
}],
})])]);
let output = generate_typst(&doc).unwrap();
assert!(
output
.source
.contains(r#"#link("https://example.com/path?q=1&r=2")[Link]"#)
);
}
#[test]
fn test_footnote_generates_typst_footnote() {
let doc = make_doc(vec![make_flow_page(vec![Block::Paragraph(Paragraph {
style: ParagraphStyle::default(),
runs: vec![
Run {
text: "Some text".to_string(),
style: TextStyle::default(),
href: None,
footnote: None,
},
Run {
text: String::new(),
style: TextStyle::default(),
href: None,
footnote: Some("This is a footnote.".to_string()),
},
],
})])]);
let output = generate_typst(&doc).unwrap();
assert!(output.source.contains("#footnote[This is a footnote.]"));
}
#[test]
fn test_footnote_with_special_chars() {
let doc = make_doc(vec![make_flow_page(vec![Block::Paragraph(Paragraph {
style: ParagraphStyle::default(),
runs: vec![Run {
text: String::new(),
style: TextStyle::default(),
href: None,
footnote: Some("Note with #special *chars*".to_string()),
}],
})])]);
let output = generate_typst(&doc).unwrap();
assert!(
output
.source
.contains(r"#footnote[Note with \#special \*chars\*]")
);
}
#[test]
fn test_table_page_with_header() {
let page = Page::Sheet(SheetPage {
name: "Sheet1".to_string(),
size: PageSize::default(),
margins: Margins::default(),
table: make_simple_table(vec![vec!["A"]]),
header: Some(HeaderFooter {
paragraphs: vec![HeaderFooterParagraph {
style: ParagraphStyle {
alignment: Some(Alignment::Center),
..ParagraphStyle::default()
},
elements: vec![HFInline::Run(Run {
text: "My Header".to_string(),
style: TextStyle::default(),
href: None,
footnote: None,
})],
}],
}),
footer: None,
charts: vec![],
});
let doc = make_doc(vec![page]);
let output = generate_typst(&doc).unwrap();
assert!(output.source.contains("header: ["));
assert!(output.source.contains("My Header"));
}
#[test]
fn test_table_page_with_page_number_footer() {
let page = Page::Sheet(SheetPage {
name: "Sheet1".to_string(),
size: PageSize::default(),
margins: Margins::default(),
table: make_simple_table(vec![vec!["A"]]),
header: None,
footer: Some(HeaderFooter {
paragraphs: vec![HeaderFooterParagraph {
style: ParagraphStyle {
alignment: Some(Alignment::Center),
..ParagraphStyle::default()
},
elements: vec![
HFInline::Run(Run {
text: "Page ".to_string(),
style: TextStyle::default(),
href: None,
footnote: None,
}),
HFInline::PageNumber,
HFInline::Run(Run {
text: " of ".to_string(),
style: TextStyle::default(),
href: None,
footnote: None,
}),
HFInline::TotalPages,
],
}],
}),
charts: vec![],
});
let doc = make_doc(vec![page]);
let output = generate_typst(&doc).unwrap();
assert!(output.source.contains("footer: context ["));
assert!(output.source.contains("#counter(page).display()"));
assert!(output.source.contains("#counter(page).final().first()"));
}
#[test]
fn test_table_page_no_header_footer() {
let page = Page::Sheet(SheetPage {
name: "Sheet1".to_string(),
size: PageSize::default(),
margins: Margins::default(),
table: make_simple_table(vec![vec!["A"]]),
header: None,
footer: None,
charts: vec![],
});
let doc = make_doc(vec![page]);
let output = generate_typst(&doc).unwrap();
assert!(!output.source.contains("header:"));
assert!(!output.source.contains("footer:"));
}
#[test]
fn test_table_page_with_chart_at_row() {
use crate::ir::{Chart, ChartSeries, ChartType};
let chart = Chart {
chart_type: ChartType::Bar,
title: Some("Sales".to_string()),
categories: vec!["Q1".to_string(), "Q2".to_string()],
series: vec![ChartSeries {
name: Some("Revenue".to_string()),
values: vec![100.0, 200.0],
}],
};
let page = Page::Sheet(SheetPage {
name: "Sheet1".to_string(),
size: PageSize::default(),
margins: Margins::default(),
table: make_simple_table(vec![
vec!["Row 1"],
vec!["Row 2"],
vec!["Row 3"],
vec!["Row 4"],
vec!["Row 5"],
]),
header: None,
footer: None,
charts: vec![(2, chart)],
});
let doc = make_doc(vec![page]);
let output = generate_typst(&doc).unwrap();
let src = &output.source;
assert_eq!(src.matches("#table(").count(), 2);
assert!(src.contains("Sales"));
}
#[test]
fn test_table_page_with_chart_at_end() {
use crate::ir::{Chart, ChartSeries, ChartType};
let chart = Chart {
chart_type: ChartType::Pie,
title: Some("Pie".to_string()),
categories: vec!["A".to_string()],
series: vec![ChartSeries {
name: None,
values: vec![100.0],
}],
};
let page = Page::Sheet(SheetPage {
name: "Sheet1".to_string(),
size: PageSize::default(),
margins: Margins::default(),
table: make_simple_table(vec![vec!["Data"]]),
header: None,
footer: None,
charts: vec![(u32::MAX, chart)],
});
let doc = make_doc(vec![page]);
let output = generate_typst(&doc).unwrap();
let src = &output.source;
let table_pos = src.find("#table(").unwrap();
let chart_pos = src.find("Pie").unwrap();
assert!(table_pos < chart_pos);
}
#[test]
fn test_paper_size_override_letter() {
use crate::config::PaperSize;
let doc = make_doc(vec![make_flow_page(vec![make_paragraph("Test")])]);
let options = ConvertOptions {
paper_size: Some(PaperSize::Letter),
..Default::default()
};
let output = generate_typst_with_options(&doc, &options).unwrap();
assert!(output.source.contains("width: 612pt"));
assert!(output.source.contains("height: 792pt"));
}
#[test]
fn test_landscape_override_swaps_dimensions() {
let doc = make_doc(vec![make_flow_page(vec![make_paragraph("Test")])]);
let options = ConvertOptions {
landscape: Some(true),
..Default::default()
};
let output = generate_typst_with_options(&doc, &options).unwrap();
assert!(output.source.contains("width: 841.89pt"));
assert!(output.source.contains("height: 595.28pt"));
}
#[test]
fn test_portrait_override_keeps_portrait() {
let doc = make_doc(vec![make_flow_page(vec![make_paragraph("Test")])]);
let options = ConvertOptions {
landscape: Some(false),
..Default::default()
};
let output = generate_typst_with_options(&doc, &options).unwrap();
assert!(output.source.contains("width: 595.28pt"));
assert!(output.source.contains("height: 841.89pt"));
}
#[test]
fn test_paper_size_with_landscape() {
use crate::config::PaperSize;
let doc = make_doc(vec![make_flow_page(vec![make_paragraph("Test")])]);
let options = ConvertOptions {
paper_size: Some(PaperSize::Letter),
landscape: Some(true),
..Default::default()
};
let output = generate_typst_with_options(&doc, &options).unwrap();
assert!(output.source.contains("width: 792pt"));
assert!(output.source.contains("height: 612pt"));
}
#[test]
fn test_no_override_uses_original_size() {
let doc = make_doc(vec![make_flow_page(vec![make_paragraph("Test")])]);
let options = ConvertOptions::default();
let output = generate_typst_with_options(&doc, &options).unwrap();
assert!(output.source.contains("width: 595.28pt"));
}