use super::support::{MockRdpServer, load_fixture};
fn ff_rdp_bin() -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_BIN_EXE_ff-rdp"))
}
fn base_args(port: u16) -> Vec<String> {
vec![
"--host".to_owned(),
"127.0.0.1".to_owned(),
"--port".to_owned(),
port.to_string(),
"--no-daemon".to_owned(),
]
}
fn styles_server(style_method: &str, style_fixture: &str) -> MockRdpServer {
MockRdpServer::new()
.on("listTabs", load_fixture("list_tabs_response.json"))
.on("getTarget", load_fixture("get_target_response.json"))
.on(
"getWalker",
load_fixture("inspector_get_walker_response.json"),
)
.on(
"getPageStyle",
load_fixture("inspector_get_page_style_response.json"),
)
.on(
"documentElement",
load_fixture("dom_walker_document_element_response.json"),
)
.on(
"querySelector",
load_fixture("dom_walker_query_selector_response.json"),
)
.on(style_method, load_fixture(style_fixture))
}
#[test]
fn styles_computed_outputs_json() {
let server = styles_server("getComputed", "page_style_get_computed_response.json");
let port = server.port();
let handle = std::thread::spawn(move || server.serve_one());
let mut args = base_args(port);
args.extend(["styles".to_owned(), "h1".to_owned()]);
let output = std::process::Command::new(ff_rdp_bin())
.args(&args)
.output()
.expect("failed to spawn ff-rdp");
handle.join().unwrap();
assert!(
output.status.success(),
"expected success, stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let json: serde_json::Value =
serde_json::from_slice(&output.stdout).expect("stdout must be valid JSON");
let results = json["results"]
.as_array()
.expect("computed results should be an array");
assert!(!results.is_empty(), "should have at least one property");
let first = &results[0];
assert!(first.get("name").is_some(), "entry must have name");
assert!(first.get("value").is_some(), "entry must have value");
assert!(first.get("priority").is_some(), "entry must have priority");
let names: Vec<&str> = results.iter().filter_map(|e| e["name"].as_str()).collect();
assert!(names.contains(&"color"), "should include color property");
assert!(
names.contains(&"font-size"),
"should include font-size property"
);
assert_eq!(json["meta"]["selector"], "h1");
}
#[test]
fn styles_applied_outputs_json() {
let server = styles_server("getApplied", "page_style_get_applied_response.json");
let port = server.port();
let handle = std::thread::spawn(move || server.serve_one());
let mut args = base_args(port);
args.extend(["styles".to_owned(), "h1".to_owned(), "--applied".to_owned()]);
let output = std::process::Command::new(ff_rdp_bin())
.args(&args)
.output()
.expect("failed to spawn ff-rdp");
handle.join().unwrap();
assert!(
output.status.success(),
"expected success, stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let json: serde_json::Value =
serde_json::from_slice(&output.stdout).expect("stdout must be valid JSON");
let results = json["results"]
.as_array()
.expect("applied results should be an array");
assert!(!results.is_empty(), "should have at least one applied rule");
let first = &results[0];
assert!(first.get("selector").is_some(), "rule must have selector");
assert!(
first.get("properties").is_some(),
"rule must have properties"
);
let selectors: Vec<&str> = results
.iter()
.filter_map(|r| r["selector"].as_str())
.collect();
assert!(selectors.contains(&"h1"), "should include h1 selector");
assert!(
selectors.contains(&"h1, .title"),
"should include combined selector"
);
let props = first["properties"]
.as_array()
.expect("properties should be an array");
assert!(!props.is_empty(), "rule should have declarations");
assert!(props[0].get("name").is_some(), "property must have name");
assert!(props[0].get("value").is_some(), "property must have value");
}
#[test]
fn styles_layout_outputs_json() {
let server = styles_server("getLayout", "page_style_get_layout_response.json");
let port = server.port();
let handle = std::thread::spawn(move || server.serve_one());
let mut args = base_args(port);
args.extend(["styles".to_owned(), "h1".to_owned(), "--layout".to_owned()]);
let output = std::process::Command::new(ff_rdp_bin())
.args(&args)
.output()
.expect("failed to spawn ff-rdp");
handle.join().unwrap();
assert!(
output.status.success(),
"expected success, stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let json: serde_json::Value =
serde_json::from_slice(&output.stdout).expect("stdout must be valid JSON");
let results = &json["results"];
assert_eq!(results["width"], 784.0, "width should match fixture");
assert_eq!(results["height"], 37.0, "height should match fixture");
assert!(results.get("margin").is_some(), "should have margin");
assert!(results.get("border").is_some(), "should have border");
assert!(results.get("padding").is_some(), "should have padding");
let margin_top = results["margin"]["top"]
.as_f64()
.expect("margin.top should be a number");
assert!(
(margin_top - 21.44).abs() < 0.01,
"margin.top should be ~21.44, got {margin_top}"
);
assert_eq!(results["boxSizing"], "content-box");
assert_eq!(results["position"], "static");
assert_eq!(results["display"], "block");
}
#[test]
fn styles_with_jq_filter() {
let server = styles_server("getComputed", "page_style_get_computed_response.json");
let port = server.port();
let handle = std::thread::spawn(move || server.serve_one());
let mut args = base_args(port);
args.extend([
"--jq".to_owned(),
".results[0].name".to_owned(),
"styles".to_owned(),
"h1".to_owned(),
]);
let output = std::process::Command::new(ff_rdp_bin())
.args(&args)
.output()
.expect("failed to spawn ff-rdp");
handle.join().unwrap();
assert!(
output.status.success(),
"expected success, stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(
stdout.trim(),
"\"color\"",
"first sorted property should be color"
);
}
#[test]
fn styles_properties_filter_returns_only_requested() {
let server = styles_server("getComputed", "page_style_get_computed_response.json");
let port = server.port();
let handle = std::thread::spawn(move || server.serve_one());
let mut args = base_args(port);
args.extend([
"styles".to_owned(),
"h1".to_owned(),
"--properties".to_owned(),
"color,display".to_owned(),
]);
let output = std::process::Command::new(ff_rdp_bin())
.args(&args)
.output()
.expect("failed to spawn ff-rdp");
handle.join().unwrap();
assert!(
output.status.success(),
"expected success, stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let json: serde_json::Value =
serde_json::from_slice(&output.stdout).expect("stdout must be valid JSON");
let results = json["results"]
.as_array()
.expect("results should be an array");
assert_eq!(results.len(), 2, "should have exactly 2 properties");
let names: Vec<&str> = results.iter().filter_map(|e| e["name"].as_str()).collect();
assert!(names.contains(&"color"), "should include color");
assert!(names.contains(&"display"), "should include display");
assert!(
!names.contains(&"font-size"),
"font-size should be filtered out"
);
assert!(
!names.contains(&"margin-top"),
"margin-top should be filtered out"
);
}