diff --git a/README.md b/README.md index fc0e591..61065ac 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,11 @@ let audit = accessibility_rs::audit(&AuditConfig::new(&html, &css, false, "en")) ### Features -1. Accurate web accessibility WCAG audits without a headless browser. -2. Re-creating layout trees to get element positions. +1. Accurate web accessibility WCAG audits. +2. Re-creating layout trees to get element positions without the DOM. 3. Ideal shapes for audits that scale. -4. Amazingly fast audits. -5. Internationalization support for translations. - -### Roadmap - -1. All WCAGA-AAA Audits with rules mapped. -2. Next level performance. -3. Clean architecure maybe the code gets merged into a browser one day. -4. Improve Layout bounding accuracy to re-create leafs. +4. Incredibly fast audits. +5. Internationalization support. ### Contributing diff --git a/RULES.md b/RULES.md index 0f5b779..4864543 100644 --- a/RULES.md +++ b/RULES.md @@ -13,6 +13,7 @@ List of techniques we want to have and whether we have it handled or not for WCA | [H32](https://www.w3.org/TR/WCAG20-TECHS/H32.html) | missing form submit button | error | ✅ | | [H36](https://www.w3.org/TR/WCAG20-TECHS/H36.html) | missing form img alt | error | ✅ | | [H37](https://www.w3.org/TR/WCAG20-TECHS/H37.html) | missing img alt | error | ✅ | +| [H42](https://www.w3.org/TR/WCAG20-TECHS/H42.html) | heading found with no content | error | ✅ | | [H57](https://www.w3.org/TR/WCAG20-TECHS/H57.html) | html contains valid lang | error | ✅ | | [H64](https://www.w3.org/TR/WCAG20-TECHS/H64.html) | iframe missing title | error | ✅ | | [F40](https://www.w3.org/TR/WCAG20-TECHS/F40.html) | meta redirect used with a time limit | error | ✅ | diff --git a/accessibility-rs/src/engine/rules/techniques.rs b/accessibility-rs/src/engine/rules/techniques.rs index 2576ea2..abc6586 100644 --- a/accessibility-rs/src/engine/rules/techniques.rs +++ b/accessibility-rs/src/engine/rules/techniques.rs @@ -14,6 +14,8 @@ pub enum Techniques { H36, /// H37, + /// + H42, /// H57, /// @@ -39,6 +41,7 @@ impl Techniques { Techniques::H32 => vec!["H32.2"], Techniques::H36 => vec!["H36"], Techniques::H37 => vec!["H37"], + Techniques::H42 => vec!["H42.2"], Techniques::H57 => vec!["H57.2", "H57.3.Lang", "H57.3.XmlLang"], Techniques::H64 => vec!["H64.1", "H64.2"], Techniques::F40 => vec!["F40.2"], diff --git a/accessibility-rs/src/engine/rules/wcag_base.rs b/accessibility-rs/src/engine/rules/wcag_base.rs index fa9302d..c52e524 100644 --- a/accessibility-rs/src/engine/rules/wcag_base.rs +++ b/accessibility-rs/src/engine/rules/wcag_base.rs @@ -67,6 +67,8 @@ pub enum Guideline { Predictable, /// Provide users enough time to read and use content. EnoughTime, + /// Create content that can be presented in different ways (for example simpler layout) without losing information or structure. + Adaptable, } impl Guideline { @@ -74,6 +76,7 @@ impl Guideline { pub fn as_str(&self) -> &'static str { match self { Guideline::TextAlternatives => "Guideline1_1", + Guideline::Adaptable => "Guideline1_3", Guideline::EnoughTime => "Guideline2_2", Guideline::Navigable => "Guideline2_4", Guideline::Readable => "Guideline3_1", @@ -84,6 +87,7 @@ impl Guideline { pub fn as_index(&self) -> &'static str { match self { Guideline::TextAlternatives => "1_1", + Guideline::Adaptable => "1_3", Guideline::EnoughTime => "2_2", Guideline::Navigable => "2_4", Guideline::Readable => "3_1", diff --git a/accessibility-rs/src/engine/rules/wcag_rule_map.rs b/accessibility-rs/src/engine/rules/wcag_rule_map.rs index b65deee..57df187 100644 --- a/accessibility-rs/src/engine/rules/wcag_rule_map.rs +++ b/accessibility-rs/src/engine/rules/wcag_rule_map.rs @@ -3,6 +3,7 @@ use crate::engine::rules::techniques::Techniques; use crate::engine::rules::wcag_base::{Criteria, Guideline, Principle}; use crate::ElementRef; use accessibility_scraper::Selector; +use slotmap::DefaultKey; use std::collections::BTreeMap; /// a valid alt attribute for image @@ -23,6 +24,16 @@ fn has_alt(ele: ElementRef<'_>) -> bool { valid } +/// elements empty +fn is_empty(nodes: &Vec<(ElementRef<'_>, Option)>) -> bool { + let mut empty = false; + for ele in nodes { + let ele = ele.0; + empty = ele.inner_html().trim().is_empty(); + } + empty +} + // todo: validate each element and add a shape that can prevent repitiion lazy_static! { /// a list of rules that should be applied for WCAG1 @@ -129,30 +140,17 @@ lazy_static! { ])), ("a", Vec::from([ Rule::new(Techniques::H30, Criteria::Error, Principle::Perceivable, Guideline::TextAlternatives, |_rule, nodes| { - // todo: use tree to see if img exist to skip let mut valid = true; let selector = unsafe { Selector::parse("img").unwrap_unchecked() }; + // todo: use tree to see if img exist to skip for ele in nodes { let ele = ele.0; let mut elements = ele.select(&selector); while let Some(el) = elements.next() { - // allow checking for role presentation not supported as wide as empty alt - match el.attr("role") { - Some(role) => { - if role == "presentation" { - continue; - } - } - _ => () - }; - match el.attr("alt") { - Some(_) => (), - _ => valid = false - } + valid = has_alt(el); } - } Validation::new_issue(valid, "2") @@ -164,22 +162,41 @@ lazy_static! { for ele in nodes { let ele = ele.0; - match ele.attr("role") { - Some(role) => { - if role == "presentation" { - continue; - } - } - _ => () - }; - match ele.attr("alt") { - Some(_) => (), - _ => valid = false - } + valid = has_alt(ele); } Validation::new_issue(valid, Techniques::H37.pairs()[0]) }), + ])), + ("h1", Vec::from([ + Rule::new(Techniques::H42, Criteria::Error, Principle::Perceivable, Guideline::Adaptable, |_rule, nodes| { + Validation::new_issue(!is_empty(nodes), Techniques::H42.pairs()[0]) + }), + ])), + ("h2", Vec::from([ + Rule::new(Techniques::H42, Criteria::Error, Principle::Perceivable, Guideline::Adaptable, |_rule, nodes| { + Validation::new_issue(!is_empty(nodes), Techniques::H42.pairs()[0]) + }), + ])), + ("h3", Vec::from([ + Rule::new(Techniques::H42, Criteria::Error, Principle::Perceivable, Guideline::Adaptable, |_rule, nodes| { + Validation::new_issue(!is_empty(nodes), Techniques::H42.pairs()[0]) + }), + ])), + ("h4", Vec::from([ + Rule::new(Techniques::H42, Criteria::Error, Principle::Perceivable, Guideline::Adaptable, |_rule, nodes| { + Validation::new_issue(!is_empty(nodes), Techniques::H42.pairs()[0]) + }), + ])), + ("h5", Vec::from([ + Rule::new(Techniques::H42, Criteria::Error, Principle::Perceivable, Guideline::Adaptable, |_rule, nodes| { + Validation::new_issue(!is_empty(nodes), Techniques::H42.pairs()[0]) + }), + ])), + ("h6", Vec::from([ + Rule::new(Techniques::H42, Criteria::Error, Principle::Perceivable, Guideline::Adaptable, |_rule, nodes| { + Validation::new_issue(!is_empty(nodes), Techniques::H42.pairs()[0]) + }), ])) ] .into_iter() diff --git a/accessibility-rs/tests/unit/heading.rs b/accessibility-rs/tests/unit/heading.rs new file mode 100644 index 0000000..2b355e1 --- /dev/null +++ b/accessibility-rs/tests/unit/heading.rs @@ -0,0 +1,94 @@ +//! Test for headings. + +use accessibility_rs::AuditConfig; + +#[test] +/// empty headings +fn _audit_headings_empty() { + let audit = accessibility_rs::audit(AuditConfig::basic( + r###" + + Valid headings + + +

Plant Foods that Humans Eat

+

There are an abundant number of plants that humans eat...

+

Fruit

+

A fruit is a structure of a plant that contains its + seeds...

+

Apple

+

The apple is the pomaceous fruit of the apple tree...

+

Orange

+

The orange is a hybrid of ancient cultivated origin...

+

Banana

+

Banana is the common name for herbaceous plants ...

+

Vegetables

+

A vegetable is an edible plant or part of a plant other than a + sweet fruit ...

+

Broccoli

+

Broccoli is a plant of the mustard/cabbage family ...

+

Brussels sprouts

+

The Brussels sprout of the Brassicaceae family, is a Cultivar + group of wild cabbage ...

+

Green beans

+

Green beans have been bred for the fleshiness, flavor, or + sweetness of their pods...

+ + "###, + )); + let mut valid = true; + + for x in &audit { + if x.code == "WCAGAAA.Principle1.Guideline1_3.H42" { + valid = false; + break; + } + } + + assert_eq!(valid, true); + + let audit = accessibility_rs::audit(AuditConfig::basic( + r###" + + Do not use missing Headings conent. + + +

+

There are an abundant number of plants that humans eat...

+

+

A fruit is a structure of a plant that contains its + seeds...

+

+

The apple is the pomaceous fruit of the apple tree...

+

+

The orange is a hybrid of ancient cultivated origin...

+

+

Banana is the common name for herbaceous plants ...

+

+

A vegetable is an edible plant or part of a plant other than a + sweet fruit ...

+

+

Broccoli is a plant of the mustard/cabbage family ...

+

+

The Brussels sprout of the Brassicaceae family, is a Cultivar + group of wild cabbage ...

+

+

Green beans have been bred for the fleshiness, flavor, or + sweetness of their pods...

+

+
+
+ + "###, + )); + let mut valid = true; + + for x in &audit { + if x.code == "WCAGAAA.Principle1.Guideline1_3.H42" { + valid = false; + break; + } + } + + assert_eq!(valid, false) +} diff --git a/accessibility-rs/tests/unit/mod.rs b/accessibility-rs/tests/unit/mod.rs index 677bd70..99f0c83 100644 --- a/accessibility-rs/tests/unit/mod.rs +++ b/accessibility-rs/tests/unit/mod.rs @@ -1,3 +1,4 @@ pub mod anchor; +pub mod heading; pub mod img; pub mod meta;