use std::hash::Hash;
use std::ops::Range;
use std::sync::Arc;
use crate::event::{Event, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind};
use crate::props::{AttrValue, Attribute, QueryResult};
use crate::state::State;
pub struct Sub<ComponentId, UserEvent>(EventClause<UserEvent>, Arc<SubClause<ComponentId>>)
where
ComponentId: Eq + PartialEq + Clone + Hash,
UserEvent: Eq + PartialEq + Clone;
impl<ComponentId, UserEvent> Sub<ComponentId, UserEvent>
where
ComponentId: Eq + PartialEq + Clone + Hash,
UserEvent: Eq + PartialEq + Clone,
{
#[must_use]
pub fn new<SC: Into<Arc<SubClause<ComponentId>>>>(
event_clause: EventClause<UserEvent>,
sub_clause: SC,
) -> Self {
Self(event_clause, sub_clause.into())
}
}
pub(crate) struct Subscription<ComponentId, UserEvent>
where
ComponentId: Eq + PartialEq + Clone + Hash,
UserEvent: Eq + PartialEq + Clone,
{
target: ComponentId,
ev: EventClause<UserEvent>,
when: Arc<SubClause<ComponentId>>,
}
impl<ComponentId, UserEvent> Subscription<ComponentId, UserEvent>
where
ComponentId: Eq + PartialEq + Clone + Hash,
UserEvent: Eq + PartialEq + Clone + Send,
{
#[must_use]
pub fn new(target: ComponentId, sub: Sub<ComponentId, UserEvent>) -> Self {
Self {
target,
ev: sub.0,
when: sub.1,
}
}
#[must_use]
pub(crate) fn target(&self) -> &ComponentId {
&self.target
}
#[must_use]
pub(crate) fn event(&self) -> &EventClause<UserEvent> {
&self.ev
}
#[must_use]
pub(crate) fn forward<'a, HasAttrFn, GetStateFn, MountedFn>(
&self,
ev: &Event<UserEvent>,
has_attr_fn: HasAttrFn,
get_state_fn: GetStateFn,
mounted_fn: MountedFn,
) -> bool
where
HasAttrFn: Fn(&ComponentId, Attribute) -> Option<QueryResult<'a>>,
GetStateFn: Fn(&ComponentId) -> Option<State>,
MountedFn: Fn(&ComponentId) -> bool,
{
self.ev.forward(ev) && self.when.forward(has_attr_fn, get_state_fn, mounted_fn)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct MouseEventClause {
pub kind: MouseEventKind,
pub modifiers: KeyModifiers,
pub column: Range<u16>,
pub row: Range<u16>,
}
impl MouseEventClause {
fn is_in_range(&self, ev: MouseEvent) -> bool {
self.column.contains(&ev.column) && self.row.contains(&ev.row)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum EventClause<UserEvent>
where
UserEvent: Eq + PartialEq + Clone,
{
Any,
Keyboard(KeyEvent),
Mouse(MouseEventClause),
WindowResize,
Tick,
User(UserEvent),
Discriminant(UserEvent),
}
impl<UserEvent> EventClause<UserEvent>
where
UserEvent: Eq + PartialEq + Clone,
{
fn forward(&self, ev: &Event<UserEvent>) -> bool {
match self {
EventClause::Any => true,
EventClause::Keyboard(k) => Some(k) == ev.as_keyboard(),
EventClause::Mouse(m) => ev.as_mouse().is_some_and(|ev| m.is_in_range(*ev)),
EventClause::WindowResize => ev.as_window_resize(),
EventClause::Tick => ev.as_tick(),
EventClause::User(u) => Some(u) == ev.as_user(),
EventClause::Discriminant(u) => {
Some(std::mem::discriminant(u)) == ev.as_user().map(|u| std::mem::discriminant(u))
}
}
}
}
#[derive(Debug, PartialEq, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum SubClause<ComponentId>
where
ComponentId: Eq + PartialEq + Clone + Hash,
{
Always,
HasAttrValue(ComponentId, Attribute, AttrValue),
HasState(ComponentId, State),
IsMounted(ComponentId),
Not(Box<SubClause<ComponentId>>),
And(Box<SubClause<ComponentId>>, Box<SubClause<ComponentId>>),
AndMany(Vec<SubClause<ComponentId>>),
Or(Box<SubClause<ComponentId>>, Box<SubClause<ComponentId>>),
OrMany(Vec<SubClause<ComponentId>>),
}
impl<ComponentId> SubClause<ComponentId>
where
ComponentId: Eq + PartialEq + Clone + Hash,
{
#[allow(clippy::should_implement_trait)]
#[must_use]
pub fn not(clause: Self) -> Self {
Self::Not(Box::new(clause))
}
#[must_use]
pub fn and(a: Self, b: Self) -> Self {
Self::And(Box::new(a), Box::new(b))
}
#[must_use]
pub fn or(a: Self, b: Self) -> Self {
Self::Or(Box::new(a), Box::new(b))
}
#[must_use]
pub(crate) fn forward<'a, HasAttrFn, GetStateFn, MountedFn>(
&self,
has_attr_fn: HasAttrFn,
get_state_fn: GetStateFn,
mounted_fn: MountedFn,
) -> bool
where
HasAttrFn: Fn(&ComponentId, Attribute) -> Option<QueryResult<'a>>,
GetStateFn: Fn(&ComponentId) -> Option<State>,
MountedFn: Fn(&ComponentId) -> bool,
{
self.check_forwarding(has_attr_fn, get_state_fn, mounted_fn)
.0
}
#[must_use]
fn check_forwarding<'a, HasAttrFn, GetStateFn, MountedFn>(
&self,
has_attr_fn: HasAttrFn,
get_state_fn: GetStateFn,
mounted_fn: MountedFn,
) -> (bool, HasAttrFn, GetStateFn, MountedFn)
where
HasAttrFn: Fn(&ComponentId, Attribute) -> Option<QueryResult<'a>>,
GetStateFn: Fn(&ComponentId) -> Option<State>,
MountedFn: Fn(&ComponentId) -> bool,
{
match self {
Self::Always => (true, has_attr_fn, get_state_fn, mounted_fn),
Self::HasAttrValue(id, query, value) => {
let (fwd, has_attr_fn) = Self::has_attribute(id, query, value, has_attr_fn);
(fwd, has_attr_fn, get_state_fn, mounted_fn)
}
Self::HasState(id, state) => {
let (fwd, get_state_fn) = Self::has_state(id, state, get_state_fn);
(fwd, has_attr_fn, get_state_fn, mounted_fn)
}
Self::IsMounted(id) => {
let (fwd, mounted_fn) = Self::is_mounted(id, mounted_fn);
(fwd, has_attr_fn, get_state_fn, mounted_fn)
}
Self::Not(clause) => {
let (fwd, has_attr_fn, get_state_fn, mounted_fn) =
clause.check_forwarding(has_attr_fn, get_state_fn, mounted_fn);
(!fwd, has_attr_fn, get_state_fn, mounted_fn)
}
Self::And(a, b) => {
let (fwd_a, has_attr_fn, get_state_fn, mounted_fn) =
a.check_forwarding(has_attr_fn, get_state_fn, mounted_fn);
let (fwd_b, has_attr_fn, get_state_fn, mounted_fn) =
b.check_forwarding(has_attr_fn, get_state_fn, mounted_fn);
(fwd_a && fwd_b, has_attr_fn, get_state_fn, mounted_fn)
}
Self::AndMany(clauses) => {
let mut has_attr_fn = has_attr_fn;
let mut get_state_fn = get_state_fn;
let mut mounted_fn = mounted_fn;
for clause in clauses {
let res = clause.check_forwarding(has_attr_fn, get_state_fn, mounted_fn);
has_attr_fn = res.1;
get_state_fn = res.2;
mounted_fn = res.3;
if !res.0 {
return (false, has_attr_fn, get_state_fn, mounted_fn);
}
}
(!clauses.is_empty(), has_attr_fn, get_state_fn, mounted_fn)
}
Self::Or(a, b) => {
let (fwd_a, has_attr_fn, get_state_fn, mounted_fn) =
a.check_forwarding(has_attr_fn, get_state_fn, mounted_fn);
let (fwd_b, has_attr_fn, get_state_fn, mounted_fn) =
b.check_forwarding(has_attr_fn, get_state_fn, mounted_fn);
(fwd_a || fwd_b, has_attr_fn, get_state_fn, mounted_fn)
}
Self::OrMany(clauses) => {
let mut has_attr_fn = has_attr_fn;
let mut get_state_fn = get_state_fn;
let mut mounted_fn = mounted_fn;
for clause in clauses {
let res = clause.check_forwarding(has_attr_fn, get_state_fn, mounted_fn);
has_attr_fn = res.1;
get_state_fn = res.2;
mounted_fn = res.3;
if res.0 {
return (true, has_attr_fn, get_state_fn, mounted_fn);
}
}
(false, has_attr_fn, get_state_fn, mounted_fn)
}
}
}
#[must_use]
fn has_attribute<'a, HasAttrFn>(
id: &ComponentId,
query: &Attribute,
value: &AttrValue,
has_attr_fn: HasAttrFn,
) -> (bool, HasAttrFn)
where
HasAttrFn: Fn(&ComponentId, Attribute) -> Option<QueryResult<'a>>,
{
(
match has_attr_fn(id, *query) {
None => false,
Some(v) => *value == v,
},
has_attr_fn,
)
}
#[must_use]
fn has_state<GetStateFn>(
id: &ComponentId,
state: &State,
get_state_fn: GetStateFn,
) -> (bool, GetStateFn)
where
GetStateFn: Fn(&ComponentId) -> Option<State>,
{
(
match get_state_fn(id) {
Some(s) => s == *state,
None => false,
},
get_state_fn,
)
}
#[must_use]
fn is_mounted<MountedFn>(id: &ComponentId, mounted_fn: MountedFn) -> (bool, MountedFn)
where
MountedFn: Fn(&ComponentId) -> bool,
{
(mounted_fn(id), mounted_fn)
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use super::*;
use crate::command::Cmd;
use crate::component::Component;
use crate::event::{Key, KeyModifiers, MouseEventKind, NoUserEvent};
use crate::mock::{MockComponentId, MockEvent, MockFooInput};
use crate::state::StateValue;
#[test]
fn subscription_should_forward() {
let ev: Event<MockEvent> = Event::WindowResize(1024, 512);
let mut component = MockFooInput::default();
component.attr(Attribute::Focus, AttrValue::Flag(true));
let sub = Subscription::new(
MockComponentId::InputFoo,
Sub::new(
EventClause::WindowResize,
SubClause::HasAttrValue(
MockComponentId::InputBar,
Attribute::Focus,
AttrValue::Flag(true),
),
),
);
assert_eq!(sub.target(), &MockComponentId::InputFoo);
assert_eq!(sub.event(), &EventClause::<MockEvent>::WindowResize);
assert_eq!(
*sub.when,
SubClause::HasAttrValue(
MockComponentId::InputBar,
Attribute::Focus,
AttrValue::Flag(true)
)
);
assert_eq!(
sub.forward(
&ev,
|_: &MockComponentId, q| component.query(q),
|_: &MockComponentId| Some(component.state()),
|_: &MockComponentId| true
),
true
);
component.attr(Attribute::Focus, AttrValue::Flag(false));
assert_eq!(
sub.forward(
&ev,
|_: &MockComponentId, q| component.query(q),
|_: &MockComponentId| Some(component.state()),
|_: &MockComponentId| true
),
false
);
assert_eq!(
sub.forward(
&Event::User(MockEvent::Foo),
|_: &MockComponentId, q| component.query(q),
|_: &MockComponentId| Some(component.state()),
|_: &MockComponentId| true
),
false
);
assert_eq!(
sub.forward(
&Event::WindowResize(0, 0),
|_: &MockComponentId, q| component.query(q),
|_: &MockComponentId| Some(component.state()),
|_: &MockComponentId| true
),
false
);
}
#[test]
fn forward_many() {
let ev: Event<MockEvent> = Event::Keyboard(Key::Char('q').into());
let mut component = MockFooInput::default();
component.attr(Attribute::Focus, AttrValue::Flag(true));
let sub = Subscription::new(
MockComponentId::InputFoo,
Sub::new(
EventClause::Keyboard(Key::Char('q').into()),
SubClause::AndMany(vec![
SubClause::IsMounted(MockComponentId::InputFoo),
SubClause::IsMounted(MockComponentId::InputBar),
SubClause::IsMounted(MockComponentId::InputOmar),
]),
),
);
assert_eq!(sub.target(), &MockComponentId::InputFoo);
assert_eq!(
sub.event(),
&EventClause::<MockEvent>::Keyboard(Key::Char('q').into())
);
assert_eq!(
*sub.when,
SubClause::AndMany(vec![
SubClause::IsMounted(MockComponentId::InputFoo),
SubClause::IsMounted(MockComponentId::InputBar),
SubClause::IsMounted(MockComponentId::InputOmar),
])
);
assert_eq!(
sub.forward(
&ev,
|_: &MockComponentId, q| component.query(q),
|_: &MockComponentId| Some(component.state()),
|_: &MockComponentId| true
),
true
);
let sub = Subscription::new(
MockComponentId::InputFoo,
Sub::new(
EventClause::Keyboard(Key::Char('q').into()),
SubClause::AndMany(vec![
SubClause::IsMounted(MockComponentId::InputFoo),
SubClause::IsMounted(MockComponentId::InputBar),
SubClause::not(SubClause::IsMounted(MockComponentId::InputOmar)),
]),
),
);
assert_eq!(sub.target(), &MockComponentId::InputFoo);
assert_eq!(
sub.event(),
&EventClause::<MockEvent>::Keyboard(Key::Char('q').into())
);
assert_eq!(
*sub.when,
SubClause::AndMany(vec![
SubClause::IsMounted(MockComponentId::InputFoo),
SubClause::IsMounted(MockComponentId::InputBar),
SubClause::not(SubClause::IsMounted(MockComponentId::InputOmar)),
])
);
assert_eq!(
sub.forward(
&ev,
|_: &MockComponentId, q| component.query(q),
|_: &MockComponentId| Some(component.state()),
|_: &MockComponentId| true
),
false
);
let sub = Subscription::new(
MockComponentId::InputFoo,
Sub::new(
EventClause::Keyboard(Key::Char('q').into()),
SubClause::OrMany(vec![
SubClause::IsMounted(MockComponentId::InputFoo),
SubClause::IsMounted(MockComponentId::InputBar),
SubClause::not(SubClause::IsMounted(MockComponentId::InputOmar)),
]),
),
);
assert_eq!(sub.target(), &MockComponentId::InputFoo);
assert_eq!(
sub.event(),
&EventClause::<MockEvent>::Keyboard(Key::Char('q').into())
);
assert_eq!(
*sub.when,
SubClause::OrMany(vec![
SubClause::IsMounted(MockComponentId::InputFoo),
SubClause::IsMounted(MockComponentId::InputBar),
SubClause::not(SubClause::IsMounted(MockComponentId::InputOmar)),
])
);
assert_eq!(
sub.forward(
&ev,
|_: &MockComponentId, q| component.query(q),
|_: &MockComponentId| Some(component.state()),
|_: &MockComponentId| true
),
true
);
let sub = Subscription::new(
MockComponentId::InputFoo,
Sub::new(
EventClause::Keyboard(Key::Char('q').into()),
SubClause::OrMany(vec![
SubClause::not(SubClause::IsMounted(MockComponentId::InputFoo)),
SubClause::not(SubClause::IsMounted(MockComponentId::InputBar)),
SubClause::not(SubClause::IsMounted(MockComponentId::InputOmar)),
]),
),
);
assert_eq!(sub.target(), &MockComponentId::InputFoo);
assert_eq!(
sub.event(),
&EventClause::<MockEvent>::Keyboard(Key::Char('q').into())
);
assert_eq!(
*sub.when,
SubClause::OrMany(vec![
SubClause::not(SubClause::IsMounted(MockComponentId::InputFoo)),
SubClause::not(SubClause::IsMounted(MockComponentId::InputBar)),
SubClause::not(SubClause::IsMounted(MockComponentId::InputOmar)),
])
);
assert_eq!(
sub.forward(
&ev,
|_: &MockComponentId, q| component.query(q),
|_: &MockComponentId| Some(component.state()),
|_: &MockComponentId| true
),
false
);
}
#[test]
fn forward_many_zero_elements() {
let ev: Event<MockEvent> = Event::Keyboard(Key::Char('q').into());
let mut component = MockFooInput::default();
component.attr(Attribute::Focus, AttrValue::Flag(true));
let sub = Subscription::new(
MockComponentId::InputFoo,
Sub::new(
EventClause::Keyboard(Key::Char('q').into()),
SubClause::AndMany(vec![]),
),
);
assert_eq!(sub.target(), &MockComponentId::InputFoo);
assert_eq!(
sub.event(),
&EventClause::<MockEvent>::Keyboard(Key::Char('q').into())
);
assert_eq!(*sub.when, SubClause::AndMany(vec![]));
assert_eq!(
sub.forward(
&ev,
|_: &MockComponentId, q| component.query(q),
|_: &MockComponentId| Some(component.state()),
|_: &MockComponentId| true
),
false
);
let sub = Subscription::new(
MockComponentId::InputFoo,
Sub::new(
EventClause::Keyboard(Key::Char('q').into()),
SubClause::OrMany(vec![]),
),
);
assert_eq!(sub.target(), &MockComponentId::InputFoo);
assert_eq!(
sub.event(),
&EventClause::<MockEvent>::Keyboard(Key::Char('q').into())
);
assert_eq!(*sub.when, SubClause::OrMany(vec![]));
assert_eq!(
sub.forward(
&ev,
|_: &MockComponentId, q| component.query(q),
|_: &MockComponentId| Some(component.state()),
|_: &MockComponentId| true
),
false
);
}
#[test]
fn event_clause_any_should_forward() {
assert!(EventClause::<MockEvent>::Any.forward(&Event::Tick));
}
#[test]
fn event_clause_keyboard_should_forward() {
assert_eq!(
EventClause::<MockEvent>::Keyboard(KeyEvent::from(Key::Enter))
.forward(&Event::Keyboard(KeyEvent::from(Key::Enter))),
true
);
assert_eq!(
EventClause::<MockEvent>::Keyboard(KeyEvent::from(Key::Enter))
.forward(&Event::Keyboard(KeyEvent::from(Key::Backspace))),
false
);
assert_eq!(
EventClause::<MockEvent>::Keyboard(KeyEvent::from(Key::Enter)).forward(&Event::Tick),
false
);
assert_eq!(
EventClause::<MockEvent>::Keyboard(KeyEvent::from(Key::Enter)).forward(&Event::Mouse(
MouseEvent {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 0,
row: 0
}
)),
false
);
}
#[test]
fn event_clause_mouse_should_forward() {
assert_eq!(
EventClause::<MockEvent>::Mouse(MouseEventClause {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 0..10,
row: 0..10
})
.forward(&Event::Mouse(MouseEvent {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 0,
row: 0
})),
true
);
assert_eq!(
EventClause::<MockEvent>::Mouse(MouseEventClause {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 0..10,
row: 0..10
})
.forward(&Event::Mouse(MouseEvent {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 20,
row: 20
})),
false
);
assert_eq!(
EventClause::<MockEvent>::Mouse(MouseEventClause {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 0..10,
row: 0..10
})
.forward(&Event::Keyboard(KeyEvent::from(Key::Backspace))),
false
);
assert_eq!(
EventClause::<MockEvent>::Mouse(MouseEventClause {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 0..10,
row: 0..10
})
.forward(&Event::Tick),
false
);
}
#[test]
fn event_clause_window_resize_should_forward() {
assert_eq!(
EventClause::<MockEvent>::WindowResize.forward(&Event::WindowResize(0, 0)),
true
);
assert_eq!(
EventClause::<MockEvent>::WindowResize.forward(&Event::Tick),
false
);
}
#[test]
fn event_clause_tick_should_forward() {
assert_eq!(EventClause::<MockEvent>::Tick.forward(&Event::Tick), true);
assert_eq!(
EventClause::<MockEvent>::Tick.forward(&Event::WindowResize(0, 0)),
false
);
}
#[test]
fn event_clause_user_should_forward() {
assert_eq!(
EventClause::<MockEvent>::User(MockEvent::Foo).forward(&Event::User(MockEvent::Foo)),
true
);
assert_eq!(
EventClause::<MockEvent>::User(MockEvent::Foo).forward(&Event::Tick),
false
);
}
#[test]
fn event_clause_discriminant_should_forward() {
assert_eq!(
EventClause::<MockEvent>::Discriminant(MockEvent::Foo)
.forward(&Event::User(MockEvent::Foo)),
true
);
assert_eq!(
EventClause::<MockEvent>::Discriminant(MockEvent::Hello("foo".to_string()))
.forward(&Event::User(MockEvent::Hello("bar".to_string()))),
true
);
assert_eq!(
EventClause::<MockEvent>::User(MockEvent::Foo).forward(&Event::Tick),
false
);
}
#[test]
fn clause_always_should_forward() {
let component = MockFooInput::default();
let clause = SubClause::Always;
assert_eq!(
clause.forward(
|_: &MockComponentId, q| component.query(q),
|_: &MockComponentId| Some(component.state()),
|_: &MockComponentId| true
),
true
);
}
#[test]
fn clause_has_attribute_should_forward() {
let mut component = MockFooInput::default();
let clause = SubClause::HasAttrValue(
MockComponentId::InputBar,
Attribute::Focus,
AttrValue::Flag(true),
);
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
false
); component.attr(Attribute::Focus, AttrValue::Flag(true));
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
true
); }
#[test]
fn clause_has_state_should_forward() {
let mut component = MockFooInput::default();
let clause = SubClause::HasState(
MockComponentId::InputBar,
State::Single(StateValue::String(String::from("a"))),
);
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
false
); component.perform(Cmd::Type('a'));
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
true
); }
#[test]
fn clause_is_mounted_should_forward() {
let component = MockFooInput::default();
let clause = SubClause::IsMounted(MockComponentId::InputBar);
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|id| *id == MockComponentId::InputBar
),
true
);
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|id| *id == MockComponentId::InputFoo
),
false
);
}
#[test]
fn clause_not_should_forward() {
let mut component = MockFooInput::default();
let clause = SubClause::not(SubClause::HasAttrValue(
MockComponentId::InputBar,
Attribute::Focus,
AttrValue::Flag(true),
));
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
true
); component.attr(Attribute::Focus, AttrValue::Flag(true));
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
false
); }
#[test]
fn clause_and_should_forward() {
let mut component = MockFooInput::default();
let clause = SubClause::and(
SubClause::HasAttrValue(
MockComponentId::InputBar,
Attribute::Focus,
AttrValue::Flag(true),
),
SubClause::HasState(
MockComponentId::InputBar,
State::Single(StateValue::String(String::from("a"))),
),
);
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
false
); component.attr(Attribute::Focus, AttrValue::Flag(true));
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
false
); component.perform(Cmd::Type('a'));
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
true
); component.attr(Attribute::Focus, AttrValue::Flag(false));
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
false
); }
#[test]
fn clause_or_should_forward() {
let mut component = MockFooInput::default();
let clause = SubClause::or(
SubClause::HasAttrValue(
MockComponentId::InputBar,
Attribute::Focus,
AttrValue::Flag(true),
),
SubClause::HasState(
MockComponentId::InputBar,
State::Single(StateValue::String(String::from("a"))),
),
);
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
false
); component.attr(Attribute::Focus, AttrValue::Flag(true));
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
true
); component.perform(Cmd::Type('a'));
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
true
); component.attr(Attribute::Focus, AttrValue::Flag(false));
assert_eq!(
clause.forward(
|_, q| component.query(q),
|_| Some(component.state()),
|_| true
),
true
); }
#[test]
fn should_create_a_sub() {
let actual: Sub<MockComponentId, MockEvent> =
Sub::new(EventClause::Tick, SubClause::Always);
let expected: Sub<MockComponentId, MockEvent> =
Sub::new(EventClause::Tick, SubClause::Always);
assert_eq!(actual.0, expected.0);
assert_eq!(actual.1, expected.1);
}
#[test]
fn should_share_subs() {
let no_popup_clause = Arc::new(SubClause::<MockComponentId>::Not(Box::new(
SubClause::Always,
)));
let subscriptions: Vec<Sub<MockComponentId, NoUserEvent>> = vec![
Sub::new(
EventClause::Keyboard(KeyEvent::new(Key::Enter, KeyModifiers::NONE)),
no_popup_clause.clone(),
),
Sub::new(
EventClause::Keyboard(KeyEvent::new(Key::Esc, KeyModifiers::NONE)),
no_popup_clause.clone(),
),
Sub::new(
EventClause::Keyboard(KeyEvent::new(Key::Backspace, KeyModifiers::NONE)),
no_popup_clause.clone(),
),
Sub::new(EventClause::Tick, SubClause::Always),
];
assert!(Arc::ptr_eq(&no_popup_clause, &subscriptions[0].1));
assert!(Arc::ptr_eq(&no_popup_clause, &subscriptions[1].1));
assert!(Arc::ptr_eq(&no_popup_clause, &subscriptions[2].1));
assert!(!Arc::ptr_eq(&no_popup_clause, &subscriptions[3].1));
}
#[test]
fn should_allow_creation_without_arc() {
let sub1 = Sub::<MockComponentId, NoUserEvent>::new(EventClause::Tick, SubClause::Always);
let sub2 = Sub::<MockComponentId, NoUserEvent>::new(EventClause::Tick, SubClause::Always);
assert!(!Arc::ptr_eq(&sub1.1, &sub2.1));
}
}