mirror of
https://github.com/duhanbalci/dreport.git
synced 2026-07-01 18:39:16 +00:00
fmt
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
#![cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
use dreport_core::models::*;
|
||||
use dreport_layout::{compute_layout, LayoutResult, ResolvedContent};
|
||||
use dreport_layout::{LayoutResult, ResolvedContent, compute_layout};
|
||||
|
||||
mod common;
|
||||
use common::load_test_fonts;
|
||||
@@ -17,7 +17,10 @@ fn base_template() -> Template {
|
||||
Template {
|
||||
id: "imp_test".to_string(),
|
||||
name: "Improvements Test".to_string(),
|
||||
page: PageSettings { width: 210.0, height: 297.0 },
|
||||
page: PageSettings {
|
||||
width: 210.0,
|
||||
height: 297.0,
|
||||
},
|
||||
fonts: vec!["Noto Sans".to_string()],
|
||||
header: None,
|
||||
footer: None,
|
||||
@@ -28,7 +31,12 @@ fn base_template() -> Template {
|
||||
size: SizeConstraint::default(),
|
||||
direction: "column".to_string(),
|
||||
gap: 5.0,
|
||||
padding: Padding { top: 15.0, right: 15.0, bottom: 15.0, left: 15.0 },
|
||||
padding: Padding {
|
||||
top: 15.0,
|
||||
right: 15.0,
|
||||
bottom: 15.0,
|
||||
left: 15.0,
|
||||
},
|
||||
align: "stretch".to_string(),
|
||||
justify: "start".to_string(),
|
||||
style: ContainerStyle::default(),
|
||||
@@ -63,7 +71,11 @@ fn test_1_2_text_wrapping_layout_height() {
|
||||
|
||||
let fonts = load_test_fonts();
|
||||
let result = compute_layout(&tpl, &serde_json::json!({}), &fonts).unwrap();
|
||||
let el = result.pages[0].elements.iter().find(|e| e.id == "long_text").unwrap();
|
||||
let el = result.pages[0]
|
||||
.elements
|
||||
.iter()
|
||||
.find(|e| e.id == "long_text")
|
||||
.unwrap();
|
||||
|
||||
// Tek satır ~4.2mm olur (12pt * 1.2 line-height ≈ 5mm).
|
||||
// Sarılmış metin daha yüksek olmalı.
|
||||
@@ -125,7 +137,11 @@ fn test_1_3_image_object_fit_in_layout() {
|
||||
|
||||
let fonts = load_test_fonts();
|
||||
let result = compute_layout(&tpl, &serde_json::json!({}), &fonts).unwrap();
|
||||
let el = result.pages[0].elements.iter().find(|e| e.id == "img_contain").unwrap();
|
||||
let el = result.pages[0]
|
||||
.elements
|
||||
.iter()
|
||||
.find(|e| e.id == "img_contain")
|
||||
.unwrap();
|
||||
|
||||
// objectFit style'da taşınmalı
|
||||
assert_eq!(
|
||||
@@ -143,27 +159,33 @@ fn test_1_3_image_object_fit_in_layout() {
|
||||
fn test_1_4_italic_font_in_pdf() {
|
||||
// fontStyle: italic ile PDF render — crash olmamalı
|
||||
let mut tpl = base_template();
|
||||
tpl.root.children.push(TemplateElement::StaticText(StaticTextElement {
|
||||
id: "italic_text".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
style: TextStyle {
|
||||
font_size: Some(12.0),
|
||||
font_style: Some("italic".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
content: "Bu metin italic olmalı".to_string(),
|
||||
}));
|
||||
tpl.root
|
||||
.children
|
||||
.push(TemplateElement::StaticText(StaticTextElement {
|
||||
id: "italic_text".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
style: TextStyle {
|
||||
font_size: Some(12.0),
|
||||
font_style: Some("italic".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
content: "Bu metin italic olmalı".to_string(),
|
||||
}));
|
||||
|
||||
let fonts = load_test_fonts();
|
||||
let layout = compute_layout(&tpl, &serde_json::json!({}), &fonts).unwrap();
|
||||
|
||||
// fontStyle layout result'ta korunmalı
|
||||
let el = layout.pages[0].elements.iter().find(|e| e.id == "italic_text").unwrap();
|
||||
let el = layout.pages[0]
|
||||
.elements
|
||||
.iter()
|
||||
.find(|e| e.id == "italic_text")
|
||||
.unwrap();
|
||||
assert_eq!(el.style.font_style.as_deref(), Some("italic"));
|
||||
|
||||
// PDF render crash olmamalı
|
||||
@@ -174,22 +196,24 @@ fn test_1_4_italic_font_in_pdf() {
|
||||
#[test]
|
||||
fn test_1_4_bold_italic_font_in_pdf() {
|
||||
let mut tpl = base_template();
|
||||
tpl.root.children.push(TemplateElement::StaticText(StaticTextElement {
|
||||
id: "bold_italic".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
style: TextStyle {
|
||||
font_size: Some(14.0),
|
||||
font_weight: Some("bold".to_string()),
|
||||
font_style: Some("italic".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
content: "Bold Italic Test".to_string(),
|
||||
}));
|
||||
tpl.root
|
||||
.children
|
||||
.push(TemplateElement::StaticText(StaticTextElement {
|
||||
id: "bold_italic".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
style: TextStyle {
|
||||
font_size: Some(14.0),
|
||||
font_weight: Some("bold".to_string()),
|
||||
font_style: Some("italic".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
content: "Bold Italic Test".to_string(),
|
||||
}));
|
||||
|
||||
let fonts = load_test_fonts();
|
||||
let layout = compute_layout(&tpl, &serde_json::json!({}), &fonts).unwrap();
|
||||
@@ -205,28 +229,30 @@ fn test_1_4_bold_italic_font_in_pdf() {
|
||||
fn test_2_1_repeat_header_false_no_repeat_on_second_page() {
|
||||
// repeat_header: false olan tablo, 2. sayfada header tekrarlamamalı
|
||||
let mut tpl = base_template();
|
||||
tpl.root.children.push(TemplateElement::RepeatingTable(RepeatingTableElement {
|
||||
id: "tbl_no_repeat".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
data_source: ArrayBinding { path: "items".to_string() },
|
||||
columns: vec![
|
||||
TableColumn {
|
||||
tpl.root
|
||||
.children
|
||||
.push(TemplateElement::RepeatingTable(RepeatingTableElement {
|
||||
id: "tbl_no_repeat".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
data_source: ArrayBinding {
|
||||
path: "items".to_string(),
|
||||
},
|
||||
columns: vec![TableColumn {
|
||||
id: "col_name".to_string(),
|
||||
field: "name".to_string(),
|
||||
title: "Name".to_string(),
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
align: "left".to_string(),
|
||||
format: None,
|
||||
},
|
||||
],
|
||||
style: TableStyle::default(),
|
||||
repeat_header: Some(false), // Header tekrarlanmasın
|
||||
}));
|
||||
}],
|
||||
style: TableStyle::default(),
|
||||
repeat_header: Some(false), // Header tekrarlanmasın
|
||||
}));
|
||||
|
||||
// Çok sayıda satır — sayfa taşması için
|
||||
let items: Vec<serde_json::Value> = (0..80)
|
||||
@@ -253,9 +279,9 @@ fn test_2_1_repeat_header_false_no_repeat_on_second_page() {
|
||||
.collect();
|
||||
|
||||
// Header row'u "tbl_no_repeat_header" pattern'inde olmalı, 2. sayfada bulunmamalı
|
||||
let has_header_clone = page2_ids.iter().any(|id| {
|
||||
id.contains("header") && id.contains("tbl_no_repeat") && id.contains("_p")
|
||||
});
|
||||
let has_header_clone = page2_ids
|
||||
.iter()
|
||||
.any(|id| id.contains("header") && id.contains("tbl_no_repeat") && id.contains("_p"));
|
||||
|
||||
assert!(
|
||||
!has_header_clone,
|
||||
@@ -268,28 +294,30 @@ fn test_2_1_repeat_header_false_no_repeat_on_second_page() {
|
||||
fn test_2_1_repeat_header_true_repeats_on_second_page() {
|
||||
// repeat_header: true (varsayılan) olan tablo, 2. sayfada header tekrarlamalı
|
||||
let mut tpl = base_template();
|
||||
tpl.root.children.push(TemplateElement::RepeatingTable(RepeatingTableElement {
|
||||
id: "tbl_repeat".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
data_source: ArrayBinding { path: "items".to_string() },
|
||||
columns: vec![
|
||||
TableColumn {
|
||||
tpl.root
|
||||
.children
|
||||
.push(TemplateElement::RepeatingTable(RepeatingTableElement {
|
||||
id: "tbl_repeat".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
data_source: ArrayBinding {
|
||||
path: "items".to_string(),
|
||||
},
|
||||
columns: vec![TableColumn {
|
||||
id: "col_name".to_string(),
|
||||
field: "name".to_string(),
|
||||
title: "Name".to_string(),
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
align: "left".to_string(),
|
||||
format: None,
|
||||
},
|
||||
],
|
||||
style: TableStyle::default(),
|
||||
repeat_header: Some(true),
|
||||
}));
|
||||
}],
|
||||
style: TableStyle::default(),
|
||||
repeat_header: Some(true),
|
||||
}));
|
||||
|
||||
let items: Vec<serde_json::Value> = (0..80)
|
||||
.map(|i| serde_json::json!({ "name": format!("Item {}", i) }))
|
||||
@@ -308,9 +336,9 @@ fn test_2_1_repeat_header_true_repeats_on_second_page() {
|
||||
.map(|e| e.id.as_str())
|
||||
.collect();
|
||||
|
||||
let has_header_clone = page2_ids.iter().any(|id| {
|
||||
id.contains("tbl_repeat_header") || id.contains("tbl_repeat_hdr")
|
||||
});
|
||||
let has_header_clone = page2_ids
|
||||
.iter()
|
||||
.any(|id| id.contains("tbl_repeat_header") || id.contains("tbl_repeat_hdr"));
|
||||
|
||||
// Eğer header tekrarı yoksa, en azından repeat_header_false testi ile
|
||||
// davranış farkını doğrulayalım: repeat=true olan tabloda page 2 header
|
||||
@@ -320,10 +348,17 @@ fn test_2_1_repeat_header_true_repeats_on_second_page() {
|
||||
if !has_header_clone {
|
||||
// Fallback: page 2'deki ilk elemanın y_mm'si, page 1'deki header yüksekliği
|
||||
// kadar offset'li olmalı (header için yer ayrılmış)
|
||||
let page1_header = result.pages[0].elements.iter().find(|e| e.id.contains("header"));
|
||||
let page1_header = result.pages[0]
|
||||
.elements
|
||||
.iter()
|
||||
.find(|e| e.id.contains("header"));
|
||||
if let Some(hdr) = page1_header {
|
||||
// Page 2 ilk elemanın y'si > 0 olmalı (header alanı ayrılmış)
|
||||
let page2_first_y = result.pages[1].elements.first().map(|e| e.y_mm).unwrap_or(0.0);
|
||||
let page2_first_y = result.pages[1]
|
||||
.elements
|
||||
.first()
|
||||
.map(|e| e.y_mm)
|
||||
.unwrap_or(0.0);
|
||||
// Header tekrarlanıyorsa page 2'de header yüksekliği kadar shift var
|
||||
assert!(
|
||||
page2_first_y > 0.0 || has_header_clone,
|
||||
@@ -342,36 +377,40 @@ fn test_2_1_repeat_header_true_repeats_on_second_page() {
|
||||
#[test]
|
||||
fn test_2_2_table_column_format_currency() {
|
||||
let mut tpl = base_template();
|
||||
tpl.root.children.push(TemplateElement::RepeatingTable(RepeatingTableElement {
|
||||
id: "tbl_fmt".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
data_source: ArrayBinding { path: "items".to_string() },
|
||||
columns: vec![
|
||||
TableColumn {
|
||||
id: "col_name".to_string(),
|
||||
field: "name".to_string(),
|
||||
title: "Ürün".to_string(),
|
||||
tpl.root
|
||||
.children
|
||||
.push(TemplateElement::RepeatingTable(RepeatingTableElement {
|
||||
id: "tbl_fmt".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
align: "left".to_string(),
|
||||
format: None,
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
TableColumn {
|
||||
id: "col_price".to_string(),
|
||||
field: "price".to_string(),
|
||||
title: "Fiyat".to_string(),
|
||||
width: SizeValue::Fixed { value: 30.0 },
|
||||
align: "right".to_string(),
|
||||
format: Some("currency".to_string()),
|
||||
data_source: ArrayBinding {
|
||||
path: "items".to_string(),
|
||||
},
|
||||
],
|
||||
style: TableStyle::default(),
|
||||
repeat_header: Some(true),
|
||||
}));
|
||||
columns: vec![
|
||||
TableColumn {
|
||||
id: "col_name".to_string(),
|
||||
field: "name".to_string(),
|
||||
title: "Ürün".to_string(),
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
align: "left".to_string(),
|
||||
format: None,
|
||||
},
|
||||
TableColumn {
|
||||
id: "col_price".to_string(),
|
||||
field: "price".to_string(),
|
||||
title: "Fiyat".to_string(),
|
||||
width: SizeValue::Fixed { value: 30.0 },
|
||||
align: "right".to_string(),
|
||||
format: Some("currency".to_string()),
|
||||
},
|
||||
],
|
||||
style: TableStyle::default(),
|
||||
repeat_header: Some(true),
|
||||
}));
|
||||
|
||||
let data = serde_json::json!({
|
||||
"items": [
|
||||
@@ -431,7 +470,11 @@ fn test_2_3_rounded_rectangle_renders() {
|
||||
let layout = compute_layout(&tpl, &serde_json::json!({}), &fonts).unwrap();
|
||||
|
||||
// Shape element mevcut olmalı
|
||||
let el = layout.pages[0].elements.iter().find(|e| e.id == "rounded_shape").unwrap();
|
||||
let el = layout.pages[0]
|
||||
.elements
|
||||
.iter()
|
||||
.find(|e| e.id == "rounded_shape")
|
||||
.unwrap();
|
||||
assert_eq!(el.element_type, "shape");
|
||||
assert_eq!(el.style.border_radius, Some(5.0));
|
||||
|
||||
@@ -448,17 +491,22 @@ fn test_2_3_container_border_radius_renders() {
|
||||
tpl.root.style.border_color = Some("#333".to_string());
|
||||
tpl.root.style.border_width = Some(0.5);
|
||||
|
||||
tpl.root.children.push(TemplateElement::StaticText(StaticTextElement {
|
||||
id: "text_in_rounded".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
style: TextStyle { font_size: Some(12.0), ..Default::default() },
|
||||
content: "Rounded container".to_string(),
|
||||
}));
|
||||
tpl.root
|
||||
.children
|
||||
.push(TemplateElement::StaticText(StaticTextElement {
|
||||
id: "text_in_rounded".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
style: TextStyle {
|
||||
font_size: Some(12.0),
|
||||
..Default::default()
|
||||
},
|
||||
content: "Rounded container".to_string(),
|
||||
}));
|
||||
|
||||
let fonts = load_test_fonts();
|
||||
let layout = compute_layout(&tpl, &serde_json::json!({}), &fonts).unwrap();
|
||||
@@ -499,7 +547,8 @@ fn test_2_7_format_config_custom() {
|
||||
currency_symbol: "$".to_string(),
|
||||
currency_position: "prefix".to_string(),
|
||||
};
|
||||
let formatted = dreport_layout::expr_eval::apply_format_with_config("18880", Some("currency"), &config);
|
||||
let formatted =
|
||||
dreport_layout::expr_eval::apply_format_with_config("18880", Some("currency"), &config);
|
||||
assert_eq!(formatted, "$18,880.00");
|
||||
}
|
||||
|
||||
@@ -511,7 +560,8 @@ fn test_2_7_format_config_number() {
|
||||
currency_symbol: "€".to_string(),
|
||||
currency_position: "suffix".to_string(),
|
||||
};
|
||||
let formatted = dreport_layout::expr_eval::apply_format_with_config("1234567", Some("number"), &config);
|
||||
let formatted =
|
||||
dreport_layout::expr_eval::apply_format_with_config("1234567", Some("number"), &config);
|
||||
assert_eq!(formatted, "1 234 567");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Integration tests for the layout engine's compute_layout() public API.
|
||||
|
||||
use dreport_core::models::*;
|
||||
use dreport_layout::{compute_layout, LayoutResult};
|
||||
use dreport_layout::{LayoutResult, compute_layout};
|
||||
|
||||
mod common;
|
||||
use common::load_test_fonts;
|
||||
@@ -205,11 +205,7 @@ fn test_compute_layout_with_data_binding() {
|
||||
let result = compute_layout(&template, &data, &fonts).unwrap();
|
||||
let page = &result.pages[0];
|
||||
|
||||
let bound = page
|
||||
.elements
|
||||
.iter()
|
||||
.find(|e| e.id == "bound_text")
|
||||
.unwrap();
|
||||
let bound = page.elements.iter().find(|e| e.id == "bound_text").unwrap();
|
||||
match &bound.content {
|
||||
Some(dreport_layout::ResolvedContent::Text { value }) => {
|
||||
assert_eq!(value, "Acme Corp");
|
||||
|
||||
@@ -66,10 +66,7 @@ fn test_render_pdf_produces_valid_output() {
|
||||
let pdf_bytes = dreport_layout::pdf_render::render_pdf(&layout, &fonts).unwrap();
|
||||
|
||||
// PDF should not be empty
|
||||
assert!(
|
||||
!pdf_bytes.is_empty(),
|
||||
"PDF output should not be empty"
|
||||
);
|
||||
assert!(!pdf_bytes.is_empty(), "PDF output should not be empty");
|
||||
|
||||
// PDF should start with %PDF magic bytes
|
||||
assert!(
|
||||
@@ -239,7 +236,10 @@ fn test_page_break_produces_multiple_pages() {
|
||||
let template = Template {
|
||||
id: "pb_test".to_string(),
|
||||
name: "Page Break Test".to_string(),
|
||||
page: PageSettings { width: 210.0, height: 297.0 },
|
||||
page: PageSettings {
|
||||
width: 210.0,
|
||||
height: 297.0,
|
||||
},
|
||||
fonts: vec!["Noto Sans".to_string()],
|
||||
header: None,
|
||||
footer: None,
|
||||
@@ -250,7 +250,12 @@ fn test_page_break_produces_multiple_pages() {
|
||||
size: SizeConstraint::default(),
|
||||
direction: "column".to_string(),
|
||||
gap: 5.0,
|
||||
padding: Padding { top: 15.0, right: 15.0, bottom: 15.0, left: 15.0 },
|
||||
padding: Padding {
|
||||
top: 15.0,
|
||||
right: 15.0,
|
||||
bottom: 15.0,
|
||||
left: 15.0,
|
||||
},
|
||||
align: "stretch".to_string(),
|
||||
justify: "start".to_string(),
|
||||
style: ContainerStyle::default(),
|
||||
@@ -259,16 +264,32 @@ fn test_page_break_produces_multiple_pages() {
|
||||
TemplateElement::StaticText(StaticTextElement {
|
||||
id: "t1".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint { width: SizeValue::Fr { value: 1.0 }, height: SizeValue::Auto, ..Default::default() },
|
||||
style: TextStyle { font_size: Some(18.0), ..Default::default() },
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
style: TextStyle {
|
||||
font_size: Some(18.0),
|
||||
..Default::default()
|
||||
},
|
||||
content: "Page 1 content".to_string(),
|
||||
}),
|
||||
TemplateElement::PageBreak(PageBreakElement { id: "pb1".to_string() }),
|
||||
TemplateElement::PageBreak(PageBreakElement {
|
||||
id: "pb1".to_string(),
|
||||
}),
|
||||
TemplateElement::StaticText(StaticTextElement {
|
||||
id: "t2".to_string(),
|
||||
position: PositionMode::Flow,
|
||||
size: SizeConstraint { width: SizeValue::Fr { value: 1.0 }, height: SizeValue::Auto, ..Default::default() },
|
||||
style: TextStyle { font_size: Some(18.0), ..Default::default() },
|
||||
size: SizeConstraint {
|
||||
width: SizeValue::Fr { value: 1.0 },
|
||||
height: SizeValue::Auto,
|
||||
..Default::default()
|
||||
},
|
||||
style: TextStyle {
|
||||
font_size: Some(18.0),
|
||||
..Default::default()
|
||||
},
|
||||
content: "Page 2 content".to_string(),
|
||||
}),
|
||||
],
|
||||
@@ -277,32 +298,43 @@ fn test_page_break_produces_multiple_pages() {
|
||||
|
||||
let data = serde_json::json!({});
|
||||
let fonts = load_test_fonts();
|
||||
|
||||
|
||||
let layout = compute_layout(&template, &data, &fonts).unwrap();
|
||||
|
||||
|
||||
println!("Layout pages: {}", layout.pages.len());
|
||||
for (i, page) in layout.pages.iter().enumerate() {
|
||||
println!("Page {}: {} elements", i, page.elements.len());
|
||||
for el in &page.elements {
|
||||
println!(" - {} (type={}, y={:.1}mm, h={:.1}mm)", el.id, el.element_type, el.y_mm, el.height_mm);
|
||||
println!(
|
||||
" - {} (type={}, y={:.1}mm, h={:.1}mm)",
|
||||
el.id, el.element_type, el.y_mm, el.height_mm
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
assert_eq!(layout.pages.len(), 2, "Page break should produce 2 pages");
|
||||
|
||||
|
||||
// Verify page 1 has t1 and page 2 has t2
|
||||
let p1_ids: Vec<&str> = layout.pages[0].elements.iter().map(|e| e.id.as_str()).collect();
|
||||
let p2_ids: Vec<&str> = layout.pages[1].elements.iter().map(|e| e.id.as_str()).collect();
|
||||
let p1_ids: Vec<&str> = layout.pages[0]
|
||||
.elements
|
||||
.iter()
|
||||
.map(|e| e.id.as_str())
|
||||
.collect();
|
||||
let p2_ids: Vec<&str> = layout.pages[1]
|
||||
.elements
|
||||
.iter()
|
||||
.map(|e| e.id.as_str())
|
||||
.collect();
|
||||
println!("Page 1 IDs: {:?}", p1_ids);
|
||||
println!("Page 2 IDs: {:?}", p2_ids);
|
||||
|
||||
|
||||
assert!(p1_ids.contains(&"t1"), "Page 1 should contain t1");
|
||||
assert!(p2_ids.contains(&"t2"), "Page 2 should contain t2");
|
||||
|
||||
|
||||
// Render PDF and verify it's valid
|
||||
let pdf_bytes = dreport_layout::pdf_render::render_pdf(&layout, &fonts).unwrap();
|
||||
assert!(pdf_bytes.starts_with(b"%PDF"));
|
||||
|
||||
|
||||
// Write PDF to temp dir for manual inspection
|
||||
let out_path = std::env::temp_dir().join("dreport_test_page_break.pdf");
|
||||
std::fs::write(&out_path, &pdf_bytes).unwrap();
|
||||
|
||||
@@ -15,8 +15,8 @@ mod visual {
|
||||
use std::process::Command;
|
||||
|
||||
use dreport_core::models::Template;
|
||||
use dreport_layout::{compute_layout, ResolvedContent};
|
||||
use dreport_layout::pdf_render::render_pdf;
|
||||
use dreport_layout::{ResolvedContent, compute_layout};
|
||||
|
||||
use crate::common::load_test_fonts;
|
||||
|
||||
@@ -101,11 +101,10 @@ mod visual {
|
||||
|
||||
for (a, r) in actual_rgba.pixels().zip(reference_rgba.pixels()) {
|
||||
// Allow per-channel tolerance of 2 for font rendering differences
|
||||
let channel_diff = a
|
||||
.0
|
||||
.iter()
|
||||
.zip(r.0.iter())
|
||||
.any(|(ac, rc)| (*ac as i32 - *rc as i32).unsigned_abs() > 2);
|
||||
let channel_diff =
|
||||
a.0.iter()
|
||||
.zip(r.0.iter())
|
||||
.any(|(ac, rc)| (*ac as i32 - *rc as i32).unsigned_abs() > 2);
|
||||
if channel_diff {
|
||||
diff_pixels += 1;
|
||||
}
|
||||
@@ -181,7 +180,9 @@ mod visual {
|
||||
|
||||
let layout = compute_layout(&template, &data, &fonts).unwrap();
|
||||
|
||||
let mut html = String::from("<!DOCTYPE html><html><head><style>body{margin:20px;font-family:sans-serif;background:#f5f5f5}.chart-box{margin:10px 0;background:white;box-shadow:0 1px 3px rgba(0,0,0,.1)}</style></head><body><h2>Chart SVG Preview (HTML render)</h2>");
|
||||
let mut html = String::from(
|
||||
"<!DOCTYPE html><html><head><style>body{margin:20px;font-family:sans-serif;background:#f5f5f5}.chart-box{margin:10px 0;background:white;box-shadow:0 1px 3px rgba(0,0,0,.1)}</style></head><body><h2>Chart SVG Preview (HTML render)</h2>",
|
||||
);
|
||||
|
||||
for page in &layout.pages {
|
||||
for el in &page.elements {
|
||||
@@ -212,9 +213,21 @@ mod visual {
|
||||
#[ignore]
|
||||
fn generate_cross_renderer_refs() {
|
||||
let fixtures = [
|
||||
("visual_test_template.json", "visual_test_data.json", "visual_test"),
|
||||
("chart_test_template.json", "chart_test_data.json", "chart_test"),
|
||||
("comprehensive_test_template.json", "comprehensive_test_data.json", "comprehensive_test"),
|
||||
(
|
||||
"visual_test_template.json",
|
||||
"visual_test_data.json",
|
||||
"visual_test",
|
||||
),
|
||||
(
|
||||
"chart_test_template.json",
|
||||
"chart_test_data.json",
|
||||
"chart_test",
|
||||
),
|
||||
(
|
||||
"comprehensive_test_template.json",
|
||||
"comprehensive_test_data.json",
|
||||
"comprehensive_test",
|
||||
),
|
||||
];
|
||||
|
||||
let out_dir = cross_renderer_dir();
|
||||
@@ -222,7 +235,11 @@ mod visual {
|
||||
|
||||
for (template_file, data_file, name) in &fixtures {
|
||||
let pdf_bytes = generate_test_pdf(template_file, data_file);
|
||||
assert!(!pdf_bytes.is_empty(), "PDF should not be empty for {}", name);
|
||||
assert!(
|
||||
!pdf_bytes.is_empty(),
|
||||
"PDF should not be empty for {}",
|
||||
name
|
||||
);
|
||||
|
||||
let png_path = out_dir.join(format!("{}.png", name));
|
||||
if !pdf_to_png(&pdf_bytes, &png_path) {
|
||||
@@ -234,7 +251,11 @@ mod visual {
|
||||
|
||||
#[test]
|
||||
fn test_visual_snapshot_basic() {
|
||||
run_visual_test("visual_test_template.json", "visual_test_data.json", "visual_test");
|
||||
run_visual_test(
|
||||
"visual_test_template.json",
|
||||
"visual_test_data.json",
|
||||
"visual_test",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -252,10 +273,18 @@ mod visual {
|
||||
|
||||
// SVG HTML ciktisini kaydet (karsilastirma icin)
|
||||
let html_path = snap_dir.join("chart_test_svg.html");
|
||||
generate_chart_svg_html("chart_test_template.json", "chart_test_data.json", &html_path);
|
||||
generate_chart_svg_html(
|
||||
"chart_test_template.json",
|
||||
"chart_test_data.json",
|
||||
&html_path,
|
||||
);
|
||||
println!("Chart SVG HTML saved to {:?}", html_path);
|
||||
|
||||
// Visual regression test
|
||||
run_visual_test("chart_test_template.json", "chart_test_data.json", "chart_test");
|
||||
run_visual_test(
|
||||
"chart_test_template.json",
|
||||
"chart_test_data.json",
|
||||
"chart_test",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user