("return Page:GetSpacing()").unwrap(), -9.0);
measure_at_16px(&mut s);
assert_eq!(
resolved_tops(&s, "Page"),
[(600.0, 584.0), (580.0, 564.0)],
"the second block starts 4px below the first's bottom"
);
}
/// A BODY-level `
` is its own `"\n"` block, which `0x5c2070` counts as one line.
#[test]
fn a_body_level_br_is_exactly_one_blank_line() {
let mut s = script();
s.set_screen_size(800.0, 600.0);
page(&s);
s.run(r#"Page:SetText("a
b
")"#)
.unwrap();
assert_eq!(texts(&s, "Page"), ["a", "\n", "b"]);
measure_at_16px(&mut s);
assert_eq!(
resolved_tops(&s, "Page"),
[(600.0, 584.0), (584.0, 568.0), (568.0, 552.0)],
"the BR block is one line tall and pushes `b` down by exactly that"
);
}
/// An inline `
` splices `|n` into the enclosing block, a line break within it.
#[test]
fn an_inline_br_stays_inside_its_block() {
let s = script();
page(&s);
s.run(r#"Page:SetText("a
b
")"#)
.unwrap();
assert_eq!(texts(&s, "Page"), ["a|nb"]);
}
/// Each fallback route is one block holding the raw, uncollapsed string; most vmangos `page_text`
/// rows are plain text and take it.
#[test]
fn the_three_fallback_routes_each_land_on_one_raw_block() {
let s = script();
page(&s);
for raw in [
"Plain prose, with an & and a < in it.\nSecond line.",
"",
"nothing",
] {
s.run(&format!("Page:SetText({})", lua_str(raw))).unwrap();
let b = blocks(&s, "Page");
assert_eq!(b.len(), 1, "{raw}");
assert_eq!(b[0].text.as_deref(), Some(raw), "{raw}");
assert_eq!(b[0].justify_h, "LEFT", "{raw}");
assert_eq!(
b[0].anchor.unwrap(),
(Point::TopLeft, Point::TopLeft, 0.0, 0.0, Rel::Frame),
"{raw}"
);
}
}
/// The login screen's server alert (`SHOW_SERVER_ALERT`'s text, `ServerAlertText:SetText(arg1)`)
/// is plain text with blank lines, brackets, an apostrophe and a final newline: one `P` block of
/// the raw string, no collapse (`0x78a501`).
#[test]
fn a_plain_text_server_alert_is_one_raw_p_block_with_its_newlines() {
let s = script();
page(&s);
let body = "[January 17, 2007 - 5:00PM PST]\n\nPlease note: free moves.\n\n\
Aggramar\nArgent Dawn\nZul'jin\n";
s.run(&format!("Page:SetText({})", lua_str(body))).unwrap();
assert_eq!(texts(&s, "Page"), [body]);
assert_eq!(blocks(&s, "Page")[0].justify_h, "LEFT");
assert!(s.errors().is_empty(), "{:?}", s.errors());
}
#[test]
fn malformed_markup_falls_back_rather_than_erroring() {
let s = script();
page(&s);
for raw in [
"a b
",
"hi
",
"hi
\nFrom: Bob",
] {
s.run(&format!("Page:SetText({})", lua_str(raw))).unwrap();
assert_eq!(texts(&s, "Page"), [raw], "{raw}");
}
assert!(s.errors().is_empty(), "{:?}", s.errors());
}
/// A second `SetText` frees the first parse's regions, so nothing of the old page draws.
#[test]
fn a_second_set_text_replaces_the_blocks() {
let mut s = script();
s.set_screen_size(800.0, 600.0);
page(&s);
s.run(r#"Page:SetText("one
two
three
")"#)
.unwrap();
assert_eq!(region_count(&s, "Page"), 3);
measure_at_16px(&mut s);
assert_eq!(page_texts_on_screen(&s).len(), 3);
s.run(r#"Page:SetText("only
")"#)
.unwrap();
assert_eq!(texts(&s, "Page"), ["only"]);
assert_eq!(
region_count(&s, "Page"),
1,
"the previous parse's FontStrings are freed, not left on the frame"
);
measure_at_16px(&mut s);
assert_eq!(
page_texts_on_screen(&s),
["only"],
"and nothing of the old page still draws"
);
assert!(s.errors().is_empty(), "{:?}", s.errors());
}
/// A real `page_text` body: the `` at the `
` size, each `
` one blank line, and the
/// newlines between tags adding nothing.
#[test]
fn the_page_text_body_renders_the_block_list_a_reader_would_draw() {
let mut s = script();
s.set_screen_size(800.0, 600.0);
page(&s);
s.run(
"Page:SetText(\"\\n
\\n\
The Green Hills of Stranglethorn
\\n
\\n\
Chapter One:\\nThe Mysteries of the Jungle
\\n
\\n\
Deep in the jungle, all is not as it seems.
\\n\
\\n\")",
)
.unwrap();
let b = blocks(&s, "Page");
assert_eq!(
b.iter()
.map(|x| (x.text.clone().unwrap(), x.justify_h, x.font_height))
.collect::>(),
[
(
"The Green Hills of Stranglethorn".to_string(),
"CENTER",
Some(15.0)
),
("\n".to_string(), "LEFT", Some(15.0)),
(
"Chapter One: The Mysteries of the Jungle".to_string(),
"CENTER",
Some(15.0)
),
("\n".to_string(), "LEFT", Some(15.0)),
(
"Deep in the jungle, all is not as it seems.".to_string(),
"CENTER",
Some(15.0)
),
],
"the H1 renders at the P size — nothing in the TU scales a header"
);
// Five flush 16 px blocks: 600 down to 520.
measure_at_16px(&mut s);
let tops = resolved_tops(&s, "Page");
assert_eq!(tops.first(), Some(&(600.0, 584.0)));
assert_eq!(tops.last(), Some(&(536.0, 520.0)));
assert!(s.errors().is_empty(), "{:?}", s.errors());
}
/// `ItemTextFrame.lua:44` pads an authorless page as `"\n" .. ItemTextGetText() .. "\n"`, and
/// whitespace around the root element is legal XML, so it still takes the markup path. The body is
/// vmangos `page_text` 2676, the Alliance Military Ranks plaque (`GameObject` 3011).
#[test]
fn the_readers_own_newline_padding_still_parses_as_markup() {
let mut s = script();
s.set_screen_size(800.0, 600.0);
page(&s);
s.run(
"Page:SetText(\"\\n\" .. \
\"\\n\\n\
ALLIANCE MILITARY RANKS
\\n\
OFFICERS
\\n\
Grand Marshal
\\n\
Field Marshal
\\n\
Knight
\\n\
ENLISTED
\\n\
Private
\\n\
\\n\" .. \"\\n\")",
)
.unwrap();
let texts = texts(&s, "Page");
assert_eq!(
texts,
[
"ALLIANCE MILITARY RANKS",
"\n",
"OFFICERS",
"\n",
"Grand Marshal",
"Field Marshal",
"Knight",
"\n",
"ENLISTED",
"\n",
"Private",
],
"the padded page took the markup path — one block per tag, one blank line per
, and \
BODY's own inter-tag newlines adding nothing"
);
// The fallback would put the whole source in one block.
assert!(
!texts[0].contains('<'),
"a fallback would render the markup itself — the reported symptom"
);
assert!(s.errors().is_empty(), "{:?}", s.errors());
}
/// `page_text` 1510, the Crystal Pylon manual, ends ` CRYSTAL PYLON USER'S MANUAL
\
Chapter 1: The Northern Pylon
") && texts[0].ends_with("Title
Body