use_accessibility_score/
lib.rs1#![forbid(unsafe_code)]
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum CheckStatus {
46 Pass,
47 Warn,
48 Fail,
49}
50
51#[derive(Debug, Clone, PartialEq)]
52pub struct AccessibilityCheckResult {
53 pub name: String,
54 pub status: CheckStatus,
55 pub weight: f64,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq)]
59pub struct AccessibilityScore {
60 pub passed: usize,
61 pub warnings: usize,
62 pub failed: usize,
63 pub score: f64,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum AccessibilityScoreError {
68 InvalidWeight,
69}
70
71#[must_use]
72pub fn count_passed(results: &[AccessibilityCheckResult]) -> usize {
73 results
74 .iter()
75 .filter(|result| result.status == CheckStatus::Pass)
76 .count()
77}
78
79#[must_use]
80pub fn count_warnings(results: &[AccessibilityCheckResult]) -> usize {
81 results
82 .iter()
83 .filter(|result| result.status == CheckStatus::Warn)
84 .count()
85}
86
87#[must_use]
88pub fn count_failed(results: &[AccessibilityCheckResult]) -> usize {
89 results
90 .iter()
91 .filter(|result| result.status == CheckStatus::Fail)
92 .count()
93}
94
95pub fn score_results(
96 results: &[AccessibilityCheckResult],
97) -> Result<AccessibilityScore, AccessibilityScoreError> {
98 if results
99 .iter()
100 .any(|result| !result.weight.is_finite() || result.weight < 0.0)
101 {
102 return Err(AccessibilityScoreError::InvalidWeight);
103 }
104
105 let total_weight = results.iter().map(|result| result.weight).sum::<f64>();
106 let earned_weight = results.iter().fold(0.0, |sum, result| {
107 sum + match result.status {
108 CheckStatus::Pass => result.weight,
109 CheckStatus::Warn => result.weight * 0.5,
110 CheckStatus::Fail => 0.0,
111 }
112 });
113
114 Ok(AccessibilityScore {
115 passed: count_passed(results),
116 warnings: count_warnings(results),
117 failed: count_failed(results),
118 score: if total_weight == 0.0 {
119 0.0
120 } else {
121 (earned_weight / total_weight) * 100.0
122 },
123 })
124}
125
126#[cfg(test)]
127mod tests {
128 use super::{
129 AccessibilityCheckResult, AccessibilityScoreError, CheckStatus, count_failed, count_passed,
130 count_warnings, score_results,
131 };
132
133 #[test]
134 fn scores_accessibility_results() {
135 let results = [
136 AccessibilityCheckResult {
137 name: String::from("contrast"),
138 status: CheckStatus::Pass,
139 weight: 2.0,
140 },
141 AccessibilityCheckResult {
142 name: String::from("label"),
143 status: CheckStatus::Warn,
144 weight: 1.0,
145 },
146 AccessibilityCheckResult {
147 name: String::from("focus"),
148 status: CheckStatus::Fail,
149 weight: 1.0,
150 },
151 ];
152 let score = score_results(&results).unwrap();
153
154 assert_eq!(count_passed(&results), 1);
155 assert_eq!(count_warnings(&results), 1);
156 assert_eq!(count_failed(&results), 1);
157 assert_eq!(score.passed, 1);
158 assert_eq!(score.warnings, 1);
159 assert_eq!(score.failed, 1);
160 assert_eq!(score.score, 62.5);
161 }
162
163 #[test]
164 fn handles_empty_and_invalid_weights() {
165 let empty = score_results(&[]).unwrap();
166 assert_eq!(empty.score, 0.0);
167
168 let invalid = [AccessibilityCheckResult {
169 name: String::from("contrast"),
170 status: CheckStatus::Pass,
171 weight: -1.0,
172 }];
173
174 assert_eq!(
175 score_results(&invalid),
176 Err(AccessibilityScoreError::InvalidWeight)
177 );
178 }
179}