use std::collections::HashMap;
use webpage_quality_analyzer::config::enhanced_models::MetricOverride;
use webpage_quality_analyzer::config::profile_builder::ProfileBuilder;
use webpage_quality_analyzer::{Analyzer, ConfigManager};
const TEST_HTML: &str = r#"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Article - Best Practices for Web Development</title>
<meta name="description" content="Learn the best practices for modern web development including accessibility, performance, and SEO optimization techniques.">
<meta property="og:title" content="Test Article - Best Practices">
<meta property="og:description" content="Comprehensive guide to web development">
<meta property="og:image" content="https://example.com/image.jpg">
<link rel="canonical" href="https://example.com/article">
</head>
<body>
<header>
<h1>Best Practices for Web Development</h1>
<p class="author">By John Doe</p>
<time datetime="2025-10-01">October 1, 2025</time>
</header>
<main>
<article>
<h2>Introduction to Modern Web Development</h2>
<p>Modern web development requires a comprehensive understanding of various technologies, frameworks, and best practices. This article explores the essential principles that every developer should know to build high-quality, maintainable, and user-friendly web applications.</p>
<h3>Core Technologies</h3>
<p>HTML, CSS, and JavaScript form the foundation of web development. Understanding these core technologies is crucial for any web developer. HTML provides the structure, CSS handles the presentation, and JavaScript adds interactivity and dynamic behavior to web pages.</p>
<img src="tech-stack.jpg" alt="Modern web development technology stack">
<h3>Responsive Design Principles</h3>
<p>Creating websites that work seamlessly across all devices is no longer optional. Responsive design ensures that your content looks great and functions properly on smartphones, tablets, and desktop computers. Mobile-first design has become the industry standard.</p>
<h2>Performance Optimization</h2>
<p>Website performance directly impacts user experience and search engine rankings. Optimizing images, minimizing HTTP requests, leveraging browser caching, and using content delivery networks are essential strategies for improving load times and overall performance.</p>
<img src="performance.jpg" alt="Web performance optimization metrics">
<h3>Code Quality</h3>
<p>Writing clean, maintainable code is fundamental to long-term project success. Follow coding standards, use meaningful variable names, write comprehensive comments, and implement proper error handling. Regular code reviews help maintain high quality standards.</p>
<h2>Accessibility Standards</h2>
<p>Web accessibility ensures that websites are usable by everyone, including people with disabilities. Implementing WCAG guidelines, using semantic HTML, providing alt text for images, and ensuring keyboard navigation are important aspects of accessible web design.</p>
<h3>Testing and Quality Assurance</h3>
<p>Thorough testing is essential for delivering reliable web applications. Unit tests, integration tests, and end-to-end tests help catch bugs early. Cross-browser testing ensures consistent behavior across different browsers and devices.</p>
</article>
</main>
<footer>
<p>Contact us at info@example.com</p>
<p>© 2025 Example.com</p>
</footer>
</body>
</html>
"#;
async fn analyze_with_custom_profile(
html: &str,
profile: webpage_quality_analyzer::config::enhanced_models::EnhancedScoringProfile,
profile_name: &str,
) -> Result<webpage_quality_analyzer::PageQualityReport, webpage_quality_analyzer::AnalyzeError> {
let mut config_manager = ConfigManager::new();
config_manager
.add_profile(profile_name.to_string(), profile)
.map_err(|e| webpage_quality_analyzer::AnalyzeError::InvalidProfile(e))?;
config_manager
.set_active_profile(profile_name)
.map_err(|e| webpage_quality_analyzer::AnalyzeError::InvalidProfile(e))?;
let analyzer: Analyzer = Analyzer::builder()
.with_config_manager(config_manager)
.build_unchecked();
analyzer.run("https://example.com/test", Some(html)).await
}
#[tokio::test]
async fn test_disable_single_metric_word_count() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 1.0);
let profile_disabled = ProfileBuilder::new("disabled_word_count")
.with_category_weights(weights.clone())
.with_metric_enabled("word_count", false)
.build()
.expect("Failed to build profile");
let profile_enabled = ProfileBuilder::new("enabled_word_count")
.with_category_weights(weights)
.with_metric_enabled("word_count", true)
.with_metric_weight("word_count", 1.0)
.unwrap()
.build()
.expect("Failed to build profile");
let report_disabled =
analyze_with_custom_profile(TEST_HTML, profile_disabled, "disabled_word_count")
.await
.expect("Failed to analyze with disabled profile");
let report_enabled =
analyze_with_custom_profile(TEST_HTML, profile_enabled, "enabled_word_count")
.await
.expect("Failed to analyze with enabled profile");
assert!(report_enabled.metrics.html_analysis.content.word_count > 0);
assert!(report_disabled.metrics.html_analysis.content.word_count > 0);
println!("Score with word_count disabled: {}", report_disabled.score);
println!("Score with word_count enabled: {}", report_enabled.score);
assert!(report_enabled.score > 0.0);
assert!(report_disabled.score > 0.0);
}
#[tokio::test]
async fn test_disable_multiple_content_metrics() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 1.0);
let profile = ProfileBuilder::new("multi_disabled")
.with_category_weights(weights)
.with_metric_enabled("word_count", false)
.with_metric_enabled("readability_fk", false)
.with_metric_enabled("avg_sentence_len", false)
.with_metric_enabled("stopword_ratio", false)
.build()
.expect("Failed to build profile");
assert!(profile.metric_overrides.contains_key("word_count"));
assert!(!profile.metric_overrides.get("word_count").unwrap().enabled);
assert!(profile.metric_overrides.contains_key("readability_fk"));
assert!(
!profile
.metric_overrides
.get("readability_fk")
.unwrap()
.enabled
);
let report = analyze_with_custom_profile(TEST_HTML, profile, "multi_disabled")
.await
.expect("Failed to analyze");
assert!(report.metrics.html_analysis.content.word_count > 0);
println!("Score with multiple metrics disabled: {}", report.score);
}
#[tokio::test]
async fn test_disable_entire_category_by_zero_weight() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 1.0);
weights.insert("seo".to_string(), 0.0);
let profile = ProfileBuilder::new("zero_seo_weight")
.with_category_weights(weights)
.build()
.expect("Failed to build profile");
let report = analyze_with_custom_profile(TEST_HTML, profile, "zero_seo_weight")
.await
.expect("Failed to analyze");
assert!(report.metrics.html_analysis.seo.title_len > 0);
println!("Score with SEO category at 0 weight: {}", report.score);
}
#[tokio::test]
async fn test_disable_seo_metrics_individually() {
let mut weights = HashMap::new();
weights.insert("seo".to_string(), 1.0);
let profile = ProfileBuilder::new("seo_metrics_disabled")
.with_category_weights(weights)
.with_metric_enabled("title_len", false)
.with_metric_enabled("meta_desc_len", false)
.with_metric_enabled("og_tags", false)
.with_metric_enabled("has_canonical", false)
.build()
.expect("Failed to build profile");
for metric in &["title_len", "meta_desc_len", "og_tags", "has_canonical"] {
assert!(profile.metric_overrides.contains_key(*metric));
assert!(!profile.metric_overrides.get(*metric).unwrap().enabled);
}
let report = analyze_with_custom_profile(TEST_HTML, profile, "seo_metrics_disabled")
.await
.expect("Failed to analyze");
println!("Score with key SEO metrics disabled: {}", report.score);
}
#[tokio::test]
async fn test_toggle_structure_metrics() {
let mut weights = HashMap::new();
weights.insert("structure".to_string(), 1.0);
let profile_enabled = ProfileBuilder::new("structure_enabled")
.with_category_weights(weights.clone())
.with_metric_enabled("heading_depth", true)
.with_metric_enabled("heading_order_score", true)
.with_metric_enabled("headings_count", true)
.with_metric_enabled("heading_structure_valid", true)
.build()
.expect("Failed to build profile");
let profile_disabled = ProfileBuilder::new("structure_disabled")
.with_category_weights(weights)
.with_metric_enabled("heading_depth", false)
.with_metric_enabled("heading_order_score", false)
.with_metric_enabled("headings_count", false)
.with_metric_enabled("heading_structure_valid", false)
.build()
.expect("Failed to build profile");
let report_enabled =
analyze_with_custom_profile(TEST_HTML, profile_enabled, "structure_enabled")
.await
.expect("Failed to analyze");
let report_disabled =
analyze_with_custom_profile(TEST_HTML, profile_disabled, "structure_disabled")
.await
.expect("Failed to analyze");
assert!(
report_enabled
.metrics
.html_analysis
.structure
.headings_count
> 0
);
assert_eq!(
report_enabled
.metrics
.html_analysis
.structure
.headings_count,
report_disabled
.metrics
.html_analysis
.structure
.headings_count
);
println!("Structure enabled score: {}", report_enabled.score);
println!("Structure disabled score: {}", report_disabled.score);
}
#[tokio::test]
async fn test_toggle_media_metrics() {
let mut weights = HashMap::new();
weights.insert("media".to_string(), 1.0);
let profile = ProfileBuilder::new("media_disabled")
.with_category_weights(weights)
.with_metric_enabled("images_count", false)
.with_metric_enabled("image_alt_coverage", false)
.with_metric_enabled("missing_alt_text_count", false)
.build()
.expect("Failed to build profile");
let report = analyze_with_custom_profile(TEST_HTML, profile, "media_disabled")
.await
.expect("Failed to analyze");
assert!(report.metrics.html_analysis.media.images_count > 0);
println!("Score with media metrics disabled: {}", report.score);
}
#[tokio::test]
async fn test_toggle_accessibility_metrics() {
let mut weights = HashMap::new();
weights.insert("accessibility".to_string(), 1.0);
let profile = ProfileBuilder::new("mixed_accessibility")
.with_category_weights(weights)
.with_metric_enabled("aria_labels_missing", true)
.with_metric_enabled("color_contrast_violations", false)
.with_metric_enabled("wcag_aa_compliance_score", true)
.with_metric_enabled("keyboard_navigation_score", false)
.build()
.expect("Failed to build profile");
assert!(
profile
.metric_overrides
.get("aria_labels_missing")
.unwrap()
.enabled
);
assert!(
!profile
.metric_overrides
.get("color_contrast_violations")
.unwrap()
.enabled
);
let report = analyze_with_custom_profile(TEST_HTML, profile, "mixed_accessibility")
.await
.expect("Failed to analyze");
println!("Score with mixed accessibility metrics: {}", report.score);
}
#[tokio::test]
async fn test_disable_all_metrics_in_category() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 1.0);
let mut builder = ProfileBuilder::new("all_content_disabled").with_category_weights(weights);
for metric in &[
"word_count",
"main_text_ratio",
"avg_sentence_len",
"readability_fk",
"stopword_ratio",
"unique_word_ratio",
"sentence_count",
"reading_time_minutes",
"content_density",
"content_to_html_ratio",
"content_structure_penalty",
] {
builder = builder.with_metric_enabled(metric, false);
}
let profile = builder.build().expect("Failed to build profile");
for metric in &[
"word_count",
"main_text_ratio",
"avg_sentence_len",
"readability_fk",
] {
if let Some(override_config) = profile.metric_overrides.get(*metric) {
assert!(
!override_config.enabled,
"Metric {} should be disabled",
metric
);
}
}
let report = analyze_with_custom_profile(TEST_HTML, profile, "all_content_disabled")
.await
.expect("Failed to analyze");
println!("Score with all content metrics disabled: {}", report.score);
}
#[tokio::test]
async fn test_toggle_persistence_through_compilation() {
use webpage_quality_analyzer::scoring::profile_compiler::ProfileCompiler;
let mut weights = HashMap::new();
weights.insert("content".to_string(), 0.60);
weights.insert("seo".to_string(), 0.40);
let profile = ProfileBuilder::new("toggle_persistence")
.with_category_weights(weights)
.with_metric_enabled("word_count", false)
.with_metric_enabled("title_len", false)
.build()
.expect("Failed to build profile");
let compiler = ProfileCompiler::new();
let compiled = compiler
.compile_profile(&profile)
.expect("Failed to compile profile");
println!("Compiled profile name: {}", compiled.profile_name);
assert_eq!(compiled.profile_name, "toggle_persistence");
}
#[tokio::test]
async fn test_enable_only_critical_metrics() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 0.50);
weights.insert("seo".to_string(), 0.30);
weights.insert("structure".to_string(), 0.20);
let profile = ProfileBuilder::new("critical_only")
.with_category_weights(weights)
.with_metric_enabled("word_count", true)
.with_metric_weight("word_count", 0.50)
.unwrap()
.with_metric_enabled("title_len", true)
.with_metric_weight("title_len", 0.50)
.unwrap()
.with_metric_enabled("heading_order_score", true)
.with_metric_weight("heading_order_score", 0.50)
.unwrap()
.with_metric_enabled("readability_fk", false)
.with_metric_enabled("avg_sentence_len", false)
.with_metric_enabled("stopword_ratio", false)
.build()
.expect("Failed to build profile");
let report = analyze_with_custom_profile(TEST_HTML, profile, "critical_only")
.await
.expect("Failed to analyze");
println!("Score with only critical metrics enabled: {}", report.score);
assert!(
report.score > 0.0,
"Score should be positive with some metrics enabled"
);
}
#[tokio::test]
async fn test_default_metrics_enabled() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 1.0);
let profile = ProfileBuilder::new("default_enabled")
.with_category_weights(weights)
.build()
.expect("Failed to build profile");
let report = analyze_with_custom_profile(TEST_HTML, profile, "default_enabled")
.await
.expect("Failed to analyze");
assert!(report.score > 0.0);
assert!(report.metrics.html_analysis.content.word_count > 0);
println!("Score with default (all enabled): {}", report.score);
}
#[tokio::test]
async fn test_toggle_boolean_metrics() {
let mut weights = HashMap::new();
weights.insert("seo".to_string(), 0.50);
weights.insert("structure".to_string(), 0.50);
let profile = ProfileBuilder::new("boolean_metrics")
.with_category_weights(weights)
.with_metric_enabled("has_canonical", true)
.with_metric_enabled("robots_noindex", false)
.with_metric_enabled("heading_structure_valid", true)
.build()
.expect("Failed to build profile");
let report = analyze_with_custom_profile(TEST_HTML, profile, "boolean_metrics")
.await
.expect("Failed to analyze");
assert!(
report.score >= 0.0 && report.score <= 100.0,
"Score should be valid"
);
let _ = report.metrics.html_analysis.seo.has_canonical;
let _ = report
.metrics
.html_analysis
.structure
.heading_structure_valid;
println!("Score with boolean metric toggles: {}", report.score);
}
#[tokio::test]
async fn test_toggle_optional_metrics() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 1.0);
let profile = ProfileBuilder::new("optional_metrics")
.with_category_weights(weights)
.with_metric_enabled("readability_fk", false) .with_metric_enabled("stopword_ratio", false) .build()
.expect("Failed to build profile");
let report = analyze_with_custom_profile(TEST_HTML, profile, "optional_metrics")
.await
.expect("Failed to analyze");
println!("Score with optional metrics disabled: {}", report.score);
}
#[tokio::test]
async fn test_progressive_metric_enabling() {
let test_cases = vec![
(vec!["word_count"], "Only word_count"),
(vec!["word_count", "title_len"], "word_count + title_len"),
(
vec!["word_count", "title_len", "heading_order_score"],
"Three metrics",
),
];
for (enabled_metrics, description) in test_cases {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 0.50);
weights.insert("seo".to_string(), 0.30);
weights.insert("structure".to_string(), 0.20);
let mut builder = ProfileBuilder::new(&format!("progressive_{}", enabled_metrics.len()))
.with_category_weights(weights);
for metric in &enabled_metrics {
builder = builder.with_metric_enabled(metric, true);
}
let profile = builder.build().expect("Failed to build profile");
let report = analyze_with_custom_profile(
TEST_HTML,
profile,
&format!("progressive_{}", enabled_metrics.len()),
)
.await
.expect("Failed to analyze");
println!("{}: Score = {}", description, report.score);
}
}
#[tokio::test]
async fn test_category_with_all_metrics_disabled() {
let mut weights = HashMap::new();
weights.insert("media".to_string(), 1.0);
let profile = ProfileBuilder::new("all_media_disabled")
.with_category_weights(weights)
.with_metric_enabled("images_count", false)
.with_metric_enabled("image_alt_coverage", false)
.with_metric_enabled("missing_alt_text_count", false)
.with_metric_enabled("images_to_text_ratio", false)
.with_metric_enabled("video_count", false)
.with_metric_enabled("audio_count", false)
.build()
.expect("Failed to build profile");
let report = analyze_with_custom_profile(TEST_HTML, profile, "all_media_disabled")
.await
.expect("Failed to analyze");
println!("Score with all media metrics disabled: {}", report.score);
}
#[test]
fn test_metric_override_structure() {
let override_config = MetricOverride {
weight: 0.5,
enabled: false,
thresholds: None,
penalty_multiplier: 1.0,
bonus_conditions: Vec::new(),
scoring_function_override: None,
};
assert!(!override_config.enabled);
assert_eq!(override_config.weight, 0.5);
}
#[test]
fn test_profile_builder_toggle_api() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 1.0);
let profile = ProfileBuilder::new("api_test")
.with_category_weights(weights)
.with_metric_enabled("word_count", true)
.with_metric_enabled("readability_fk", false)
.build()
.expect("Failed to build profile");
assert_eq!(profile.metadata.name, "api_test");
assert!(profile.metric_overrides.contains_key("word_count"));
assert!(profile.metric_overrides.contains_key("readability_fk"));
let word_count_override = profile.metric_overrides.get("word_count").unwrap();
assert!(word_count_override.enabled);
let readability_override = profile.metric_overrides.get("readability_fk").unwrap();
assert!(!readability_override.enabled);
}
#[tokio::test]
async fn test_toggle_affects_category_score() {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 1.0);
let profile_all_enabled = ProfileBuilder::new("all_enabled")
.with_category_weights(weights.clone())
.build()
.expect("Failed to build profile");
let profile_some_disabled = ProfileBuilder::new("some_disabled")
.with_category_weights(weights)
.with_metric_enabled("word_count", false)
.with_metric_enabled("readability_fk", false)
.with_metric_enabled("avg_sentence_len", false)
.build()
.expect("Failed to build profile");
let report_all = analyze_with_custom_profile(TEST_HTML, profile_all_enabled, "all_enabled")
.await
.expect("Failed to analyze");
let report_some =
analyze_with_custom_profile(TEST_HTML, profile_some_disabled, "some_disabled")
.await
.expect("Failed to analyze");
println!("All enabled score: {}", report_all.score);
println!("Some disabled score: {}", report_some.score);
assert!(report_all.score > 0.0);
assert!(report_some.score > 0.0);
}