use crate::element::Rect;
use super::{tag_for, Screenshot};
#[non_exhaustive]
pub struct Annotated {
pub screenshot: Screenshot,
pub legend: Vec<LegendEntry>,
pub omitted: Vec<Omission>,
pub truncated: usize,
}
impl Annotated {
pub fn for_capture(
screenshot: Screenshot,
legend: Vec<LegendEntry>,
omitted: Vec<Omission>,
truncated: usize,
) -> Self {
Self {
screenshot,
legend,
omitted,
truncated,
}
}
}
impl std::fmt::Debug for Annotated {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Annotated")
.field(
"screenshot",
&format_args!(
"{}x{} @{}x",
self.screenshot.width, self.screenshot.height, self.screenshot.scale
),
)
.field("legend", &self.legend)
.field("omitted", &self.omitted)
.field("truncated", &self.truncated)
.finish()
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct LegendEntry {
pub tag: String,
pub group: usize,
pub index: usize,
pub selector: String,
pub role: String,
pub name: Option<String>,
pub bounds: Rect,
pub color: [u8; 3],
}
impl LegendEntry {
pub fn new(
group: usize,
index: usize,
selector: impl Into<String>,
role: impl Into<String>,
name: Option<String>,
bounds: Rect,
color: [u8; 3],
) -> Self {
Self {
tag: tag_for(group, index),
group,
index,
selector: selector.into(),
role: role.into(),
name,
bounds,
color,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct Omission {
pub selector: String,
pub role: String,
pub name: Option<String>,
pub reason: OmissionReason,
}
impl Omission {
pub fn new(
selector: impl Into<String>,
role: impl Into<String>,
name: Option<String>,
reason: OmissionReason,
) -> Self {
Self {
selector: selector.into(),
role: role.into(),
name,
reason,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum OmissionReason {
NoBounds,
ZeroArea,
OutsideCapture,
}
impl OmissionReason {
pub fn as_str(self) -> &'static str {
match self {
OmissionReason::NoBounds => "no_bounds",
OmissionReason::ZeroArea => "zero_area",
OmissionReason::OutsideCapture => "outside_capture",
}
}
}
impl std::fmt::Display for OmissionReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rect(x: i32, y: i32, width: u32, height: u32) -> Rect {
Rect {
x,
y,
width,
height,
}
}
#[test]
fn an_entrys_tag_is_derived_from_its_group_and_index() {
let entry = LegendEntry::new(
2,
7,
"button:nth(7)",
"button",
Some("Back".to_string()),
rect(1, 2, 3, 4),
[230, 159, 0],
);
assert_eq!(entry.tag, "B7");
assert_eq!(entry.tag, tag_for(entry.group, entry.index));
assert_eq!(entry.selector, "button:nth(7)");
assert_eq!(entry.bounds, rect(1, 2, 3, 4));
}
#[test]
fn an_omission_keeps_the_selector_that_would_reach_its_element() {
let omission = Omission::new(
"check_box:nth(1)",
"check_box",
Some("Agree".to_string()),
OmissionReason::NoBounds,
);
assert_eq!(omission.selector, "check_box:nth(1)");
assert_eq!(omission.reason, OmissionReason::NoBounds);
}
#[test]
fn an_annotated_summarises_its_capture_rather_than_dumping_the_pixels() {
let shot = Screenshot::new(4, 2, vec![0xAB; 4 * 2 * 4], 2.0);
let annotated = Annotated::for_capture(shot, Vec::new(), Vec::new(), 3);
assert_eq!(annotated.truncated, 3);
assert!(annotated.legend.is_empty());
let rendered = format!("{annotated:?}");
assert!(rendered.contains("4x2 @2x"), "got {rendered}");
assert!(!rendered.contains("171"), "pixels must not be printed");
}
#[test]
fn omission_reasons_spell_themselves_in_snake_case() {
assert_eq!(OmissionReason::NoBounds.as_str(), "no_bounds");
assert_eq!(OmissionReason::ZeroArea.as_str(), "zero_area");
assert_eq!(OmissionReason::OutsideCapture.as_str(), "outside_capture");
assert_eq!(OmissionReason::ZeroArea.to_string(), "zero_area");
}
#[test]
fn a_serialized_omission_reason_matches_its_string_spelling() {
for reason in [
OmissionReason::NoBounds,
OmissionReason::ZeroArea,
OmissionReason::OutsideCapture,
] {
let json = serde_json::to_string(&reason).expect("serialize");
assert_eq!(json, format!("\"{}\"", reason.as_str()));
}
}
}