use super::*;
#[test]
fn control_to_block_image_produces_image_block() {
let ctrl = HwpControl::Image {
bin_data_id: 7,
width: 100,
height: 200,
};
let doc_info = DocInfo::default();
let block = control_to_block(&ctrl, &doc_info).expect("Some");
assert!(
matches!(block, ir::Block::Image { ref src, .. } if src == "image_7.bin"),
"expected Image block with src=image_7.bin, got {block:?}"
);
}
#[test]
fn control_to_block_empty_table_produces_table_block() {
let ctrl = HwpControl::Table {
row_count: 2,
col_count: 3,
cells: Vec::new(),
};
let doc_info = DocInfo::default();
let block = control_to_block(&ctrl, &doc_info).expect("Some");
assert!(matches!(block, ir::Block::Table { col_count: 3, .. }));
}
#[test]
fn control_to_block_footnote_produces_footnote_block() {
let ctrl = HwpControl::FootnoteEndnote {
is_endnote: false,
paragraphs: Vec::new(),
};
let doc_info = DocInfo::default();
let block = control_to_block(&ctrl, &doc_info).expect("Some");
assert!(matches!(block, ir::Block::Footnote { .. }));
}
#[test]
fn control_to_block_page_break_returns_none() {
let ctrl = HwpControl::PageBreak;
let doc_info = DocInfo::default();
assert!(control_to_block(&ctrl, &doc_info).is_none());
}
#[test]
fn control_to_block_table_groups_cells_into_rows() {
let make_cell = |row: u16, col: u16, text: &str| HwpTableCell {
row,
col,
row_span: 1,
col_span: 1,
vertical_align: 0,
is_header: row == 0,
paragraphs: vec![HwpParagraph {
text: text.to_string(),
char_shape_ids: Vec::new(),
para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
}],
};
let ctrl = HwpControl::Table {
row_count: 2,
col_count: 2,
cells: vec![
make_cell(0, 0, "r0c0"),
make_cell(0, 1, "r0c1"),
make_cell(1, 0, "r1c0"),
make_cell(1, 1, "r1c1"),
],
};
let doc_info = DocInfo::default();
let block = control_to_block(&ctrl, &doc_info).expect("Some");
if let ir::Block::Table { rows, col_count } = block {
assert_eq!(col_count, 2);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].cells.len(), 2);
assert_eq!(rows[1].cells.len(), 2);
assert!(rows[0].is_header);
assert!(!rows[1].is_header);
} else {
panic!("Expected Table block");
}
}
#[test]
fn control_to_block_caps_malformed_row_index() {
let ctrl = HwpControl::Table {
row_count: 1,
col_count: 1,
cells: vec![HwpTableCell {
row: 50_000, col: 0,
row_span: 1,
col_span: 1,
vertical_align: 0,
is_header: false,
paragraphs: vec![],
}],
};
let doc_info = DocInfo::default();
let block = control_to_block(&ctrl, &doc_info).expect("Some");
if let ir::Block::Table { rows, .. } = block {
assert!(rows.len() <= 10_000);
} else {
panic!("Expected Table block");
}
}
#[test]
fn control_to_block_hyperlink_with_url_produces_paragraph() {
let ctrl = HwpControl::Hyperlink {
url: "https://example.com".into(),
};
let doc_info = DocInfo::default();
let block = control_to_block(&ctrl, &doc_info).expect("Some");
if let ir::Block::Paragraph { inlines } = block {
assert_eq!(inlines.len(), 1);
assert_eq!(inlines[0].text, "https://example.com");
assert_eq!(inlines[0].link.as_deref(), Some("https://example.com"));
} else {
panic!("Expected Paragraph block");
}
}
#[test]
fn control_to_block_hyperlink_empty_url_returns_none() {
let ctrl = HwpControl::Hyperlink { url: String::new() };
let doc_info = DocInfo::default();
assert!(control_to_block(&ctrl, &doc_info).is_none());
}
#[test]
fn control_to_block_hyperlink_javascript_url_rejected() {
let ctrl = HwpControl::Hyperlink {
url: "javascript:alert(1)".into(),
};
let doc_info = DocInfo::default();
assert!(control_to_block(&ctrl, &doc_info).is_none());
}
#[test]
fn guess_mime_png_magic() {
let data = [0x89, b'P', b'N', b'G', 0x00, 0x00];
assert_eq!(guess_mime(&data), "image/png");
}
#[test]
fn guess_mime_jpeg_magic() {
let data = [0xFF, 0xD8, 0xFF, 0xE0, 0x00];
assert_eq!(guess_mime(&data), "image/jpeg");
}
#[test]
fn guess_mime_gif_magic() {
let data = [b'G', b'I', b'F', b'8', b'9', b'a'];
assert_eq!(guess_mime(&data), "image/gif");
}
#[test]
fn guess_mime_bmp_magic() {
let data = [b'B', b'M', 0x00, 0x00];
assert_eq!(guess_mime(&data), "image/bmp");
}
#[test]
fn guess_mime_webp_magic() {
let mut data = b"RIFF".to_vec();
data.extend_from_slice(&[0x00u8; 4]);
data.extend_from_slice(b"WEBP");
assert_eq!(guess_mime(&data), "image/webp");
}
#[test]
fn guess_mime_unknown_returns_octet_stream() {
let data = [0x00, 0x01, 0x02, 0x03, 0x04];
assert_eq!(guess_mime(&data), "application/octet-stream");
}
#[test]
fn guess_mime_too_short_returns_octet_stream() {
assert_eq!(guess_mime(&[0x89]), "application/octet-stream");
assert_eq!(guess_mime(&[]), "application/octet-stream");
}
#[test]
fn mime_to_ext_known_types() {
assert_eq!(mime_to_ext("image/png"), "png");
assert_eq!(mime_to_ext("image/jpeg"), "jpg");
assert_eq!(mime_to_ext("image/gif"), "gif");
assert_eq!(mime_to_ext("image/bmp"), "bmp");
assert_eq!(mime_to_ext("image/webp"), "webp");
}
#[test]
fn mime_to_ext_unknown_returns_bin() {
assert_eq!(mime_to_ext("application/octet-stream"), "bin");
assert_eq!(mime_to_ext("text/plain"), "bin");
}
fn make_para(text: &str, para_shape_id: u16) -> HwpParagraph {
HwpParagraph {
text: text.to_string(),
char_shape_ids: Vec::new(),
para_shape_id,
controls: Vec::new(),
raw_para_text: None,
}
}
fn make_para_with_cs(text: &str, cs_id: u16) -> HwpParagraph {
HwpParagraph {
text: text.to_string(),
char_shape_ids: vec![(0, cs_id)],
para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
}
}
#[test]
fn detect_heading_level_from_para_shape() {
let mut doc_info = DocInfo::default();
let mut ps = ParaShape::default();
ps.heading_type = Some(1); doc_info.para_shapes.push(ps);
let para = make_para("A heading", 0);
assert_eq!(detect_heading_level(¶, &doc_info), Some(2));
}
#[test]
fn detect_heading_level_para_shape_level_0() {
let mut doc_info = DocInfo::default();
let mut ps = ParaShape::default();
ps.heading_type = Some(0); doc_info.para_shapes.push(ps);
let para = make_para("Top heading", 0);
assert_eq!(detect_heading_level(¶, &doc_info), Some(1));
}
#[test]
fn detect_heading_level_para_shape_level_6_clamped() {
let mut doc_info = DocInfo::default();
let mut ps = ParaShape::default();
ps.heading_type = Some(6); doc_info.para_shapes.push(ps);
let para = make_para("level6 heading", 0);
assert_eq!(detect_heading_level(¶, &doc_info), Some(6));
}
#[test]
fn detect_heading_level_para_shape_level_7_rejected() {
let mut doc_info = DocInfo::default();
let mut ps = ParaShape::default();
ps.heading_type = Some(7); doc_info.para_shapes.push(ps);
let para = make_para("not a heading", 0);
assert_eq!(detect_heading_level(¶, &doc_info), None);
}
#[test]
fn detect_heading_level_from_char_shape_bold_large_font() {
let mut doc_info = DocInfo::default();
doc_info.para_shapes.push(ParaShape::default());
let mut cs = CharShape::default();
cs.height = 1600; cs.bold = true;
doc_info.char_shapes.push(cs);
let para = make_para_with_cs("Big bold text", 0);
assert_eq!(detect_heading_level(¶, &doc_info), Some(1));
}
#[test]
fn detect_heading_level_bold_medium_font_returns_h2() {
let mut doc_info = DocInfo::default();
doc_info.para_shapes.push(ParaShape::default());
let mut cs = CharShape::default();
cs.height = 1400; cs.bold = true;
doc_info.char_shapes.push(cs);
let para = make_para_with_cs("Medium bold", 0);
assert_eq!(detect_heading_level(¶, &doc_info), Some(2));
}
#[test]
fn detect_heading_level_bold_small_font_returns_h3() {
let mut doc_info = DocInfo::default();
doc_info.para_shapes.push(ParaShape::default());
let mut cs = CharShape::default();
cs.height = 1200; cs.bold = true;
doc_info.char_shapes.push(cs);
let para = make_para_with_cs("Small bold", 0);
assert_eq!(detect_heading_level(¶, &doc_info), Some(3));
}
#[test]
fn detect_heading_level_not_bold_returns_none() {
let mut doc_info = DocInfo::default();
doc_info.para_shapes.push(ParaShape::default());
let mut cs = CharShape::default();
cs.height = 1600; cs.bold = false;
doc_info.char_shapes.push(cs);
let para = make_para_with_cs("Large not bold", 0);
assert_eq!(detect_heading_level(¶, &doc_info), None);
}
#[test]
fn detect_heading_level_long_text_skips_heuristic() {
let mut doc_info = DocInfo::default();
doc_info.para_shapes.push(ParaShape::default());
let mut cs = CharShape::default();
cs.height = 1600;
cs.bold = true;
doc_info.char_shapes.push(cs);
let long_text = "A".repeat(101);
let para = make_para_with_cs(&long_text, 0);
assert_eq!(detect_heading_level(¶, &doc_info), None);
}
#[test]
fn build_inlines_empty_text_returns_empty() {
let doc_info = DocInfo::default();
let para = make_para("", 0);
let inlines = build_inlines(¶, &doc_info);
assert!(inlines.is_empty());
}
#[test]
fn build_inlines_no_char_shapes_returns_plain_inline() {
let doc_info = DocInfo::default();
let para = make_para("Hello world", 0);
let inlines = build_inlines(¶, &doc_info);
assert_eq!(inlines.len(), 1);
assert_eq!(inlines[0].text, "Hello world");
assert!(!inlines[0].bold);
}
#[test]
fn build_inlines_with_bold_char_shape() {
let mut doc_info = DocInfo::default();
let mut cs = CharShape::default();
cs.bold = true;
doc_info.char_shapes.push(cs);
let para = HwpParagraph {
text: "Bold text".to_string(),
char_shape_ids: vec![(0, 0)], para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
};
let inlines = build_inlines(¶, &doc_info);
assert!(!inlines.is_empty());
assert!(inlines[0].bold);
assert_eq!(inlines[0].text.trim_end_matches('\r'), "Bold text");
}
#[test]
fn build_inlines_with_italic_char_shape() {
let mut doc_info = DocInfo::default();
let mut cs = CharShape::default();
cs.italic = true;
doc_info.char_shapes.push(cs);
let para = HwpParagraph {
text: "Italic text".to_string(),
char_shape_ids: vec![(0, 0)],
para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
};
let inlines = build_inlines(¶, &doc_info);
assert!(!inlines.is_empty());
assert!(inlines[0].italic);
}
#[test]
fn build_inlines_unknown_cs_id_falls_back_to_plain() {
let doc_info = DocInfo::default();
let para = HwpParagraph {
text: "Plain fallback".to_string(),
char_shape_ids: vec![(0, 99)], para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
};
let inlines = build_inlines(¶, &doc_info);
assert!(!inlines.is_empty());
assert!(!inlines[0].bold);
}
#[test]
fn build_inlines_position_past_end_stops() {
let doc_info = DocInfo::default();
let para = HwpParagraph {
text: "Hi".to_string(),
char_shape_ids: vec![(100, 0)], para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
};
let inlines = build_inlines(¶, &doc_info);
assert!(inlines.is_empty());
}
#[test]
fn build_inlines_multiple_segments() {
let mut doc_info = DocInfo::default();
let mut cs0 = CharShape::default();
cs0.bold = true;
let cs1 = CharShape::default(); doc_info.char_shapes.push(cs0);
doc_info.char_shapes.push(cs1);
let para = HwpParagraph {
text: "BoldNormal".to_string(),
char_shape_ids: vec![(0, 0), (4, 1)],
para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
};
let inlines = build_inlines(¶, &doc_info);
assert_eq!(inlines.len(), 2);
assert!(inlines[0].bold);
assert!(!inlines[1].bold);
}
fn make_hwp_document() -> HwpDocument {
HwpDocument {
header: FileHeader::default(),
doc_info: DocInfo::default(),
sections: Vec::new(),
bin_data: std::collections::HashMap::new(),
summary_title: None,
summary_author: None,
summary_subject: None,
summary_keywords: Vec::new(),
}
}
#[test]
fn hwp_to_ir_empty_document_produces_empty_ir() {
let hwp = make_hwp_document();
let doc = hwp_to_ir(&hwp);
assert!(doc.sections.is_empty());
assert!(doc.assets.is_empty());
assert!(doc.metadata.title.is_none());
}
#[test]
fn hwp_to_ir_copies_metadata() {
let mut hwp = make_hwp_document();
hwp.summary_title = Some("My Title".into());
hwp.summary_author = Some("The Author".into());
hwp.summary_subject = Some("Subject matter".into());
hwp.summary_keywords = vec!["key1".into(), "key2".into()];
let doc = hwp_to_ir(&hwp);
assert_eq!(doc.metadata.title.as_deref(), Some("My Title"));
assert_eq!(doc.metadata.author.as_deref(), Some("The Author"));
assert_eq!(doc.metadata.subject.as_deref(), Some("Subject matter"));
assert_eq!(doc.metadata.keywords, vec!["key1", "key2"]);
}
#[test]
fn hwp_to_ir_paragraph_text_becomes_ir_paragraph() {
let mut hwp = make_hwp_document();
hwp.sections.push(HwpSection {
paragraphs: vec![HwpParagraph {
text: "Hello world".into(),
char_shape_ids: Vec::new(),
para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
}],
});
let doc = hwp_to_ir(&hwp);
assert_eq!(doc.sections.len(), 1);
assert_eq!(doc.sections[0].blocks.len(), 1);
if let ir::Block::Paragraph { inlines } = &doc.sections[0].blocks[0] {
assert_eq!(inlines[0].text, "Hello world");
} else {
panic!("Expected Paragraph block");
}
}
#[test]
fn hwp_to_ir_bin_data_becomes_asset() {
let mut hwp = make_hwp_document();
let png_header = vec![0x89u8, b'P', b'N', b'G', 0x00, 0x00, 0x00, 0x00];
hwp.bin_data.insert(1, png_header.clone());
let doc = hwp_to_ir(&hwp);
assert_eq!(doc.assets.len(), 1);
assert_eq!(doc.assets[0].mime_type, "image/png");
assert!(doc.assets[0].name.ends_with(".png"));
assert_eq!(doc.assets[0].data, png_header);
}
#[test]
fn hwp_to_ir_empty_paragraph_text_not_emitted() {
let mut hwp = make_hwp_document();
hwp.sections.push(HwpSection {
paragraphs: vec![HwpParagraph {
text: " ".into(), char_shape_ids: Vec::new(),
para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
}],
});
let doc = hwp_to_ir(&hwp);
assert_eq!(doc.sections[0].blocks.len(), 0);
}
#[test]
fn control_to_block_column_break_returns_none() {
let ctrl = HwpControl::ColumnBreak;
let doc_info = DocInfo::default();
assert!(control_to_block(&ctrl, &doc_info).is_none());
}
#[test]
fn control_to_block_endnote_produces_footnote_with_endnote_id() {
let ctrl = HwpControl::FootnoteEndnote {
is_endnote: true,
paragraphs: Vec::new(),
};
let doc_info = DocInfo::default();
let block = control_to_block(&ctrl, &doc_info).expect("Some");
assert!(matches!(block, ir::Block::Footnote { .. }));
}
#[test]
fn hwp_to_ir_two_footnotes_get_sequential_ids() {
let make_fn_ctrl = || HwpControl::FootnoteEndnote {
is_endnote: false,
paragraphs: Vec::new(),
};
let mut hwp = make_hwp_document();
hwp.sections.push(HwpSection {
paragraphs: vec![
HwpParagraph {
text: String::new(),
char_shape_ids: Vec::new(),
para_shape_id: 0,
controls: vec![make_fn_ctrl()],
raw_para_text: None,
},
HwpParagraph {
text: String::new(),
char_shape_ids: Vec::new(),
para_shape_id: 0,
controls: vec![make_fn_ctrl()],
raw_para_text: None,
},
],
});
let doc = hwp_to_ir(&hwp);
let footnote_ids: Vec<&str> = doc.sections[0]
.blocks
.iter()
.filter_map(|b| {
if let ir::Block::Footnote { id, .. } = b {
Some(id.as_str())
} else {
None
}
})
.collect();
assert_eq!(footnote_ids, vec!["footnote-1", "footnote-2"]);
}
#[test]
fn hwp_to_ir_two_endnotes_get_sequential_ids() {
let make_en_ctrl = || HwpControl::FootnoteEndnote {
is_endnote: true,
paragraphs: Vec::new(),
};
let mut hwp = make_hwp_document();
hwp.sections.push(HwpSection {
paragraphs: vec![
HwpParagraph {
text: String::new(),
char_shape_ids: Vec::new(),
para_shape_id: 0,
controls: vec![make_en_ctrl()],
raw_para_text: None,
},
HwpParagraph {
text: String::new(),
char_shape_ids: Vec::new(),
para_shape_id: 0,
controls: vec![make_en_ctrl()],
raw_para_text: None,
},
],
});
let doc = hwp_to_ir(&hwp);
let endnote_ids: Vec<&str> = doc.sections[0]
.blocks
.iter()
.filter_map(|b| {
if let ir::Block::Footnote { id, .. } = b {
Some(id.as_str())
} else {
None
}
})
.collect();
assert_eq!(endnote_ids, vec!["endnote-1", "endnote-2"]);
}
#[test]
fn hwp_to_ir_footnote_and_endnote_counters_are_independent() {
let mut hwp = make_hwp_document();
hwp.sections.push(HwpSection {
paragraphs: vec![
HwpParagraph {
text: String::new(),
char_shape_ids: Vec::new(),
para_shape_id: 0,
controls: vec![HwpControl::FootnoteEndnote {
is_endnote: false,
paragraphs: Vec::new(),
}],
raw_para_text: None,
},
HwpParagraph {
text: String::new(),
char_shape_ids: Vec::new(),
para_shape_id: 0,
controls: vec![HwpControl::FootnoteEndnote {
is_endnote: true,
paragraphs: Vec::new(),
}],
raw_para_text: None,
},
HwpParagraph {
text: String::new(),
char_shape_ids: Vec::new(),
para_shape_id: 0,
controls: vec![HwpControl::FootnoteEndnote {
is_endnote: false,
paragraphs: Vec::new(),
}],
raw_para_text: None,
},
],
});
let doc = hwp_to_ir(&hwp);
let ids: Vec<&str> = doc.sections[0]
.blocks
.iter()
.filter_map(|b| {
if let ir::Block::Footnote { id, .. } = b {
Some(id.as_str())
} else {
None
}
})
.collect();
assert_eq!(ids, vec!["footnote-1", "endnote-1", "footnote-2"]);
}
#[test]
fn control_to_block_equation_produces_math_block() {
let ctrl = HwpControl::Equation {
script: "a + b".into(),
};
let doc_info = DocInfo::default();
let block = control_to_block(&ctrl, &doc_info).expect("Some");
assert!(matches!(block, ir::Block::Math { display: false, .. }));
}
#[test]
fn control_to_block_ftp_hyperlink_accepted() {
let ctrl = HwpControl::Hyperlink {
url: "ftp://files.example.com/archive.tar.gz".into(),
};
let doc_info = DocInfo::default();
let block = control_to_block(&ctrl, &doc_info).expect("ftp should be accepted");
assert!(matches!(block, ir::Block::Paragraph { .. }));
}
#[test]
fn control_to_block_table_with_zero_col_count_infers_from_cells() {
let ctrl = HwpControl::Table {
row_count: 1,
col_count: 0, cells: vec![
HwpTableCell {
row: 0,
col: 0,
row_span: 1,
col_span: 1,
vertical_align: 0,
is_header: true,
paragraphs: vec![],
},
HwpTableCell {
row: 0,
col: 1,
row_span: 1,
col_span: 1,
vertical_align: 0,
is_header: true,
paragraphs: vec![],
},
],
};
let doc_info = DocInfo::default();
let block = control_to_block(&ctrl, &doc_info).expect("Some");
if let ir::Block::Table { col_count, .. } = block {
assert!(col_count >= 2);
} else {
panic!("Expected Table block");
}
}
#[test]
fn build_inlines_non_black_color_sets_css_hex() {
let mut doc_info = DocInfo::default();
let mut cs = CharShape::default();
cs.color = 0x00FF_0000; doc_info.char_shapes.push(cs);
let para = HwpParagraph {
text: "Red text".to_string(),
char_shape_ids: vec![(0, 0)],
para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
};
let inlines = build_inlines(¶, &doc_info);
assert_eq!(inlines.len(), 1);
assert_eq!(inlines[0].color.as_deref(), Some("#FF0000"));
}
#[test]
fn build_inlines_black_color_is_none() {
let mut doc_info = DocInfo::default();
let mut cs = CharShape::default();
cs.color = 0x0000_0000; doc_info.char_shapes.push(cs);
let para = HwpParagraph {
text: "Black text".to_string(),
char_shape_ids: vec![(0, 0)],
para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
};
let inlines = build_inlines(¶, &doc_info);
assert_eq!(inlines.len(), 1);
assert!(inlines[0].color.is_none(), "black must not set color");
}
#[test]
fn build_inlines_bgr_green_color_maps_correctly() {
let mut doc_info = DocInfo::default();
let mut cs = CharShape::default();
cs.color = 0x0000_FF00;
doc_info.char_shapes.push(cs);
let para = HwpParagraph {
text: "Green".to_string(),
char_shape_ids: vec![(0, 0)],
para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
};
let inlines = build_inlines(¶, &doc_info);
assert_eq!(inlines[0].color.as_deref(), Some("#00FF00"));
}
#[test]
fn build_inlines_face_id_resolves_font_name() {
let mut doc_info = DocInfo::default();
doc_info.face_names = vec!["Arial".to_string(), "Batang".to_string()];
let mut cs = CharShape::default();
cs.face_id = 1; doc_info.char_shapes.push(cs);
let para = HwpParagraph {
text: "Korean".to_string(),
char_shape_ids: vec![(0, 0)],
para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
};
let inlines = build_inlines(¶, &doc_info);
assert_eq!(inlines.len(), 1);
assert_eq!(inlines[0].font_name.as_deref(), Some("Batang"));
}
#[test]
fn build_inlines_face_id_out_of_bounds_font_name_is_none() {
let mut doc_info = DocInfo::default();
let mut cs = CharShape::default();
cs.face_id = 5;
doc_info.char_shapes.push(cs);
let para = HwpParagraph {
text: "Text".to_string(),
char_shape_ids: vec![(0, 0)],
para_shape_id: 0,
controls: Vec::new(),
raw_para_text: None,
};
let inlines = build_inlines(¶, &doc_info);
assert!(inlines[0].font_name.is_none());
}
#[test]
fn control_to_block_ruby_with_both_texts_produces_paragraph() {
let ctrl = HwpControl::Ruby {
base_text: "漢字".into(),
ruby_text: "한자".into(),
};
let doc_info = DocInfo::default();
let block = control_to_block(&ctrl, &doc_info).expect("should return Some");
if let ir::Block::Paragraph { inlines } = block {
assert_eq!(inlines.len(), 1);
assert_eq!(inlines[0].text, "漢字");
assert_eq!(inlines[0].ruby.as_deref(), Some("한자"));
} else {
panic!("expected Paragraph, got {block:?}");
}
}
#[test]
fn control_to_block_ruby_with_empty_ruby_text_no_annotation() {
let ctrl = HwpControl::Ruby {
base_text: "漢字".into(),
ruby_text: String::new(),
};
let doc_info = DocInfo::default();
let block = control_to_block(&ctrl, &doc_info).expect("should return Some for non-empty base");
if let ir::Block::Paragraph { inlines } = block {
assert_eq!(inlines[0].text, "漢字");
assert!(
inlines[0].ruby.is_none(),
"empty ruby_text must produce None annotation"
);
} else {
panic!("expected Paragraph, got {block:?}");
}
}
#[test]
fn control_to_block_ruby_both_empty_returns_none() {
let ctrl = HwpControl::Ruby {
base_text: String::new(),
ruby_text: String::new(),
};
let doc_info = DocInfo::default();
assert!(
control_to_block(&ctrl, &doc_info).is_none(),
"both empty must return None"
);
}