diff --git a/Cargo.lock b/Cargo.lock index 8c26c61..5627293 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "accessibility-rs" -version = "0.0.6" +version = "0.0.7" dependencies = [ "accessibility-scraper", "accessibility-tree", @@ -1049,9 +1049,9 @@ dependencies = [ [[package]] name = "taffy" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "798053135b826949571726ab5e30bf9509a65b3bae6425d2e2b2dd391c50a101" +checksum = "c488aa2bf4bb0cafed312e0876b79a591e3cfa47391f842b7198f9a56547561b" dependencies = [ "arrayvec", "grid", diff --git a/RULES.md b/RULES.md index b774301..89416d5 100644 --- a/RULES.md +++ b/RULES.md @@ -6,11 +6,13 @@ List of techniques we want to have and whether we have it handled or not for WCA ### WCAGA -| Technique | Description | Complete | -| -------------------------------------------------- | -------------------------- | -------- | -| [H25](https://www.w3.org/TR/WCAG20-TECHS/H25.html) | empty titles | ✅ | -| [H32](https://www.w3.org/TR/WCAG20-TECHS/H32.html) | missing form submit button | ✅ | -| [H57](https://www.w3.org/TR/WCAG20-TECHS/H57.html) | html contains valid lang | ✅ | +| Technique | Description | Type | Complete | +| -------------------------------------------------- | -------------------------------------------- | ----- | -------- | +| [H25](https://www.w3.org/TR/WCAG20-TECHS/H25.html) | empty titles | error | ✅ | +| [H32](https://www.w3.org/TR/WCAG20-TECHS/H32.html) | missing form submit button | error | ✅ | +| [H57](https://www.w3.org/TR/WCAG20-TECHS/H57.html) | html contains valid lang | error | ✅ | +| [F40](https://www.w3.org/TR/WCAG20-TECHS/F40.html) | using meta redirect with a time limit | error | ✅ | +| [F40](https://www.w3.org/TR/WCAG20-TECHS/F41.html) | due to using meta refresh to reload the page | error | ✅ | ### WCAGAA diff --git a/accessibility-rs/Cargo.toml b/accessibility-rs/Cargo.toml index 69dd252..e5e7472 100644 --- a/accessibility-rs/Cargo.toml +++ b/accessibility-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "accessibility-rs" -version = "0.0.6" +version = "0.0.7" authors = ["The A11yWatch Project Developers", "Jeff Mendez "] edition = "2021" license = "MIT OR Apache-2.0" diff --git a/accessibility-rs/src/engine/rules/ids.rs b/accessibility-rs/src/engine/rules/ids.rs index 0c2a976..ba74bda 100644 --- a/accessibility-rs/src/engine/rules/ids.rs +++ b/accessibility-rs/src/engine/rules/ids.rs @@ -9,14 +9,17 @@ pub enum Techniques { /// H32, /// - H57 + H57, + /// + F40, + /// + F41, } impl Techniques { /// get rule id to string pub fn as_str(&self) -> &'static str { - // todo: make macro - self.into() + self.into() } /// get pairs for a rule pub fn pairs(&self) -> Vec<&'static str> { @@ -24,6 +27,9 @@ impl Techniques { Techniques::H25 => vec!["H25.1.NoTitleEl", "H25.1.EmptyTitle"], Techniques::H32 => vec!["H32.2"], Techniques::H57 => vec!["H57.2", "H57.3.Lang", "H57.3.XmlLang"], + Techniques::F40 => vec!["F40.2"], + Techniques::F41 => vec!["F41.2"], + _ => vec![""], } } } diff --git a/accessibility-rs/src/engine/rules/wcag_base.rs b/accessibility-rs/src/engine/rules/wcag_base.rs index 0027444..69c59ea 100644 --- a/accessibility-rs/src/engine/rules/wcag_base.rs +++ b/accessibility-rs/src/engine/rules/wcag_base.rs @@ -51,11 +51,14 @@ pub enum Guideline { Readable, /// Make Web pages appear and operate in predictable ways. Predictable, + /// Provide users enough time to read and use content. + EnoughTime } impl Guideline { pub fn as_str(&self) -> &'static str { match self { + Guideline::EnoughTime => "Guideline2_2", Guideline::Navigable => "Guideline2_4", Guideline::Readable => "Guideline3_1", Guideline::Predictable => "Guideline3_2", diff --git a/accessibility-rs/src/engine/rules/wcag_rule_map.rs b/accessibility-rs/src/engine/rules/wcag_rule_map.rs index b3a4c6b..7ea3173 100644 --- a/accessibility-rs/src/engine/rules/wcag_rule_map.rs +++ b/accessibility-rs/src/engine/rules/wcag_rule_map.rs @@ -19,13 +19,48 @@ lazy_static! { (lang.chars().all(|x| x.is_alphanumeric()) && !lang.contains("_") && lang.len() < 12, "3.Lang", Default::default()) }), ])), + // empty titles + ("meta", Vec::from([ + Rule::new(Techniques::F40, Criteria::Error, Principle::Operable, Guideline::EnoughTime, |_rule, nodes| { + let mut valid = true; + + for node in nodes { + let element = node.0; + let meta_refresh = element.attr("http-equiv").unwrap_or_default(); + if meta_refresh == "refresh" { + let content = element.attr("content").unwrap_or_default(); + if content.contains("url") { + valid = content.starts_with("0;"); + } + } + } + + (valid, "2", Default::default()) + }), + Rule::new(Techniques::F41, Criteria::Error, Principle::Understandable, Guideline::EnoughTime, |_rule, nodes| { + let mut valid = true; + + for node in nodes { + let element = node.0; + let meta_refresh = element.attr("http-equiv").unwrap_or_default(); + if meta_refresh == "refresh" { + let content = element.attr("content").unwrap_or_default(); + if !content.is_empty() { + valid = content == "0"; + } + } + } + + (valid, "2", Default::default()) + }), + ])), // empty titles ("title", Vec::from([ Rule::new(Techniques::H25, Criteria::Error, Principle::Operable, Guideline::Navigable, |_rule, nodes| { (!nodes.is_empty(), "1.NoTitleEl", Default::default()) }), Rule::new(Techniques::H25, Criteria::Error, Principle::Understandable, Guideline::Predictable, |_rule, nodes| { - (nodes.is_empty() || nodes[0].0.html().is_empty(), "2", Default::default()) + (nodes.is_empty() || nodes[0].0.html().is_empty(), "1.EmptyTitle", Default::default()) }), ])), // missing form submit diff --git a/accessibility-rs/src/i18n/locales.rs b/accessibility-rs/src/i18n/locales.rs index f2ea21b..b864196 100644 --- a/accessibility-rs/src/i18n/locales.rs +++ b/accessibility-rs/src/i18n/locales.rs @@ -105,6 +105,8 @@ lazy_static! { (Techniques::H57.pairs()[0], Messages::new(&"The html element should have a lang or xml:lang attribute which describes the language of the document.", "", "")), (Techniques::H57.pairs()[1], Messages::new(&"The language specified in the lang attribute of the document element does not appear to be well-formed.", "", "")), (Techniques::H57.pairs()[2], Messages::new(&"The language specified in the xml:lang attribute of the document element does not appear to be well-formed.", "", "")), + (Techniques::F40.pairs()[0], Messages::new(&"Meta refresh tag used to redirect to another page, with a time limit that is not zero. Users cannot control this time limit.", "", "")), + (Techniques::F41.pairs()[0], Messages::new(&"Meta refresh tag used to refresh the current page. Users cannot control the time limit for this refresh.", "", "")), ]) }; } diff --git a/accessibility-rs/tests/mocks/mod.rs b/accessibility-rs/tests/mocks/mod.rs index 604a57c..9afc1d5 100644 --- a/accessibility-rs/tests/mocks/mod.rs +++ b/accessibility-rs/tests/mocks/mod.rs @@ -1 +1 @@ -pub mod mock; \ No newline at end of file +pub mod mock; diff --git a/accessibility-rs/tests/mod.rs b/accessibility-rs/tests/mod.rs index ecc8e80..f997c3b 100644 --- a/accessibility-rs/tests/mod.rs +++ b/accessibility-rs/tests/mod.rs @@ -1,2 +1,2 @@ +pub mod mocks; pub mod unit; -pub mod mocks; \ No newline at end of file diff --git a/accessibility-rs/tests/unit/meta.rs b/accessibility-rs/tests/unit/meta.rs index 9e3d2eb..8b21ff0 100644 --- a/accessibility-rs/tests/unit/meta.rs +++ b/accessibility-rs/tests/unit/meta.rs @@ -5,7 +5,7 @@ use crate::mocks::mock; #[test] /// missing title element fn _audit_missing_headers() { - let audit = accessibility_rs::audit(mock::MOCK_WEBSITE_HTML, &mock::MOCK_CSS_RULES, false); + let audit = accessibility_rs::audit(mock::MOCK_WEBSITE_HTML, &"", false); let mut valid = true; for x in &audit { @@ -17,3 +17,54 @@ fn _audit_missing_headers() { assert_eq!(valid, false) } + + +#[test] +/// meta refresh redirect +fn _audit_meta_refresh() { + let audit = accessibility_rs::audit(r###" + + Do not use this! + + + +

+ If your browser supports Refresh, you'll be + transported to our + new site + in 5 seconds, otherwise, select the link manually. +

+ + "###, &"", false); + let mut valid = true; + + for x in &audit { + if x.code == "WCAGAAA.Principle2.Guideline2_2.F40" { + valid = false; + break; + } + } + + assert_eq!(valid, false); + + let audit = accessibility_rs::audit(r###" + + HTML Techniques for WCAG 2.0 + + + + + "###, &"", false); + let mut valid = true; + + for x in &audit { + if x.code == "WCAGAAA.Principle3.Guideline2_2.F41" { + valid = false; + break; + } + } + + assert_eq!(valid, false); +} + diff --git a/accessibility-rs/tests/unit/mod.rs b/accessibility-rs/tests/unit/mod.rs index c941336..6b50816 100644 --- a/accessibility-rs/tests/unit/mod.rs +++ b/accessibility-rs/tests/unit/mod.rs @@ -1 +1 @@ -pub mod meta; \ No newline at end of file +pub mod meta;