ixa/people/
event.rs

1use crate::people::PeoplePlugin;
2use crate::{Context, ContextPeopleExt, IxaEvent, PersonId, PersonProperty};
3use ixa_derive::IxaEvent;
4
5/// Emitted when a new person is created
6/// These should not be emitted outside this module
7#[derive(Clone, Copy, IxaEvent)]
8#[allow(clippy::manual_non_exhaustive)]
9pub struct PersonCreatedEvent {
10    /// The [`PersonId`] of the new person.
11    pub person_id: PersonId,
12}
13
14/// Emitted when a person property is updated
15/// These should not be emitted outside this module
16#[derive(Copy, Clone)]
17#[allow(clippy::manual_non_exhaustive)]
18pub struct PersonPropertyChangeEvent<T: PersonProperty> {
19    /// The [`PersonId`] that changed
20    pub person_id: PersonId,
21    /// The new value
22    pub current: T::Value,
23    /// The old value
24    pub previous: T::Value,
25}
26
27impl<T: PersonProperty> IxaEvent for PersonPropertyChangeEvent<T> {
28    fn on_subscribe(context: &mut Context) {
29        if T::is_derived() {
30            let _ = context.get_data_container_mut(PeoplePlugin); // make sure the plugin is initialized
31            context.register_property::<T>();
32        }
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use crate::{
39        define_derived_property, define_global_property, define_person_property,
40        define_person_property_with_default, Context, ContextPeopleExt, PersonCreatedEvent,
41        PersonId, PersonPropertyChangeEvent,
42    };
43    use serde_derive::Serialize;
44    use std::cell::RefCell;
45    use std::rc::Rc;
46
47    define_person_property!(Age, u8);
48    #[derive(Serialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
49    pub enum AgeGroupValue {
50        Child,
51        Adult,
52    }
53    define_global_property!(Threshold, u8);
54    define_derived_property!(IsEligible, bool, [Age], [Threshold], |age, threshold| {
55        age >= threshold
56    });
57
58    define_derived_property!(AgeGroup, AgeGroupValue, [Age], |age| {
59        if age < 18 {
60            AgeGroupValue::Child
61        } else {
62            AgeGroupValue::Adult
63        }
64    });
65
66    #[derive(Serialize, Copy, Clone, PartialEq, Eq, Debug)]
67    pub enum RiskCategoryValue {
68        High,
69        Low,
70    }
71    define_person_property!(RiskCategory, RiskCategoryValue);
72    define_person_property_with_default!(IsRunner, bool, false);
73    define_person_property!(RunningShoes, u8, |context: &Context, person: PersonId| {
74        let is_runner = context.get_person_property(person, IsRunner);
75        if is_runner {
76            4
77        } else {
78            0
79        }
80    });
81
82    #[test]
83    fn observe_person_addition() {
84        let mut context = Context::new();
85
86        let flag = Rc::new(RefCell::new(false));
87        let flag_clone = flag.clone();
88        context.subscribe_to_event(move |_context, event: PersonCreatedEvent| {
89            *flag_clone.borrow_mut() = true;
90            assert_eq!(event.person_id.0, 0);
91        });
92
93        let _ = context.add_person(()).unwrap();
94        context.execute();
95        assert!(*flag.borrow());
96    }
97
98    #[test]
99    fn observe_person_property_change() {
100        let mut context = Context::new();
101
102        let flag = Rc::new(RefCell::new(false));
103        let flag_clone = flag.clone();
104        context.subscribe_to_event(
105            move |_context, event: PersonPropertyChangeEvent<RiskCategory>| {
106                *flag_clone.borrow_mut() = true;
107                assert_eq!(event.person_id.0, 0, "Person id is correct");
108                assert_eq!(
109                    event.previous,
110                    RiskCategoryValue::Low,
111                    "Previous value is correct"
112                );
113                assert_eq!(
114                    event.current,
115                    RiskCategoryValue::High,
116                    "Current value is correct"
117                );
118            },
119        );
120        let person_id = context
121            .add_person((RiskCategory, RiskCategoryValue::Low))
122            .unwrap();
123        context.set_person_property(person_id, RiskCategory, RiskCategoryValue::High);
124        context.execute();
125        assert!(*flag.borrow());
126    }
127
128    #[test]
129    fn observe_person_property_change_with_set() {
130        let mut context = Context::new();
131
132        let flag = Rc::new(RefCell::new(false));
133        let flag_clone = flag.clone();
134        context.subscribe_to_event(
135            move |_context, _event: PersonPropertyChangeEvent<RunningShoes>| {
136                *flag_clone.borrow_mut() = true;
137            },
138        );
139        let person_id = context.add_person(()).unwrap();
140        // Initializer called as a side effect of set, so event fires.
141        context.set_person_property(person_id, RunningShoes, 42);
142        context.execute();
143        assert!(*flag.borrow());
144    }
145
146    #[test]
147    fn get_person_property_change_event() {
148        let mut context = Context::new();
149        let person = context.add_person((Age, 17)).unwrap();
150
151        let flag = Rc::new(RefCell::new(false));
152
153        let flag_clone = flag.clone();
154        context.subscribe_to_event(
155            move |_context, event: PersonPropertyChangeEvent<AgeGroup>| {
156                assert_eq!(event.person_id.0, 0);
157                assert_eq!(event.previous, AgeGroupValue::Child);
158                assert_eq!(event.current, AgeGroupValue::Adult);
159                *flag_clone.borrow_mut() = true;
160            },
161        );
162        context.set_person_property(person, Age, 18);
163        context.execute();
164        assert!(*flag.borrow());
165    }
166
167    #[test]
168    fn test_person_property_change_event_no_people() {
169        let mut context = Context::new();
170        // Non derived person property -- no problems
171        context.subscribe_to_event(|_context, _event: PersonPropertyChangeEvent<IsRunner>| {
172            unreachable!();
173        });
174
175        // Derived person property -- can't add an event without people being present
176        context.subscribe_to_event(|_context, _event: PersonPropertyChangeEvent<AgeGroup>| {
177            unreachable!();
178        });
179    }
180}