use htui::html::css_parse::{OverflowWrap, WhiteSpace, WordBreak};
use htui::layout::layoutbox::{InlineRun, wrap_runs};
use htui::tui::graph_api::FontStyle;
fn run(s: &str, width: usize) -> Vec<Vec<InlineRun>> {
wrap_runs(
&[InlineRun::new(s, FontStyle::empty(), None, None)],
width,
1,
WhiteSpace::Normal,
WordBreak::Normal,
OverflowWrap::Normal,
0,
)
}
fn run_ws(s: &str, width: usize, ws: WhiteSpace) -> Vec<Vec<InlineRun>> {
wrap_runs(
&[InlineRun::new(s, FontStyle::empty(), None, None)],
width,
1,
ws,
WordBreak::Normal,
OverflowWrap::Normal,
0,
)
}
#[test]
fn ascii_breaks_at_spaces() {
let lines = run("hello world foo bar", 6);
assert_eq!(lines.len(), 4, "got {} lines", lines.len());
}
#[test]
fn cjk_breaks_between_characters() {
let lines = run("你好世界", 3);
assert_eq!(lines.len(), 4, "got {} lines", lines.len());
}
#[test]
fn nowrap_does_not_wrap() {
let lines = run_ws("hello world", 5, WhiteSpace::NoWrap);
assert_eq!(lines.len(), 1);
}
#[test]
fn pre_preserves_spaces_no_wrap() {
let lines = run_ws("a b", 10, WhiteSpace::Pre);
assert_eq!(lines.len(), 1);
}
#[test]
fn pre_wrap_preserves_and_wraps() {
let lines = run_ws("a b", 3, WhiteSpace::PreWrap);
assert!(lines.len() >= 2, "got {} lines", lines.len());
}
#[test]
fn pre_line_collapses_spaces_keeps_newlines() {
let lines = run_ws("a b\nc d", 10, WhiteSpace::PreLine);
assert_eq!(lines.len(), 2, "got {} lines", lines.len());
}
#[test]
fn break_all_splits_long_word() {
let lines = wrap_runs(
&[InlineRun::new("abcdef", FontStyle::empty(), None, None)],
3,
1,
WhiteSpace::Normal,
WordBreak::BreakAll,
OverflowWrap::Normal,
0,
);
assert!(lines.len() >= 2, "got {} lines", lines.len());
}
#[test]
fn keep_all_keeps_cjk_intact() {
let lines = wrap_runs(
&[InlineRun::new("你好世界", FontStyle::empty(), None, None)],
3,
1,
WhiteSpace::Normal,
WordBreak::KeepAll,
OverflowWrap::Normal,
0,
);
assert_eq!(lines.len(), 1);
}
#[test]
fn break_word_splits_unbreakable() {
let lines = wrap_runs(
&[InlineRun::new("abcdef", FontStyle::empty(), None, None)],
3,
1,
WhiteSpace::Normal,
WordBreak::Normal,
OverflowWrap::BreakWord,
0,
);
assert!(lines.len() >= 2, "got {} lines", lines.len());
}
#[test]
fn overflow_normal_does_not_split() {
let lines = wrap_runs(
&[InlineRun::new("abcdef", FontStyle::empty(), None, None)],
3,
1,
WhiteSpace::Normal,
WordBreak::Normal,
OverflowWrap::Normal,
0,
);
assert_eq!(lines.len(), 1);
}
#[test]
fn empty_input_yields_one_empty_line() {
let lines = wrap_runs(
&[],
80,
1,
WhiteSpace::Normal,
WordBreak::Normal,
OverflowWrap::Normal,
0,
);
assert_eq!(lines.len(), 1);
assert!(lines[0].is_empty());
}
#[test]
fn newlines_create_separate_lines() {
let lines = run_ws("a\nb\nc", 80, WhiteSpace::PreWrap);
assert_eq!(lines.len(), 3, "got {} lines", lines.len());
}
#[test]
fn consecutive_spaces_collapse_to_single_space() {
let lines = run("a b", 80);
assert_eq!(lines.len(), 1);
let joined: String = lines[0].iter().map(|r| r.text.as_str()).collect();
assert_eq!(joined, "a b");
}
#[test]
fn nbsp_is_not_collapsible() {
let lines = run("a\u{00A0}\u{00A0}\u{00A0}b", 80);
assert_eq!(lines.len(), 1);
let joined: String = lines[0].iter().map(|r| r.text.as_str()).collect();
assert_eq!(joined, "a\u{00A0}\u{00A0}\u{00A0}b");
}
#[test]
fn cjk_closing_punctuation_never_starts_a_line() {
let lines = run("甲乙丙丁。", 4);
let l2: String = lines[2].iter().map(|r| r.text.as_str()).collect();
assert_eq!(l2, "丁。", "the closing mark rides with 丁");
assert!(
lines
.iter()
.all(|l| l.first().map(|r| !r.text.starts_with('。')).unwrap_or(true)),
"no visual line may open with the closing mark: {lines:?}"
);
}
#[test]
fn cjk_kinsoku_applies_under_break_all() {
let lines = wrap_runs(
&[InlineRun::new("甲乙丙丁,", FontStyle::empty(), None, None)],
4,
1,
WhiteSpace::Normal,
WordBreak::BreakAll,
OverflowWrap::Normal,
0,
);
let l2: String = lines[2].iter().map(|r| r.text.as_str()).collect();
assert_eq!(l2, "丁,", "the comma rides with the preceding glyph");
assert!(
lines
.iter()
.all(|l| l.first().map(|r| !r.text.starts_with(',')).unwrap_or(true)),
"no visual line may open with the comma: {lines:?}"
);
}
#[test]
fn word_spacing_wrap_budget_matches_painted_advance() {
let runs = ["a b c d e f g h i j k"]
.iter()
.map(|s| {
let mut r = InlineRun::new(*s, FontStyle::empty(), None, None);
r.word_spacing = 4;
r
})
.collect::<Vec<_>>();
let lines = wrap_runs(
&runs,
20,
1,
WhiteSpace::Normal,
WordBreak::Normal,
OverflowWrap::Normal,
0,
);
for (i, line) in lines.iter().enumerate() {
let sum: usize = line.iter().map(|r| r.width_at(1)).sum();
assert!(sum <= 20, "line {i} exceeds width ({sum}): {line:?}");
}
let joined: String = lines
.iter()
.flat_map(|l| l.iter().map(|r| r.text.as_str()))
.collect();
for ch in "abcdefghijk".chars() {
assert!(joined.contains(ch), "letter {ch} lost: {joined:?}");
}
}