1use crate::session::Session;
7use std::collections::hash_map::DefaultHasher;
8use std::hash::{Hash, Hasher};
9
10#[derive(Debug, Clone)]
12pub enum WaitCondition {
13 Text(String),
15 Element(String),
17 Focused(String),
19 NotVisible(String),
21 Stable,
23 TextGone(String),
25 Value { element: String, expected: String },
27}
28
29impl WaitCondition {
30 pub fn parse(
32 condition: Option<&str>,
33 target: Option<&str>,
34 text: Option<&str>,
35 ) -> Option<Self> {
36 match condition {
37 Some("text") => text.map(|t| WaitCondition::Text(t.to_string())),
38 Some("element") => target.map(|t| WaitCondition::Element(t.to_string())),
39 Some("focused") => target.map(|t| WaitCondition::Focused(t.to_string())),
40 Some("not_visible") => target.map(|t| WaitCondition::NotVisible(t.to_string())),
41 Some("stable") => Some(WaitCondition::Stable),
42 Some("text_gone") => target.map(|t| WaitCondition::TextGone(t.to_string())),
43 Some("value") => {
44 target.and_then(|t| {
46 let parts: Vec<&str> = t.splitn(2, '=').collect();
47 if parts.len() == 2 {
48 Some(WaitCondition::Value {
50 element: parts[0].to_string(),
51 expected: parts[1].to_string(),
52 })
53 } else {
54 text.map(|expected_value| WaitCondition::Value {
56 element: t.to_string(),
57 expected: expected_value.to_string(),
58 })
59 }
60 })
61 }
62 None => text.map(|t| WaitCondition::Text(t.to_string())),
63 _ => None,
64 }
65 }
66
67 pub fn description(&self) -> String {
69 match self {
70 WaitCondition::Text(t) => format!("text \"{}\"", t),
71 WaitCondition::Element(e) => format!("element {}", e),
72 WaitCondition::Focused(e) => format!("{} to be focused", e),
73 WaitCondition::NotVisible(e) => format!("{} to disappear", e),
74 WaitCondition::Stable => "screen to stabilize".to_string(),
75 WaitCondition::TextGone(t) => format!("text \"{}\" to disappear", t),
76 WaitCondition::Value { element, expected } => {
77 format!("{} to have value \"{}\"", element, expected)
78 }
79 }
80 }
81}
82
83#[derive(Default)]
85pub struct StableTracker {
86 last_hashes: Vec<u64>,
87 required_consecutive: usize,
88}
89
90impl StableTracker {
91 pub fn new(required_consecutive: usize) -> Self {
92 Self {
93 last_hashes: Vec::new(),
94 required_consecutive,
95 }
96 }
97
98 pub fn add_hash(&mut self, screen: &str) -> bool {
100 let mut hasher = DefaultHasher::new();
101 screen.hash(&mut hasher);
102 let hash = hasher.finish();
103
104 self.last_hashes.push(hash);
105
106 if self.last_hashes.len() > self.required_consecutive {
107 self.last_hashes.remove(0);
108 }
109
110 if self.last_hashes.len() >= self.required_consecutive {
111 let first = self.last_hashes[0];
112 self.last_hashes.iter().all(|&h| h == first)
113 } else {
114 false
115 }
116 }
117}
118
119pub fn check_condition(
121 session: &mut Session,
122 condition: &WaitCondition,
123 stable_tracker: &mut StableTracker,
124) -> bool {
125 let _ = session.update();
126
127 match condition {
128 WaitCondition::Text(text) => {
129 let screen = session.screen_text();
130 screen.contains(text)
131 }
132
133 WaitCondition::Element(element_ref) => {
134 session.detect_elements();
135 session.find_element(element_ref).is_some()
136 }
137
138 WaitCondition::Focused(element_ref) => {
139 session.detect_elements();
140 session
141 .find_element(element_ref)
142 .map(|el| el.focused)
143 .unwrap_or(false)
144 }
145
146 WaitCondition::NotVisible(element_ref) => {
147 session.detect_elements();
148 session.find_element(element_ref).is_none()
149 }
150
151 WaitCondition::Stable => {
152 let screen = session.screen_text();
153 stable_tracker.add_hash(&screen)
154 }
155
156 WaitCondition::TextGone(text) => {
157 let screen = session.screen_text();
158 !screen.contains(text)
159 }
160
161 WaitCondition::Value { element, expected } => {
162 session.detect_elements();
163 session
164 .find_element(element)
165 .and_then(|el| el.value.as_ref())
166 .map(|v| v == expected)
167 .unwrap_or(false)
168 }
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_parse_text_condition() {
178 let cond = WaitCondition::parse(Some("text"), None, Some("hello"));
179 assert!(matches!(cond, Some(WaitCondition::Text(t)) if t == "hello"));
180 }
181
182 #[test]
183 fn test_parse_element_condition() {
184 let cond = WaitCondition::parse(Some("element"), Some("@btn1"), None);
185 assert!(matches!(cond, Some(WaitCondition::Element(e)) if e == "@btn1"));
186 }
187
188 #[test]
189 fn test_parse_stable_condition() {
190 let cond = WaitCondition::parse(Some("stable"), None, None);
191 assert!(matches!(cond, Some(WaitCondition::Stable)));
192 }
193
194 #[test]
195 fn test_parse_value_condition() {
196 let cond = WaitCondition::parse(Some("value"), Some("@inp1=hello"), None);
197 assert!(
198 matches!(cond, Some(WaitCondition::Value { element, expected }) if element == "@inp1" && expected == "hello")
199 );
200 }
201
202 #[test]
203 fn test_parse_value_condition_with_separate_text() {
204 let cond = WaitCondition::parse(Some("value"), Some("@e2"), Some("expected value"));
206 assert!(
207 matches!(cond, Some(WaitCondition::Value { element, expected }) if element == "@e2" && expected == "expected value")
208 );
209 }
210
211 #[test]
212 fn test_stable_tracker() {
213 let mut tracker = StableTracker::new(3);
214
215 assert!(!tracker.add_hash("screen1"));
217 assert!(!tracker.add_hash("screen2"));
218 assert!(!tracker.add_hash("screen3"));
219
220 assert!(!tracker.add_hash("stable"));
222 assert!(!tracker.add_hash("stable"));
223 assert!(tracker.add_hash("stable"));
224 }
225
226 #[test]
227 fn test_default_to_text_condition() {
228 let cond = WaitCondition::parse(None, None, Some("hello"));
229 assert!(matches!(cond, Some(WaitCondition::Text(t)) if t == "hello"));
230 }
231}