use justext::{extract_text, get_stoplist, justext, ClassType, Config};
fn english() -> std::collections::HashSet<String> {
get_stoplist("English").unwrap()
}
#[test]
fn test_words_split_by_br_tag() {
let paragraphs = justext(
"abc<br/>def becoming abcdef",
&english(),
&Config::default(),
);
let texts: Vec<&str> = paragraphs.iter().map(|p| p.text.as_str()).collect();
assert_eq!(texts, vec!["abc def becoming abcdef"]);
}
#[test]
fn test_empty_html() {
let ps = justext("<html><body></body></html>", &english(), &Config::default());
assert!(ps.is_empty());
}
#[test]
fn test_single_good_paragraph() {
let text = "This is a sentence that contains many common English stopwords and it \
should be classified as good content by the algorithm because the text is \
long enough that it exceeds the length_high threshold of two hundred characters.";
assert!(text.len() > 200, "test text must exceed length_high=200");
let html = format!("<html><body><p>{text}</p></body></html>");
let ps = justext(&html, &english(), &Config::default());
assert!(!ps.is_empty());
assert_eq!(ps[0].class_type, ClassType::Good);
}
#[test]
fn test_boilerplate_nav_links() {
let html = concat!(
"<html><body>",
"<p><a>Home</a> | <a>About</a> | <a>Contact</a> | <a>Privacy</a> | <a>Terms</a></p>",
"</body></html>"
);
let ps = justext(html, &english(), &Config::default());
assert!(!ps.is_empty());
for p in &ps {
assert_eq!(
p.class_type,
ClassType::Bad,
"nav link paragraph should be Bad: {:?}",
p.text
);
}
}
#[test]
fn test_extract_text_returns_only_good() {
let good = "This is a content paragraph with many common stopwords and it is long enough \
to exceed the length_high threshold so that it will be classified as good \
content worth extracting by the justext algorithm when applied here.";
assert!(good.len() > 200);
let html = format!(
"<html><body>\
<p><a>nav link boilerplate here</a></p>\
<p>{good}</p>\
</body></html>"
);
let text = extract_text(&html, &english(), &Config::default());
assert!(
text.contains("content paragraph"),
"extracted text should contain good content"
);
}
#[test]
fn test_language_independent_mode() {
let config = Config::default()
.with_stopwords_low(0.0)
.with_stopwords_high(0.0);
let stoplist = std::collections::HashSet::new();
let long = "This paragraph is long enough to exceed both the length_low and the \
length_high thresholds so it will be classified as good content by the \
algorithm even when using an empty stoplist in language independent mode.";
assert!(long.len() > 200);
let html = format!("<html><body><p>Short.</p><p>{long}</p></body></html>");
let ps = justext(&html, &stoplist, &config);
assert_eq!(ps.len(), 2);
assert_eq!(ps[0].class_type, ClassType::Bad);
assert_eq!(ps[1].class_type, ClassType::Good);
}
#[test]
fn test_copyright_paragraph_is_bad() {
let html = "<html><body><p>\u{00A9} 2024 Example Corp. All rights reserved.</p></body></html>";
let ps = justext(html, &english(), &Config::default());
assert!(!ps.is_empty());
assert_eq!(ps[0].class_type, ClassType::Bad);
}
#[test]
fn test_heading_near_content_is_promoted() {
let content = "This paragraph contains many common English stopwords and it is long \
enough to be classified as good content with the English stoplist applied \
correctly by the justext algorithm when processing this article text here.";
assert!(content.len() > 200);
let html = format!(
"<html><body>\
<h1>Article Title</h1>\
<p>{content}</p>\
</body></html>"
);
let ps = justext(&html, &english(), &Config::default());
assert!(ps.len() >= 2);
assert_eq!(ps[1].class_type, ClassType::Good);
assert_ne!(
ps[0].class_type,
ClassType::Bad,
"heading near good content should not be Bad"
);
}
#[test]
fn test_paragraph_struct_fields() {
let html = "<html><body><h2>My Heading</h2></body></html>";
let ps = justext(html, &english(), &Config::default());
assert!(!ps.is_empty());
let h = &ps[0];
assert_eq!(h.text, "My Heading");
assert!(h.dom_path.contains("h2"), "dom_path should contain h2");
assert!(h.xpath.contains("h2"), "xpath should contain h2");
assert!(h.heading, "h2 paragraph should have heading=true");
}