use chromiumoxide::Page;
use futures::future::BoxFuture;
use crate::cli::WcagLevel;
use crate::wcag::Violation;
use super::{
check_abbreviations_with_page, check_accessible_authentication_with_page,
check_background_audio_with_page, check_checkbox_group_with_page,
check_content_on_hover_with_page, check_display_modes_with_page,
check_fake_navigation_link_with_page, check_focus_not_obscured_enhanced_with_page,
check_focus_not_obscured_minimum_with_page, check_focus_visible_css_with_page,
check_form_no_submit_with_page, check_frame_tested_with_page, check_frame_title_with_page,
check_identify_purpose_with_page, check_image_input_rules_with_page,
check_input_purpose_with_page, check_language_extended_with_page,
check_language_of_parts_with_page, check_location_with_page,
check_meaningful_sequence_with_page, check_media_alternative_with_page,
check_modern_attributes_with_page, check_motion_actuation_with_page,
check_no_interruptions_with_page, check_no_timing_with_page,
check_non_text_contrast_css_with_page, check_on_focus_with_page, check_on_input_with_page,
check_orientation_with_page, check_page_titled_with_page, check_pause_stop_hide_with_page,
check_pointer_cancellation_with_page, check_pointer_gestures_with_page,
check_presentation_semantic_children_with_page, check_re_authenticate_with_page,
check_reduced_motion_with_page, check_redundant_entry_with_page,
check_redundant_role_with_page, check_same_origin_iframes_with_page,
check_scrollable_region_focusable_with_page, check_server_side_image_map_with_page,
check_table_headers_attr_with_page, check_target_size_enhanced_with_page,
check_target_size_minimum_with_page, check_text_spacing_with_page, check_timeouts_with_page,
check_timing_with_page, check_use_of_color_with_page, check_video_caption_tracks_with_page,
check_visual_presentation_with_page,
};
use crate::wcag::engine::check_click_handlers_with_page;
pub struct PageRuleEntry {
pub rule_id: &'static str,
pub name: &'static str,
pub min_level: WcagLevel,
pub check_fn: for<'a> fn(&'a Page) -> BoxFuture<'a, Vec<Violation>>,
}
pub const PAGE_RULES: &[PageRuleEntry] = &[
PageRuleEntry {
rule_id: "2.4.1/frame-title",
name: "frame-title",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_frame_title_with_page(p)),
},
PageRuleEntry {
rule_id: "4.1.2/frame-tested",
name: "frame-tested",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_frame_tested_with_page(p)),
},
PageRuleEntry {
rule_id: "1.2.2/video-caption-track",
name: "video-caption-track",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_video_caption_tracks_with_page(p)),
},
PageRuleEntry {
rule_id: "iframe/same-origin-content",
name: "same-origin iframe content",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_same_origin_iframes_with_page(p)),
},
PageRuleEntry {
rule_id: "2.4.2/document-title",
name: "document-title",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_page_titled_with_page(p)),
},
PageRuleEntry {
rule_id: "2.1.1/click-handler",
name: "inline click-handler",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_click_handlers_with_page(p)),
},
PageRuleEntry {
rule_id: "2.1.1/scrollable-region-focusable",
name: "scrollable-region-focusable",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_scrollable_region_focusable_with_page(p)),
},
PageRuleEntry {
rule_id: "3.2.2/form-no-submit",
name: "form-no-submit",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_form_no_submit_with_page(p)),
},
PageRuleEntry {
rule_id: "1.3.1/form-field-group",
name: "form-field-group (checkbox)",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_checkbox_group_with_page(p)),
},
PageRuleEntry {
rule_id: "3.3.7/redundant-entry",
name: "redundant-entry",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_redundant_entry_with_page(p)),
},
PageRuleEntry {
rule_id: "3.3.8/accessible-authentication",
name: "accessible-authentication",
min_level: WcagLevel::AA,
check_fn: |p| Box::pin(check_accessible_authentication_with_page(p)),
},
PageRuleEntry {
rule_id: "1.3.1/presentation-semantic-children",
name: "presentation semantic children",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_presentation_semantic_children_with_page(p)),
},
PageRuleEntry {
rule_id: "1.3.2/meaningful-sequence",
name: "meaningful-sequence (CSS order)",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_meaningful_sequence_with_page(p)),
},
PageRuleEntry {
rule_id: "4.1.2/modern-attributes",
name: "modern interaction attributes",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_modern_attributes_with_page(p)),
},
PageRuleEntry {
rule_id: "2.2.1/meta-refresh",
name: "meta-refresh",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_timing_with_page(p)),
},
PageRuleEntry {
rule_id: "1.4.1/use-of-color",
name: "use-of-color",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_use_of_color_with_page(p)),
},
PageRuleEntry {
rule_id: "3.2.1/on-focus",
name: "on-focus context change",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_on_focus_with_page(p)),
},
PageRuleEntry {
rule_id: "3.2.2/on-input",
name: "on-input context change",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_on_input_with_page(p)),
},
PageRuleEntry {
rule_id: "1.1.1/image-input-object-alt",
name: "area/input-image/object alt",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_image_input_rules_with_page(p)),
},
PageRuleEntry {
rule_id: "1.1.1/server-side-image-map",
name: "server-side image map",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_server_side_image_map_with_page(p)),
},
PageRuleEntry {
rule_id: "1.3.1/td-headers-attr",
name: "table cell headers attribute",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_table_headers_attr_with_page(p)),
},
PageRuleEntry {
rule_id: "3.1.1/language-extended",
name: "xml-lang-mismatch",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_language_extended_with_page(p)),
},
PageRuleEntry {
rule_id: "3.1.2/language-of-parts",
name: "language-of-parts",
min_level: WcagLevel::AA,
check_fn: |p| Box::pin(check_language_of_parts_with_page(p)),
},
PageRuleEntry {
rule_id: "4.1.2/redundant-role",
name: "redundant role",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_redundant_role_with_page(p)),
},
PageRuleEntry {
rule_id: "4.1.2/link-as-button",
name: "link as button",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_fake_navigation_link_with_page(p)),
},
PageRuleEntry {
rule_id: "display/modes",
name: "display-mode convention",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_display_modes_with_page(p)),
},
PageRuleEntry {
rule_id: "2.5.1/pointer-gestures",
name: "pointer-gestures",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_pointer_gestures_with_page(p)),
},
PageRuleEntry {
rule_id: "2.5.2/pointer-cancellation",
name: "pointer-cancellation",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_pointer_cancellation_with_page(p)),
},
PageRuleEntry {
rule_id: "2.5.4/motion-actuation",
name: "motion-actuation",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_motion_actuation_with_page(p)),
},
PageRuleEntry {
rule_id: "2.2.2/pause-stop-hide",
name: "pause-stop-hide",
min_level: WcagLevel::A,
check_fn: |p| Box::pin(check_pause_stop_hide_with_page(p)),
},
PageRuleEntry {
rule_id: "1.3.5/autocomplete-valid",
name: "autocomplete-valid",
min_level: WcagLevel::AA,
check_fn: |p| Box::pin(check_input_purpose_with_page(p)),
},
PageRuleEntry {
rule_id: "1.3.4/orientation",
name: "orientation",
min_level: WcagLevel::AA,
check_fn: |p| Box::pin(check_orientation_with_page(p)),
},
PageRuleEntry {
rule_id: "2.4.7/focus-visible-css",
name: "CSS focus-suppression",
min_level: WcagLevel::AA,
check_fn: |p| Box::pin(check_focus_visible_css_with_page(p)),
},
PageRuleEntry {
rule_id: "1.4.11/non-text-contrast-css",
name: "CSS non-text contrast",
min_level: WcagLevel::AA,
check_fn: |p| Box::pin(check_non_text_contrast_css_with_page(p)),
},
PageRuleEntry {
rule_id: "2.3.3/reduced-motion",
name: "reduced-motion",
min_level: WcagLevel::AA,
check_fn: |p| Box::pin(check_reduced_motion_with_page(p)),
},
PageRuleEntry {
rule_id: "1.4.13/content-on-hover",
name: "content-on-hover",
min_level: WcagLevel::AA,
check_fn: |p| Box::pin(check_content_on_hover_with_page(p)),
},
PageRuleEntry {
rule_id: "2.5.8/target-size-minimum",
name: "target-size-minimum",
min_level: WcagLevel::AA,
check_fn: |p| Box::pin(check_target_size_minimum_with_page(p)),
},
PageRuleEntry {
rule_id: "1.4.12/text-spacing",
name: "text-spacing",
min_level: WcagLevel::AA,
check_fn: |p| Box::pin(check_text_spacing_with_page(p)),
},
PageRuleEntry {
rule_id: "2.4.11/focus-not-obscured-minimum",
name: "focus-not-obscured-minimum",
min_level: WcagLevel::AA,
check_fn: |p| Box::pin(check_focus_not_obscured_minimum_with_page(p)),
},
PageRuleEntry {
rule_id: "1.3.6/identify-purpose",
name: "identify-purpose",
min_level: WcagLevel::AAA,
check_fn: |p| Box::pin(check_identify_purpose_with_page(p)),
},
PageRuleEntry {
rule_id: "1.4.7/background-audio",
name: "background-audio",
min_level: WcagLevel::AAA,
check_fn: |p| Box::pin(check_background_audio_with_page(p)),
},
PageRuleEntry {
rule_id: "1.2.8/media-alternative",
name: "media-alternative",
min_level: WcagLevel::AAA,
check_fn: |p| Box::pin(check_media_alternative_with_page(p)),
},
PageRuleEntry {
rule_id: "1.4.8/visual-presentation",
name: "visual-presentation",
min_level: WcagLevel::AAA,
check_fn: |p| Box::pin(check_visual_presentation_with_page(p)),
},
PageRuleEntry {
rule_id: "2.2.3/no-timing",
name: "no-timing",
min_level: WcagLevel::AAA,
check_fn: |p| Box::pin(check_no_timing_with_page(p)),
},
PageRuleEntry {
rule_id: "2.2.4/no-interruptions",
name: "no-interruptions",
min_level: WcagLevel::AAA,
check_fn: |p| Box::pin(check_no_interruptions_with_page(p)),
},
PageRuleEntry {
rule_id: "2.2.5/re-authenticate",
name: "re-authenticate",
min_level: WcagLevel::AAA,
check_fn: |p| Box::pin(check_re_authenticate_with_page(p)),
},
PageRuleEntry {
rule_id: "2.2.6/timeouts",
name: "timeouts",
min_level: WcagLevel::AAA,
check_fn: |p| Box::pin(check_timeouts_with_page(p)),
},
PageRuleEntry {
rule_id: "2.4.8/location",
name: "location",
min_level: WcagLevel::AAA,
check_fn: |p| Box::pin(check_location_with_page(p)),
},
PageRuleEntry {
rule_id: "2.5.5/target-size-enhanced",
name: "target-size-enhanced",
min_level: WcagLevel::AAA,
check_fn: |p| Box::pin(check_target_size_enhanced_with_page(p)),
},
PageRuleEntry {
rule_id: "2.4.12/focus-not-obscured-enhanced",
name: "focus-not-obscured-enhanced",
min_level: WcagLevel::AAA,
check_fn: |p| Box::pin(check_focus_not_obscured_enhanced_with_page(p)),
},
PageRuleEntry {
rule_id: "3.1.4/abbreviations",
name: "abbreviations",
min_level: WcagLevel::AAA,
check_fn: |p| Box::pin(check_abbreviations_with_page(p)),
},
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn page_rules_table_is_non_empty() {
assert!(!PAGE_RULES.is_empty());
}
#[test]
fn page_rules_have_unique_rule_ids() {
let mut ids: Vec<&'static str> = PAGE_RULES.iter().map(|r| r.rule_id).collect();
ids.sort_unstable();
let before = ids.len();
ids.dedup();
assert_eq!(before, ids.len(), "duplicate rule_id in PAGE_RULES");
}
#[test]
fn level_a_filter_includes_only_level_a_rules() {
let count = PAGE_RULES
.iter()
.filter(|r| WcagLevel::A >= r.min_level)
.count();
assert_eq!(count, 28);
}
#[test]
fn level_aa_filter_includes_aa_plus_a_rules() {
let count = PAGE_RULES
.iter()
.filter(|r| WcagLevel::AA >= r.min_level)
.count();
assert_eq!(count, 39);
}
#[test]
fn level_aaa_filter_includes_all_rules() {
let count = PAGE_RULES
.iter()
.filter(|r| WcagLevel::AAA >= r.min_level)
.count();
assert_eq!(count, PAGE_RULES.len());
}
}