#[cfg(test)]
mod snapshot_surface_tests {
use std::time::Duration;
use xa11y::{ShellSurface, ShellSurfaceKind, mock};
use crate::computer::snapshot::{DEFAULT_MAX_DEPTH, snapshot_surface};
use crate::computer::types::{ComputerSnapshot, SnapshotFormat};
fn taskbar() -> xa11y::ShellSurface {
ShellSurface::by_kind_with(
mock::build_provider(),
ShellSurfaceKind::Taskbar,
Duration::ZERO,
)
.expect("mock fixture carries a taskbar")
}
#[test]
fn surface_root_reports_surface_identity() {
let input = ComputerSnapshot::default();
let out = snapshot_surface(
&input,
taskbar(),
Some(DEFAULT_MAX_DEPTH as usize),
SnapshotFormat::default(),
)
.expect("mock taskbar must snapshot");
assert_eq!(out.app, "Taskbar");
assert_eq!(out.pid, Some(mock::MOCK_SHELL_PID));
assert!(out.elements >= 1);
assert!(out.snapshot.contains("Taskbar"), "{}", out.snapshot);
}
#[test]
fn surface_selector_narrows_like_app_path() {
let input = ComputerSnapshot {
selector: Some("button".into()),
nth: Some(1),
..ComputerSnapshot::default()
};
let out = snapshot_surface(
&input,
taskbar(),
Some(DEFAULT_MAX_DEPTH as usize),
SnapshotFormat::default(),
)
.expect("mock taskbar carries buttons");
assert_eq!(out.app, "Taskbar");
assert_eq!(out.elements, 1, "nth(1) picks exactly one match");
}
#[test]
fn surface_selector_miss_is_honest_error() {
let input = ComputerSnapshot {
selector: Some("text_field[name='definitely-not-here']".into()),
..ComputerSnapshot::default()
};
let err = snapshot_surface(
&input,
taskbar(),
Some(DEFAULT_MAX_DEPTH as usize),
SnapshotFormat::default(),
)
.expect_err("missing selector must fail");
assert!(!err.is_empty());
}
}
#[cfg(test)]
mod snapshot_render_tests {
use crate::computer::snapshot::{render_tree, write_outline};
use crate::computer::types::SnapshotFormat;
use crate::computer::types::{ElementStates, StateNode, ToggleState};
#[test]
fn default_states_render_nothing() {
let node = StateNode {
role: "button".into(),
name: Some("OK".into()),
value: None,
states: ElementStates::default(),
children: vec![],
};
assert_eq!(write_outline_str(&node), "button name=\"OK\"\n");
}
#[test]
fn non_default_states_render_as_tokens_in_fixed_order() {
let states = ElementStates {
enabled: false,
visible: false,
focused: true,
checked: Some(ToggleState::On),
selected: true,
expanded: Some(false),
editable: true,
busy: true,
};
let node = StateNode {
role: "checkbox".into(),
name: None,
value: None,
states,
children: vec![],
};
assert_eq!(
write_outline_str(&node),
"checkbox disabled hidden focused checked selected collapsed editable busy\n"
);
}
#[test]
fn unchecked_is_reported_but_non_checkable_is_silent() {
let unchecked = ElementStates {
checked: Some(ToggleState::Off),
..ElementStates::default()
};
let unchecked_node = StateNode {
role: "checkbox".into(),
name: None,
value: None,
states: unchecked,
children: vec![],
};
assert_eq!(write_outline_str(&unchecked_node), "checkbox unchecked\n");
let mixed = ElementStates {
checked: Some(ToggleState::Mixed),
..ElementStates::default()
};
let mixed_node = StateNode {
role: "checkbox".into(),
name: None,
value: None,
states: mixed,
children: vec![],
};
assert_eq!(write_outline_str(&mixed_node), "checkbox mixed\n");
}
#[test]
fn json_format_embeds_states_object() {
let child = StateNode {
role: "text_field".into(),
name: Some("Search".into()),
value: Some("hi".into()),
states: ElementStates {
editable: true,
..ElementStates::default()
},
children: vec![],
};
let root = StateNode {
role: "window".into(),
name: Some("Main".into()),
value: None,
states: ElementStates::default(),
children: vec![child],
};
let json = render_tree(&SnapshotFormat::Json, &root);
let value: serde_json::Value = serde_json::from_str(&json).expect("valid json");
assert_eq!(value["role"], "window");
assert_eq!(value["states"]["enabled"], true);
assert_eq!(value["states"]["visible"], true);
assert_eq!(value["states"]["focused"], false);
assert_eq!(
value["states"].get("checked"),
Some(&serde_json::Value::Null)
);
assert_eq!(
value["states"].get("expanded"),
Some(&serde_json::Value::Null)
);
let grandchild = &value["children"][0];
assert_eq!(grandchild["states"]["editable"], true);
assert_eq!(
grandchild["states"].get("checked"),
Some(&serde_json::Value::Null)
);
let toggled = ElementStates {
checked: Some(ToggleState::On),
..ElementStates::default()
};
let toggled_node = StateNode {
role: "checkbox".into(),
name: None,
value: None,
states: toggled,
children: vec![],
};
let toggled_json = render_tree(&SnapshotFormat::Json, &toggled_node);
let toggled_value: serde_json::Value =
serde_json::from_str(&toggled_json).expect("valid json");
assert_eq!(toggled_value["states"]["checked"], "on");
let mixed_json_value = ElementStates {
checked: Some(ToggleState::Mixed),
..ElementStates::default()
};
let mixed_node = StateNode {
role: "checkbox".into(),
name: None,
value: None,
states: mixed_json_value,
children: vec![],
};
let mixed_json = render_tree(&SnapshotFormat::Json, &mixed_node);
let mixed_value: serde_json::Value = serde_json::from_str(&mixed_json).expect("valid json");
assert_eq!(mixed_value["states"]["checked"], "mixed");
}
fn write_outline_str(node: &StateNode) -> String {
let mut out = String::new();
write_outline(node, 0, &mut out);
out
}
}
#[cfg(test)]
mod surface_tests {
use std::sync::Arc;
use std::time::Duration;
use crate::computer::surface::{
kind, resolve_with, surface_label, validate_act, validate_screenshot, validate_snapshot,
};
use crate::computer::types::SurfaceKind;
use crate::computer::types::{ComputerAct, ComputerScreenshot, ComputerSnapshot};
use xa11y::mock::build_provider;
use xa11y::{Provider, ShellSurfaceKind};
fn mock() -> Arc<dyn Provider> {
build_provider()
}
#[test]
fn kind_mapping_is_total() {
let pairs = [
(SurfaceKind::MenuBar, ShellSurfaceKind::MenuBar),
(SurfaceKind::StatusItems, ShellSurfaceKind::StatusItems),
(SurfaceKind::Taskbar, ShellSurfaceKind::Taskbar),
(SurfaceKind::Panel, ShellSurfaceKind::Panel),
(SurfaceKind::Dock, ShellSurfaceKind::Dock),
(SurfaceKind::Desktop, ShellSurfaceKind::Desktop),
(SurfaceKind::Flyout, ShellSurfaceKind::Flyout),
(SurfaceKind::Unknown, ShellSurfaceKind::Unknown),
];
for (wire, native) in pairs {
assert_eq!(kind(wire), native);
assert_eq!(surface_label(wire), native.to_snake_case());
}
}
#[test]
fn resolve_taskbar_through_mock() {
let surface = resolve_with(mock(), SurfaceKind::Taskbar, Duration::ZERO)
.expect("mock fixture carries a taskbar");
assert_eq!(surface.kind, ShellSurfaceKind::Taskbar);
assert_eq!(surface.name, "Taskbar");
let locator = surface.locator("button");
assert!(locator.count().map(|n| n >= 1).unwrap_or(false));
}
#[test]
fn resolve_missing_kind_is_honest_scope() {
let err = resolve_with(mock(), SurfaceKind::Dock, Duration::ZERO)
.expect_err("mock has no dock surface");
assert!(err.contains("dock"), "err: {err}");
}
#[test]
fn targeting_exactly_one_of_three() {
let ok = || validate_snapshot(&ComputerSnapshot::default());
let err = ok().unwrap_err();
assert!(
err.contains("provide `name`, `pid` or `surface`"),
"err: {err}"
);
let err = validate_snapshot(&ComputerSnapshot {
name: Some("Safari".into()),
pid: Some(1),
..Default::default()
})
.unwrap_err();
assert!(err.contains("not both"), "err: {err}");
let err = validate_snapshot(&ComputerSnapshot {
name: Some("Safari".into()),
surface: Some(SurfaceKind::MenuBar),
..Default::default()
})
.unwrap_err();
assert!(
err.contains("cannot share a call"),
"app+surface mix must be rejected: {err}"
);
assert!(
validate_snapshot(&ComputerSnapshot {
surface: Some(SurfaceKind::MenuBar),
..Default::default()
})
.is_ok()
);
}
#[test]
fn targeting_act_and_screenshot_match_their_wire_shapes() {
let err = validate_act(&ComputerAct::default()).unwrap_err();
assert!(err.contains("`name`"), "err: {err}");
let err = validate_act(&ComputerAct {
name: Some("Safari".into()),
surface: Some(SurfaceKind::Dock),
..Default::default()
})
.unwrap_err();
assert!(err.contains("cannot share a call"), "err: {err}");
assert!(validate_screenshot(&ComputerScreenshot::default()).is_ok());
let err = validate_screenshot(&ComputerScreenshot {
app: Some("Safari".into()),
surface: Some(SurfaceKind::Dock),
..Default::default()
})
.unwrap_err();
assert!(err.contains("cannot share a call"), "err: {err}");
let err = validate_screenshot(&ComputerScreenshot {
pid: Some(1),
surface: Some(SurfaceKind::Dock),
..Default::default()
})
.unwrap_err();
assert!(err.contains("cannot share a call"), "err: {err}");
}
}