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

veeso / tui-realm-stdlib / 8154353584

05 Mar 2024 10:00AM UTC coverage: 73.477% (+0.02%) from 73.457%
8154353584

push

github

web-flow
fix: compatibility with ratatui 0.26 (#21)

1 of 22 new or added lines in 5 files covered. (4.55%)

1 existing line in 1 file now uncovered.

2762 of 3759 relevant lines covered (73.48%)

1.99 hits per line

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

68.01
/src/components/select.rs
1
//! ## Select
2
//!
3
//! `Select` represents a select field, like in HTML. The size for the component must be 3 (border + selected) + the quantity of rows
4
//! you want to display other options when opened (at least 3)
5

6
use tuirealm::command::{Cmd, CmdResult, Direction};
7
use tuirealm::props::{
8
    Alignment, AttrValue, Attribute, BorderSides, Borders, Color, PropPayload, PropValue, Props,
9
    Style, TextModifiers,
10
};
11
#[cfg(feature = "ratatui")]
12
use tuirealm::tui::text::Line as Spans;
13
#[cfg(feature = "tui")]
14
use tuirealm::tui::text::Spans;
15
use tuirealm::tui::{
16
    layout::{Constraint, Direction as LayoutDirection, Layout, Rect},
17
    widgets::{Block, List, ListItem, ListState, Paragraph},
18
};
19
use tuirealm::{Frame, MockComponent, State, StateValue};
20

21
// -- states
22

23
/// ## SelectStates
24
///
25
/// Component states
26
#[derive(Default)]
4✔
27
pub struct SelectStates {
28
    /// Available choices
29
    pub choices: Vec<String>,
2✔
30
    /// Currently selected choice
31
    pub selected: usize,
2✔
32
    /// Choice selected before opening the tab
33
    pub previously_selected: usize,
2✔
34
    pub tab_open: bool,
2✔
35
}
36

37
impl SelectStates {
38
    /// ### next_choice
39
    ///
40
    /// Move choice index to next choice
41
    pub fn next_choice(&mut self, rewind: bool) {
11✔
42
        if self.tab_open {
11✔
43
            if rewind && self.selected + 1 >= self.choices.len() {
9✔
44
                self.selected = 0;
1✔
45
            } else if self.selected + 1 < self.choices.len() {
8✔
46
                self.selected += 1;
6✔
47
            }
48
        }
49
    }
11✔
50

51
    /// ### prev_choice
52
    ///
53
    /// Move choice index to previous choice
54
    pub fn prev_choice(&mut self, rewind: bool) {
11✔
55
        if self.tab_open {
11✔
56
            if rewind && self.selected == 0 && !self.choices.is_empty() {
8✔
57
                self.selected = self.choices.len() - 1;
1✔
58
            } else if self.selected > 0 {
7✔
59
                self.selected -= 1;
6✔
60
            }
61
        }
62
    }
11✔
63

64
    /// ### set_choices
65
    ///
66
    /// Set SelectStates choices from a vector of str
67
    /// In addition resets current selection and keep index if possible or set it to the first value
68
    /// available
69
    pub fn set_choices(&mut self, choices: &[String]) {
5✔
70
        self.choices = choices.to_vec();
5✔
71
        // Keep index if possible
72
        if self.selected >= self.choices.len() {
7✔
73
            self.selected = match self.choices.len() {
4✔
74
                0 => 0,
1✔
75
                l => l - 1,
1✔
76
            };
77
        }
78
    }
5✔
79

80
    pub fn select(&mut self, i: usize) {
3✔
81
        if i < self.choices.len() {
3✔
82
            self.selected = i;
3✔
83
        }
84
    }
3✔
85

86
    /// ### close_tab
87
    ///
88
    /// Close tab
89
    pub fn close_tab(&mut self) {
6✔
90
        self.tab_open = false;
6✔
91
    }
6✔
92

93
    /// ### open_tab
94
    ///
95
    /// Open tab
96
    pub fn open_tab(&mut self) {
6✔
97
        self.previously_selected = self.selected;
6✔
98
        self.tab_open = true;
6✔
99
    }
6✔
100

101
    /// Cancel tab open
102
    pub fn cancel_tab(&mut self) {
1✔
103
        self.close_tab();
1✔
104
        self.selected = self.previously_selected;
1✔
105
    }
1✔
106

107
    /// ### is_tab_open
108
    ///
109
    /// Returns whether the tab is open
110
    pub fn is_tab_open(&self) -> bool {
23✔
111
        self.tab_open
23✔
112
    }
23✔
113
}
114

115
// -- component
116

117
#[derive(Default)]
2✔
118
pub struct Select {
119
    props: Props,
1✔
120
    pub states: SelectStates,
1✔
121
    hg_str: Option<String>, // CRAP CRAP CRAP
1✔
122
}
123

124
impl Select {
125
    pub fn foreground(mut self, fg: Color) -> Self {
1✔
126
        self.attr(Attribute::Foreground, AttrValue::Color(fg));
1✔
127
        self
1✔
128
    }
1✔
129

130
    pub fn background(mut self, bg: Color) -> Self {
1✔
131
        self.attr(Attribute::Background, AttrValue::Color(bg));
1✔
132
        self
1✔
133
    }
1✔
134

135
    pub fn borders(mut self, b: Borders) -> Self {
1✔
136
        self.attr(Attribute::Borders, AttrValue::Borders(b));
1✔
137
        self
1✔
138
    }
1✔
139

140
    pub fn title<S: AsRef<str>>(mut self, t: S, a: Alignment) -> Self {
1✔
141
        self.attr(
1✔
142
            Attribute::Title,
1✔
143
            AttrValue::Title((t.as_ref().to_string(), a)),
1✔
144
        );
145
        self
1✔
146
    }
1✔
147

148
    pub fn highlighted_str<S: AsRef<str>>(mut self, s: S) -> Self {
1✔
149
        self.attr(
1✔
150
            Attribute::HighlightedStr,
1✔
151
            AttrValue::String(s.as_ref().to_string()),
1✔
152
        );
153
        self
1✔
154
    }
1✔
155

156
    pub fn highlighted_color(mut self, c: Color) -> Self {
1✔
157
        self.attr(Attribute::HighlightedColor, AttrValue::Color(c));
1✔
158
        self
1✔
159
    }
1✔
160

161
    pub fn inactive(mut self, s: Style) -> Self {
×
162
        self.attr(Attribute::FocusStyle, AttrValue::Style(s));
×
163
        self
×
164
    }
×
165

166
    pub fn rewind(mut self, r: bool) -> Self {
1✔
167
        self.attr(Attribute::Rewind, AttrValue::Flag(r));
1✔
168
        self
1✔
169
    }
1✔
170

171
    pub fn choices<S: AsRef<str>>(mut self, choices: &[S]) -> Self {
1✔
172
        self.attr(
1✔
173
            Attribute::Content,
1✔
174
            AttrValue::Payload(PropPayload::Vec(
1✔
175
                choices
1✔
176
                    .iter()
177
                    .map(|x| PropValue::Str(x.as_ref().to_string()))
3✔
178
                    .collect(),
179
            )),
180
        );
181
        self
1✔
182
    }
1✔
183

184
    pub fn value(mut self, i: usize) -> Self {
1✔
185
        // Set state
186
        self.attr(
1✔
187
            Attribute::Value,
1✔
188
            AttrValue::Payload(PropPayload::One(PropValue::Usize(i))),
1✔
189
        );
190
        self
1✔
191
    }
1✔
192

193
    /// ### render_open_tab
194
    ///
195
    /// Render component when tab is open
196
    fn render_open_tab(&mut self, render: &mut Frame, area: Rect) {
×
197
        // Make choices
198
        let choices: Vec<ListItem> = self
×
199
            .states
200
            .choices
201
            .iter()
202
            .map(|x| ListItem::new(Spans::from(x.clone())))
×
203
            .collect();
204
        let foreground = self
×
205
            .props
206
            .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
×
207
            .unwrap_color();
208
        let background = self
×
209
            .props
210
            .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
×
211
            .unwrap_color();
212
        let hg: Color = self
×
213
            .props
214
            .get_or(Attribute::HighlightedColor, AttrValue::Color(foreground))
×
215
            .unwrap_color();
216
        // Prepare layout
217
        let chunks = Layout::default()
×
218
            .direction(LayoutDirection::Vertical)
×
219
            .margin(0)
220
            .constraints([Constraint::Length(2), Constraint::Min(1)].as_ref())
×
221
            .split(area);
×
222
        // Render like "closed" tab in chunk 0
223
        let selected_text: String = match self.states.choices.get(self.states.selected) {
×
224
            None => String::default(),
×
225
            Some(s) => s.clone(),
×
226
        };
227
        let borders = self
×
228
            .props
229
            .get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
×
230
            .unwrap_borders();
231
        let block: Block = Block::default()
×
232
            .borders(BorderSides::LEFT | BorderSides::TOP | BorderSides::RIGHT)
×
233
            .border_style(borders.style())
×
234
            .border_type(borders.modifiers)
×
235
            .style(Style::default().bg(background));
×
236
        let title = self.props.get(Attribute::Title).map(|x| x.unwrap_title());
×
237
        let block = match title {
×
238
            Some((text, alignment)) => block.title(text).title_alignment(alignment),
×
239
            None => block,
×
240
        };
241
        let focus = self
×
242
            .props
243
            .get_or(Attribute::Focus, AttrValue::Flag(false))
×
244
            .unwrap_flag();
245
        let inactive_style = self
×
246
            .props
247
            .get(Attribute::FocusStyle)
×
248
            .map(|x| x.unwrap_style());
×
249
        let p: Paragraph = Paragraph::new(selected_text)
×
250
            .style(match focus {
×
251
                true => borders.style(),
×
252
                false => inactive_style.unwrap_or_default(),
×
253
            })
254
            .block(block);
×
255
        render.render_widget(p, chunks[0]);
×
256
        // Render the list of elements in chunks [1]
257
        // Make list
258
        #[cfg(feature = "tui")]
259
        let mut list = List::new(choices)
260
            .block(
261
                Block::default()
262
                    .borders(BorderSides::LEFT | BorderSides::BOTTOM | BorderSides::RIGHT)
263
                    .border_style(match focus {
264
                        true => borders.style(),
265
                        false => Style::default(),
266
                    })
267
                    .border_type(borders.modifiers)
268
                    .style(Style::default().bg(background)),
269
            )
270
            .start_corner(tuirealm::tui::layout::Corner::TopLeft)
271
            .style(Style::default().fg(foreground).bg(background))
272
            .highlight_style(
273
                Style::default()
274
                    .fg(hg)
275
                    .add_modifier(TextModifiers::REVERSED),
276
            );
277
        #[cfg(feature = "ratatui")]
NEW
278
        let mut list = List::new(choices)
×
279
            .block(
NEW
280
                Block::default()
×
NEW
281
                    .borders(BorderSides::LEFT | BorderSides::BOTTOM | BorderSides::RIGHT)
×
NEW
282
                    .border_style(match focus {
×
NEW
283
                        true => borders.style(),
×
NEW
284
                        false => Style::default(),
×
285
                    })
NEW
286
                    .border_type(borders.modifiers)
×
NEW
287
                    .style(Style::default().bg(background)),
×
288
            )
NEW
289
            .direction(tuirealm::tui::widgets::ListDirection::TopToBottom)
×
UNCOV
290
            .style(Style::default().fg(foreground).bg(background))
×
291
            .highlight_style(
292
                Style::default()
×
293
                    .fg(hg)
294
                    .add_modifier(TextModifiers::REVERSED),
295
            );
296
        // Highlighted symbol
297
        self.hg_str = self
×
298
            .props
299
            .get(Attribute::HighlightedStr)
×
300
            .map(|x| x.unwrap_string());
×
301
        if let Some(hg_str) = &self.hg_str {
×
302
            list = list.highlight_symbol(hg_str);
×
303
        }
304
        let mut state: ListState = ListState::default();
×
305
        state.select(Some(self.states.selected));
×
306
        render.render_stateful_widget(list, chunks[1], &mut state);
×
307
    }
×
308

309
    /// ### render_closed_tab
310
    ///
311
    /// Render component when tab is closed
312
    fn render_closed_tab(&self, render: &mut Frame, area: Rect) {
×
313
        let foreground = self
×
314
            .props
315
            .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
×
316
            .unwrap_color();
317
        let background = self
×
318
            .props
319
            .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
×
320
            .unwrap_color();
321
        let inactive_style = self
×
322
            .props
323
            .get(Attribute::FocusStyle)
×
324
            .map(|x| x.unwrap_style());
×
325
        let focus = self
×
326
            .props
327
            .get_or(Attribute::Focus, AttrValue::Flag(false))
×
328
            .unwrap_flag();
329
        let style = match focus {
×
330
            true => Style::default().bg(background).fg(foreground),
×
331
            false => inactive_style.unwrap_or_default(),
×
332
        };
333
        let borders = self
×
334
            .props
335
            .get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
×
336
            .unwrap_borders();
337
        let borders_style = match focus {
×
338
            true => borders.style(),
×
339
            false => inactive_style.unwrap_or_default(),
×
340
        };
341
        let block: Block = Block::default()
×
342
            .borders(BorderSides::ALL)
343
            .border_style(borders_style)
×
344
            .border_type(borders.modifiers)
×
345
            .style(style);
×
346
        let title = self.props.get(Attribute::Title).map(|x| x.unwrap_title());
×
347
        let block = match title {
×
348
            Some((text, alignment)) => block.title(text).title_alignment(alignment),
×
349
            None => block,
×
350
        };
351
        let selected_text: String = match self.states.choices.get(self.states.selected) {
×
352
            None => String::default(),
×
353
            Some(s) => s.clone(),
×
354
        };
355
        let p: Paragraph = Paragraph::new(selected_text).style(style).block(block);
×
356
        render.render_widget(p, area);
×
357
    }
×
358

359
    fn rewindable(&self) -> bool {
8✔
360
        self.props
16✔
361
            .get_or(Attribute::Rewind, AttrValue::Flag(false))
8✔
362
            .unwrap_flag()
363
    }
8✔
364
}
365

366
impl MockComponent for Select {
367
    fn view(&mut self, render: &mut Frame, area: Rect) {
×
368
        if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
×
369
            match self.states.is_tab_open() {
×
370
                true => self.render_open_tab(render, area),
×
371
                false => self.render_closed_tab(render, area),
×
372
            }
373
        }
374
    }
×
375

376
    fn query(&self, attr: Attribute) -> Option<AttrValue> {
×
377
        self.props.get(attr)
×
378
    }
×
379

380
    fn attr(&mut self, attr: Attribute, value: AttrValue) {
10✔
381
        match attr {
10✔
382
            Attribute::Content => {
383
                // Reset choices
384
                let choices: Vec<String> = value
1✔
385
                    .unwrap_payload()
386
                    .unwrap_vec()
387
                    .iter()
388
                    .map(|x| x.clone().unwrap_str())
3✔
389
                    .collect();
1✔
390
                self.states.set_choices(&choices);
1✔
391
            }
1✔
392
            Attribute::Value => {
393
                self.states
2✔
394
                    .select(value.unwrap_payload().unwrap_one().unwrap_usize());
2✔
395
            }
396
            Attribute::Focus if self.states.is_tab_open() => {
×
397
                if let AttrValue::Flag(false) = value {
×
398
                    self.states.cancel_tab();
×
399
                }
400
                self.props.set(attr, value);
×
401
            }
402
            attr => {
403
                self.props.set(attr, value);
7✔
404
            }
405
        }
406
    }
10✔
407

408
    fn state(&self) -> State {
3✔
409
        if self.states.is_tab_open() {
3✔
410
            State::None
×
411
        } else {
412
            State::One(StateValue::Usize(self.states.selected))
3✔
413
        }
414
    }
3✔
415

416
    fn perform(&mut self, cmd: Cmd) -> CmdResult {
11✔
417
        match cmd {
11✔
418
            Cmd::Move(Direction::Down) => {
419
                // Increment choice
420
                self.states.next_choice(self.rewindable());
4✔
421
                // Return CmdResult On Change or None if tab is closed
422
                match self.states.is_tab_open() {
4✔
423
                    false => CmdResult::None,
1✔
424
                    true => CmdResult::Changed(State::One(StateValue::Usize(self.states.selected))),
3✔
425
                }
426
            }
427
            Cmd::Move(Direction::Up) => {
428
                // Increment choice
429
                self.states.prev_choice(self.rewindable());
4✔
430
                // Return CmdResult On Change or None if tab is closed
431
                match self.states.is_tab_open() {
4✔
432
                    false => CmdResult::None,
1✔
433
                    true => CmdResult::Changed(State::One(StateValue::Usize(self.states.selected))),
3✔
434
                }
435
            }
436
            Cmd::Cancel => {
437
                self.states.cancel_tab();
×
438
                CmdResult::Changed(self.state())
×
439
            }
440
            Cmd::Submit => {
441
                // Open or close tab
442
                if self.states.is_tab_open() {
3✔
443
                    self.states.close_tab();
2✔
444
                    CmdResult::Submit(self.state())
2✔
445
                } else {
446
                    self.states.open_tab();
1✔
447
                    CmdResult::None
1✔
448
                }
449
            }
450
            _ => CmdResult::None,
×
451
        }
452
    }
11✔
453
}
454

455
#[cfg(test)]
456
mod test {
457

458
    use super::*;
459

460
    use pretty_assertions::assert_eq;
461

462
    use tuirealm::props::{PropPayload, PropValue};
463

464
    #[test]
465
    fn test_components_select_states() {
2✔
466
        let mut states: SelectStates = SelectStates::default();
1✔
467
        assert_eq!(states.selected, 0);
1✔
468
        assert_eq!(states.choices.len(), 0);
1✔
469
        assert_eq!(states.tab_open, false);
1✔
470
        let choices: &[String] = &[
1✔
471
            "lemon".to_string(),
1✔
472
            "strawberry".to_string(),
1✔
473
            "vanilla".to_string(),
1✔
474
            "chocolate".to_string(),
1✔
475
        ];
476
        states.set_choices(&choices);
1✔
477
        assert_eq!(states.selected, 0);
1✔
478
        assert_eq!(states.choices.len(), 4);
1✔
479
        // Move
480
        states.prev_choice(false);
1✔
481
        assert_eq!(states.selected, 0);
1✔
482
        states.next_choice(false);
1✔
483
        // Tab is closed!!!
484
        assert_eq!(states.selected, 0);
1✔
485
        states.open_tab();
1✔
486
        assert_eq!(states.is_tab_open(), true);
1✔
487
        // Now we can move
488
        states.next_choice(false);
1✔
489
        assert_eq!(states.selected, 1);
1✔
490
        states.next_choice(false);
1✔
491
        assert_eq!(states.selected, 2);
1✔
492
        // Forward overflow
493
        states.next_choice(false);
1✔
494
        states.next_choice(false);
1✔
495
        assert_eq!(states.selected, 3);
1✔
496
        states.prev_choice(false);
1✔
497
        assert_eq!(states.selected, 2);
1✔
498
        // Close tab
499
        states.close_tab();
1✔
500
        assert_eq!(states.is_tab_open(), false);
1✔
501
        states.prev_choice(false);
1✔
502
        assert_eq!(states.selected, 2);
1✔
503
        // Update
504
        let choices: &[String] = &["lemon".to_string(), "strawberry".to_string()];
1✔
505
        states.set_choices(&choices);
1✔
506
        assert_eq!(states.selected, 1); // Move to first index available
1✔
507
        assert_eq!(states.choices.len(), 2);
1✔
508
        let choices = vec![];
1✔
509
        states.set_choices(&choices);
1✔
510
        assert_eq!(states.selected, 0); // Move to first index available
1✔
511
        assert_eq!(states.choices.len(), 0);
1✔
512
        // Rewind
513
        let choices: &[String] = &[
1✔
514
            "lemon".to_string(),
1✔
515
            "strawberry".to_string(),
1✔
516
            "vanilla".to_string(),
1✔
517
            "chocolate".to_string(),
1✔
518
        ];
519
        states.set_choices(choices);
1✔
520
        states.open_tab();
1✔
521
        assert_eq!(states.selected, 0);
1✔
522
        states.prev_choice(true);
1✔
523
        assert_eq!(states.selected, 3);
1✔
524
        states.next_choice(true);
1✔
525
        assert_eq!(states.selected, 0);
1✔
526
        states.next_choice(true);
1✔
527
        assert_eq!(states.selected, 1);
1✔
528
        states.prev_choice(true);
1✔
529
        assert_eq!(states.selected, 0);
1✔
530
        // Cancel tab
531
        states.close_tab();
1✔
532
        states.select(2);
1✔
533
        states.open_tab();
1✔
534
        states.prev_choice(true);
1✔
535
        states.prev_choice(true);
1✔
536
        assert_eq!(states.selected, 0);
1✔
537
        states.cancel_tab();
1✔
538
        assert_eq!(states.selected, 2);
1✔
539
        assert_eq!(states.is_tab_open(), false);
1✔
540
    }
2✔
541

542
    #[test]
543
    fn test_components_select() {
2✔
544
        // Make component
545
        let mut component = Select::default()
6✔
546
            .foreground(Color::Red)
1✔
547
            .background(Color::Black)
1✔
548
            .borders(Borders::default())
1✔
549
            .highlighted_color(Color::Red)
1✔
550
            .highlighted_str(">>")
551
            .title("C'est oui ou bien c'est non?", Alignment::Center)
1✔
552
            .choices(&["Oui!", "Non", "Peut-ĂȘtre"])
553
            .value(1)
554
            .rewind(false);
555
        assert_eq!(component.states.is_tab_open(), false);
1✔
556
        component.states.open_tab();
1✔
557
        assert_eq!(component.states.is_tab_open(), true);
1✔
558
        component.states.close_tab();
1✔
559
        assert_eq!(component.states.is_tab_open(), false);
1✔
560
        // Update
561
        component.attr(
1✔
562
            Attribute::Value,
1✔
563
            AttrValue::Payload(PropPayload::One(PropValue::Usize(2))),
1✔
564
        );
565
        // Get value
566
        assert_eq!(component.state(), State::One(StateValue::Usize(2)));
1✔
567
        // Open tab
568
        component.states.open_tab();
1✔
569
        // Events
570
        // Move cursor
571
        assert_eq!(
1✔
572
            component.perform(Cmd::Move(Direction::Up)),
1✔
573
            CmdResult::Changed(State::One(StateValue::Usize(1))),
574
        );
575
        assert_eq!(
1✔
576
            component.perform(Cmd::Move(Direction::Up)),
1✔
577
            CmdResult::Changed(State::One(StateValue::Usize(0))),
578
        );
579
        // Upper boundary
580
        assert_eq!(
1✔
581
            component.perform(Cmd::Move(Direction::Up)),
1✔
582
            CmdResult::Changed(State::One(StateValue::Usize(0))),
583
        );
584
        // Move down
585
        assert_eq!(
1✔
586
            component.perform(Cmd::Move(Direction::Down)),
1✔
587
            CmdResult::Changed(State::One(StateValue::Usize(1))),
588
        );
589
        assert_eq!(
1✔
590
            component.perform(Cmd::Move(Direction::Down)),
1✔
591
            CmdResult::Changed(State::One(StateValue::Usize(2))),
592
        );
593
        // Lower boundary
594
        assert_eq!(
1✔
595
            component.perform(Cmd::Move(Direction::Down)),
1✔
596
            CmdResult::Changed(State::One(StateValue::Usize(2))),
597
        );
598
        // Press enter
599
        assert_eq!(
1✔
600
            component.perform(Cmd::Submit),
1✔
601
            CmdResult::Submit(State::One(StateValue::Usize(2))),
602
        );
603
        // Tab should be closed
604
        assert_eq!(component.states.is_tab_open(), false);
1✔
605
        // Re open
606
        assert_eq!(component.perform(Cmd::Submit), CmdResult::None);
1✔
607
        assert_eq!(component.states.is_tab_open(), true);
1✔
608
        // Move arrows
609
        assert_eq!(
1✔
610
            component.perform(Cmd::Submit),
1✔
611
            CmdResult::Submit(State::One(StateValue::Usize(2))),
612
        );
613
        assert_eq!(component.states.is_tab_open(), false);
1✔
614
        assert_eq!(
1✔
615
            component.perform(Cmd::Move(Direction::Down)),
1✔
616
            CmdResult::None
617
        );
618
        assert_eq!(component.perform(Cmd::Move(Direction::Up)), CmdResult::None);
1✔
619
    }
2✔
620
}
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