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

veeso / tui-realm-stdlib / 22143900361

18 Feb 2026 02:31PM UTC coverage: 76.863% (+4.4%) from 72.486%
22143900361

Pull #58

github

web-flow
Merge 82269e219 into 237158c92
Pull Request #58: Remove unnecessary features

588 of 1352 new or added lines in 21 files covered. (43.49%)

40 existing lines in 15 files now uncovered.

3940 of 5126 relevant lines covered (76.86%)

4.91 hits per line

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

75.89
/src/components/chart/chart.rs
1
//! A component to plot one or more dataset in a cartesian coordinate system
2

3
use std::any::Any;
4

5
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
6
use tuirealm::props::{
7
    AttrValue, Attribute, Borders, Color, PropPayload, PropValue, Props, Style, TextModifiers,
8
    Title,
9
};
10
use tuirealm::ratatui::text::Line;
11
use tuirealm::ratatui::{
12
    layout::Rect,
13
    text::Span,
14
    widgets::{Axis, Chart as TuiChart, Dataset as TuiDataset},
15
};
16
use tuirealm::{Frame, MockComponent, State};
17

18
// -- Props
19
use super::dataset::ChartDataset;
20
use crate::prop_ext::CommonProps;
21
use crate::props::{
22
    CHART_X_BOUNDS, CHART_X_LABELS, CHART_X_STYLE, CHART_X_TITLE, CHART_Y_BOUNDS, CHART_Y_LABELS,
23
    CHART_Y_STYLE, CHART_Y_TITLE,
24
};
25

26
/// The state that needs to be kepts for the [`Chart`] component.
27
#[derive(Default)]
28
pub struct ChartStates {
29
    pub cursor: usize,
30
    pub data: Vec<ChartDataset>,
31
}
32

33
impl ChartStates {
34
    /// Move cursor to the left.
35
    pub fn move_cursor_left(&mut self) {
6✔
36
        if self.cursor > 0 {
6✔
37
            self.cursor -= 1;
4✔
38
        }
4✔
39
    }
6✔
40

41
    /// Move cursor to the right.
42
    pub fn move_cursor_right(&mut self, data_len: usize) {
6✔
43
        if data_len > 0 && self.cursor + 1 < data_len {
6✔
44
            self.cursor += 1;
4✔
45
        }
4✔
46
    }
6✔
47

48
    /// Reset cursor to 0.
49
    pub fn reset_cursor(&mut self) {
18✔
50
        self.cursor = 0;
18✔
51
    }
18✔
52

53
    /// Move cursor to the end of the chart.
54
    pub fn cursor_at_end(&mut self, data_len: usize) {
6✔
55
        if data_len > 0 {
6✔
56
            self.cursor = data_len - 1;
6✔
57
        } else {
6✔
58
            self.cursor = 0;
×
59
        }
×
60
    }
6✔
61
}
62

63
// -- component
64

65
/// A component to display a chart on a cartesian coordinate system.
66
/// The chart can work both in "active" and "disabled" mode.
67
///
68
/// ## Disabled mode
69
///
70
/// When in disabled mode, the chart won't be interactive, so you won't be able to move through data using keys.
71
/// If you have more data than the maximum amount of bars that can be displayed, you'll have to update data to display the remaining entries.
72
///
73
/// ## Active mode
74
///
75
/// While in active mode (default) you can put as many entries as you wish. You can move with [`Cmd::Move`] or [`Cmd::GoTo`].
76
#[derive(Default)]
77
#[must_use]
78
pub struct Chart {
79
    common: CommonProps,
80
    props: Props,
81
    pub states: ChartStates,
82
}
83

84
impl Chart {
85
    /// Set the main foreground color. This may get overwritten by individual text styles.
86
    pub fn foreground(mut self, fg: Color) -> Self {
2✔
87
        self.props.set(Attribute::Foreground, AttrValue::Color(fg));
2✔
88
        self
2✔
89
    }
2✔
90

91
    /// Set the main background color. This may get overwritten by individual text styles.
92
    pub fn background(mut self, bg: Color) -> Self {
2✔
93
        self.props.set(Attribute::Background, AttrValue::Color(bg));
2✔
94
        self
2✔
95
    }
2✔
96

97
    /// Set the main text modifiers. This may get overwritten by individual text styles.
NEW
98
    pub fn modifiers(mut self, m: TextModifiers) -> Self {
×
NEW
99
        self.attr(Attribute::TextProps, AttrValue::TextModifiers(m));
×
NEW
100
        self
×
NEW
101
    }
×
102

103
    /// Set the main style. This may get overwritten by individual text styles.
104
    ///
105
    /// This option will overwrite any previous [`foreground`](Self::foreground), [`background`](Self::background) and [`modifiers`](Self::modifiers)!
NEW
106
    pub fn style(mut self, style: Style) -> Self {
×
NEW
107
        self.attr(Attribute::Style, AttrValue::Style(style));
×
NEW
108
        self
×
NEW
109
    }
×
110

111
    /// Add a border to the component.
112
    pub fn borders(mut self, b: Borders) -> Self {
2✔
113
        self.props.set(Attribute::Borders, AttrValue::Borders(b));
2✔
114
        self
2✔
115
    }
2✔
116

117
    /// Add a title to the component.
118
    pub fn title<T: Into<Title>>(mut self, title: T) -> Self {
2✔
119
        self.attr(Attribute::Title, AttrValue::Title(title.into()));
2✔
120
        self
2✔
121
    }
2✔
122

123
    /// Set whether this component should appear "disabled" (or also known as "locked").
124
    pub fn disabled(mut self, disabled: bool) -> Self {
2✔
125
        self.attr(Attribute::Disabled, AttrValue::Flag(disabled));
2✔
126
        self
2✔
127
    }
2✔
128

129
    /// Set the inactive style for the whole component
130
    pub fn inactive(mut self, s: Style) -> Self {
×
131
        self.props.set(Attribute::FocusStyle, AttrValue::Style(s));
×
132
        self
×
133
    }
×
134

135
    /// Builder-Style function to set the initial data
136
    pub fn data(mut self, data: impl IntoIterator<Item = ChartDataset>) -> Self {
4✔
137
        self.set_data(data.into_iter().collect());
4✔
138
        self
4✔
139
    }
4✔
140

141
    /// Set the bounds for the X-axis.
142
    pub fn x_bounds(mut self, bounds: (f64, f64)) -> Self {
2✔
143
        self.props.set(
2✔
144
            Attribute::Custom(CHART_X_BOUNDS),
2✔
145
            AttrValue::Payload(PropPayload::Pair((
2✔
146
                PropValue::F64(bounds.0),
2✔
147
                PropValue::F64(bounds.1),
2✔
148
            ))),
2✔
149
        );
1✔
150
        self
2✔
151
    }
2✔
152

153
    /// Set the bounds for the Y-axis.
154
    pub fn y_bounds(mut self, bounds: (f64, f64)) -> Self {
2✔
155
        self.props.set(
2✔
156
            Attribute::Custom(CHART_Y_BOUNDS),
2✔
157
            AttrValue::Payload(PropPayload::Pair((
2✔
158
                PropValue::F64(bounds.0),
2✔
159
                PropValue::F64(bounds.1),
2✔
160
            ))),
2✔
161
        );
1✔
162
        self
2✔
163
    }
2✔
164

165
    /// Set labels for the X-Axis, see [`Axis::labels`].
166
    pub fn x_labels(mut self, labels: &[&str]) -> Self {
2✔
167
        self.attr(
2✔
168
            Attribute::Custom(CHART_X_LABELS),
2✔
169
            AttrValue::Payload(PropPayload::Vec(
1✔
170
                labels
2✔
171
                    .iter()
2✔
172
                    .map(|x| PropValue::Str((*x).to_string()))
24✔
173
                    .collect(),
2✔
174
            )),
1✔
175
        );
1✔
176
        self
2✔
177
    }
2✔
178

179
    /// Set labels for the Y-Axis, see [`Axis::labels`].
180
    pub fn y_labels(mut self, labels: &[&str]) -> Self {
2✔
181
        self.attr(
2✔
182
            Attribute::Custom(CHART_Y_LABELS),
2✔
183
            AttrValue::Payload(PropPayload::Vec(
1✔
184
                labels
2✔
185
                    .iter()
2✔
186
                    .map(|x| PropValue::Str((*x).to_string()))
18✔
187
                    .collect(),
2✔
188
            )),
1✔
189
        );
1✔
190
        self
2✔
191
    }
2✔
192

193
    /// Set a specific style for the X-Axis.
194
    pub fn x_style(mut self, s: Style) -> Self {
2✔
195
        self.attr(Attribute::Custom(CHART_X_STYLE), AttrValue::Style(s));
2✔
196
        self
2✔
197
    }
2✔
198

199
    /// Set a specific style for the Y-Axis.
200
    pub fn y_style(mut self, s: Style) -> Self {
2✔
201
        self.attr(Attribute::Custom(CHART_Y_STYLE), AttrValue::Style(s));
2✔
202
        self
2✔
203
    }
2✔
204

205
    /// Give the X axis a title
206
    pub fn x_title<S: Into<String>>(mut self, t: S) -> Self {
2✔
207
        self.props.set(
2✔
208
            Attribute::Custom(CHART_X_TITLE),
2✔
209
            AttrValue::String(t.into()),
2✔
210
        );
1✔
211
        self
2✔
212
    }
2✔
213

214
    /// Give the Y axis a title
215
    pub fn y_title<S: Into<String>>(mut self, t: S) -> Self {
2✔
216
        self.props.set(
2✔
217
            Attribute::Custom(CHART_Y_TITLE),
2✔
218
            AttrValue::String(t.into()),
2✔
219
        );
1✔
220
        self
2✔
221
    }
2✔
222

223
    /// Overwrite the data in this Chart, also applies all resets necessary.
224
    fn set_data(&mut self, data: Vec<ChartDataset>) {
14✔
225
        self.states.data = data;
14✔
226
        self.states.reset_cursor();
14✔
227
    }
14✔
228

229
    /// Determine if the [`Attribute::Disabled`] is enabled.
230
    fn is_disabled(&self) -> bool {
10✔
231
        self.props
10✔
232
            .get_or(Attribute::Disabled, AttrValue::Flag(false))
10✔
233
            .unwrap_flag()
10✔
234
    }
10✔
235

236
    /// Get the maximum len among the datasets
237
    fn max_dataset_len(&self) -> usize {
8✔
238
        self.states
8✔
239
            .data
8✔
240
            .iter()
8✔
241
            .map(|v| v.get_data().len())
12✔
242
            .max()
8✔
243
            .unwrap_or(0)
8✔
244
    }
8✔
245

246
    /// Get data to be displayed, starting from provided index at `start`
247
    fn get_tui_data(&self, start: usize) -> Vec<TuiDataset<'_>> {
4✔
248
        self.states
4✔
249
            .data
4✔
250
            .iter()
4✔
251
            .map(|x| x.as_tuichart(start))
6✔
252
            .collect()
4✔
253
    }
4✔
254

255
    /// Try downcasting the given [`Box<Any>`] into a concrete type.
256
    fn try_downcast(value: Box<dyn Any + Send + Sync>) -> Option<Vec<ChartDataset>> {
10✔
257
        value
10✔
258
            .downcast::<Vec<ChartDataset>>()
10✔
259
            .map(|v| *v)
10✔
260
            .or_else(|value| value.downcast::<ChartDataset>().map(|v| vec![*v]))
10✔
261
            .ok()
10✔
262
    }
10✔
263

264
    /// Get our data from a [`AttrValue`].
265
    fn data_from_attr(&mut self, attr: AttrValue) {
10✔
266
        if let AttrValue::Payload(PropPayload::Any(val)) = attr {
10✔
267
            if let Some(data) = Self::try_downcast(val) {
10✔
268
                self.set_data(data);
10✔
269
            }
10✔
270
        }
×
271
    }
10✔
272

273
    /// Clone our data into a [`AttrValue`].
274
    fn data_to_attr(&self) -> AttrValue {
×
275
        AttrValue::Payload(PropPayload::Any(Box::new(self.states.data.to_vec())))
×
276
    }
×
277
}
278

279
impl MockComponent for Chart {
280
    fn view(&mut self, render: &mut Frame, area: Rect) {
×
NEW
281
        if !self.common.display {
×
NEW
282
            return;
×
NEW
283
        }
×
284

NEW
285
        let normal_style = self.common.style;
×
286

287
        // Create widget
288
        // -- x axis
NEW
289
        let mut x_axis: Axis = Axis::default();
×
NEW
290
        if let Some((PropValue::F64(floor), PropValue::F64(ceil))) = self
×
NEW
291
            .props
×
NEW
292
            .get(Attribute::Custom(CHART_X_BOUNDS))
×
NEW
293
            .map(|x| x.unwrap_payload().unwrap_pair())
×
NEW
294
        {
×
NEW
295
            let why_using_vecs_when_you_can_use_useless_arrays: [f64; 2] = [floor, ceil];
×
NEW
296
            x_axis = x_axis.bounds(why_using_vecs_when_you_can_use_useless_arrays);
×
NEW
297
        }
×
NEW
298
        if let Some(PropPayload::Vec(labels)) = self
×
NEW
299
            .props
×
NEW
300
            .get(Attribute::Custom(CHART_X_LABELS))
×
NEW
301
            .map(|x| x.unwrap_payload())
×
302
        {
NEW
303
            x_axis = x_axis.labels(labels.iter().cloned().map(|x| Line::from(x.unwrap_str())));
×
NEW
304
        }
×
NEW
305
        if let Some(s) = self
×
NEW
306
            .props
×
NEW
307
            .get(Attribute::Custom(CHART_X_STYLE))
×
NEW
308
            .map(|x| x.unwrap_style())
×
NEW
309
        {
×
NEW
310
            x_axis = x_axis.style(s);
×
NEW
311
        }
×
NEW
312
        if let Some(title) = self
×
NEW
313
            .props
×
NEW
314
            .get(Attribute::Custom(CHART_X_TITLE))
×
NEW
315
            .map(|x| x.unwrap_string())
×
NEW
316
        {
×
NEW
317
            x_axis = x_axis.title(Span::styled(title, normal_style));
×
NEW
318
        }
×
319
        // -- y axis
NEW
320
        let mut y_axis: Axis = Axis::default();
×
NEW
321
        if let Some((PropValue::F64(floor), PropValue::F64(ceil))) = self
×
NEW
322
            .props
×
NEW
323
            .get(Attribute::Custom(CHART_Y_BOUNDS))
×
NEW
324
            .map(|x| x.unwrap_payload().unwrap_pair())
×
NEW
325
        {
×
NEW
326
            let why_using_vecs_when_you_can_use_useless_arrays: [f64; 2] = [floor, ceil];
×
NEW
327
            y_axis = y_axis.bounds(why_using_vecs_when_you_can_use_useless_arrays);
×
NEW
328
        }
×
NEW
329
        if let Some(PropPayload::Vec(labels)) = self
×
NEW
330
            .props
×
NEW
331
            .get(Attribute::Custom(CHART_Y_LABELS))
×
NEW
332
            .map(|x| x.unwrap_payload())
×
333
        {
NEW
334
            y_axis = y_axis.labels(labels.iter().cloned().map(|x| Line::from(x.unwrap_str())));
×
NEW
335
        }
×
NEW
336
        if let Some(s) = self
×
NEW
337
            .props
×
NEW
338
            .get(Attribute::Custom(CHART_Y_STYLE))
×
NEW
339
            .map(|x| x.unwrap_style())
×
NEW
340
        {
×
NEW
341
            y_axis = y_axis.style(s);
×
342
        }
×
NEW
343
        if let Some(title) = self
×
NEW
344
            .props
×
NEW
345
            .get(Attribute::Custom(CHART_Y_TITLE))
×
NEW
346
            .map(|x| x.unwrap_string())
×
NEW
347
        {
×
NEW
348
            y_axis = y_axis.title(Span::styled(title, normal_style));
×
NEW
349
        }
×
350

351
        // Get data
NEW
352
        let data: Vec<TuiDataset> = self.get_tui_data(self.states.cursor);
×
353
        // Build widget
NEW
354
        let mut widget: TuiChart = TuiChart::new(data)
×
NEW
355
            .style(normal_style)
×
NEW
356
            .x_axis(x_axis)
×
NEW
357
            .y_axis(y_axis);
×
358

NEW
359
        if let Some(block) = self.common.get_block() {
×
NEW
360
            widget = widget.block(block);
×
NEW
361
        }
×
362

363
        // Render
NEW
364
        render.render_widget(widget, area);
×
UNCOV
365
    }
×
366

367
    fn query(&self, attr: Attribute) -> Option<AttrValue> {
×
NEW
368
        if let Some(value) = self.common.get(attr) {
×
NEW
369
            return Some(value);
×
NEW
370
        }
×
371

372
        if attr == Attribute::Dataset {
×
373
            return Some(self.data_to_attr());
×
374
        }
×
375

376
        self.props.get(attr)
×
377
    }
×
378

379
    fn attr(&mut self, attr: Attribute, value: AttrValue) {
22✔
380
        if let Some(value) = self.common.set(attr, value) {
22✔
381
            if attr == Attribute::Dataset {
20✔
382
                self.data_from_attr(value);
10✔
383
                return;
10✔
384
            }
10✔
385
            self.props.set(attr, value);
10✔
386
        }
2✔
387
    }
22✔
388

389
    fn perform(&mut self, cmd: Cmd) -> CmdResult {
8✔
390
        if !self.is_disabled() {
8✔
391
            match cmd {
4✔
392
                Cmd::Move(Direction::Left) => {
2✔
393
                    self.states.move_cursor_left();
2✔
394
                }
2✔
395
                Cmd::Move(Direction::Right) => {
2✔
396
                    self.states.move_cursor_right(self.max_dataset_len());
2✔
397
                }
2✔
398
                Cmd::GoTo(Position::Begin) => {
2✔
399
                    self.states.reset_cursor();
2✔
400
                }
2✔
401
                Cmd::GoTo(Position::End) => {
2✔
402
                    self.states.cursor_at_end(self.max_dataset_len());
2✔
403
                }
2✔
404
                _ => {}
×
405
            }
406
        }
×
407
        CmdResult::None
8✔
408
    }
8✔
409

410
    fn state(&self) -> State {
2✔
411
        State::None
2✔
412
    }
2✔
413
}
414

415
#[cfg(test)]
416
mod test {
417

418
    use super::*;
419

420
    use pretty_assertions::assert_eq;
421
    use tuirealm::{
422
        props::HorizontalAlignment,
423
        ratatui::{symbols::Marker, widgets::GraphType},
424
    };
425

426
    #[test]
427
    fn test_components_chart_states() {
2✔
428
        let mut states: ChartStates = ChartStates::default();
2✔
429
        assert_eq!(states.cursor, 0);
2✔
430
        // Incr
431
        states.move_cursor_right(2);
2✔
432
        assert_eq!(states.cursor, 1);
2✔
433
        // At end
434
        states.move_cursor_right(2);
2✔
435
        assert_eq!(states.cursor, 1);
2✔
436
        // Decr
437
        states.move_cursor_left();
2✔
438
        assert_eq!(states.cursor, 0);
2✔
439
        // At begin
440
        states.move_cursor_left();
2✔
441
        assert_eq!(states.cursor, 0);
2✔
442
        // Move at end
443
        states.cursor_at_end(3);
2✔
444
        assert_eq!(states.cursor, 2);
2✔
445
        states.reset_cursor();
2✔
446
        assert_eq!(states.cursor, 0);
2✔
447
    }
2✔
448

449
    #[test]
450
    fn test_components_chart() {
2✔
451
        let mut component: Chart = Chart::default()
2✔
452
            .disabled(false)
2✔
453
            .background(Color::Reset)
2✔
454
            .foreground(Color::Reset)
2✔
455
            .borders(Borders::default())
2✔
456
            .title(
2✔
457
                Title::from("average temperatures in Udine").alignment(HorizontalAlignment::Center),
2✔
458
            )
1✔
459
            .x_bounds((0.0, 11.0))
2✔
460
            .x_labels(&[
2✔
461
                "january",
2✔
462
                "february",
2✔
463
                "march",
2✔
464
                "april",
2✔
465
                "may",
2✔
466
                "june",
2✔
467
                "july",
2✔
468
                "august",
2✔
469
                "september",
2✔
470
                "october",
2✔
471
                "november",
2✔
472
                "december",
2✔
473
            ])
2✔
474
            .x_style(Style::default().fg(Color::LightBlue))
2✔
475
            .x_title("Temperature (°C)")
2✔
476
            .y_bounds((-5.0, 35.0))
2✔
477
            .y_labels(&["-5", "0", "5", "10", "15", "20", "25", "30", "35"])
2✔
478
            .y_style(Style::default().fg(Color::LightYellow))
2✔
479
            .y_title("Month")
2✔
480
            .data([
2✔
481
                ChartDataset::default()
2✔
482
                    .name("Minimum")
2✔
483
                    .graph_type(GraphType::Scatter)
2✔
484
                    .marker(Marker::Braille)
2✔
485
                    .style(Style::default().fg(Color::Cyan))
2✔
486
                    .data(vec![
2✔
487
                        (0.0, -1.0),
2✔
488
                        (1.0, 1.0),
2✔
489
                        (2.0, 3.0),
2✔
490
                        (3.0, 7.0),
2✔
491
                        (4.0, 11.0),
2✔
492
                        (5.0, 15.0),
2✔
493
                        (6.0, 17.0),
2✔
494
                        (7.0, 17.0),
2✔
495
                        (8.0, 13.0),
2✔
496
                        (9.0, 9.0),
2✔
497
                        (10.0, 4.0),
2✔
498
                        (11.0, 0.0),
2✔
499
                    ]),
2✔
500
                ChartDataset::default()
2✔
501
                    .name("Maximum")
2✔
502
                    .graph_type(GraphType::Line)
2✔
503
                    .marker(Marker::Dot)
2✔
504
                    .style(Style::default().fg(Color::LightRed))
2✔
505
                    .data(vec![
2✔
506
                        (0.0, 7.0),
2✔
507
                        (1.0, 9.0),
2✔
508
                        (2.0, 13.0),
2✔
509
                        (3.0, 17.0),
2✔
510
                        (4.0, 22.0),
2✔
511
                        (5.0, 25.0),
2✔
512
                        (6.0, 28.0),
2✔
513
                        (7.0, 28.0),
2✔
514
                        (8.0, 24.0),
2✔
515
                        (9.0, 19.0),
2✔
516
                        (10.0, 13.0),
2✔
517
                        (11.0, 8.0),
2✔
518
                    ]),
2✔
519
            ]);
2✔
520
        // Commands
1✔
521
        assert_eq!(component.state(), State::None);
2✔
522
        // -> Right
523
        assert_eq!(
2✔
524
            component.perform(Cmd::Move(Direction::Right)),
2✔
525
            CmdResult::None
1✔
526
        );
1✔
527
        assert_eq!(component.states.cursor, 1);
2✔
528
        // <- Left
529
        assert_eq!(
2✔
530
            component.perform(Cmd::Move(Direction::Left)),
2✔
531
            CmdResult::None
1✔
532
        );
1✔
533
        assert_eq!(component.states.cursor, 0);
2✔
534
        // End
535
        assert_eq!(component.perform(Cmd::GoTo(Position::End)), CmdResult::None);
2✔
536
        assert_eq!(component.states.cursor, 11);
2✔
537
        // Home
538
        assert_eq!(
2✔
539
            component.perform(Cmd::GoTo(Position::Begin)),
2✔
540
            CmdResult::None
1✔
541
        );
1✔
542
        assert_eq!(component.states.cursor, 0);
2✔
543
        // component funcs
544
        assert_eq!(component.max_dataset_len(), 12);
2✔
545
        assert_eq!(component.is_disabled(), false);
2✔
546
        assert_eq!(component.get_tui_data(2).len(), 2);
2✔
547

548
        let comp = Chart::default().data([ChartDataset::default()
2✔
549
            .name("Maximum")
2✔
550
            .graph_type(GraphType::Line)
2✔
551
            .marker(Marker::Dot)
2✔
552
            .style(Style::default().fg(Color::LightRed))
2✔
553
            .data(vec![(0.0, 7.0)])]);
2✔
554
        assert!(!comp.get_tui_data(0).is_empty());
2✔
555

556
        // Update and test empty data
557
        component.states.cursor_at_end(12);
2✔
558
        component.attr(
2✔
559
            Attribute::Dataset,
2✔
560
            AttrValue::Payload(PropPayload::Any(Box::new(Vec::<ChartDataset>::new()))),
2✔
561
        );
1✔
562
        assert_eq!(component.max_dataset_len(), 0);
2✔
563
        // Cursor is reset
564
        assert_eq!(component.states.cursor, 0);
2✔
565
    }
2✔
566

567
    #[test]
568
    fn allowed_dataset_attrs() {
2✔
569
        let mut component = Chart::default();
2✔
570
        assert!(component.states.data.is_empty());
2✔
571

572
        // allow overwriting multiple datasets at once
573
        component.attr(
2✔
574
            Attribute::Dataset,
2✔
575
            AttrValue::Payload(PropPayload::Any(Box::new(vec![ChartDataset::default()]))),
2✔
576
        );
1✔
577
        assert_eq!(component.states.data.len(), 1);
2✔
578

579
        component.attr(
2✔
580
            Attribute::Dataset,
2✔
581
            AttrValue::Payload(PropPayload::Any(Box::new(vec![ChartDataset::default()]))),
2✔
582
        );
1✔
583
        assert_eq!(component.states.data.len(), 1);
2✔
584

585
        // allow overwriting using with just one dataset
586
        component.attr(
2✔
587
            Attribute::Dataset,
2✔
588
            AttrValue::Payload(PropPayload::Any(Box::new(ChartDataset::default()))),
2✔
589
        );
1✔
590
        assert_eq!(component.states.data.len(), 1);
2✔
591

592
        component.attr(
2✔
593
            Attribute::Dataset,
2✔
594
            AttrValue::Payload(PropPayload::Any(Box::new(ChartDataset::default()))),
2✔
595
        );
1✔
596
        assert_eq!(component.states.data.len(), 1);
2✔
597
    }
2✔
598
}
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