use super::dimensions::Subcategory;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ScoreArea {
Semantics,
Forms,
Keyboard,
FocusManagement,
ImagesAlternativeText,
Aria,
HeadingStructure,
Landmarks,
}
impl ScoreArea {
pub const ALL: [ScoreArea; 8] = [
ScoreArea::Semantics,
ScoreArea::Forms,
ScoreArea::Keyboard,
ScoreArea::FocusManagement,
ScoreArea::ImagesAlternativeText,
ScoreArea::Aria,
ScoreArea::HeadingStructure,
ScoreArea::Landmarks,
];
pub fn label(self) -> &'static str {
match self {
ScoreArea::Semantics => "Semantics",
ScoreArea::Forms => "Forms",
ScoreArea::Keyboard => "Keyboard",
ScoreArea::FocusManagement => "Focus management",
ScoreArea::ImagesAlternativeText => "Images / alternative text",
ScoreArea::Aria => "ARIA",
ScoreArea::HeadingStructure => "Heading structure",
ScoreArea::Landmarks => "Landmarks / page structure",
}
}
}
use ScoreArea::*;
static RULE_AREAS: &[(&str, ScoreArea)] = &[
("a11y.alt_text.missing", ImagesAlternativeText),
("a11y.alt_text.suspicious", ImagesAlternativeText),
("a11y.area_alt.missing", ImagesAlternativeText),
("a11y.image_map_server_side.invalid", ImagesAlternativeText),
("a11y.input_image_alt.missing", ImagesAlternativeText),
("a11y.meter_name.missing", ImagesAlternativeText),
("a11y.object_alt.missing", ImagesAlternativeText),
("a11y.progressbar_name.missing", ImagesAlternativeText),
("a11y.svg_alt.missing", ImagesAlternativeText),
("a11y.media.alternative", ImagesAlternativeText),
("a11y.captions.missing", ImagesAlternativeText),
("a11y.media_alternative.missing", ImagesAlternativeText),
("a11y.frame_title.missing", ImagesAlternativeText),
("a11y.display_text_layer.missing", ImagesAlternativeText),
("a11y.display_text_layer.hidden", ImagesAlternativeText),
("a11y.display_toggle.missing", Semantics),
("a11y.display_init.late", Semantics),
("a11y.display_text_media.visible", Semantics),
("a11y.landmark_banner.missing", Landmarks),
("a11y.landmark_contentinfo.missing", Landmarks),
("a11y.landmark_navigation.missing", Landmarks),
("a11y.landmark_banner_duplicate.invalid", Landmarks),
("a11y.landmark_banner_nested.invalid", Landmarks),
("a11y.landmark_contentinfo_duplicate.invalid", Landmarks),
("a11y.landmark_contentinfo_nested.invalid", Landmarks),
("a11y.landmark_main.missing", Landmarks),
("a11y.landmark_main_duplicate.invalid", Landmarks),
("a11y.landmark_main_nested.invalid", Landmarks),
("a11y.landmark_region.missing", Landmarks),
("a11y.landmark_unique.invalid", Landmarks),
("a11y.form_field_group.missing", Forms),
("a11y.label_title_only.invalid", Forms),
("a11y.presentation_semantic_children.invalid", Aria),
("a11y.definition_description.missing", Semantics),
("a11y.html_content_model.invalid", Semantics),
("a11y.list_children.invalid", Semantics),
("a11y.list_empty.invalid", Semantics),
("a11y.list_structure.missing", Semantics),
("a11y.structure.missing", Semantics),
("a11y.table_header_data.missing", Semantics),
("a11y.table_headers.missing", Semantics),
("a11y.table_headers_ref.invalid", Semantics),
("a11y.table_name.missing", Semantics),
("a11y.table_presentational_headers.invalid", Semantics),
("a11y.meaningful_sequence.invalid", Landmarks),
("a11y.page_title.missing", Landmarks),
("a11y.orientation.restricted", Semantics),
("a11y.color.link_indicator", Semantics),
("a11y.contrast.weak", Semantics),
("a11y.resize_text.weak", Semantics),
("a11y.viewport_meta.missing", Semantics),
("a11y.viewport_zoom.restricted", Semantics),
("a11y.background_audio.uncontrolled", Semantics),
("a11y.visual_presentation.weak", Semantics),
("a11y.reflow.missing", Semantics),
("a11y.non_text_contrast.weak", Semantics),
("a11y.text_spacing.clipped", Semantics),
("a11y.hover.content_visibility", Semantics),
("a11y.motion.reduced_motion", Semantics),
("a11y.input_purpose.missing", Forms),
("a11y.form_no_submit.missing", Forms),
("a11y.error_description.missing", Forms),
("a11y.error_id.missing_description", Forms),
("a11y.form_labels.missing", Forms),
("a11y.help.missing", Forms),
("a11y.redundant_entry.missing_reuse", Forms),
("a11y.accessible_auth.paste_blocked", Forms),
("a11y.accessible_auth.captcha_review", Forms),
("a11y.combobox_options.missing", Forms),
("a11y.input_field_name.missing", Forms),
("a11y.toggle_field_name.missing", Forms),
("a11y.click_handler_keyboard.missing", Keyboard),
("a11y.focusable_no_role.invalid", Keyboard),
("a11y.keyboard.missing", Keyboard),
("a11y.scrollable_region_focus.missing", Keyboard),
("a11y.keyboard_trap.risk", Keyboard),
("a11y.pointer_gestures.missing_alternative", Keyboard),
("a11y.pointer_cancellation.invalid", Keyboard),
("a11y.motion_actuation.missing_alternative", Keyboard),
("a11y.target_size_enhanced.small", Keyboard),
("a11y.target_size_minimum.small", Keyboard),
("a11y.timing.unadjustable", Semantics),
("a11y.pause_stop_hide.no_control", Semantics),
("a11y.timing.essential", Semantics),
("a11y.interruptions.uncontrollable", Semantics),
("a11y.re_authenticate.data_loss", Semantics),
("a11y.timeouts.unwarned", Semantics),
("a11y.bypass_blocks.missing", Landmarks),
("a11y.bypass_main_landmark.missing", Landmarks),
("a11y.skip_link.missing", Landmarks),
("a11y.focus_order.weak", FocusManagement),
("a11y.positive_tabindex.invalid", FocusManagement),
("a11y.focus_indicator_suppressed.invalid", FocusManagement),
("a11y.focus_visible.missing", FocusManagement),
("a11y.focus_not_obscured_minimum.hidden", FocusManagement),
("a11y.focus_not_obscured_enhanced.hidden", FocusManagement),
("a11y.on_focus.risk", FocusManagement),
("a11y.link_purpose.weak", Semantics),
("a11y.link_purpose_context.weak", Semantics),
("a11y.link_purpose_only.weak", Semantics),
("a11y.location.missing", Semantics),
("a11y.headings.missing", HeadingStructure),
("a11y.section_headings.missing", HeadingStructure),
("a11y.heading_level.skipped", HeadingStructure),
("a11y.heading_empty.invalid", HeadingStructure),
("a11y.main_heading.missing", HeadingStructure),
("a11y.main_heading_multiple.weak", HeadingStructure),
("a11y.language.missing", Semantics),
("a11y.language_mismatch.invalid", Semantics),
("a11y.page_language.invalid", Semantics),
("a11y.unusual_words.missing_definition", Semantics),
("a11y.abbreviations.missing", Semantics),
("a11y.on_input.risk", Semantics),
("a11y.identify_purpose.missing", Aria),
("a11y.label_in_name.invalid", Aria),
("a11y.duplicate_id_aria.invalid", Aria),
("a11y.parsing.invalid", Aria),
("a11y.aria_allowed_attr.invalid", Aria),
("a11y.aria_attr_name.invalid", Aria),
("a11y.aria_hidden_focus.invalid", Aria),
("a11y.aria_prohibited_attr.invalid", Aria),
("a11y.aria_relationships.invalid", Aria),
("a11y.aria_required_attr.missing", Aria),
("a11y.aria_required_children.missing", Aria),
("a11y.aria_required_parent.invalid", Aria),
("a11y.aria_roles.invalid", Aria),
("a11y.aria_valid_attr_value.invalid", Aria),
("a11y.command_name.missing", Aria),
("a11y.control_name.missing", Aria),
("a11y.dialog_missing_name.invalid", Aria),
("a11y.dialog_name.missing", Aria),
("a11y.frame_tested.cross_origin", Aria),
("a11y.interactive_name.missing", Aria),
("a11y.title_only_description.weak", Aria),
("a11y.description_duplicates_name.redundant", Aria),
("a11y.link_as_button.invalid", Aria),
("a11y.modern_attributes.invalid", Aria),
("a11y.name_role.missing", Aria),
("a11y.redundant_role.invalid", Aria),
("a11y.summary_name.missing", Aria),
("a11y.tablist_tabpanel.missing", Aria),
("a11y.tab_selected.missing", Aria),
("a11y.dialog_modal.missing", Aria),
("a11y.treeitem_name.missing", Aria),
("a11y.status_messages.broken", Aria),
];
pub fn score_area_for_rule(rule_id: &str) -> Option<ScoreArea> {
RULE_AREAS
.iter()
.find(|(id, _)| *id == rule_id)
.map(|(_, area)| *area)
}
pub fn score_area_for_subcategory(subcategory: Subcategory) -> ScoreArea {
match subcategory {
Subcategory::ContentAlternatives => ImagesAlternativeText,
Subcategory::FormsInteraction => Forms,
Subcategory::TechnicalRobustness => Aria,
Subcategory::NavigationInteraction => Keyboard,
_ => Semantics,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::taxonomy::dimensions::Dimension;
use crate::taxonomy::rules::RULES;
#[test]
fn every_accessibility_rule_has_a_score_area() {
let missing: Vec<&str> = RULES
.iter()
.filter(|r| r.dimension == Dimension::Accessibility)
.map(|r| r.id)
.filter(|id| score_area_for_rule(id).is_none())
.collect();
assert!(
missing.is_empty(),
"these accessibility rules have no score area in RULE_AREAS: {missing:#?}",
);
}
#[test]
fn score_area_table_has_no_stale_entries() {
let stale: Vec<&str> = RULE_AREAS
.iter()
.map(|(id, _)| *id)
.filter(|id| !RULES.iter().any(|r| r.id == *id))
.collect();
assert!(
stale.is_empty(),
"these RULE_AREAS entries reference unknown rules: {stale:#?}",
);
}
#[test]
fn score_area_table_has_no_duplicates() {
let mut seen = std::collections::HashSet::new();
let dupes: Vec<&str> = RULE_AREAS
.iter()
.map(|(id, _)| *id)
.filter(|id| !seen.insert(*id))
.collect();
assert!(dupes.is_empty(), "duplicate RULE_AREAS entries: {dupes:#?}");
}
#[test]
fn area_labels_match_the_output_contract() {
let labels: Vec<&str> = ScoreArea::ALL.iter().map(|a| a.label()).collect();
assert_eq!(
labels,
vec![
"Semantics",
"Forms",
"Keyboard",
"Focus management",
"Images / alternative text",
"ARIA",
"Heading structure",
"Landmarks / page structure",
],
);
}
}