use scraper::Html;
use url::Url;
#[derive(Debug, Default, Clone, Copy)]
pub struct Extractor;
impl Extractor {
pub fn new() -> Self {
Self
}
pub fn extract_links(&self, base: &Url, html: &str) -> Vec<Url> {
crate::discovery::links::extract_links(base, html)
}
pub fn extract_links_from_document(&self, base: &Url, doc: &Html) -> Vec<Url> {
crate::discovery::links::extract_links_from_document(base, doc)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn base() -> Url {
Url::parse("https://example.com/posts/42").unwrap()
}
#[test]
fn resolves_relative_anchor() {
let html = r#"<html><body><a href="/about">about</a></body></html>"#;
let links = Extractor::new().extract_links(&base(), html);
assert!(links
.iter()
.any(|u| u.as_str() == "https://example.com/about"));
}
#[test]
fn empty_document_yields_no_links() {
let html = "<html><body></body></html>";
let links = Extractor::new().extract_links(&base(), html);
assert!(links.is_empty(), "no anchors → no links, got {links:?}");
}
#[test]
fn parity_with_underlying_discovery_links() {
let html = r#"<html><body>
<a href="/a">a</a>
<a href="https://other.example/b">b</a>
<a href="?q=c">c</a>
</body></html>"#;
let via_extractor = Extractor::new().extract_links(&base(), html);
let via_discovery = crate::discovery::links::extract_links(&base(), html);
assert_eq!(via_extractor, via_discovery);
}
#[test]
fn document_path_matches_html_path() {
let html = r#"<html><body><a href="/p/1">a</a><a href="/p/2">b</a></body></html>"#;
let parsed = Html::parse_document(html);
let from_html = Extractor::new().extract_links(&base(), html);
let from_doc = Extractor::new().extract_links_from_document(&base(), &parsed);
assert_eq!(from_html, from_doc);
}
#[test]
fn extractor_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Extractor>();
}
}