#![allow(clippy::float_cmp)]
use layout_cat::{Error, LayoutBox, Viewport, layout};
fn run(html: &str, css: &str) -> Result<layout_cat::LayoutTree, Error> {
let html_doc = html_cat::parse(html).map_err(|_| fail("html parse"))?;
let dom = dom_cat::Document::from_html_doc(&html_doc);
let sheet = css_cat::parse(css)?;
Ok(layout(&dom, &sheet, Viewport::new(800, 600)))
}
fn fail(msg: &'static str) -> Error {
Error::Css(css_cat::Error::EmptySelectorList {
at: css_cat::span::Span::new(
css_cat::span::Position::new(1, 1, 0),
css_cat::span::Position::new(1, 1, 0),
),
})
.ignore(msg)
}
trait ErrorTagged {
fn ignore(self, _msg: &'static str) -> Self;
}
impl ErrorTagged for Error {
fn ignore(self, _msg: &'static str) -> Self {
self
}
}
fn first_descendant(node: &LayoutBox, idx: usize) -> Option<&LayoutBox> {
node.children().get(idx)
}
#[test]
fn root_box_present() -> Result<(), Error> {
let tree = run("<html><body><p>hi</p></body></html>", "")?;
tree.root_box()
.map(|_| ())
.ok_or_else(|| fail("expected root box"))
}
#[test]
fn body_fills_viewport_width_by_default() -> Result<(), Error> {
let tree = run("<html><body><p>hi</p></body></html>", "")?;
let body = tree.root_box().ok_or_else(|| fail("no root"))?;
(body.rect().width() == 800.0)
.then_some(())
.ok_or_else(|| fail("body width != viewport"))
}
#[test]
fn explicit_width_applied() -> Result<(), Error> {
let tree = run("<html><body><p>hi</p></body></html>", "p { width: 200px; }")?;
let body = tree.root_box().ok_or_else(|| fail("no root"))?;
let p = first_descendant(body, 0).ok_or_else(|| fail("no p"))?;
(p.rect().width() == 200.0)
.then_some(())
.ok_or_else(|| fail("p width != 200"))
}
#[test]
fn padding_consumed() -> Result<(), Error> {
let tree = run(
"<html><body><p>hi</p></body></html>",
"p { width: 100px; padding: 8px; }",
)?;
let body = tree.root_box().ok_or_else(|| fail("no root"))?;
let p = first_descendant(body, 0).ok_or_else(|| fail("no p"))?;
(p.padding().left() == 8.0 && p.padding().right() == 8.0)
.then_some(())
.ok_or_else(|| fail("padding wrong"))
}
#[test]
fn vertical_stacking() -> Result<(), Error> {
let tree = run(
"<html><body><p>a</p><p>b</p></body></html>",
"p { height: 50px; }",
)?;
let body = tree.root_box().ok_or_else(|| fail("no root"))?;
let a = first_descendant(body, 0).ok_or_else(|| fail("no a"))?;
let b = first_descendant(body, 1).ok_or_else(|| fail("no b"))?;
(a.rect().origin().y() == 0.0 && b.rect().origin().y() == 50.0)
.then_some(())
.ok_or_else(|| fail("vertical stacking wrong"))
}
#[test]
fn margin_shorthand_four_sides() -> Result<(), Error> {
let tree = run(
"<html><body><p>x</p></body></html>",
"p { margin: 10px 20px 30px 40px; }",
)?;
let body = tree.root_box().ok_or_else(|| fail("no root"))?;
let p = first_descendant(body, 0).ok_or_else(|| fail("no p"))?;
let m = p.margin();
(m.top() == 10.0 && m.right() == 20.0 && m.bottom() == 30.0 && m.left() == 40.0)
.then_some(())
.ok_or_else(|| fail("margin shorthand wrong"))
}
#[test]
fn padding_shorthand_two_values() -> Result<(), Error> {
let tree = run(
"<html><body><p>x</p></body></html>",
"p { padding: 5px 10px; }",
)?;
let body = tree.root_box().ok_or_else(|| fail("no root"))?;
let p = first_descendant(body, 0).ok_or_else(|| fail("no p"))?;
let pad = p.padding();
(pad.top() == 5.0 && pad.right() == 10.0 && pad.bottom() == 5.0 && pad.left() == 10.0)
.then_some(())
.ok_or_else(|| fail("padding two-value wrong"))
}
#[test]
fn display_none_skipped() -> Result<(), Error> {
let tree = run(
"<html><body><p>shown</p><p class=\"hidden\">gone</p></body></html>",
".hidden { display: none; }",
)?;
let body = tree.root_box().ok_or_else(|| fail("no root"))?;
(body.children().len() == 1)
.then_some(())
.ok_or_else(|| fail("display:none should drop child"))
}
#[test]
fn color_resolved_from_hex() -> Result<(), Error> {
let tree = run(
"<html><body><p>x</p></body></html>",
"p { color: #ff0000; }",
)?;
let body = tree.root_box().ok_or_else(|| fail("no root"))?;
let p = first_descendant(body, 0).ok_or_else(|| fail("no p"))?;
let c = p.style().color();
(c.red() > 0.99 && c.green() < 0.01 && c.blue() < 0.01)
.then_some(())
.ok_or_else(|| fail("color wrong"))
}
#[test]
fn percentage_width_resolved() -> Result<(), Error> {
let tree = run("<html><body><p>x</p></body></html>", "p { width: 50%; }")?;
let body = tree.root_box().ok_or_else(|| fail("no root"))?;
let p = first_descendant(body, 0).ok_or_else(|| fail("no p"))?;
(p.rect().width() == 400.0)
.then_some(())
.ok_or_else(|| fail("percentage width wrong"))
}
#[test]
fn inline_style_attribute() -> Result<(), Error> {
let tree = run(
"<html><body><p style=\"width: 123px;\">x</p></body></html>",
"",
)?;
let body = tree.root_box().ok_or_else(|| fail("no root"))?;
let p = first_descendant(body, 0).ok_or_else(|| fail("no p"))?;
(p.rect().width() == 123.0)
.then_some(())
.ok_or_else(|| fail("inline style ignored"))
}