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

veeso / tui-realm-stdlib / 15137208932

20 May 2025 12:13PM UTC coverage: 67.595% (-0.7%) from 68.289%
15137208932

push

github

web-flow
Fix clippy lints, apply some pedantic fixes and general small improvements (#32)

* style(examples): directly have values as a type instead of casting

* style(examples/utils): ignore unused warnings

* style: run clippy auto fix

and remove redundant tests from "label"

* style: apply some clippy pedantic auto fixes

* test: set specific strings for "should_panic"

So that other panics are catched as failed tests.

* style(bar_chart): remove casting to "u64" when "usize" is directly provided and needed

* refactor(table): move making rows to own function

To appease "clippy::too_many_lines" and slightly reduce nesting.

* style: add "#[must_use]" where applicable

To hint not to forget a value.

104 of 192 new or added lines in 19 files covered. (54.17%)

12 existing lines in 2 files now uncovered.

2985 of 4416 relevant lines covered (67.6%)

1.66 hits per line

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

62.05
/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
use tuirealm::ratatui::text::Line as Spans;
12
use tuirealm::ratatui::{
13
    layout::{Constraint, Direction as LayoutDirection, Layout, Rect},
14
    widgets::{Block, List, ListItem, ListState, Paragraph},
15
};
16
use tuirealm::{Frame, MockComponent, State, StateValue};
17

18
// -- states
19

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

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

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

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

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

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

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

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

104
    /// ### is_tab_open
105
    ///
106
    /// Returns whether the tab is open
107
    #[must_use]
108
    pub fn is_tab_open(&self) -> bool {
23✔
109
        self.tab_open
23✔
110
    }
23✔
111
}
112

113
// -- component
114

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

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

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

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

139
    pub fn title<S: Into<String>>(mut self, t: S, a: Alignment) -> Self {
1✔
140
        self.attr(Attribute::Title, AttrValue::Title((t.into(), a)));
1✔
141
        self
1✔
142
    }
1✔
143

144
    pub fn highlighted_str<S: Into<String>>(mut self, s: S) -> Self {
1✔
145
        self.attr(Attribute::HighlightedStr, AttrValue::String(s.into()));
1✔
146
        self
1✔
147
    }
1✔
148

149
    pub fn highlighted_color(mut self, c: Color) -> Self {
1✔
150
        self.attr(Attribute::HighlightedColor, AttrValue::Color(c));
1✔
151
        self
1✔
152
    }
1✔
153

154
    pub fn inactive(mut self, s: Style) -> Self {
×
155
        self.attr(Attribute::FocusStyle, AttrValue::Style(s));
×
156
        self
×
157
    }
×
158

159
    pub fn rewind(mut self, r: bool) -> Self {
1✔
160
        self.attr(Attribute::Rewind, AttrValue::Flag(r));
1✔
161
        self
1✔
162
    }
1✔
163

164
    pub fn choices<S: AsRef<str>>(mut self, choices: &[S]) -> Self {
1✔
165
        self.attr(
1✔
166
            Attribute::Content,
1✔
167
            AttrValue::Payload(PropPayload::Vec(
168
                choices
1✔
169
                    .iter()
1✔
170
                    .map(|x| PropValue::Str(x.as_ref().to_string()))
3✔
171
                    .collect(),
1✔
172
            )),
173
        );
174
        self
1✔
175
    }
1✔
176

177
    pub fn value(mut self, i: usize) -> Self {
1✔
178
        // Set state
179
        self.attr(
1✔
180
            Attribute::Value,
1✔
181
            AttrValue::Payload(PropPayload::One(PropValue::Usize(i))),
1✔
182
        );
183
        self
1✔
184
    }
1✔
185

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

284
    /// ### render_closed_tab
285
    ///
286
    /// Render component when tab is closed
287
    fn render_closed_tab(&self, render: &mut Frame, area: Rect) {
×
288
        let foreground = self
×
289
            .props
×
290
            .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
×
291
            .unwrap_color();
×
292
        let background = self
×
293
            .props
×
294
            .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
×
295
            .unwrap_color();
×
296
        let inactive_style = self
×
297
            .props
×
298
            .get(Attribute::FocusStyle)
×
299
            .map(|x| x.unwrap_style());
×
300
        let focus = self
×
301
            .props
×
302
            .get_or(Attribute::Focus, AttrValue::Flag(false))
×
303
            .unwrap_flag();
×
NEW
304
        let style = if focus {
×
NEW
305
            Style::default().bg(background).fg(foreground)
×
306
        } else {
NEW
307
            inactive_style.unwrap_or_default()
×
308
        };
309
        let borders = self
×
310
            .props
×
311
            .get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
×
312
            .unwrap_borders();
×
NEW
313
        let borders_style = if focus {
×
NEW
314
            borders.style()
×
315
        } else {
NEW
316
            inactive_style.unwrap_or_default()
×
317
        };
318
        let block: Block = Block::default()
×
319
            .borders(BorderSides::ALL)
×
320
            .border_style(borders_style)
×
321
            .border_type(borders.modifiers)
×
322
            .style(style);
×
323
        let title = self.props.get(Attribute::Title).map(|x| x.unwrap_title());
×
324
        let block = match title {
×
325
            Some((text, alignment)) => block.title(text).title_alignment(alignment),
×
326
            None => block,
×
327
        };
328
        let selected_text: String = match self.states.choices.get(self.states.selected) {
×
329
            None => String::default(),
×
330
            Some(s) => s.clone(),
×
331
        };
332
        let p: Paragraph = Paragraph::new(selected_text).style(style).block(block);
×
333
        render.render_widget(p, area);
×
334
    }
×
335

336
    fn rewindable(&self) -> bool {
8✔
337
        self.props
8✔
338
            .get_or(Attribute::Rewind, AttrValue::Flag(false))
8✔
339
            .unwrap_flag()
8✔
340
    }
8✔
341
}
342

343
impl MockComponent for Select {
344
    fn view(&mut self, render: &mut Frame, area: Rect) {
×
345
        if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
×
NEW
346
            if self.states.is_tab_open() {
×
NEW
347
                self.render_open_tab(render, area);
×
NEW
348
            } else {
×
NEW
349
                self.render_closed_tab(render, area);
×
350
            }
×
351
        }
×
352
    }
×
353

354
    fn query(&self, attr: Attribute) -> Option<AttrValue> {
×
355
        self.props.get(attr)
×
356
    }
×
357

358
    fn attr(&mut self, attr: Attribute, value: AttrValue) {
10✔
359
        match attr {
×
360
            Attribute::Content => {
361
                // Reset choices
362
                let choices: Vec<String> = value
1✔
363
                    .unwrap_payload()
1✔
364
                    .unwrap_vec()
1✔
365
                    .iter()
1✔
366
                    .map(|x| x.clone().unwrap_str())
3✔
367
                    .collect();
1✔
368
                self.states.set_choices(&choices);
1✔
369
            }
370
            Attribute::Value => {
2✔
371
                self.states
2✔
372
                    .select(value.unwrap_payload().unwrap_one().unwrap_usize());
2✔
373
            }
2✔
374
            Attribute::Focus if self.states.is_tab_open() => {
×
375
                if let AttrValue::Flag(false) = value {
×
376
                    self.states.cancel_tab();
×
377
                }
×
378
                self.props.set(attr, value);
×
379
            }
380
            attr => {
7✔
381
                self.props.set(attr, value);
7✔
382
            }
7✔
383
        }
384
    }
10✔
385

386
    fn state(&self) -> State {
3✔
387
        if self.states.is_tab_open() {
3✔
388
            State::None
×
389
        } else {
390
            State::One(StateValue::Usize(self.states.selected))
3✔
391
        }
392
    }
3✔
393

394
    fn perform(&mut self, cmd: Cmd) -> CmdResult {
11✔
395
        match cmd {
8✔
396
            Cmd::Move(Direction::Down) => {
397
                // Increment choice
398
                self.states.next_choice(self.rewindable());
4✔
399
                // Return CmdResult On Change or None if tab is closed
400
                if self.states.is_tab_open() {
4✔
401
                    CmdResult::Changed(State::One(StateValue::Usize(self.states.selected)))
3✔
402
                } else {
403
                    CmdResult::None
1✔
404
                }
405
            }
406
            Cmd::Move(Direction::Up) => {
407
                // Increment choice
408
                self.states.prev_choice(self.rewindable());
4✔
409
                // Return CmdResult On Change or None if tab is closed
410
                if self.states.is_tab_open() {
4✔
411
                    CmdResult::Changed(State::One(StateValue::Usize(self.states.selected)))
3✔
412
                } else {
413
                    CmdResult::None
1✔
414
                }
415
            }
416
            Cmd::Cancel => {
417
                self.states.cancel_tab();
×
418
                CmdResult::Changed(self.state())
×
419
            }
420
            Cmd::Submit => {
421
                // Open or close tab
422
                if self.states.is_tab_open() {
3✔
423
                    self.states.close_tab();
2✔
424
                    CmdResult::Submit(self.state())
2✔
425
                } else {
426
                    self.states.open_tab();
1✔
427
                    CmdResult::None
1✔
428
                }
429
            }
430
            _ => CmdResult::None,
×
431
        }
432
    }
11✔
433
}
434

435
#[cfg(test)]
436
mod test {
437

438
    use super::*;
439

440
    use pretty_assertions::assert_eq;
441

442
    use tuirealm::props::{PropPayload, PropValue};
443

444
    #[test]
445
    fn test_components_select_states() {
1✔
446
        let mut states: SelectStates = SelectStates::default();
1✔
447
        assert_eq!(states.selected, 0);
1✔
448
        assert_eq!(states.choices.len(), 0);
1✔
449
        assert_eq!(states.tab_open, false);
1✔
450
        let choices: &[String] = &[
1✔
451
            "lemon".to_string(),
1✔
452
            "strawberry".to_string(),
1✔
453
            "vanilla".to_string(),
1✔
454
            "chocolate".to_string(),
1✔
455
        ];
1✔
456
        states.set_choices(choices);
1✔
457
        assert_eq!(states.selected, 0);
1✔
458
        assert_eq!(states.choices.len(), 4);
1✔
459
        // Move
460
        states.prev_choice(false);
1✔
461
        assert_eq!(states.selected, 0);
1✔
462
        states.next_choice(false);
1✔
463
        // Tab is closed!!!
464
        assert_eq!(states.selected, 0);
1✔
465
        states.open_tab();
1✔
466
        assert_eq!(states.is_tab_open(), true);
1✔
467
        // Now we can move
468
        states.next_choice(false);
1✔
469
        assert_eq!(states.selected, 1);
1✔
470
        states.next_choice(false);
1✔
471
        assert_eq!(states.selected, 2);
1✔
472
        // Forward overflow
473
        states.next_choice(false);
1✔
474
        states.next_choice(false);
1✔
475
        assert_eq!(states.selected, 3);
1✔
476
        states.prev_choice(false);
1✔
477
        assert_eq!(states.selected, 2);
1✔
478
        // Close tab
479
        states.close_tab();
1✔
480
        assert_eq!(states.is_tab_open(), false);
1✔
481
        states.prev_choice(false);
1✔
482
        assert_eq!(states.selected, 2);
1✔
483
        // Update
484
        let choices: &[String] = &["lemon".to_string(), "strawberry".to_string()];
1✔
485
        states.set_choices(choices);
1✔
486
        assert_eq!(states.selected, 1); // Move to first index available
1✔
487
        assert_eq!(states.choices.len(), 2);
1✔
488
        let choices = vec![];
1✔
489
        states.set_choices(&choices);
1✔
490
        assert_eq!(states.selected, 0); // Move to first index available
1✔
491
        assert_eq!(states.choices.len(), 0);
1✔
492
        // Rewind
493
        let choices: &[String] = &[
1✔
494
            "lemon".to_string(),
1✔
495
            "strawberry".to_string(),
1✔
496
            "vanilla".to_string(),
1✔
497
            "chocolate".to_string(),
1✔
498
        ];
1✔
499
        states.set_choices(choices);
1✔
500
        states.open_tab();
1✔
501
        assert_eq!(states.selected, 0);
1✔
502
        states.prev_choice(true);
1✔
503
        assert_eq!(states.selected, 3);
1✔
504
        states.next_choice(true);
1✔
505
        assert_eq!(states.selected, 0);
1✔
506
        states.next_choice(true);
1✔
507
        assert_eq!(states.selected, 1);
1✔
508
        states.prev_choice(true);
1✔
509
        assert_eq!(states.selected, 0);
1✔
510
        // Cancel tab
511
        states.close_tab();
1✔
512
        states.select(2);
1✔
513
        states.open_tab();
1✔
514
        states.prev_choice(true);
1✔
515
        states.prev_choice(true);
1✔
516
        assert_eq!(states.selected, 0);
1✔
517
        states.cancel_tab();
1✔
518
        assert_eq!(states.selected, 2);
1✔
519
        assert_eq!(states.is_tab_open(), false);
1✔
520
    }
1✔
521

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