#![forbid(unsafe_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VerbEntry {
pub maestro_name: &'static str,
pub smix_name: &'static str,
pub category: VerbCategory,
pub arg_shape: ArgShape,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VerbCategory {
Tap,
Input,
Assert,
ControlFlow,
Lifecycle,
Media,
Gesture,
Device,
SmixNative,
Utility,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ArgShape {
None,
BareString,
Selector,
Mapping,
Path,
Coord,
Integer,
}
pub static VERB_TABLE: &[VerbEntry] = &[
v("tapOn", "tap", VerbCategory::Tap, ArgShape::Selector),
v(
"doubleTapOn",
"doubleTap",
VerbCategory::Tap,
ArgShape::Selector,
),
v(
"longPressOn",
"longPress",
VerbCategory::Tap,
ArgShape::Selector,
),
v(
"tapByCoord",
"tapByCoord",
VerbCategory::Tap,
ArgShape::Coord,
),
v(
"inputText",
"fill",
VerbCategory::Input,
ArgShape::BareString,
),
v("eraseText", "clear", VerbCategory::Input, ArgShape::Integer),
v(
"pasteText",
"pasteText",
VerbCategory::Input,
ArgShape::None,
),
v(
"setClipboard",
"setClipboard",
VerbCategory::Input,
ArgShape::BareString,
),
v(
"copyTextFrom",
"copyTextFrom",
VerbCategory::Input,
ArgShape::Selector,
),
v(
"assertVisible",
"expect",
VerbCategory::Assert,
ArgShape::Selector,
),
v(
"assertNotVisible",
"expectNotVisible",
VerbCategory::Assert,
ArgShape::Selector,
),
v(
"extendedWaitUntil",
"expect",
VerbCategory::Assert,
ArgShape::Mapping,
),
v(
"assertTrue",
"assertTrue",
VerbCategory::Assert,
ArgShape::BareString,
),
v(
"assertScreenshot",
"assertScreenshot",
VerbCategory::Assert,
ArgShape::Mapping,
),
v(
"runFlow",
"runFlow",
VerbCategory::ControlFlow,
ArgShape::Path,
),
v(
"retry",
"retry",
VerbCategory::ControlFlow,
ArgShape::Mapping,
),
v(
"repeat",
"repeat",
VerbCategory::ControlFlow,
ArgShape::Mapping,
),
v(
"pressKey",
"pressKey",
VerbCategory::ControlFlow,
ArgShape::BareString,
),
v(
"back",
"pressKey",
VerbCategory::ControlFlow,
ArgShape::None,
),
v(
"launchApp",
"launchApp",
VerbCategory::Lifecycle,
ArgShape::Mapping,
),
v(
"stopApp",
"terminate",
VerbCategory::Lifecycle,
ArgShape::None,
),
v(
"killApp",
"terminate",
VerbCategory::Lifecycle,
ArgShape::BareString,
),
v(
"clearState",
"reset",
VerbCategory::Lifecycle,
ArgShape::None,
),
v(
"clearKeychain",
"resetKeychain",
VerbCategory::Lifecycle,
ArgShape::None,
),
v(
"takeScreenshot",
"takeScreenshot",
VerbCategory::Media,
ArgShape::BareString,
),
v(
"startRecording",
"startRecording",
VerbCategory::Media,
ArgShape::BareString,
),
v(
"stopRecording",
"stopRecording",
VerbCategory::Media,
ArgShape::None,
),
v(
"addMedia",
"addMedia",
VerbCategory::Media,
ArgShape::Mapping,
),
v("scroll", "scroll", VerbCategory::Gesture, ArgShape::None),
v(
"scrollUntilVisible",
"scroll",
VerbCategory::Gesture,
ArgShape::Mapping,
),
v("swipe", "swipe", VerbCategory::Gesture, ArgShape::Mapping),
v(
"hideKeyboard",
"hideKeyboard",
VerbCategory::Gesture,
ArgShape::None,
),
v(
"openLink",
"openUrl",
VerbCategory::Device,
ArgShape::BareString,
),
v(
"setLocation",
"setLocation",
VerbCategory::Device,
ArgShape::Mapping,
),
v("travel", "travel", VerbCategory::Device, ArgShape::Mapping),
v(
"setPermissions",
"setPermissions",
VerbCategory::Device,
ArgShape::Mapping,
),
v(
"setOrientation",
"setOrientation",
VerbCategory::Device,
ArgShape::BareString,
),
v(
"toggleAirplaneMode",
"toggleAirplaneMode",
VerbCategory::Device,
ArgShape::None,
),
v(
"webview_eval",
"webviewEval",
VerbCategory::SmixNative,
ArgShape::BareString,
),
v(
"webviewEval",
"webviewEval",
VerbCategory::SmixNative,
ArgShape::BareString,
),
v(
"fixture",
"fixture",
VerbCategory::SmixNative,
ArgShape::Mapping,
),
v(
"tapById",
"tapById",
VerbCategory::SmixNative,
ArgShape::BareString,
),
v(
"tapAtCoord",
"tapAtCoord",
VerbCategory::SmixNative,
ArgShape::Coord,
),
v(
"swipeAtCoord",
"swipeAtCoord",
VerbCategory::SmixNative,
ArgShape::Coord,
),
v(
"doubleTap",
"doubleTap",
VerbCategory::SmixNative,
ArgShape::Selector,
),
v(
"longPress",
"longPress",
VerbCategory::SmixNative,
ArgShape::Selector,
),
v(
"ocrText",
"ocrText",
VerbCategory::SmixNative,
ArgShape::BareString,
),
v(
"anchorRelative",
"anchorRelative",
VerbCategory::SmixNative,
ArgShape::Mapping,
),
v(
"findTextByOcr",
"findTextByOcr",
VerbCategory::SmixNative,
ArgShape::Mapping,
),
v(
"expect",
"expect",
VerbCategory::SmixNative,
ArgShape::Mapping,
),
v(
"waitForAnimationToEnd",
"waitForAnimationToEnd",
VerbCategory::Utility,
ArgShape::None,
),
];
const fn v(
maestro_name: &'static str,
smix_name: &'static str,
category: VerbCategory,
arg_shape: ArgShape,
) -> VerbEntry {
VerbEntry {
maestro_name,
smix_name,
category,
arg_shape,
}
}
pub fn find_by_maestro(name: &str) -> Option<&'static VerbEntry> {
VERB_TABLE.iter().find(|e| e.maestro_name == name)
}
pub fn find_by_smix(name: &str) -> Option<&'static VerbEntry> {
VERB_TABLE.iter().find(|e| e.smix_name == name)
}
pub fn is_known_verb(name: &str) -> bool {
find_by_maestro(name).is_some() || find_by_smix(name).is_some()
}
pub fn verbs_in_category(category: VerbCategory) -> impl Iterator<Item = &'static VerbEntry> {
VERB_TABLE.iter().filter(move |e| e.category == category)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn find_by_maestro_hits() {
let e = find_by_maestro("tapOn").unwrap();
assert_eq!(e.smix_name, "tap");
assert_eq!(e.category, VerbCategory::Tap);
}
#[test]
fn find_by_smix_hits() {
let e = find_by_smix("tap").unwrap();
assert_eq!(e.maestro_name, "tapOn");
}
#[test]
fn find_by_smix_returns_first_when_multiple_match() {
let e = find_by_smix("pressKey").unwrap();
assert!(e.maestro_name == "pressKey" || e.maestro_name == "back");
}
#[test]
fn is_known_covers_both_forms() {
assert!(is_known_verb("tapOn"));
assert!(is_known_verb("tap"));
assert!(is_known_verb("assertVisible"));
assert!(is_known_verb("expect"));
assert!(!is_known_verb("nonexistent"));
}
#[test]
fn extendedwait_maps_to_expect() {
let e = find_by_maestro("extendedWaitUntil").unwrap();
assert_eq!(e.smix_name, "expect");
assert_eq!(e.category, VerbCategory::Assert);
assert_eq!(e.arg_shape, ArgShape::Mapping);
}
#[test]
fn table_has_no_duplicate_maestro_names() {
let mut seen = Vec::new();
for e in VERB_TABLE {
if seen.contains(&e.maestro_name) {
panic!("duplicate maestro_name: {}", e.maestro_name);
}
seen.push(e.maestro_name);
}
}
#[test]
fn each_category_has_at_least_one_entry() {
for cat in [
VerbCategory::Tap,
VerbCategory::Input,
VerbCategory::Assert,
VerbCategory::ControlFlow,
VerbCategory::Lifecycle,
VerbCategory::Media,
VerbCategory::Gesture,
VerbCategory::Device,
VerbCategory::SmixNative,
VerbCategory::Utility,
] {
let count = verbs_in_category(cat).count();
assert!(count > 0, "category {cat:?} has no entries");
}
}
#[test]
fn table_covers_v0_2_5_migrate_default_rules() {
for name in [
"tapOn",
"assertVisible",
"assertNotVisible",
"extendedWaitUntil",
"inputText",
"eraseText",
"clearState",
"clearKeychain",
"runFlow",
"launchApp",
"stopApp",
"killApp",
"openLink",
"back",
"hideKeyboard",
"pressKey",
"scroll",
"scrollUntilVisible",
"swipe",
"retry",
] {
assert!(
find_by_maestro(name).is_some(),
"v0.2.5 DEFAULT_RULES verb {name:?} missing from VERB_TABLE"
);
}
}
#[test]
fn table_covers_smix_native_verbs() {
for name in ["fixture", "webviewEval"] {
assert!(
find_by_smix(name).is_some() || find_by_maestro(name).is_some(),
"smix-native verb {name:?} missing from VERB_TABLE"
);
}
}
}