• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

veeso / tui-realm / 5878955737

16 Aug 2023 12:47PM UTC coverage: 93.373% (-0.1%) from 93.51%
5878955737

push

github

veeso
added example for user events

2860 of 3063 relevant lines covered (93.37%)

8.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.83
/src/core/application.rs
1
//! ## Application
2✔
2
//!
3
//! This module exposes the Application, which is the core struct of tui-realm.
4

5
use super::{Subscription, View, WrappedComponent};
6
use crate::listener::{EventListener, EventListenerCfg, ListenerError};
7
use crate::tui::layout::Rect;
8
use crate::{AttrValue, Attribute, Event, Frame, Injector, State, Sub, SubEventClause, ViewError};
9

10
use std::hash::Hash;
11
use std::time::{Duration, Instant};
12
use thiserror::Error;
13

14
/// Result retuned by `Application`.
15
/// Ok depends on method
16
/// Err is always `ApplicationError`
17
pub type ApplicationResult<T> = Result<T, ApplicationError>;
18

19
/// The application defines a tui-realm application.
20
/// It will handle events, subscriptions and the view too.
21
/// It provides functions to interact with the view (mount, umount, query, etc), but also
22
/// the main function: `tick()`. See [tick](#tick)
23
pub struct Application<ComponentId, Msg, UserEvent>
24
where
25
    ComponentId: Eq + PartialEq + Clone + Hash,
26
    Msg: PartialEq,
27
    UserEvent: Eq + PartialEq + Clone + PartialOrd + Send + 'static,
28
{
29
    listener: EventListener<UserEvent>,
30
    subs: Vec<Subscription<ComponentId, UserEvent>>,
31
    /// If true, subs won't be processed. (Default: False)
32
    sub_lock: bool,
33
    view: View<ComponentId, Msg, UserEvent>,
34
}
35

36
impl<K, Msg, UserEvent> Application<K, Msg, UserEvent>
37
where
38
    K: Eq + PartialEq + Clone + Hash,
39
    Msg: PartialEq,
40
    UserEvent: Eq + PartialEq + Clone + PartialOrd + Send + 'static,
41
{
42
    /// Initialize a new `Application`.
43
    /// The event listener is immediately created and started.
44
    pub fn init(listener_cfg: EventListenerCfg<UserEvent>) -> Self {
15✔
45
        Self {
15✔
46
            listener: listener_cfg.start(),
15✔
47
            subs: Vec::new(),
15✔
48
            sub_lock: false,
49
            view: View::default(),
15✔
50
        }
51
    }
15✔
52

53
    /// Restart listener in case the previous listener has died or if you want to start a new one with a new configuration.
54
    ///
55
    /// > The listener has died if you received a `ApplicationError::Listener(ListenerError::ListenerDied))`
56
    pub fn restart_listener(
1✔
57
        &mut self,
58
        listener_cfg: EventListenerCfg<UserEvent>,
59
    ) -> ApplicationResult<()> {
60
        self.listener.stop()?;
1✔
61
        self.listener = listener_cfg.start();
1✔
62
        Ok(())
1✔
63
    }
1✔
64

65
    /// Lock ports. As long as Ports are locked, ports won't be polled.
66
    /// Locking ports will also prevent Tick events from being generated.
67
    pub fn lock_ports(&mut self) -> ApplicationResult<()> {
1✔
68
        self.listener.pause().map_err(ApplicationError::from)
1✔
69
    }
1✔
70

71
    /// Unlock Ports. Once called, the event listener will resume polling Ports.
72
    pub fn unlock_ports(&mut self) -> ApplicationResult<()> {
1✔
73
        self.listener.unpause().map_err(ApplicationError::from)
1✔
74
    }
1✔
75

76
    /// The tick method makes the application to run once.
77
    /// The workflow of the tick method is the following one:
78
    ///
79
    /// 1. The event listener is fetched according to the provided `PollStrategy`
80
    /// 2. All the received events are sent to the current active component
81
    /// 3. All the received events are forwarded to the subscribed components which satisfy the received events and conditions.
82
    /// 4. Returns messages to process
83
    ///
84
    /// As soon as function returns, you should call the `view()` method.
85
    ///
86
    /// > You can also call `view` from the `update()` if you need it
87
    pub fn tick(&mut self, strategy: PollStrategy) -> ApplicationResult<Vec<Msg>> {
13✔
88
        // Poll event listener
89
        let events = self.poll(strategy)?;
13✔
90
        // Forward to active element
91
        let mut messages: Vec<Msg> = events
26✔
92
            .iter()
93
            .filter_map(|x| self.forward_to_active_component(x.clone()))
37✔
94
            .collect();
95
        // Forward to subscriptions and extend vector
96
        if !self.sub_lock {
13✔
97
            messages.extend(self.forward_to_subscriptions(events));
12✔
98
        }
99
        Ok(messages)
13✔
100
    }
13✔
101

102
    // -- view bridge
103

104
    /// Add an injector to the view
105
    pub fn add_injector(&mut self, injector: Box<dyn Injector<K>>) {
1✔
106
        self.view.add_injector(injector);
1✔
107
    }
1✔
108

109
    /// Mount component to view and associate subscriptions for it.
110
    /// Returns error if component is already mounted
111
    /// NOTE: if subs vector contains duplicated, these will be discarded
112
    pub fn mount(
24✔
113
        &mut self,
114
        id: K,
115
        component: WrappedComponent<Msg, UserEvent>,
116
        subs: Vec<Sub<K, UserEvent>>,
117
    ) -> ApplicationResult<()> {
118
        // Mount
119
        self.view.mount(id.clone(), component)?;
24✔
120
        // Subscribe
121
        self.insert_subscriptions(id, subs);
23✔
122
        Ok(())
23✔
123
    }
24✔
124

125
    /// Umount component associated to `id` and remove ALL its SUBSCRIPTIONS.
126
    /// Returns Error if the component doesn't exist
127
    pub fn umount(&mut self, id: &K) -> ApplicationResult<()> {
3✔
128
        self.view.umount(id)?;
3✔
129
        self.unsubscribe_component(id);
2✔
130
        Ok(())
2✔
131
    }
3✔
132

133
    /// Remount provided component.
134
    /// Returns Err if failed to mount. It ignores whether the component already exists or not.
135
    /// If component had focus, focus is preserved
136
    pub fn remount(
1✔
137
        &mut self,
138
        id: K,
139
        component: WrappedComponent<Msg, UserEvent>,
140
        subs: Vec<Sub<K, UserEvent>>,
141
    ) -> ApplicationResult<()> {
142
        // remove subs
143
        self.unsubscribe_component(&id);
1✔
144
        // remount into view
145
        self.view.remount(id.clone(), component)?;
2✔
146
        // re-add subs
147
        self.insert_subscriptions(id, subs);
1✔
148
        Ok(())
1✔
149
    }
1✔
150

151
    /// Umount all components in the view and removed all associated subscriptions
152
    pub fn umount_all(&mut self) {
1✔
153
        self.view.umount_all();
1✔
154
        self.subs.clear();
1✔
155
    }
1✔
156

157
    /// Returns whether component `id` is mounted
158
    pub fn mounted(&self, id: &K) -> bool {
5✔
159
        self.view.mounted(id)
5✔
160
    }
5✔
161

162
    /// Render component called `id`
163
    pub fn view(&mut self, id: &K, f: &mut Frame, area: Rect) {
164
        self.view.view(id, f, area);
165
    }
166

167
    /// Query view component for a certain `AttrValue`
168
    /// Returns error if the component doesn't exist
169
    /// Returns None if the attribute doesn't exist.
170
    pub fn query(&self, id: &K, query: Attribute) -> ApplicationResult<Option<AttrValue>> {
2✔
171
        self.view.query(id, query).map_err(ApplicationError::from)
2✔
172
    }
2✔
173

174
    /// Set attribute for component `id`
175
    /// Returns error if the component doesn't exist
176
    pub fn attr(&mut self, id: &K, attr: Attribute, value: AttrValue) -> ApplicationResult<()> {
1✔
177
        self.view
1✔
178
            .attr(id, attr, value)
179
            .map_err(ApplicationError::from)
180
    }
1✔
181

182
    /// Get state for component `id`.
183
    /// Returns `Err` if component doesn't exist
184
    pub fn state(&self, id: &K) -> ApplicationResult<State> {
1✔
185
        self.view.state(id).map_err(ApplicationError::from)
1✔
186
    }
1✔
187

188
    /// Shorthand for `attr(id, Attribute::Focus(AttrValue::Flag(true)))`.
189
    /// It also sets the component as the current one having focus.
190
    /// Previous active component, if any, GETS PUSHED to the STACK
191
    /// Returns error: if component doesn't exist. Use `mounted()` to check if component exists
192
    ///
193
    /// > NOTE: users should always use this function to give focus to components.
194
    pub fn active(&mut self, id: &K) -> ApplicationResult<()> {
14✔
195
        self.view.active(id).map_err(ApplicationError::from)
14✔
196
    }
14✔
197

198
    /// Blur selected element AND DON'T PUSH CURRENT ACTIVE ELEMENT INTO THE STACK
199
    /// Shorthand for `attr(id, Attribute::Focus(AttrValue::Flag(false)))`.
200
    /// It also unset the current focus and give it to the first element in stack.
201
    /// Returns error: if no component has focus
202
    ///
203
    /// > NOTE: users should always use this function to remove focus to components.
204
    pub fn blur(&mut self) -> ApplicationResult<()> {
3✔
205
        self.view.blur().map_err(ApplicationError::from)
3✔
206
    }
3✔
207

208
    /// Get a reference to the id of the current active component in the view
209
    pub fn focus(&self) -> Option<&K> {
1✔
210
        self.view.focus()
1✔
211
    }
1✔
212

213
    // -- subs bridge
214

215
    /// Subscribe component to a certain event.
216
    /// Returns Error if the component doesn't exist or if the component is already subscribed to this event
217
    pub fn subscribe(&mut self, id: &K, sub: Sub<K, UserEvent>) -> ApplicationResult<()> {
3✔
218
        if !self.view.mounted(id) {
3✔
219
            return Err(ViewError::ComponentNotFound.into());
1✔
220
        }
221
        let subscription = Subscription::new(id.clone(), sub);
2✔
222
        if self.subscribed(id, subscription.event()) {
2✔
223
            return Err(ApplicationError::AlreadySubscribed);
1✔
224
        }
225
        self.subs.push(subscription);
1✔
226
        Ok(())
1✔
227
    }
3✔
228

229
    /// Unsubscribe a component from a certain event.
230
    /// Returns error if the component doesn't exist or if the component is not subscribed to this event
231
    pub fn unsubscribe(&mut self, id: &K, ev: SubEventClause<UserEvent>) -> ApplicationResult<()> {
2✔
232
        if !self.view.mounted(id) {
2✔
233
            return Err(ViewError::ComponentNotFound.into());
×
234
        }
235
        if !self.subscribed(id, &ev) {
2✔
236
            return Err(ApplicationError::NoSuchSubscription);
1✔
237
        }
238
        self.subs.retain(|s| s.target() != id && s.event() != &ev);
4✔
239
        Ok(())
1✔
240
    }
2✔
241

242
    /// Lock subscriptions. As long as the subscriptions are locked, events won't be propagated to
243
    /// subscriptions.
244
    pub fn lock_subs(&mut self) {
1✔
245
        self.sub_lock = true;
1✔
246
    }
1✔
247

248
    /// Unlock subscriptions. Application will now resume propagating events to subscriptions.
249
    pub fn unlock_subs(&mut self) {
1✔
250
        self.sub_lock = false;
1✔
251
    }
1✔
252

253
    // -- private
254

255
    /// remove all subscriptions for component
256
    fn unsubscribe_component(&mut self, id: &K) {
3✔
257
        self.subs.retain(|x| x.target() != id)
3✔
258
    }
3✔
259

260
    /// Returns whether component `id` is subscribed to event described by `clause`
261
    fn subscribed(&self, id: &K, clause: &SubEventClause<UserEvent>) -> bool {
21✔
262
        self.subs
42✔
263
            .iter()
264
            .any(|s| s.target() == id && s.event() == clause)
36✔
265
    }
21✔
266

267
    /// Insert subscriptions
268
    fn insert_subscriptions(&mut self, id: K, subs: Vec<Sub<K, UserEvent>>) {
24✔
269
        subs.into_iter().for_each(|x| {
41✔
270
            // Push only if not already subscribed
271
            let subscription = Subscription::new(id.clone(), x);
17✔
272
            if !self.subscribed(&id, subscription.event()) {
17✔
273
                self.subs.push(subscription);
16✔
274
            }
275
        });
17✔
276
    }
24✔
277

278
    /// Poll listener according to provided strategy
279
    fn poll(&mut self, strategy: PollStrategy) -> ApplicationResult<Vec<Event<UserEvent>>> {
13✔
280
        match strategy {
13✔
281
            PollStrategy::Once => self
1✔
282
                .poll_listener()
283
                .map(|x| x.map(|x| vec![x]).unwrap_or_default()),
2✔
284
            PollStrategy::TryFor(timeout) => self.poll_with_timeout(timeout),
1✔
285
            PollStrategy::UpTo(times) => self.poll_times(times),
11✔
286
        }
287
    }
13✔
288

289
    /// Poll event listener up to `t` times
290
    fn poll_times(&mut self, t: usize) -> ApplicationResult<Vec<Event<UserEvent>>> {
11✔
291
        let mut evs: Vec<Event<UserEvent>> = Vec::with_capacity(t);
11✔
292
        for _ in 0..t {
31✔
293
            match self.poll_listener() {
31✔
294
                Err(err) => return Err(err),
×
295
                Ok(None) => break,
296
                Ok(Some(ev)) => evs.push(ev),
20✔
297
            }
298
        }
31✔
299
        Ok(evs)
11✔
300
    }
11✔
301

302
    /// Poll event listener until `timeout` is elapsed
303
    fn poll_with_timeout(&mut self, timeout: Duration) -> ApplicationResult<Vec<Event<UserEvent>>> {
1✔
304
        let started = Instant::now();
1✔
305
        let mut evs: Vec<Event<UserEvent>> = Vec::new();
1✔
306
        while started.elapsed() < timeout {
31✔
307
            match self.poll_listener() {
30✔
308
                Err(err) => return Err(err),
×
309
                Ok(None) => continue,
310
                Ok(Some(ev)) => evs.push(ev),
3✔
311
            }
312
        }
30✔
313
        Ok(evs)
1✔
314
    }
1✔
315

316
    /// Poll event listener once
317
    fn poll_listener(&mut self) -> ApplicationResult<Option<Event<UserEvent>>> {
62✔
318
        self.listener.poll().map_err(ApplicationError::from)
62✔
319
    }
62✔
320

321
    /// Forward event to current active component, if any.
322
    fn forward_to_active_component(&mut self, ev: Event<UserEvent>) -> Option<Msg> {
24✔
323
        self.view
48✔
324
            .focus()
325
            .cloned()
326
            .and_then(|x| self.view.forward(&x, ev).ok().unwrap())
48✔
327
    }
24✔
328

329
    /// Forward events to subscriptions listening to the incoming event.
330
    fn forward_to_subscriptions(&mut self, events: Vec<Event<UserEvent>>) -> Vec<Msg> {
12✔
331
        let mut messages: Vec<Msg> = Vec::new();
12✔
332
        // NOTE: don't touch this code again and don't try to use iterators, cause it's not gonna work :)
333
        for ev in events.iter() {
34✔
334
            for sub in self.subs.iter() {
50✔
335
                // ! Active component must be different from sub !
336
                if self.view.has_focus(sub.target()) {
28✔
337
                    continue;
338
                }
339
                if !sub.forward(
20✔
340
                    ev,
341
                    |id, q| self.view.query(id, q).ok().flatten(),
23✔
342
                    |id| self.view.state(id).ok(),
22✔
343
                    |id| self.view.mounted(id),
24✔
344
                ) {
345
                    continue;
346
                }
347
                if let Some(msg) = self.view.forward(sub.target(), ev.clone()).ok().unwrap() {
6✔
348
                    messages.push(msg);
6✔
349
                }
350
            }
6✔
351
        }
352
        messages
12✔
353
    }
12✔
354
}
355

356
/// Poll strategy defines how to call `poll` on the event listener.
357
pub enum PollStrategy {
358
    /// The poll() function will be called once
359
    Once,
360
    /// The application will keep waiting for events for the provided duration
361
    TryFor(Duration),
362
    /// The poll() function will be called up to `n` times, until it will return `None`.
363
    UpTo(usize),
364
}
365

366
// -- error
367

368
/// Error variants returned by `Application`
369
#[derive(Debug, Error)]
×
370
pub enum ApplicationError {
371
    #[error("already subscribed")]
372
    AlreadySubscribed,
373
    #[error("listener error: {0}")]
×
374
    Listener(ListenerError),
×
375
    #[error("no such subscription")]
376
    NoSuchSubscription,
377
    #[error("view error: {0}")]
×
378
    View(ViewError),
×
379
}
380

381
impl From<ListenerError> for ApplicationError {
382
    fn from(e: ListenerError) -> Self {
×
383
        Self::Listener(e)
×
384
    }
×
385
}
386

387
impl From<ViewError> for ApplicationError {
388
    fn from(e: ViewError) -> Self {
5✔
389
        Self::View(e)
5✔
390
    }
5✔
391
}
392

393
#[cfg(test)]
394
mod test {
395

396
    use super::*;
397
    use crate::event::{Key, KeyEvent};
398
    use crate::mock::{
399
        MockBarInput, MockComponentId, MockEvent, MockFooInput, MockInjector, MockMsg, MockPoll,
400
    };
401
    use crate::{StateValue, SubClause};
402

403
    use pretty_assertions::assert_eq;
404
    use std::time::Duration;
405

406
    #[test]
407
    fn should_initialize_application() {
2✔
408
        let application: Application<MockComponentId, MockMsg, MockEvent> =
409
            Application::init(listener_config());
1✔
410
        assert!(application.subs.is_empty());
1✔
411
        assert_eq!(application.view.mounted(&MockComponentId::InputFoo), false);
1✔
412
        assert_eq!(application.sub_lock, false);
1✔
413
    }
2✔
414

415
    #[test]
416
    fn should_restart_listener() {
2✔
417
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
418
            Application::init(listener_config());
1✔
419
        assert!(application.restart_listener(listener_config()).is_ok());
1✔
420
    }
2✔
421

422
    #[test]
423
    fn should_manipulate_components() {
2✔
424
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
425
            Application::init(listener_config());
1✔
426
        // Mount
427
        assert!(application
1✔
428
            .mount(
429
                MockComponentId::InputFoo,
1✔
430
                Box::new(MockFooInput::default()),
1✔
431
                vec![]
1✔
432
            )
433
            .is_ok());
434
        // Remount with mount
435
        assert!(application
1✔
436
            .mount(
437
                MockComponentId::InputFoo,
1✔
438
                Box::new(MockFooInput::default()),
1✔
439
                vec![]
1✔
440
            )
441
            .is_err());
442
        assert!(application.active(&MockComponentId::InputFoo).is_ok());
1✔
443
        assert_eq!(application.focus().unwrap(), &MockComponentId::InputFoo);
1✔
444
        // Remount
445
        assert!(application
1✔
446
            .remount(
447
                MockComponentId::InputFoo,
1✔
448
                Box::new(MockFooInput::default()),
1✔
449
                vec![]
1✔
450
            )
451
            .is_ok());
452
        assert!(application.view.has_focus(&MockComponentId::InputFoo));
1✔
453
        // Mount bar
454
        assert!(application
1✔
455
            .mount(
456
                MockComponentId::InputBar,
1✔
457
                Box::new(MockBarInput::default()),
1✔
458
                vec![]
1✔
459
            )
460
            .is_ok());
461
        // Mounted
462
        assert!(application.mounted(&MockComponentId::InputFoo));
1✔
463
        assert!(application.mounted(&MockComponentId::InputBar));
1✔
464
        assert_eq!(application.mounted(&MockComponentId::InputOmar), false);
1✔
465
        // Attribute and Query
466
        assert!(application
1✔
467
            .query(&MockComponentId::InputFoo, Attribute::InputLength)
1✔
468
            .ok()
469
            .unwrap()
470
            .is_none());
471
        assert!(application
1✔
472
            .attr(
473
                &MockComponentId::InputFoo,
474
                Attribute::InputLength,
1✔
475
                AttrValue::Length(8)
1✔
476
            )
477
            .is_ok());
478
        assert_eq!(
1✔
479
            application
1✔
480
                .query(&MockComponentId::InputFoo, Attribute::InputLength)
1✔
481
                .ok()
482
                .unwrap()
483
                .unwrap(),
484
            AttrValue::Length(8)
485
        );
486
        // State
487
        assert_eq!(
1✔
488
            application.state(&MockComponentId::InputFoo).ok().unwrap(),
1✔
489
            State::One(StateValue::String(String::default()))
1✔
490
        );
491
        // Active / blur
492
        assert!(application.active(&MockComponentId::InputFoo).is_ok());
1✔
493
        assert!(application.active(&MockComponentId::InputBar).is_ok());
1✔
494
        assert!(application.active(&MockComponentId::InputOmar).is_err());
1✔
495
        assert!(application.blur().is_ok());
1✔
496
        assert!(application.blur().is_ok());
1✔
497
        // no focus
498
        assert!(application.blur().is_err());
1✔
499
        // Umount
500
        assert!(application.umount(&MockComponentId::InputFoo).is_ok());
1✔
501
        assert!(application.umount(&MockComponentId::InputFoo).is_err());
1✔
502
        assert!(application.umount(&MockComponentId::InputBar).is_ok());
1✔
503
    }
2✔
504

505
    #[test]
506
    fn should_subscribe_components() {
2✔
507
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
508
            Application::init(listener_config());
1✔
509
        assert!(application
1✔
510
            .mount(
511
                MockComponentId::InputFoo,
1✔
512
                Box::new(MockFooInput::default()),
1✔
513
                vec![
2✔
514
                    Sub::new(SubEventClause::Tick, SubClause::Always),
1✔
515
                    Sub::new(
1✔
516
                        SubEventClause::Tick,
1✔
517
                        SubClause::HasAttrValue(
1✔
518
                            MockComponentId::InputFoo,
1✔
519
                            Attribute::InputLength,
1✔
520
                            AttrValue::Length(8)
1✔
521
                        )
522
                    ), // NOTE: This event will be ignored
523
                    Sub::new(
1✔
524
                        SubEventClause::User(MockEvent::Bar),
1✔
525
                        SubClause::HasAttrValue(
1✔
526
                            MockComponentId::InputFoo,
1✔
527
                            Attribute::Focus,
1✔
528
                            AttrValue::Flag(true)
1✔
529
                        )
530
                    )
531
                ]
532
            )
533
            .is_ok());
534
        assert_eq!(application.subs.len(), 2);
1✔
535
        // Subscribe for another event
536
        assert!(application
1✔
537
            .subscribe(
538
                &MockComponentId::InputFoo,
539
                Sub::new(
1✔
540
                    SubEventClause::User(MockEvent::Foo),
1✔
541
                    SubClause::HasAttrValue(
1✔
542
                        MockComponentId::InputFoo,
1✔
543
                        Attribute::Focus,
1✔
544
                        AttrValue::Flag(false)
1✔
545
                    )
546
                )
547
            )
548
            .is_ok());
549
        assert_eq!(application.subs.len(), 3);
1✔
550
        // Try to re-subscribe
551
        assert!(application
1✔
552
            .subscribe(
553
                &MockComponentId::InputFoo,
554
                Sub::new(
1✔
555
                    SubEventClause::User(MockEvent::Foo),
1✔
556
                    SubClause::HasAttrValue(
1✔
557
                        MockComponentId::InputFoo,
1✔
558
                        Attribute::Focus,
1✔
559
                        AttrValue::Flag(false)
1✔
560
                    )
561
                )
562
            )
563
            .is_err());
564
        // Subscribe for unexisting component
565
        assert!(application
1✔
566
            .subscribe(
567
                &MockComponentId::InputBar,
568
                Sub::new(
1✔
569
                    SubEventClause::User(MockEvent::Foo),
1✔
570
                    SubClause::HasAttrValue(
1✔
571
                        MockComponentId::InputBar,
1✔
572
                        Attribute::Focus,
1✔
573
                        AttrValue::Flag(false)
1✔
574
                    )
575
                )
576
            )
577
            .is_err());
578
        // Unsubscribe element
579
        assert!(application
1✔
580
            .unsubscribe(
581
                &MockComponentId::InputFoo,
582
                SubEventClause::User(MockEvent::Foo)
1✔
583
            )
584
            .is_ok());
585
        // Unsubcribe twice
586
        assert!(application
1✔
587
            .unsubscribe(
588
                &MockComponentId::InputFoo,
589
                SubEventClause::User(MockEvent::Foo)
1✔
590
            )
591
            .is_err());
592
    }
2✔
593

594
    #[test]
595
    fn should_umount_all() {
2✔
596
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
597
            Application::init(listener_config());
1✔
598
        assert!(application
1✔
599
            .mount(
600
                MockComponentId::InputFoo,
1✔
601
                Box::new(MockFooInput::default()),
1✔
602
                vec![
2✔
603
                    Sub::new(SubEventClause::Tick, SubClause::Always),
1✔
604
                    Sub::new(
1✔
605
                        SubEventClause::User(MockEvent::Bar),
1✔
606
                        SubClause::HasAttrValue(
1✔
607
                            MockComponentId::InputFoo,
1✔
608
                            Attribute::Focus,
1✔
609
                            AttrValue::Flag(true)
1✔
610
                        )
611
                    )
612
                ]
613
            )
614
            .is_ok());
615
        assert!(application
1✔
616
            .mount(
617
                MockComponentId::InputBar,
1✔
618
                Box::new(MockFooInput::default()),
1✔
619
                vec![Sub::new(SubEventClause::Any, SubClause::Always)]
1✔
620
            )
621
            .is_ok());
622
        assert_eq!(application.subs.len(), 3);
1✔
623
        // Let's umount all
624
        application.umount_all();
1✔
625
        assert_eq!(application.mounted(&MockComponentId::InputFoo), false);
1✔
626
        assert_eq!(application.mounted(&MockComponentId::InputBar), false);
1✔
627
        assert!(application.subs.is_empty());
1✔
628
    }
2✔
629

630
    #[test]
631
    fn should_do_tick() {
2✔
632
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
633
            Application::init(listener_config_with_tick(Duration::from_secs(60)));
1✔
634
        // Mount foo and bar
635
        assert!(application
1✔
636
            .mount(
637
                MockComponentId::InputFoo,
1✔
638
                Box::new(MockFooInput::default()),
1✔
639
                vec![]
1✔
640
            )
641
            .is_ok());
642
        assert!(application
1✔
643
            .mount(
644
                MockComponentId::InputBar,
1✔
645
                Box::new(MockBarInput::default()),
1✔
646
                vec![
2✔
647
                    Sub::new(SubEventClause::Tick, SubClause::Always),
1✔
648
                    Sub::new(
1✔
649
                        // NOTE: won't be thrown, since requires focus
650
                        SubEventClause::Keyboard(KeyEvent::from(Key::Enter)),
1✔
651
                        SubClause::HasAttrValue(
1✔
652
                            MockComponentId::InputBar,
1✔
653
                            Attribute::Focus,
1✔
654
                            AttrValue::Flag(true)
1✔
655
                        )
656
                    )
657
                ]
658
            )
659
            .is_ok());
660
        // Active FOO
661
        assert!(application.active(&MockComponentId::InputFoo).is_ok());
1✔
662
        /*
663
         * Here we should:
664
         *
665
         * - receive an Enter from MockPoll, sent to FOO and will return a `FooSubmit`
666
         * - receive a Tick from MockPoll, sent to FOO, but won't return a msg
667
         * - the Tick will be sent also to BAR since is subscribed and will return a `BarTick`
668
         */
669
        assert_eq!(
1✔
670
            application
1✔
671
                .tick(PollStrategy::UpTo(5))
1✔
672
                .ok()
673
                .unwrap()
674
                .as_slice(),
675
            &[MockMsg::FooSubmit(String::from("")), MockMsg::BarTick]
1✔
676
        );
677
        // Active BAR
678
        assert!(application.active(&MockComponentId::InputBar).is_ok());
1✔
679
        // Wait 200ms (wait for poll)
680
        std::thread::sleep(Duration::from_millis(100));
1✔
681
        /*
682
         * Here we should:
683
         *
684
         * - receive an Enter from MockPoll, sent to BAR and will return a `BarSubmit`
685
         */
686
        assert_eq!(
1✔
687
            application
1✔
688
                .tick(PollStrategy::Once)
1✔
689
                .ok()
690
                .unwrap()
691
                .as_slice(),
692
            &[MockMsg::BarSubmit(String::from(""))]
1✔
693
        );
694
        // Let's try TryFor strategy
695
        let events = application
1✔
696
            .tick(PollStrategy::TryFor(Duration::from_millis(300)))
1✔
697
            .ok()
698
            .unwrap();
699
        assert!(events.len() >= 2);
1✔
700
    }
2✔
701

702
    #[test]
703
    fn should_not_propagate_event_when_subs_are_locked() {
2✔
704
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
705
            Application::init(listener_config_with_tick(Duration::from_secs(60)));
1✔
706
        // Mount foo and bar
707
        assert!(application
1✔
708
            .mount(
709
                MockComponentId::InputFoo,
1✔
710
                Box::new(MockFooInput::default()),
1✔
711
                vec![]
1✔
712
            )
713
            .is_ok());
714
        assert!(application
1✔
715
            .mount(
716
                MockComponentId::InputBar,
1✔
717
                Box::new(MockBarInput::default()),
1✔
718
                vec![
2✔
719
                    Sub::new(SubEventClause::Tick, SubClause::Always),
1✔
720
                    Sub::new(
1✔
721
                        // NOTE: won't be thrown, since requires focus
722
                        SubEventClause::Keyboard(KeyEvent::from(Key::Enter)),
1✔
723
                        SubClause::HasAttrValue(
1✔
724
                            MockComponentId::InputBar,
1✔
725
                            Attribute::Focus,
1✔
726
                            AttrValue::Flag(true)
1✔
727
                        )
728
                    )
729
                ]
730
            )
731
            .is_ok());
732
        // Active FOO
733
        assert!(application.active(&MockComponentId::InputFoo).is_ok());
1✔
734
        // lock subs
735
        application.lock_subs();
1✔
736
        assert_eq!(application.sub_lock, true);
1✔
737
        assert_eq!(
1✔
738
            application
1✔
739
                .tick(PollStrategy::UpTo(5))
1✔
740
                .ok()
741
                .unwrap()
742
                .as_slice(),
743
            &[MockMsg::FooSubmit(String::from(""))]
1✔
744
        );
745
        // unlock subs
746
        application.unlock_subs();
1✔
747
        assert_eq!(application.sub_lock, false);
1✔
748
    }
2✔
749

750
    #[test]
751
    fn should_not_propagate_events_if_has_attr_cond_is_not_satisfied() {
2✔
752
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
753
            Application::init(listener_config_with_tick(Duration::from_secs(60)));
1✔
754
        // Mount foo and bar
755
        assert!(application
1✔
756
            .mount(
757
                MockComponentId::InputFoo,
1✔
758
                Box::new(MockFooInput::default()),
1✔
759
                vec![]
1✔
760
            )
761
            .is_ok());
762
        assert!(application
1✔
763
            .mount(
764
                MockComponentId::InputBar,
1✔
765
                Box::new(MockBarInput::default()),
1✔
766
                vec![Sub::new(
2✔
767
                    // NOTE: won't be thrown, since requires focus
768
                    SubEventClause::Tick,
1✔
769
                    SubClause::HasAttrValue(
1✔
770
                        MockComponentId::InputBar,
1✔
771
                        Attribute::Focus,
1✔
772
                        AttrValue::Flag(true)
1✔
773
                    )
774
                )]
775
            )
776
            .is_ok());
777
        // Active FOO
778
        assert!(application.active(&MockComponentId::InputFoo).is_ok());
1✔
779
        assert_eq!(
1✔
780
            application
1✔
781
                .tick(PollStrategy::UpTo(5))
1✔
782
                .ok()
783
                .unwrap()
784
                .as_slice(),
785
            &[MockMsg::FooSubmit(String::from(""))]
1✔
786
        );
787
    }
2✔
788

789
    #[test]
790
    fn should_propagate_events_if_has_attr_cond_is_satisfied() {
2✔
791
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
792
            Application::init(listener_config_with_tick(Duration::from_secs(60)));
1✔
793
        // Mount foo and bar
794
        assert!(application
1✔
795
            .mount(
796
                MockComponentId::InputFoo,
1✔
797
                Box::new(MockFooInput::default()),
1✔
798
                vec![]
1✔
799
            )
800
            .is_ok());
801
        assert!(application
1✔
802
            .mount(
803
                MockComponentId::InputBar,
1✔
804
                Box::new(MockBarInput::default()),
1✔
805
                vec![Sub::new(
2✔
806
                    SubEventClause::Tick,
1✔
807
                    SubClause::HasAttrValue(
1✔
808
                        MockComponentId::InputFoo,
1✔
809
                        Attribute::Focus,
1✔
810
                        AttrValue::Flag(true)
1✔
811
                    )
812
                )]
813
            )
814
            .is_ok());
815
        // Active FOO
816
        assert!(application.active(&MockComponentId::InputFoo).is_ok());
1✔
817
        assert_eq!(
1✔
818
            application
1✔
819
                .tick(PollStrategy::UpTo(5))
1✔
820
                .ok()
821
                .unwrap()
822
                .as_slice(),
823
            &[MockMsg::FooSubmit(String::from("")), MockMsg::BarTick]
1✔
824
        );
825
    }
2✔
826

827
    #[test]
828
    fn should_not_propagate_events_if_has_state_cond_is_not_satisfied() {
2✔
829
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
830
            Application::init(listener_config_with_tick(Duration::from_secs(60)));
1✔
831
        // Mount foo and bar
832
        assert!(application
1✔
833
            .mount(
834
                MockComponentId::InputFoo,
1✔
835
                Box::new(MockFooInput::default()),
1✔
836
                vec![]
1✔
837
            )
838
            .is_ok());
839
        assert!(application
1✔
840
            .mount(
841
                MockComponentId::InputBar,
1✔
842
                Box::new(MockBarInput::default()),
1✔
843
                vec![Sub::new(
2✔
844
                    SubEventClause::Tick,
1✔
845
                    SubClause::HasState(MockComponentId::InputFoo, State::None)
1✔
846
                )]
847
            )
848
            .is_ok());
849
        // Active FOO
850
        assert!(application.active(&MockComponentId::InputFoo).is_ok());
1✔
851
        assert_eq!(
1✔
852
            application
1✔
853
                .tick(PollStrategy::UpTo(5))
1✔
854
                .ok()
855
                .unwrap()
856
                .as_slice(),
857
            &[MockMsg::FooSubmit(String::from(""))]
1✔
858
        );
859
    }
2✔
860

861
    #[test]
862
    fn should_propagate_events_if_has_state_cond_is_satisfied() {
2✔
863
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
864
            Application::init(listener_config_with_tick(Duration::from_secs(60)));
1✔
865
        // Mount foo and bar
866
        assert!(application
1✔
867
            .mount(
868
                MockComponentId::InputFoo,
1✔
869
                Box::new(MockFooInput::default()),
1✔
870
                vec![]
1✔
871
            )
872
            .is_ok());
873
        assert!(application
1✔
874
            .mount(
875
                MockComponentId::InputBar,
1✔
876
                Box::new(MockBarInput::default()),
1✔
877
                vec![Sub::new(
2✔
878
                    SubEventClause::Tick,
1✔
879
                    SubClause::HasState(
1✔
880
                        MockComponentId::InputFoo,
1✔
881
                        State::One(StateValue::String(String::new()))
1✔
882
                    )
883
                )]
884
            )
885
            .is_ok());
886
        // Active FOO
887
        assert!(application.active(&MockComponentId::InputFoo).is_ok());
1✔
888
        // No event should be generated
889
        assert_eq!(
1✔
890
            application
1✔
891
                .tick(PollStrategy::UpTo(5))
1✔
892
                .ok()
893
                .unwrap()
894
                .as_slice(),
895
            &[MockMsg::FooSubmit(String::from("")), MockMsg::BarTick]
1✔
896
        );
897
    }
2✔
898

899
    #[test]
900
    fn should_not_propagate_events_if_is_mounted_cond_is_not_satisfied() {
2✔
901
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
902
            Application::init(listener_config_with_tick(Duration::from_secs(60)));
1✔
903
        // Mount foo and bar
904
        assert!(application
1✔
905
            .mount(
906
                MockComponentId::InputFoo,
1✔
907
                Box::new(MockFooInput::default()),
1✔
908
                vec![]
1✔
909
            )
910
            .is_ok());
911
        assert!(application
1✔
912
            .mount(
913
                MockComponentId::InputBar,
1✔
914
                Box::new(MockBarInput::default()),
1✔
915
                vec![Sub::new(
2✔
916
                    SubEventClause::Tick,
1✔
917
                    SubClause::IsMounted(MockComponentId::InputOmar)
1✔
918
                )]
919
            )
920
            .is_ok());
921
        // Active FOO
922
        assert!(application.active(&MockComponentId::InputFoo).is_ok());
1✔
923
        assert_eq!(
1✔
924
            application
1✔
925
                .tick(PollStrategy::UpTo(5))
1✔
926
                .ok()
927
                .unwrap()
928
                .as_slice(),
929
            &[MockMsg::FooSubmit(String::from(""))]
1✔
930
        );
931
    }
2✔
932

933
    #[test]
934
    fn should_propagate_events_if_is_mounted_cond_is_not_satisfied() {
2✔
935
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
936
            Application::init(listener_config_with_tick(Duration::from_secs(60)));
1✔
937
        // Mount foo and bar
938
        assert!(application
1✔
939
            .mount(
940
                MockComponentId::InputFoo,
1✔
941
                Box::new(MockFooInput::default()),
1✔
942
                vec![]
1✔
943
            )
944
            .is_ok());
945
        assert!(application
1✔
946
            .mount(
947
                MockComponentId::InputBar,
1✔
948
                Box::new(MockBarInput::default()),
1✔
949
                vec![Sub::new(
2✔
950
                    SubEventClause::Tick,
1✔
951
                    SubClause::IsMounted(MockComponentId::InputFoo)
1✔
952
                )]
953
            )
954
            .is_ok());
955
        // Active FOO
956
        assert!(application.active(&MockComponentId::InputFoo).is_ok());
1✔
957
        assert_eq!(
1✔
958
            application
1✔
959
                .tick(PollStrategy::UpTo(5))
1✔
960
                .ok()
961
                .unwrap()
962
                .as_slice(),
963
            &[MockMsg::FooSubmit(String::from("")), MockMsg::BarTick]
1✔
964
        );
965
    }
2✔
966

967
    #[test]
968
    fn should_lock_ports() {
2✔
969
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
970
            Application::init(listener_config_with_tick(Duration::from_millis(500)));
1✔
971
        // Mount foo and bar
972
        assert!(application
1✔
973
            .mount(
974
                MockComponentId::InputFoo,
1✔
975
                Box::new(MockFooInput::default()),
1✔
976
                vec![]
1✔
977
            )
978
            .is_ok());
979
        assert!(application
1✔
980
            .mount(
981
                MockComponentId::InputBar,
1✔
982
                Box::new(MockBarInput::default()),
1✔
983
                vec![Sub::new(
2✔
984
                    SubEventClause::Tick,
1✔
985
                    SubClause::IsMounted(MockComponentId::InputFoo)
1✔
986
                )]
987
            )
988
            .is_ok());
989
        // Active FOO
990
        assert!(application.active(&MockComponentId::InputFoo).is_ok());
1✔
991
        assert_eq!(
1✔
992
            application
1✔
993
                .tick(PollStrategy::UpTo(5))
1✔
994
                .ok()
995
                .unwrap()
996
                .as_slice(),
997
            &[MockMsg::FooSubmit(String::from("")), MockMsg::BarTick]
1✔
998
        );
999
        // Lock ports
1000
        assert!(application.lock_ports().is_ok());
1✔
1001
        // Wait 1 sec
1002
        std::thread::sleep(Duration::from_millis(1000));
1✔
1003
        // Tick ( No tick event )
1004
        assert_eq!(
1✔
1005
            application
1✔
1006
                .tick(PollStrategy::UpTo(5))
1✔
1007
                .ok()
1008
                .unwrap()
1009
                .as_slice(),
1010
            &[]
1011
        );
1012
        // Unlock ports
1013
        assert!(application.unlock_ports().is_ok());
1✔
1014
        // Wait 100 ms
1015
        std::thread::sleep(Duration::from_millis(50));
1✔
1016
        // Tick
1017
        assert_eq!(
1✔
1018
            application
1✔
1019
                .tick(PollStrategy::UpTo(5))
1✔
1020
                .ok()
1021
                .unwrap()
1022
                .as_slice(),
1023
            &[MockMsg::FooSubmit(String::from("")), MockMsg::BarTick]
1✔
1024
        );
1025
    }
2✔
1026

1027
    #[test]
1028
    fn application_should_add_injectors() {
2✔
1029
        let mut application: Application<MockComponentId, MockMsg, MockEvent> =
1030
            Application::init(listener_config_with_tick(Duration::from_millis(500)));
1✔
1031
        application.add_injector(Box::new(MockInjector::default()));
1✔
1032
    }
2✔
1033

1034
    fn listener_config() -> EventListenerCfg<MockEvent> {
16✔
1035
        EventListenerCfg::default().port(
32✔
1036
            Box::new(MockPoll::<MockEvent>::default()),
16✔
1037
            Duration::from_millis(100),
16✔
1038
        )
1039
    }
16✔
1040

1041
    fn listener_config_with_tick(tick: Duration) -> EventListenerCfg<MockEvent> {
10✔
1042
        listener_config().tick_interval(tick)
10✔
1043
    }
10✔
1044
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc