extern crate alloc;
use alloc::string::String;
use crate::widget::Color;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum PropertyValue {
Int(i32),
Bool(bool),
Color(Color),
Text(String),
}
impl PropertyValue {
pub fn as_int(&self) -> Option<i32> {
match self {
Self::Int(v) => Some(*v),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Bool(v) => Some(*v),
_ => None,
}
}
pub fn as_color(&self) -> Option<Color> {
match self {
Self::Color(v) => Some(*v),
_ => None,
}
}
pub fn as_text(&self) -> Option<&str> {
match self {
Self::Text(v) => Some(v.as_str()),
_ => None,
}
}
}
pub trait Queryable {
fn get_property(&self, key: &str) -> Option<PropertyValue>;
fn set_property(&mut self, key: &str, value: PropertyValue) -> bool {
let _ = (key, value);
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
struct Counter {
count: i32,
enabled: bool,
label: String,
tint: Color,
}
impl Counter {
fn new() -> Self {
Self {
count: 0,
enabled: true,
label: "counter".into(),
tint: Color(0, 128, 255, 255),
}
}
}
impl Queryable for Counter {
fn get_property(&self, key: &str) -> Option<PropertyValue> {
match key {
"count" => Some(PropertyValue::Int(self.count)),
"enabled" => Some(PropertyValue::Bool(self.enabled)),
"label" => Some(PropertyValue::Text(self.label.clone())),
"tint" => Some(PropertyValue::Color(self.tint)),
_ => None,
}
}
fn set_property(&mut self, key: &str, value: PropertyValue) -> bool {
match (key, value) {
("count", PropertyValue::Int(v)) => {
self.count = v;
true
}
("enabled", PropertyValue::Bool(v)) => {
self.enabled = v;
true
}
("label", PropertyValue::Text(v)) => {
self.label = v;
true
}
("tint", PropertyValue::Color(v)) => {
self.tint = v;
true
}
_ => false,
}
}
}
#[test]
fn get_int_property_round_trip() {
let c = Counter::new();
assert_eq!(c.get_property("count"), Some(PropertyValue::Int(0)));
}
#[test]
fn get_bool_property_round_trip() {
let c = Counter::new();
assert_eq!(c.get_property("enabled"), Some(PropertyValue::Bool(true)));
}
#[test]
fn get_text_property_round_trip() {
let c = Counter::new();
assert_eq!(
c.get_property("label"),
Some(PropertyValue::Text("counter".into()))
);
}
#[test]
fn get_color_property_round_trip() {
let c = Counter::new();
assert_eq!(
c.get_property("tint"),
Some(PropertyValue::Color(Color(0, 128, 255, 255)))
);
}
#[test]
fn get_unknown_property_returns_none() {
let c = Counter::new();
assert_eq!(c.get_property("nonexistent"), None);
}
#[test]
fn set_int_property_accepted_and_applied() {
let mut c = Counter::new();
let accepted = c.set_property("count", PropertyValue::Int(99));
assert!(accepted);
assert_eq!(c.count, 99);
}
#[test]
fn set_bool_property_accepted_and_applied() {
let mut c = Counter::new();
let accepted = c.set_property("enabled", PropertyValue::Bool(false));
assert!(accepted);
assert!(!c.enabled);
}
#[test]
fn set_text_property_accepted_and_applied() {
let mut c = Counter::new();
let accepted = c.set_property("label", PropertyValue::Text("new".into()));
assert!(accepted);
assert_eq!(c.label, "new");
}
#[test]
fn set_color_property_accepted_and_applied() {
let mut c = Counter::new();
let new_color = Color(255, 0, 0, 255);
let accepted = c.set_property("tint", PropertyValue::Color(new_color));
assert!(accepted);
assert_eq!(c.tint, new_color);
}
#[test]
fn set_unknown_property_returns_false() {
let mut c = Counter::new();
let accepted = c.set_property("bogus", PropertyValue::Int(1));
assert!(!accepted);
}
#[test]
fn set_property_type_mismatch_returns_false_and_no_corruption() {
let mut c = Counter::new();
let original_count = c.count;
let accepted = c.set_property("count", PropertyValue::Bool(true));
assert!(!accepted, "type mismatch must be rejected");
assert_eq!(c.count, original_count, "state must not be corrupted");
}
#[test]
fn set_property_type_mismatch_text_for_int_no_corruption() {
let mut c = Counter::new();
let original = c.count;
let accepted = c.set_property("count", PropertyValue::Text("five".into()));
assert!(!accepted);
assert_eq!(c.count, original);
}
#[test]
fn as_int_correct_variant() {
assert_eq!(PropertyValue::Int(7).as_int(), Some(7));
}
#[test]
fn as_int_wrong_variant() {
assert_eq!(PropertyValue::Bool(true).as_int(), None);
}
#[test]
fn as_bool_correct_variant() {
assert_eq!(PropertyValue::Bool(false).as_bool(), Some(false));
}
#[test]
fn as_bool_wrong_variant() {
assert_eq!(PropertyValue::Int(0).as_bool(), None);
}
#[test]
fn as_color_correct_variant() {
let c = Color(1, 2, 3, 4);
assert_eq!(PropertyValue::Color(c).as_color(), Some(c));
}
#[test]
fn as_color_wrong_variant() {
assert_eq!(PropertyValue::Text("x".into()).as_color(), None);
}
#[test]
fn as_text_correct_variant() {
assert_eq!(PropertyValue::Text("hello".into()).as_text(), Some("hello"));
}
#[test]
fn as_text_wrong_variant() {
assert_eq!(PropertyValue::Int(0).as_text(), None);
}
struct ReadOnly;
impl Queryable for ReadOnly {
fn get_property(&self, key: &str) -> Option<PropertyValue> {
match key {
"version" => Some(PropertyValue::Int(1)),
_ => None,
}
}
}
#[test]
fn default_set_property_returns_false() {
let mut ro = ReadOnly;
assert!(!ro.set_property("version", PropertyValue::Int(2)));
}
#[test]
fn property_value_clone_and_eq() {
let v = PropertyValue::Text("abc".to_string());
assert_eq!(v.clone(), v);
}
}