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