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

veeso / tui-realm-stdlib / 13283025409

12 Feb 2025 10:15AM UTC coverage: 68.369% (-4.7%) from 73.043%
13283025409

push

github

web-flow
Fix coverage tui (stdlib) (#29)

* chore(workflows/coverage): replace "actions-rs/toolchain" with "dtolnay/rust-toolchain"

* chore(workflows/ratatui): remove unknown option "override"

* chore(workflows/coverage): update "coverallsapp/github-action" to 2.x

* chore(workflows/coverage): replace coverage generation with "cargo-llvm-cov"

3279 of 4796 relevant lines covered (68.37%)

1.64 hits per line

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

72.54
/src/components/input.rs
1
//! ## Input
2
//!
3
//! `Input` represents a read-write input field. This component supports different input types, input length
4
//! and handles input events related to cursor position, backspace, canc, ...
5

6
use super::props::{INPUT_INVALID_STYLE, INPUT_PLACEHOLDER, INPUT_PLACEHOLDER_STYLE};
7
use crate::utils::calc_utf8_cursor_position;
8
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
9
use tuirealm::props::{
10
    Alignment, AttrValue, Attribute, Borders, Color, InputType, Props, Style, TextModifiers,
11
};
12
use tuirealm::ratatui::{layout::Rect, widgets::Paragraph};
13
use tuirealm::{Frame, MockComponent, State, StateValue};
14

15
// -- states
16

17
#[derive(Default)]
18
pub struct InputStates {
19
    pub input: Vec<char>, // Current input
20
    pub cursor: usize,    // Input position
21
}
22

23
impl InputStates {
24
    /// ### append
25
    ///
26
    /// Append, if possible according to input type, the character to the input vec
27
    pub fn append(&mut self, ch: char, itype: &InputType, max_len: Option<usize>) {
35✔
28
        // Check if max length has been reached
35✔
29
        if self.input.len() < max_len.unwrap_or(usize::MAX) {
35✔
30
            // Check whether can push
31
            if itype.char_valid(self.input.iter().collect::<String>().as_str(), ch) {
33✔
32
                self.input.insert(self.cursor, ch);
24✔
33
                self.incr_cursor();
24✔
34
            }
24✔
35
        }
2✔
36
    }
35✔
37

38
    /// ### backspace
39
    ///
40
    /// Delete element at cursor -1; then decrement cursor by 1
41
    pub fn backspace(&mut self) {
3✔
42
        if self.cursor > 0 && !self.input.is_empty() {
3✔
43
            self.input.remove(self.cursor - 1);
2✔
44
            // Decrement cursor
2✔
45
            self.cursor -= 1;
2✔
46
        }
2✔
47
    }
3✔
48

49
    /// ### delete
50
    ///
51
    /// Delete element at cursor
52
    pub fn delete(&mut self) {
3✔
53
        if self.cursor < self.input.len() {
3✔
54
            self.input.remove(self.cursor);
1✔
55
        }
2✔
56
    }
3✔
57

58
    /// ### incr_cursor
59
    ///
60
    /// Increment cursor value by one if possible
61
    pub fn incr_cursor(&mut self) {
28✔
62
        if self.cursor < self.input.len() {
28✔
63
            self.cursor += 1;
28✔
64
        }
28✔
65
    }
28✔
66

67
    /// ### cursoro_at_begin
68
    ///
69
    /// Place cursor at the begin of the input
70
    pub fn cursor_at_begin(&mut self) {
1✔
71
        self.cursor = 0;
1✔
72
    }
1✔
73

74
    /// ### cursor_at_end
75
    ///
76
    /// Place cursor at the end of the input
77
    pub fn cursor_at_end(&mut self) {
2✔
78
        self.cursor = self.input.len();
2✔
79
    }
2✔
80

81
    /// ### decr_cursor
82
    ///
83
    /// Decrement cursor value by one if possible
84
    pub fn decr_cursor(&mut self) {
6✔
85
        if self.cursor > 0 {
6✔
86
            self.cursor -= 1;
4✔
87
        }
4✔
88
    }
6✔
89

90
    /// ### render_value
91
    ///
92
    /// Get value as string to render
93
    pub fn render_value(&self, itype: InputType) -> String {
2✔
94
        self.render_value_chars(itype).iter().collect::<String>()
2✔
95
    }
2✔
96

97
    /// ### render_value_chars
98
    ///
99
    /// Render value as a vec of chars
100
    pub fn render_value_chars(&self, itype: InputType) -> Vec<char> {
2✔
101
        match itype {
2✔
102
            InputType::Password(ch) | InputType::CustomPassword(ch, _, _) => {
1✔
103
                (0..self.input.len()).map(|_| ch).collect()
3✔
104
            }
105
            _ => self.input.clone(),
1✔
106
        }
107
    }
2✔
108

109
    /// ### get_value
110
    ///
111
    /// Get value as string
112
    pub fn get_value(&self) -> String {
35✔
113
        self.input.iter().collect()
35✔
114
    }
35✔
115
}
116

117
// -- Component
118

119
/// ## Input
120
///
121
/// Input list component
122
#[derive(Default)]
123
pub struct Input {
124
    props: Props,
125
    pub states: InputStates,
126
}
127

128
impl Input {
129
    pub fn foreground(mut self, fg: Color) -> Self {
1✔
130
        self.attr(Attribute::Foreground, AttrValue::Color(fg));
1✔
131
        self
1✔
132
    }
1✔
133

134
    pub fn background(mut self, bg: Color) -> Self {
1✔
135
        self.attr(Attribute::Background, AttrValue::Color(bg));
1✔
136
        self
1✔
137
    }
1✔
138

139
    pub fn inactive(mut self, s: Style) -> Self {
1✔
140
        self.attr(Attribute::FocusStyle, AttrValue::Style(s));
1✔
141
        self
1✔
142
    }
1✔
143

144
    pub fn borders(mut self, b: Borders) -> Self {
1✔
145
        self.attr(Attribute::Borders, AttrValue::Borders(b));
1✔
146
        self
1✔
147
    }
1✔
148

149
    pub fn title<S: Into<String>>(mut self, t: S, a: Alignment) -> Self {
1✔
150
        self.attr(Attribute::Title, AttrValue::Title((t.into(), a)));
1✔
151
        self
1✔
152
    }
1✔
153

154
    pub fn input_type(mut self, itype: InputType) -> Self {
1✔
155
        self.attr(Attribute::InputType, AttrValue::InputType(itype));
1✔
156
        self
1✔
157
    }
1✔
158

159
    pub fn input_len(mut self, ilen: usize) -> Self {
1✔
160
        self.attr(Attribute::InputLength, AttrValue::Length(ilen));
1✔
161
        self
1✔
162
    }
1✔
163

164
    pub fn value<S: Into<String>>(mut self, s: S) -> Self {
1✔
165
        self.attr(Attribute::Value, AttrValue::String(s.into()));
1✔
166
        self
1✔
167
    }
1✔
168

169
    pub fn invalid_style(mut self, s: Style) -> Self {
×
170
        self.attr(Attribute::Custom(INPUT_INVALID_STYLE), AttrValue::Style(s));
×
171
        self
×
172
    }
×
173

174
    pub fn placeholder<S: Into<String>>(mut self, placeholder: S, style: Style) -> Self {
×
175
        self.attr(
×
176
            Attribute::Custom(INPUT_PLACEHOLDER),
×
177
            AttrValue::String(placeholder.into()),
×
178
        );
×
179
        self.attr(
×
180
            Attribute::Custom(INPUT_PLACEHOLDER_STYLE),
×
181
            AttrValue::Style(style),
×
182
        );
×
183
        self
×
184
    }
×
185

186
    fn get_input_len(&self) -> Option<usize> {
9✔
187
        self.props
9✔
188
            .get(Attribute::InputLength)
9✔
189
            .map(|x| x.unwrap_length())
9✔
190
    }
9✔
191

192
    fn get_input_type(&self) -> InputType {
27✔
193
        self.props
27✔
194
            .get_or(Attribute::InputType, AttrValue::InputType(InputType::Text))
27✔
195
            .unwrap_input_type()
27✔
196
    }
27✔
197

198
    /// ### is_valid
199
    ///
200
    /// Checks whether current input is valid
201
    fn is_valid(&self) -> bool {
18✔
202
        let value = self.states.get_value();
18✔
203
        self.get_input_type().validate(value.as_str())
18✔
204
    }
18✔
205
}
206

207
impl MockComponent for Input {
208
    fn view(&mut self, render: &mut Frame, area: Rect) {
×
209
        if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
×
210
            let mut foreground = self
×
211
                .props
×
212
                .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
×
213
                .unwrap_color();
×
214
            let mut background = self
×
215
                .props
×
216
                .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
×
217
                .unwrap_color();
×
218
            let modifiers = self
×
219
                .props
×
220
                .get_or(
×
221
                    Attribute::TextProps,
×
222
                    AttrValue::TextModifiers(TextModifiers::empty()),
×
223
                )
×
224
                .unwrap_text_modifiers();
×
225
            let title = self
×
226
                .props
×
227
                .get_or(
×
228
                    Attribute::Title,
×
229
                    AttrValue::Title((String::default(), Alignment::Center)),
×
230
                )
×
231
                .unwrap_title();
×
232
            let borders = self
×
233
                .props
×
234
                .get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
×
235
                .unwrap_borders();
×
236
            let focus = self
×
237
                .props
×
238
                .get_or(Attribute::Focus, AttrValue::Flag(false))
×
239
                .unwrap_flag();
×
240
            let inactive_style = self
×
241
                .props
×
242
                .get(Attribute::FocusStyle)
×
243
                .map(|x| x.unwrap_style());
×
244
            let itype = self.get_input_type();
×
245
            let mut block = crate::utils::get_block(borders, Some(title), focus, inactive_style);
×
246
            // Apply invalid style
×
247
            if focus && !self.is_valid() {
×
248
                if let Some(style) = self
×
249
                    .props
×
250
                    .get(Attribute::Custom(INPUT_INVALID_STYLE))
×
251
                    .map(|x| x.unwrap_style())
×
252
                {
×
253
                    let borders = self
×
254
                        .props
×
255
                        .get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
×
256
                        .unwrap_borders()
×
257
                        .color(style.fg.unwrap_or(Color::Reset));
×
258
                    let title = self
×
259
                        .props
×
260
                        .get_or(
×
261
                            Attribute::Title,
×
262
                            AttrValue::Title((String::default(), Alignment::Center)),
×
263
                        )
×
264
                        .unwrap_title();
×
265
                    block = crate::utils::get_block(borders, Some(title), focus, None);
×
266
                    foreground = style.fg.unwrap_or(Color::Reset);
×
267
                    background = style.bg.unwrap_or(Color::Reset);
×
268
                }
×
269
            }
×
270
            let text_to_display = self.states.render_value(self.get_input_type());
×
271
            let show_placeholder = text_to_display.is_empty();
×
272
            // Choose whether to show placeholder; if placeholder is unset, show nothing
273
            let text_to_display = match show_placeholder {
×
274
                true => self
×
275
                    .props
×
276
                    .get_or(
×
277
                        Attribute::Custom(INPUT_PLACEHOLDER),
×
278
                        AttrValue::String(String::new()),
×
279
                    )
×
280
                    .unwrap_string(),
×
281
                false => text_to_display,
×
282
            };
283
            // Choose paragraph style based on whether is valid or not and if has focus and if should show placeholder
284
            let paragraph_style = match focus {
×
285
                true => Style::default()
×
286
                    .fg(foreground)
×
287
                    .bg(background)
×
288
                    .add_modifier(modifiers),
×
289
                false => inactive_style.unwrap_or_default(),
×
290
            };
291
            let paragraph_style = match show_placeholder {
×
292
                true => self
×
293
                    .props
×
294
                    .get_or(
×
295
                        Attribute::Custom(INPUT_PLACEHOLDER_STYLE),
×
296
                        AttrValue::Style(paragraph_style),
×
297
                    )
×
298
                    .unwrap_style(),
×
299
                false => paragraph_style,
×
300
            };
301
            // Create widget
302
            let block_inner_area = block.inner(area);
×
303
            let p: Paragraph = Paragraph::new(text_to_display)
×
304
                .style(paragraph_style)
×
305
                .block(block);
×
306
            render.render_widget(p, area);
×
307
            // Set cursor, if focus
×
308
            if focus {
×
309
                let x: u16 = block_inner_area.x
×
310
                    + calc_utf8_cursor_position(
×
311
                        &self.states.render_value_chars(itype)[0..self.states.cursor],
×
312
                    );
×
313
                render
×
314
                    .set_cursor_position(tuirealm::ratatui::prelude::Position { x, y: area.y + 1 });
×
315
            }
×
316
        }
×
317
    }
×
318

319
    fn query(&self, attr: Attribute) -> Option<AttrValue> {
×
320
        self.props.get(attr)
×
321
    }
×
322

323
    fn attr(&mut self, attr: Attribute, value: AttrValue) {
11✔
324
        let sanitize_input = matches!(
11✔
325
            attr,
11✔
326
            Attribute::InputLength | Attribute::InputType | Attribute::Value
327
        );
328
        // Check if new input
329
        let new_input = match attr {
11✔
330
            Attribute::Value => Some(value.clone().unwrap_string()),
2✔
331
            _ => None,
9✔
332
        };
333
        self.props.set(attr, value);
11✔
334
        if sanitize_input {
11✔
335
            let input = match new_input {
6✔
336
                None => self.states.input.clone(),
4✔
337
                Some(v) => v.chars().collect(),
2✔
338
            };
339
            self.states.input = Vec::new();
6✔
340
            self.states.cursor = 0;
6✔
341
            let itype = self.get_input_type();
6✔
342
            let max_len = self.get_input_len();
6✔
343
            for ch in input.into_iter() {
27✔
344
                self.states.append(ch, &itype, max_len);
27✔
345
            }
27✔
346
        }
5✔
347
    }
11✔
348

349
    fn state(&self) -> State {
18✔
350
        // Validate input
18✔
351
        if self.is_valid() {
18✔
352
            State::One(StateValue::String(self.states.get_value()))
17✔
353
        } else {
354
            State::None
1✔
355
        }
356
    }
18✔
357

358
    fn perform(&mut self, cmd: Cmd) -> CmdResult {
17✔
359
        match cmd {
4✔
360
            Cmd::Delete => {
361
                // Backspace and None
362
                let prev_input = self.states.input.clone();
3✔
363
                self.states.backspace();
3✔
364
                if prev_input != self.states.input {
3✔
365
                    CmdResult::Changed(self.state())
2✔
366
                } else {
367
                    CmdResult::None
1✔
368
                }
369
            }
370
            Cmd::Cancel => {
371
                // Delete and None
372
                let prev_input = self.states.input.clone();
3✔
373
                self.states.delete();
3✔
374
                if prev_input != self.states.input {
3✔
375
                    CmdResult::Changed(self.state())
1✔
376
                } else {
377
                    CmdResult::None
2✔
378
                }
379
            }
380
            Cmd::Submit => CmdResult::Submit(self.state()),
1✔
381
            Cmd::Move(Direction::Left) => {
382
                self.states.decr_cursor();
3✔
383
                CmdResult::None
3✔
384
            }
385
            Cmd::Move(Direction::Right) => {
386
                self.states.incr_cursor();
1✔
387
                CmdResult::None
1✔
388
            }
389
            Cmd::GoTo(Position::Begin) => {
390
                self.states.cursor_at_begin();
1✔
391
                CmdResult::None
1✔
392
            }
393
            Cmd::GoTo(Position::End) => {
394
                self.states.cursor_at_end();
2✔
395
                CmdResult::None
2✔
396
            }
397
            Cmd::Type(ch) => {
3✔
398
                // Push char to input
3✔
399
                let prev_input = self.states.input.clone();
3✔
400
                self.states
3✔
401
                    .append(ch, &self.get_input_type(), self.get_input_len());
3✔
402
                // Message on change
3✔
403
                if prev_input != self.states.input {
3✔
404
                    CmdResult::Changed(self.state())
2✔
405
                } else {
406
                    CmdResult::None
1✔
407
                }
408
            }
409
            _ => CmdResult::None,
×
410
        }
411
    }
17✔
412
}
413

414
#[cfg(test)]
415
mod tests {
416

417
    use super::*;
418

419
    use pretty_assertions::assert_eq;
420

421
    #[test]
422
    fn test_components_input_states() {
1✔
423
        let mut states: InputStates = InputStates::default();
1✔
424
        states.append('a', &InputType::Text, Some(3));
1✔
425
        assert_eq!(states.input, vec!['a']);
1✔
426
        states.append('b', &InputType::Text, Some(3));
1✔
427
        assert_eq!(states.input, vec!['a', 'b']);
1✔
428
        states.append('c', &InputType::Text, Some(3));
1✔
429
        assert_eq!(states.input, vec!['a', 'b', 'c']);
1✔
430
        // Reached length
431
        states.append('d', &InputType::Text, Some(3));
1✔
432
        assert_eq!(states.input, vec!['a', 'b', 'c']);
1✔
433
        // Push char to numbers
434
        states.append('d', &InputType::Number, None);
1✔
435
        assert_eq!(states.input, vec!['a', 'b', 'c']);
1✔
436
        // move cursor
437
        // decr cursor
438
        states.decr_cursor();
1✔
439
        assert_eq!(states.cursor, 2);
1✔
440
        states.cursor = 1;
1✔
441
        states.decr_cursor();
1✔
442
        assert_eq!(states.cursor, 0);
1✔
443
        states.decr_cursor();
1✔
444
        assert_eq!(states.cursor, 0);
1✔
445
        // Incr
446
        states.incr_cursor();
1✔
447
        assert_eq!(states.cursor, 1);
1✔
448
        states.incr_cursor();
1✔
449
        assert_eq!(states.cursor, 2);
1✔
450
        states.incr_cursor();
1✔
451
        assert_eq!(states.cursor, 3);
1✔
452
        // Render value
453
        assert_eq!(states.render_value(InputType::Text).as_str(), "abc");
1✔
454
        assert_eq!(
1✔
455
            states.render_value(InputType::Password('*')).as_str(),
1✔
456
            "***"
1✔
457
        );
1✔
458
    }
1✔
459

460
    #[test]
461
    fn test_components_input_text() {
1✔
462
        // Instantiate Input with value
1✔
463
        let mut component: Input = Input::default()
1✔
464
            .background(Color::Yellow)
1✔
465
            .borders(Borders::default())
1✔
466
            .foreground(Color::Cyan)
1✔
467
            .inactive(Style::default())
1✔
468
            .input_len(5)
1✔
469
            .input_type(InputType::Text)
1✔
470
            .title("pippo", Alignment::Center)
1✔
471
            .value("home");
1✔
472
        // Verify initial state
1✔
473
        assert_eq!(component.states.cursor, 4);
1✔
474
        assert_eq!(component.states.input.len(), 4);
1✔
475
        // Get value
476
        assert_eq!(
1✔
477
            component.state(),
1✔
478
            State::One(StateValue::String(String::from("home")))
1✔
479
        );
1✔
480
        // Character
481
        assert_eq!(
1✔
482
            component.perform(Cmd::Type('/')),
1✔
483
            CmdResult::Changed(State::One(StateValue::String(String::from("home/"))))
1✔
484
        );
1✔
485
        assert_eq!(
1✔
486
            component.state(),
1✔
487
            State::One(StateValue::String(String::from("home/")))
1✔
488
        );
1✔
489
        assert_eq!(component.states.cursor, 5);
1✔
490
        // Verify max length (shouldn't push any character)
491
        assert_eq!(component.perform(Cmd::Type('a')), CmdResult::None);
1✔
492
        assert_eq!(
1✔
493
            component.state(),
1✔
494
            State::One(StateValue::String(String::from("home/")))
1✔
495
        );
1✔
496
        assert_eq!(component.states.cursor, 5);
1✔
497
        // Submit
498
        assert_eq!(
1✔
499
            component.perform(Cmd::Submit),
1✔
500
            CmdResult::Submit(State::One(StateValue::String(String::from("home/"))))
1✔
501
        );
1✔
502
        // Backspace
503
        assert_eq!(
1✔
504
            component.perform(Cmd::Delete),
1✔
505
            CmdResult::Changed(State::One(StateValue::String(String::from("home"))))
1✔
506
        );
1✔
507
        assert_eq!(
1✔
508
            component.state(),
1✔
509
            State::One(StateValue::String(String::from("home")))
1✔
510
        );
1✔
511
        assert_eq!(component.states.cursor, 4);
1✔
512
        // Check backspace at 0
513
        component.states.input = vec!['h'];
1✔
514
        component.states.cursor = 1;
1✔
515
        assert_eq!(
1✔
516
            component.perform(Cmd::Delete),
1✔
517
            CmdResult::Changed(State::One(StateValue::String(String::from(""))))
1✔
518
        );
1✔
519
        assert_eq!(
1✔
520
            component.state(),
1✔
521
            State::One(StateValue::String(String::from("")))
1✔
522
        );
1✔
523
        assert_eq!(component.states.cursor, 0);
1✔
524
        // Another one...
525
        assert_eq!(component.perform(Cmd::Delete), CmdResult::None);
1✔
526
        assert_eq!(
1✔
527
            component.state(),
1✔
528
            State::One(StateValue::String(String::from("")))
1✔
529
        );
1✔
530
        assert_eq!(component.states.cursor, 0);
1✔
531
        // See del behaviour here
532
        assert_eq!(component.perform(Cmd::Cancel), CmdResult::None);
1✔
533
        assert_eq!(
1✔
534
            component.state(),
1✔
535
            State::One(StateValue::String(String::from("")))
1✔
536
        );
1✔
537
        assert_eq!(component.states.cursor, 0);
1✔
538
        // Check del behaviour
539
        component.states.input = vec!['h', 'e'];
1✔
540
        component.states.cursor = 1;
1✔
541
        assert_eq!(
1✔
542
            component.perform(Cmd::Cancel),
1✔
543
            CmdResult::Changed(State::One(StateValue::String(String::from("h"))))
1✔
544
        );
1✔
545
        assert_eq!(
1✔
546
            component.state(),
1✔
547
            State::One(StateValue::String(String::from("h")))
1✔
548
        );
1✔
549
        assert_eq!(component.states.cursor, 1);
1✔
550
        // Another one (should do nothing)
551
        assert_eq!(component.perform(Cmd::Cancel), CmdResult::None);
1✔
552
        assert_eq!(
1✔
553
            component.state(),
1✔
554
            State::One(StateValue::String(String::from("h")))
1✔
555
        );
1✔
556
        assert_eq!(component.states.cursor, 1);
1✔
557
        // Move cursor right
558
        component.states.input = vec!['h', 'e', 'l', 'l', 'o'];
1✔
559
        // Update length to 16
1✔
560
        component.attr(Attribute::InputLength, AttrValue::Length(16));
1✔
561
        component.states.cursor = 1;
1✔
562
        assert_eq!(
1✔
563
            component.perform(Cmd::Move(Direction::Right)), // between 'e' and 'l'
1✔
564
            CmdResult::None
1✔
565
        );
1✔
566
        assert_eq!(component.states.cursor, 2);
1✔
567
        // Put a character here
568
        assert_eq!(
1✔
569
            component.perform(Cmd::Type('a')),
1✔
570
            CmdResult::Changed(State::One(StateValue::String(String::from("heallo"))))
1✔
571
        );
1✔
572
        assert_eq!(
1✔
573
            component.state(),
1✔
574
            State::One(StateValue::String(String::from("heallo")))
1✔
575
        );
1✔
576
        assert_eq!(component.states.cursor, 3);
1✔
577
        // Move left
578
        assert_eq!(
1✔
579
            component.perform(Cmd::Move(Direction::Left)),
1✔
580
            CmdResult::None
1✔
581
        );
1✔
582
        assert_eq!(component.states.cursor, 2);
1✔
583
        // Go at the end
584
        component.states.cursor = 6;
1✔
585
        // Move right
1✔
586
        assert_eq!(component.perform(Cmd::GoTo(Position::End)), CmdResult::None);
1✔
587
        assert_eq!(component.states.cursor, 6);
1✔
588
        // Move left
589
        assert_eq!(
1✔
590
            component.perform(Cmd::Move(Direction::Left)),
1✔
591
            CmdResult::None
1✔
592
        );
1✔
593
        assert_eq!(component.states.cursor, 5);
1✔
594
        // Go at the beginning
595
        component.states.cursor = 0;
1✔
596
        assert_eq!(
1✔
597
            component.perform(Cmd::Move(Direction::Left)),
1✔
598
            CmdResult::None
1✔
599
        );
1✔
600
        //assert_eq!(component.render().unwrap().cursor, 0); // Should stay
601
        assert_eq!(component.states.cursor, 0);
1✔
602
        // End - begin
603
        assert_eq!(component.perform(Cmd::GoTo(Position::End)), CmdResult::None);
1✔
604
        assert_eq!(component.states.cursor, 6);
1✔
605
        assert_eq!(
1✔
606
            component.perform(Cmd::GoTo(Position::Begin)),
1✔
607
            CmdResult::None
1✔
608
        );
1✔
609
        assert_eq!(component.states.cursor, 0);
1✔
610
        // Update value
611
        component.attr(Attribute::Value, AttrValue::String("new-value".to_string()));
1✔
612
        assert_eq!(
1✔
613
            component.state(),
1✔
614
            State::One(StateValue::String(String::from("new-value")))
1✔
615
        );
1✔
616
        // Invalidate input type
617
        component.attr(
1✔
618
            Attribute::InputType,
1✔
619
            AttrValue::InputType(InputType::Number),
1✔
620
        );
1✔
621
        assert_eq!(component.state(), State::None);
1✔
622
    }
1✔
623
}
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