1use super::AuditGate;
11
12pub mod util;
13#[allow(unused_imports)]
17pub use util::{find_tag_end, hreflang_attr, strip_script_and_style};
18
19pub mod ai_discovery;
20pub mod broken_links;
21pub mod csp_sri;
22pub mod feeds;
23pub mod hreflang;
24pub mod html5;
25pub mod images;
26pub mod jsonld;
27pub mod lang_consistency;
28pub mod markdownlint;
29pub mod metadata;
30pub mod performance;
31pub mod pqc_tls;
32pub mod search_index;
33pub mod wcag;
34
35#[must_use]
50pub fn all() -> Vec<Box<dyn AuditGate>> {
51 vec![
52 Box::new(wcag::WcagGate),
53 Box::new(jsonld::JsonLdGate),
54 Box::new(hreflang::HreflangGate),
55 Box::new(csp_sri::CspSriGate),
56 Box::new(pqc_tls::PqcTlsGate),
57 Box::new(html5::Html5Gate),
58 Box::new(broken_links::BrokenLinksGate),
59 Box::new(metadata::MetadataGate),
60 Box::new(markdownlint::MarkdownlintGate),
61 Box::new(performance::PerformanceGate),
62 Box::new(ai_discovery::AiDiscoveryGate),
63 Box::new(feeds::FeedsGate),
64 Box::new(images::ImagesGate),
65 Box::new(search_index::SearchIndexGate),
66 Box::new(lang_consistency::LangConsistencyGate),
67 ]
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn all_returns_fifteen_gates_in_stable_order() {
76 let gates = all();
77 let names: Vec<&str> = gates.iter().map(|g| g.name()).collect();
78 assert_eq!(names.len(), 15);
79 assert_eq!(
80 names,
81 vec![
82 "wcag",
83 "jsonld",
84 "hreflang",
85 "csp_sri",
86 "pqc_tls",
87 "html5",
88 "links",
89 "metadata",
90 "markdownlint",
91 "performance",
92 "ai_discovery",
93 "feeds",
94 "images",
95 "search_index",
96 "lang_consistency",
97 ]
98 );
99 }
100
101 #[test]
102 fn every_gate_has_a_non_empty_explainer() {
103 for g in all() {
104 let name = g.name();
105 let explainer = g.explain();
106 assert!(
107 !explainer.trim().is_empty(),
108 "gate `{name}` has empty explainer"
109 );
110 }
111 }
112
113 #[test]
114 fn gate_names_are_unique() {
115 let gates = all();
116 let mut names: Vec<&str> = gates.iter().map(|g| g.name()).collect();
117 names.sort_unstable();
118 let original_len = names.len();
119 names.dedup();
120 assert_eq!(names.len(), original_len, "duplicate gate name detected");
121 }
122
123 #[test]
124 fn gate_names_are_snake_case_no_whitespace() {
125 for g in all() {
126 let name = g.name();
127 assert!(!name.is_empty(), "empty name");
128 assert!(
129 name.chars().all(|c| c.is_ascii_lowercase()
130 || c.is_ascii_digit()
131 || c == '_'),
132 "gate `{name}` is not snake_case"
133 );
134 assert!(!name.contains(' '), "gate `{name}` contains whitespace");
135 }
136 }
137
138 #[test]
139 fn explainers_are_reasonably_long() {
140 for g in all() {
141 let name = g.name();
142 let len = g.explain().len();
143 assert!(
144 len >= 40,
145 "gate `{name}` explainer is too short ({len} chars)"
146 );
147 }
148 }
149
150 #[test]
151 fn first_gate_is_wcag() {
152 let gates = all();
153 assert_eq!(gates.first().map(|g| g.name()), Some("wcag"));
154 }
155
156 #[test]
157 fn last_gate_is_lang_consistency() {
158 let gates = all();
160 assert_eq!(gates.last().map(|g| g.name()), Some("lang_consistency"));
161 }
162
163 #[test]
164 fn util_re_exports_compile() {
165 let tag = r#"<a href="x">"#;
166 assert_eq!(hreflang_attr(tag, "href"), Some("x".to_string()));
167 let end = find_tag_end(tag, 0);
168 assert_eq!(end, tag.len());
169 }
170
171 #[test]
172 fn all_returns_fresh_boxes_each_call() {
173 let a = all();
174 let b = all();
175 assert_eq!(a.len(), b.len());
176 for (x, y) in a.iter().zip(b.iter()) {
177 assert_eq!(x.name(), y.name());
178 }
179 }
180}