• 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

73.97
/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
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
    #[must_use]
94
    pub fn render_value(&self, itype: InputType) -> String {
2✔
95
        self.render_value_chars(itype).iter().collect::<String>()
2✔
96
    }
2✔
97

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

111
    /// ### get_value
112
    ///
113
    /// Get value as string
114
    #[must_use]
115
    pub fn get_value(&self) -> String {
35✔
116
        self.input.iter().collect()
35✔
117
    }
35✔
118
}
119

120
// -- Component
121

122
/// ## Input
123
///
124
/// Input list component
125
#[derive(Default)]
126
#[must_use]
127
pub struct Input {
128
    props: Props,
129
    pub states: InputStates,
130
}
131

132
impl Input {
133
    pub fn foreground(mut self, fg: Color) -> Self {
1✔
134
        self.attr(Attribute::Foreground, AttrValue::Color(fg));
1✔
135
        self
1✔
136
    }
1✔
137

138
    pub fn background(mut self, bg: Color) -> Self {
1✔
139
        self.attr(Attribute::Background, AttrValue::Color(bg));
1✔
140
        self
1✔
141
    }
1✔
142

143
    pub fn inactive(mut self, s: Style) -> Self {
1✔
144
        self.attr(Attribute::FocusStyle, AttrValue::Style(s));
1✔
145
        self
1✔
146
    }
1✔
147

148
    pub fn borders(mut self, b: Borders) -> Self {
1✔
149
        self.attr(Attribute::Borders, AttrValue::Borders(b));
1✔
150
        self
1✔
151
    }
1✔
152

153
    pub fn title<S: Into<String>>(mut self, t: S, a: Alignment) -> Self {
1✔
154
        self.attr(Attribute::Title, AttrValue::Title((t.into(), a)));
1✔
155
        self
1✔
156
    }
1✔
157

158
    pub fn input_type(mut self, itype: InputType) -> Self {
1✔
159
        self.attr(Attribute::InputType, AttrValue::InputType(itype));
1✔
160
        self
1✔
161
    }
1✔
162

163
    pub fn input_len(mut self, ilen: usize) -> Self {
1✔
164
        self.attr(Attribute::InputLength, AttrValue::Length(ilen));
1✔
165
        self
1✔
166
    }
1✔
167

168
    pub fn value<S: Into<String>>(mut self, s: S) -> Self {
1✔
169
        self.attr(Attribute::Value, AttrValue::String(s.into()));
1✔
170
        self
1✔
171
    }
1✔
172

173
    pub fn invalid_style(mut self, s: Style) -> Self {
×
174
        self.attr(Attribute::Custom(INPUT_INVALID_STYLE), AttrValue::Style(s));
×
175
        self
×
176
    }
×
177

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

190
    fn get_input_len(&self) -> Option<usize> {
9✔
191
        self.props
9✔
192
            .get(Attribute::InputLength)
9✔
193
            .map(|x| x.unwrap_length())
9✔
194
    }
9✔
195

196
    fn get_input_type(&self) -> InputType {
27✔
197
        self.props
27✔
198
            .get_or(Attribute::InputType, AttrValue::InputType(InputType::Text))
27✔
199
            .unwrap_input_type()
27✔
200
    }
27✔
201

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

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

311
    fn query(&self, attr: Attribute) -> Option<AttrValue> {
×
312
        self.props.get(attr)
×
313
    }
×
314

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

341
    fn state(&self) -> State {
18✔
342
        // Validate input
343
        if self.is_valid() {
18✔
344
            State::One(StateValue::String(self.states.get_value()))
17✔
345
        } else {
346
            State::None
1✔
347
        }
348
    }
18✔
349

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

406
#[cfg(test)]
407
mod tests {
408

409
    use super::*;
410

411
    use pretty_assertions::assert_eq;
412

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

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