1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5#[inline]
7fn is_false(b: &bool) -> bool {
8 !*b
9}
10
11#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
12pub struct BoundsDto {
13 pub x: u16,
14 pub y: u16,
15 pub width: u16,
16 pub height: u16,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct ElementRefDto {
21 pub role: String,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub name: Option<String>,
24 pub bounds: BoundsDto,
25 pub visual_hash: u64,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub nth: Option<usize>,
28 #[serde(default, skip_serializing_if = "is_false")]
29 pub selected: bool,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct RefMapDto {
34 #[serde(flatten)]
35 pub refs: HashMap<String, ElementRefDto>,
36}
37
38#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
39pub struct SnapshotStatsDto {
40 pub total: usize,
41 pub interactive: usize,
42 pub lines: usize,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct AccessibilitySnapshotDto {
47 pub tree: String,
48 pub refs: RefMapDto,
49 pub stats: SnapshotStatsDto,
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn test_bounds_dto_independent_creation() {
58 let bounds = BoundsDto {
59 x: 10,
60 y: 5,
61 width: 20,
62 height: 3,
63 };
64 assert_eq!(bounds.x, 10);
65 assert_eq!(bounds.y, 5);
66 assert_eq!(bounds.width, 20);
67 assert_eq!(bounds.height, 3);
68 }
69
70 #[test]
71 fn test_element_ref_dto_independent_creation() {
72 let element = ElementRefDto {
73 role: "button".to_string(),
74 name: Some("OK".to_string()),
75 bounds: BoundsDto {
76 x: 5,
77 y: 10,
78 width: 4,
79 height: 1,
80 },
81 visual_hash: 12345,
82 nth: Some(2),
83 selected: false,
84 };
85 assert_eq!(element.role, "button");
86 assert_eq!(element.name, Some("OK".to_string()));
87 assert_eq!(element.bounds.x, 5);
88 assert_eq!(element.visual_hash, 12345);
89 assert_eq!(element.nth, Some(2));
90 }
91
92 #[test]
93 fn test_snapshot_dto_serialization() {
94 let snapshot = AccessibilitySnapshotDto {
95 tree: "- button \"OK\" [ref=e1]".to_string(),
96 refs: RefMapDto {
97 refs: {
98 let mut map = HashMap::new();
99 map.insert(
100 "e1".to_string(),
101 ElementRefDto {
102 role: "button".to_string(),
103 name: Some("OK".to_string()),
104 bounds: BoundsDto {
105 x: 10,
106 y: 5,
107 width: 2,
108 height: 1,
109 },
110 visual_hash: 12345,
111 nth: None,
112 selected: false,
113 },
114 );
115 map
116 },
117 },
118 stats: SnapshotStatsDto {
119 total: 1,
120 interactive: 1,
121 lines: 1,
122 },
123 };
124
125 let json = serde_json::to_string(&snapshot).unwrap();
126
127 assert!(json.contains("\"tree\""));
128 assert!(json.contains("\"stats\""));
129 assert!(json.contains("\"total\""));
130 assert!(json.contains("\"interactive\""));
131 assert!(json.contains("button"));
132 }
133
134 #[test]
135 fn test_snapshot_dto_roundtrip() {
136 let snapshot = AccessibilitySnapshotDto {
137 tree: "- button \"OK\" [ref=e1]\n- input \">\" [ref=e2]".to_string(),
138 refs: RefMapDto {
139 refs: {
140 let mut map = HashMap::new();
141 map.insert(
142 "e1".to_string(),
143 ElementRefDto {
144 role: "button".to_string(),
145 name: Some("OK".to_string()),
146 bounds: BoundsDto {
147 x: 10,
148 y: 5,
149 width: 2,
150 height: 1,
151 },
152 visual_hash: 12345,
153 nth: None,
154 selected: false,
155 },
156 );
157 map.insert(
158 "e2".to_string(),
159 ElementRefDto {
160 role: "input".to_string(),
161 name: Some(">".to_string()),
162 bounds: BoundsDto {
163 x: 0,
164 y: 0,
165 width: 1,
166 height: 1,
167 },
168 visual_hash: 67890,
169 nth: None,
170 selected: false,
171 },
172 );
173 map
174 },
175 },
176 stats: SnapshotStatsDto {
177 total: 2,
178 interactive: 2,
179 lines: 2,
180 },
181 };
182
183 let json = serde_json::to_string(&snapshot).unwrap();
184 let restored: AccessibilitySnapshotDto = serde_json::from_str(&json).unwrap();
185
186 assert_eq!(restored.tree, snapshot.tree);
187 assert_eq!(restored.stats.total, snapshot.stats.total);
188 assert_eq!(restored.stats.interactive, snapshot.stats.interactive);
189 }
190
191 #[test]
192 fn test_refs_structure() {
193 let snapshot = AccessibilitySnapshotDto {
194 tree: "- button \"Submit\" [ref=e1]".to_string(),
195 refs: RefMapDto {
196 refs: {
197 let mut map = HashMap::new();
198 map.insert(
199 "e1".to_string(),
200 ElementRefDto {
201 role: "button".to_string(),
202 name: Some("Submit".to_string()),
203 bounds: BoundsDto {
204 x: 0,
205 y: 0,
206 width: 6,
207 height: 1,
208 },
209 visual_hash: 12345,
210 nth: None,
211 selected: false,
212 },
213 );
214 map
215 },
216 },
217 stats: SnapshotStatsDto {
218 total: 1,
219 interactive: 1,
220 lines: 1,
221 },
222 };
223
224 let json = serde_json::to_string_pretty(&snapshot).unwrap();
225
226 assert!(json.contains("\"e1\""));
227 assert!(json.contains("\"role\": \"button\""));
228 assert!(json.contains("\"name\": \"Submit\""));
229 assert!(json.contains("\"bounds\""));
230 }
231}