#![allow(missing_docs)]
use hpx_browser::{
html_parser::parse_html,
page::Page,
resource_loader::{ResourceType, extract_resource_urls, filter_by_block_types},
};
#[cfg(feature = "v8")]
#[tokio::test]
async fn inline_scripts_set_globals() {
let html = r#"<!DOCTYPE html><html><head>
<script>window.x = 42;</script>
</head><body></body></html>"#;
let mut page = Page::from_html(html, false).await.unwrap();
page.execute_inline_scripts().unwrap();
let result = page.evaluate("window.x").unwrap();
assert_eq!(result, "42", "inline script should set global");
}
#[cfg(feature = "v8")]
#[tokio::test]
async fn inline_scripts_execute_in_order() {
let html = r#"<!DOCTYPE html><html><head>
<script>window.order = [];</script>
<script>window.order.push(1);</script>
<script>window.order.push(2);</script>
</head><body></body></html>"#;
let mut page = Page::from_html(html, false).await.unwrap();
page.execute_inline_scripts().unwrap();
let result = page.evaluate("window.order.join(',')").unwrap();
assert_eq!(result, "1,2", "scripts should execute in document order");
}
#[cfg(feature = "v8")]
#[tokio::test]
async fn inline_script_error_does_not_stop_others() {
let html = r#"<!DOCTYPE html><html><head>
<script>window.before = true;</script>
<script>throw new Error('intentional');</script>
<script>window.after = true;</script>
</head><body></body></html>"#;
let mut page = Page::from_html(html, false).await.unwrap();
page.execute_inline_scripts().unwrap();
assert_eq!(page.evaluate("window.before").unwrap(), "true");
assert_eq!(page.evaluate("window.after").unwrap(), "true");
}
#[cfg(feature = "v8")]
#[tokio::test]
async fn external_scripts_loaded() {
use hpx_browser::resource_loader::LoadedResource;
let html = r#"<!DOCTYPE html><html><head></head><body></body></html>"#;
let mut page = Page::from_html(html, false).await.unwrap();
let scripts = vec![LoadedResource {
url: "http://example.com/app.js".to_string(),
resource_type: ResourceType::Script,
content: "window.loaded = 'yes';".to_string(),
content_type: Some("application/javascript".to_string()),
}];
page.execute_scripts(&scripts).unwrap();
let result = page.evaluate("window.loaded").unwrap();
assert_eq!(result, "yes", "external script should be executed");
}
#[cfg(feature = "v8")]
#[tokio::test]
async fn inline_then_external_order() {
use hpx_browser::resource_loader::LoadedResource;
let html = r#"<!DOCTYPE html><html><head>
<script>window.seq = ['inline'];</script>
</head><body></body></html>"#;
let mut page = Page::from_html(html, false).await.unwrap();
let scripts = vec![LoadedResource {
url: "http://example.com/ext.js".to_string(),
resource_type: ResourceType::Script,
content: "window.seq.push('external');".to_string(),
content_type: Some("application/javascript".to_string()),
}];
page.execute_scripts(&scripts).unwrap();
let result = page.evaluate("window.seq.join(',')").unwrap();
assert_eq!(result, "inline,external", "inline runs before external");
}
#[tokio::test]
async fn stylesheets_injected_into_head() {
use hpx_browser::resource_loader::LoadedResource;
let html = r#"<!DOCTYPE html><html><head></head><body></body></html>"#;
let mut page = Page::from_html(html, false).await.unwrap();
let styles = vec![LoadedResource {
url: "http://example.com/style.css".to_string(),
resource_type: ResourceType::Stylesheet,
content: "body { margin: 0; }".to_string(),
content_type: Some("text/css".to_string()),
}];
page.apply_stylesheets(&styles);
let dom_html = page.dom().serialize_html(page.dom().document());
assert!(dom_html.contains("<style>"), "should have <style> tag");
assert!(dom_html.contains("margin"), "should contain CSS content");
}
#[tokio::test]
async fn multiple_stylesheets_all_injected() {
use hpx_browser::resource_loader::LoadedResource;
let html = r#"<!DOCTYPE html><html><head></head><body></body></html>"#;
let mut page = Page::from_html(html, false).await.unwrap();
let styles = vec![
LoadedResource {
url: "http://example.com/a.css".to_string(),
resource_type: ResourceType::Stylesheet,
content: "body { color: red; }".to_string(),
content_type: Some("text/css".to_string()),
},
LoadedResource {
url: "http://example.com/b.css".to_string(),
resource_type: ResourceType::Stylesheet,
content: "p { font-size: 16px; }".to_string(),
content_type: Some("text/css".to_string()),
},
];
page.apply_stylesheets(&styles);
let dom_html = page.dom().serialize_html(page.dom().document());
assert!(
dom_html.contains("color: red"),
"first CSS should be injected"
);
assert!(
dom_html.contains("font-size"),
"second CSS should be injected"
);
}
#[test]
fn extract_stylesheet_urls() {
let dom = parse_html(
r#"<html><head>
<link rel="stylesheet" href="/style.css">
</head><body></body></html>"#,
);
let urls = extract_resource_urls(&dom);
assert_eq!(urls.len(), 1);
assert_eq!(urls[0].resource_type, ResourceType::Stylesheet);
assert_eq!(urls[0].url, "/style.css");
}
#[test]
fn extract_script_src_urls() {
let dom = parse_html(
r#"<html><head>
<script src="/app.js"></script>
</head><body></body></html>"#,
);
let urls = extract_resource_urls(&dom);
assert_eq!(urls.len(), 1);
assert_eq!(urls[0].resource_type, ResourceType::Script);
assert_eq!(urls[0].url, "/app.js");
}
#[test]
fn extract_img_urls() {
let dom = parse_html(r#"<html><body><img src="/photo.jpg"></body></html>"#);
let urls = extract_resource_urls(&dom);
assert_eq!(urls.len(), 1);
assert_eq!(urls[0].resource_type, ResourceType::Image);
assert_eq!(urls[0].url, "/photo.jpg");
}
#[test]
fn extract_all_resource_types() {
let dom = parse_html(
r#"<html><head>
<link rel="stylesheet" href="/style.css">
<script src="/app.js"></script>
</head><body>
<img src="/logo.png">
<video src="/clip.mp4"></video>
<audio src="/sound.mp3"></audio>
</body></html>"#,
);
let urls = extract_resource_urls(&dom);
let types: std::collections::HashSet<_> = urls.iter().map(|u| u.resource_type).collect();
assert!(types.contains(&ResourceType::Stylesheet));
assert!(types.contains(&ResourceType::Script));
assert!(types.contains(&ResourceType::Image));
assert!(types.contains(&ResourceType::Media));
}
#[test]
fn inline_scripts_not_extracted() {
let dom = parse_html(
r#"<html><head>
<script>window.x = 1;</script>
<script src="/ext.js"></script>
</head><body></body></html>"#,
);
let urls = extract_resource_urls(&dom);
assert_eq!(
urls.len(),
1,
"inline scripts should not be in resource URLs"
);
assert_eq!(urls[0].url, "/ext.js");
}
#[test]
fn duplicate_urls_deduplicated() {
let dom = parse_html(
r#"<html><head>
<link rel="stylesheet" href="/style.css">
<link rel="stylesheet" href="/style.css">
</head><body></body></html>"#,
);
let urls = extract_resource_urls(&dom);
assert_eq!(urls.len(), 1, "duplicate URLs should be deduplicated");
}
#[test]
fn block_images_filters_image_resources() {
let dom = parse_html(
r#"<html><body>
<img src="/photo.jpg">
<img src="/icon.png">
</body></html>"#,
);
let urls = extract_resource_urls(&dom);
let mut block = std::collections::HashSet::new();
block.insert(ResourceType::Image);
let filtered = filter_by_block_types(urls, &block);
assert!(filtered.is_empty(), "all images should be blocked");
}
#[test]
fn block_images_preserves_scripts() {
let dom = parse_html(
r#"<html><head>
<script src="/app.js"></script>
</head><body>
<img src="/photo.jpg">
</body></html>"#,
);
let urls = extract_resource_urls(&dom);
let mut block = std::collections::HashSet::new();
block.insert(ResourceType::Image);
let filtered = filter_by_block_types(urls, &block);
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].resource_type, ResourceType::Script);
}
#[test]
fn block_multiple_types() {
let dom = parse_html(
r#"<html><head>
<link rel="stylesheet" href="/style.css">
<script src="/app.js"></script>
</head><body>
<img src="/photo.jpg">
</body></html>"#,
);
let urls = extract_resource_urls(&dom);
let mut block = std::collections::HashSet::new();
block.insert(ResourceType::Image);
block.insert(ResourceType::Stylesheet);
let filtered = filter_by_block_types(urls, &block);
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].resource_type, ResourceType::Script);
}
#[test]
fn empty_block_list_keeps_all() {
let dom = parse_html(
r#"<html><head>
<link rel="stylesheet" href="/style.css">
</head><body>
<img src="/photo.jpg">
</body></html>"#,
);
let urls = extract_resource_urls(&dom);
let block = std::collections::HashSet::new();
let filtered = filter_by_block_types(urls.clone(), &block);
assert_eq!(filtered.len(), urls.len(), "empty block should keep all");
}
#[tokio::test]
async fn page_title_extraction() {
let html = r#"<!DOCTYPE html><html><head><title>My Page</title></head><body></body></html>"#;
let page = Page::from_html(html, false).await.unwrap();
assert_eq!(page.title(), "My Page");
}
#[tokio::test]
async fn page_url_set_on_reload() {
let mut page = Page::from_html("", false).await.unwrap();
page.reload_html("<html></html>", "https://example.com/test");
assert_eq!(page.url(), "https://example.com/test");
}
#[tokio::test]
async fn collect_inline_scripts_finds_scripts() {
let html = r#"<!DOCTYPE html><html><head>
<script>var a = 1;</script>
<script src="/ext.js"></script>
<script>var b = 2;</script>
</head><body></body></html>"#;
let page = Page::from_html(html, false).await.unwrap();
let scripts = page.collect_inline_scripts();
assert_eq!(scripts.len(), 2, "should find 2 inline scripts");
assert_eq!(scripts[0], "var a = 1;");
assert_eq!(scripts[1], "var b = 2;");
}
#[tokio::test]
async fn reload_html_updates_dom() {
let mut page = Page::from_html("<html><body>old</body></html>", false)
.await
.unwrap();
assert!(page.content().contains("old"));
page.reload_html("<html><body>new</body></html>", "http://example.com");
assert!(page.content().contains("new"));
assert!(!page.content().contains("old"));
}
#[tokio::test]
async fn clean_page_verdict_pass() {
let mut html = String::from("<html><body>");
for _ in 0..400 {
html.push_str("<p>Substantial content for testing challenge detection.</p>");
}
html.push_str("</body></html>");
let page = Page::from_html(&html, false).await.unwrap();
assert_eq!(
page.challenge_verdict(),
hpx_browser::challenge::ChallengeVerdict::Pass
);
}
#[tokio::test]
async fn cloudflare_challenge_detected() {
let html = r#"<!DOCTYPE html><html><head><title>Just a moment...</title></head>
<body><script>window._cf_chl_opt={cvId:'3',cType:'managed'};</script>
Checking your browser...</body></html>"#;
let page = Page::from_html(html, false).await.unwrap();
assert!(page.challenge_verdict().is_challenge());
}