use blitz_dom::{Document, DocumentConfig};
use blitz_html::{HtmlDocument, HtmlProvider};
use blitz_traits::{
events::{
BlitzPointerEvent, BlitzPointerId, MouseEventButton, MouseEventButtons, Point,
PointerCoords, PointerDetails, UiEvent,
},
shell::{ColorScheme, Viewport},
};
use markup5ever::local_name;
use std::sync::Arc;
fn doc(html: &str) -> HtmlDocument {
doc_scaled(html, 1.0)
}
fn doc_scaled(html: &str, scale: f32) -> HtmlDocument {
let mut doc = HtmlDocument::from_html(
html,
DocumentConfig {
viewport: Some(Viewport::new(
(400.0 * scale) as u32,
(400.0 * scale) as u32,
scale,
ColorScheme::Light,
)),
html_parser_provider: Some(Arc::new(HtmlProvider) as _),
..Default::default()
},
);
doc.resolve(0.0);
doc
}
fn node_id(doc: &HtmlDocument, selector: &str) -> usize {
doc.query_selector(selector).unwrap().expect(selector)
}
fn pointer_event(x: f32, y: f32) -> BlitzPointerEvent {
BlitzPointerEvent {
id: BlitzPointerId::Mouse,
is_primary: true,
coords: PointerCoords {
page_x: x,
page_y: y,
screen_x: x,
screen_y: y,
client_x: x,
client_y: y,
},
button: MouseEventButton::Main,
buttons: MouseEventButtons::from(MouseEventButton::Main),
mods: Default::default(),
details: PointerDetails::default(),
element: Point::default(),
active_pointers: Default::default(),
}
}
fn click(doc: &mut HtmlDocument, x: f32, y: f32) {
let event = pointer_event(x, y);
doc.handle_ui_event(UiEvent::PointerDown(event.clone()));
doc.handle_ui_event(UiEvent::PointerUp(event));
doc.resolve(0.0);
}
fn is_open(doc: &HtmlDocument, selector: &str) -> bool {
let id = node_id(doc, selector);
doc.get_node(id).unwrap().data.has_attr(local_name!("open"))
}
fn body_height(doc: &HtmlDocument, selector: &str) -> f32 {
let id = node_id(doc, selector);
doc.get_node(id).unwrap().final_layout.size.height
}
#[test]
fn clicking_summary_toggles_open() {
let mut doc = doc(r#"<html><body style="margin:0">
<details>
<summary style="height:20px;">Summary</summary>
<p id="body" style="height:40px;">Hidden content</p>
</details>
</body></html>"#);
assert!(!is_open(&doc, "details"));
assert_eq!(body_height(&doc, "#body"), 0.0, "body hidden while closed");
click(&mut doc, 40.0, 10.0);
assert!(is_open(&doc, "details"), "open after first click");
assert!(body_height(&doc, "#body") > 0.0, "body visible while open");
click(&mut doc, 40.0, 10.0);
assert!(!is_open(&doc, "details"), "closed after second click");
assert_eq!(body_height(&doc, "#body"), 0.0, "body hidden again");
}
#[test]
fn summary_hit_area_matches_box_at_hidpi() {
let mut doc = doc_scaled(
r#"<html><body style="margin:0">
<details open>
<summary style="height:20px;">Summary</summary>
<p id="body" style="height:40px; margin-top:40px;">Content</p>
</details>
</body></html>"#,
2.0,
);
assert!(is_open(&doc, "details"));
let summary = node_id(&doc, "summary");
let hit = doc.hit(40.0, 30.0).expect("should hit the details");
assert_ne!(
hit.node_id, summary,
"point below the summary must not hit the summary"
);
click(&mut doc, 40.0, 30.0);
assert!(
is_open(&doc, "details"),
"clicking below the summary must not toggle the details"
);
}
fn after_text(doc: &HtmlDocument, selector: &str) -> Option<String> {
let id = node_id(doc, selector);
let after_id = doc.get_node(id).unwrap().after?;
let text_id = doc.get_node(after_id)?.children.first().copied()?;
doc.get_node(text_id)?
.text_data()
.map(|t| t.content.clone())
}
#[test]
fn pseudo_element_content_updates_on_toggle() {
let mut doc = doc(r#"<html><head><style>
summary::after { content: "+"; }
details[open] > summary::after { content: "-"; }
</style></head><body style="margin:0">
<details>
<summary style="height:20px;">Summary</summary>
<p id="body" style="height:40px;">Content</p>
</details>
</body></html>"#);
assert_eq!(after_text(&doc, "summary").as_deref(), Some("+"));
click(&mut doc, 40.0, 10.0);
assert!(is_open(&doc, "details"));
assert_eq!(
after_text(&doc, "summary").as_deref(),
Some("-"),
"::after content should update when the details is opened"
);
click(&mut doc, 40.0, 10.0);
assert!(!is_open(&doc, "details"));
assert_eq!(
after_text(&doc, "summary").as_deref(),
Some("+"),
"::after content should update when the details is closed"
);
}
#[test]
fn details_starts_open_when_open_attribute_present() {
let doc = doc(r#"<html><body style="margin:0">
<details open>
<summary style="height:20px;">Summary</summary>
<p id="body" style="height:40px;">Visible content</p>
</details>
</body></html>"#);
assert!(is_open(&doc, "details"));
assert!(
body_height(&doc, "#body") > 0.0,
"body visible when initially open"
);
}
#[test]
fn initially_open_details_can_be_closed() {
let mut doc = doc(r#"<html><body style="margin:0">
<details open>
<summary style="height:20px;">Summary</summary>
<p id="body" style="height:40px;">Visible content</p>
</details>
</body></html>"#);
assert!(is_open(&doc, "details"));
click(&mut doc, 40.0, 10.0);
assert!(
!is_open(&doc, "details"),
"clicking the summary should close an initially-open details"
);
assert_eq!(body_height(&doc, "#body"), 0.0, "body hidden after closing");
click(&mut doc, 40.0, 10.0);
assert!(is_open(&doc, "details"));
assert!(body_height(&doc, "#body") > 0.0, "body visible again");
}