Skip to main content

metro/
events.rs

1use std::error;
2use std::fmt;
3use std::io::{self, Write};
4use std::iter;
5use std::string::FromUtf8Error;
6
7/// `Event`s are produced automatically by using [`Metro`],
8/// but can also be created and used manually.
9///
10/// An `Event` specifies an action and is used when rendering
11/// the metro lines graph.
12///
13/// [`Metro`]: struct.Metro.html
14///
15/// # Example
16///
17/// ```no_run
18/// use metro::Event::*;
19///
20/// let events = [
21///     Station(0, "Station 1"),
22///     Station(0, "Station 2"),
23///     Station(0, "Station 3"),
24///     SplitTrack(0, 1),
25///     Station(1, "Station 4"),
26///     SplitTrack(1, 2),
27///     Station(1, "Station 5"),
28///     Station(2, "Station 6"),
29///     Station(0, "Station 7"),
30///     Station(1, "Station 8"),
31///     Station(2, "Station 9"),
32///     SplitTrack(2, 3),
33///     SplitTrack(3, 4),
34///     Station(5, "Station 10 (Detached)"),
35///     JoinTrack(4, 0),
36///     Station(3, "Station 11"),
37///     StopTrack(1),
38///     Station(0, "Station 12"),
39///     Station(2, "Station 13"),
40///     Station(3, "Station 14"),
41///     JoinTrack(3, 0),
42///     Station(2, "Station 15"),
43///     StopTrack(2),
44///     Station(0, "Station 16"),
45/// ];
46///
47/// let string = metro::to_string(&events).unwrap();
48///
49/// println!("{}", string);
50/// ```
51///
52/// This will output the following:
53///
54/// ```text
55/// * Station 1
56/// * Station 2
57/// * Station 3
58/// |\
59/// | * Station 4
60/// | |\
61/// | * | Station 5
62/// | | * Station 6
63/// * | | Station 7
64/// | * | Station 8
65/// | | * Station 9
66/// | | |\
67/// | | | |\
68/// | | | | | Station 10 (Detached)
69/// | |_|_|/
70/// |/| | |
71/// | | | * Station 11
72/// | " | |
73/// |  / /
74/// * | | Station 12
75/// | * | Station 13
76/// | | * Station 14
77/// | |/
78/// |/|
79/// | * Station 15
80/// | "
81/// * Station 16
82/// ```
83#[derive(Clone, Debug)]
84pub enum Event<'a> {
85    /// `StartTrack(track_id)`
86    ///
87    /// - If `track_id` already exists, then this event does nothing.
88    ///
89    /// New `track_id`s are added rightmost.
90    ///
91    /// ## Output Example
92    ///
93    /// Given 3 tracks `0, 1, 2` then `StartTrack(4)` would render as:
94    ///
95    /// ```text
96    /// | | |
97    /// | | | |
98    /// | | | |
99    /// ```
100    StartTrack(usize),
101
102    /// `StartTracks(track_ids)`
103    ///
104    /// - If a `track_id` from `track_ids` already exists, then it is ignored.
105    /// - If all `track_ids` already exists, then this event does nothing.
106    ///
107    /// New `track_id`s are added rightmost.
108    ///
109    /// ## Output Example
110    ///
111    /// Given 3 tracks `0, 1, 2` then `StartTracks(&[4, 5])` would render as:
112    ///
113    /// ```text
114    /// | | |
115    /// | | | | |
116    /// | | | | |
117    /// ```
118    StartTracks(&'a [usize]),
119
120    /// `StopTrack(track_id)`
121    ///
122    /// - If `track_id` does not exist, then this event does nothing.
123    ///
124    /// All rails to the right of `track_id`, are pulled to the left.
125    ///
126    /// ## Output Example
127    ///
128    /// Given 3 tracks `0, 1, 2` then `StopTrack(1)` would render as:
129    ///
130    /// ```text
131    /// | | |
132    /// | " |
133    /// |  /
134    /// | |
135    /// ```
136    StopTrack(usize),
137
138    /// `Station(track_id, text)`
139    ///
140    /// - If the `track_id` does not exist, then `text` is still
141    /// rendered, just not tied to any track.
142    ///
143    /// ## Output Example
144    ///
145    /// Given 3 tracks `0, 1, 2` then `Station(1, "Hello World")` would render as:
146    ///
147    /// ```text
148    /// | | |
149    /// | * | Hello World
150    /// | | |
151    /// ```
152    ///
153    /// If the `track_id` does not exist, then no rail is highlighted.
154    /// Thus `Station(10, "Hello World")` would render as:
155    ///
156    /// ```text
157    /// | | |
158    /// | | | Hello World
159    /// | | |
160    /// ```
161    Station(usize, &'a str),
162
163    /// `SplitTrack(from_track_id, new_track_id)`
164    ///
165    /// Creates a new track diverging from `from_track_id` to the right.
166    /// All rails to the right of `from_track_id`, are pushed to the
167    /// right to make space for the new track.
168    ///
169    /// - If `from_track_id` does not exist, then this event is the
170    /// same as `StartTrack(new_track_id)`.
171    /// - If `new_track_id` already exists, then this event does nothing.
172    ///
173    /// ## Output Example
174    ///
175    /// Given 3 tracks `0, 1, 2` then `SplitTrack(1, 4)` would render as:
176    ///
177    /// ```text
178    /// | | |
179    /// | |\ \
180    /// | | | |
181    /// ```
182    SplitTrack(usize, usize),
183
184    /// `JoinTrack(from_track_id, to_track_id)`
185    ///
186    /// Joins `from_track_id` and `to_track_id`
187    /// resulting in the `from_track_id` being removed.
188    ///
189    /// The rails are joined towards the leftmost rail.
190    ///
191    /// - If `from_track_id` does not exist, then this event does nothing.
192    /// - If `to_track_id` does not exist, then it turns into `StopTrack(from_track_id)`.
193    /// - If `from_track_id` and `to_track_id` are the same, then it turns into `StopTrack(from_track_id)`
194    ///
195    /// The track ID (`from_track_id`) can be reused for
196    /// a new track after this event.
197    ///
198    /// ## Output Example
199    ///
200    /// Given 3 tracks `0, 1, 2` then `JoinTrack(1, 0)` would render as:
201    ///
202    /// ```text
203    /// | | |
204    /// |/ /
205    /// | |
206    /// ```
207    ///
208    /// Given 6 tracks `0, 1, 2, 3, 4, 5` then `JoinTrack(4, 0)` would render as:
209    ///
210    /// ```text
211    /// | | | | | |
212    /// | |_|_|/ /
213    /// |/| | | |
214    /// | | | | |
215    /// ```
216    JoinTrack(usize, usize),
217
218    /// `NoEvent` produces one row of rails.
219    ///
220    /// ## Output Example
221    ///
222    /// Given 3 tracks `0, 1, 2` then `NoEvent` would render as:
223    ///
224    /// ```text
225    /// | | |
226    /// ```
227    NoEvent,
228}
229
230/// Write `&[`[`Event`]`]` to [`<W: io::Write>`].
231/// Defines a default track with `track_id` of `0`.
232///
233/// *[See also `Metro::to_writer`.][`Metro::to_writer`]*
234///
235/// *See also [`to_string`] and [`to_vec`].*
236///
237/// [`to_vec`]: fn.to_vec.html
238/// [`to_string`]: fn.to_string.html
239///
240/// [`Event`]: enum.Event.html
241///
242/// [`Metro::to_writer`]: struct.Metro.html#method.to_writer
243///
244/// [`<W: io::Write>`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html
245pub fn to_writer<W: Write>(mut writer: W, events: &[Event]) -> Result<(), Error> {
246    let mut tracks = vec![0];
247
248    for event in events {
249        use Event::*;
250        match event {
251            &StartTrack(track_id) => {
252                if !tracks.contains(&track_id) {
253                    tracks.push(track_id);
254
255                    let line = iter::repeat("|")
256                        .take(tracks.len())
257                        .collect::<Vec<_>>()
258                        .join(" ");
259
260                    writeln!(&mut writer, "{}", line)?;
261                }
262            }
263
264            &StartTracks(track_ids) => {
265                let mut render = false;
266
267                for track_id in track_ids.iter() {
268                    if !tracks.contains(track_id) {
269                        tracks.push(*track_id);
270
271                        render = true;
272                    }
273                }
274
275                if render {
276                    let line = iter::repeat("|")
277                        .take(tracks.len())
278                        .collect::<Vec<_>>()
279                        .join(" ");
280
281                    writeln!(&mut writer, "{}", line)?;
282                }
283            }
284
285            &StopTrack(track_id) => stop_track(&mut writer, &mut tracks, track_id)?,
286
287            &Station(track_id, station_name) => {
288                let line = tracks
289                    .iter()
290                    .map(|&id| if id == track_id { "*" } else { "|" })
291                    .collect::<Vec<_>>()
292                    .join(" ");
293
294                writeln!(&mut writer, "{} {}", line, station_name)?;
295            }
296
297            &SplitTrack(from_track_id, new_track_id) => {
298                if !tracks.contains(&new_track_id) {
299                    let from_track_index = tracks.iter().position(|&id| id == from_track_id);
300
301                    if let Some(from_track_index) = from_track_index {
302                        let line = (0..tracks.len())
303                            .map(|i| {
304                                use std::cmp::Ordering::*;
305                                match i.cmp(&from_track_index) {
306                                    Greater => "\\",
307                                    Equal => "|\\",
308                                    Less => "|",
309                                }
310                            })
311                            .collect::<Vec<_>>()
312                            .join(" ");
313
314                        writeln!(&mut writer, "{}", line)?;
315
316                        tracks.insert(from_track_index + 1, new_track_id);
317                    } else {
318                        tracks.push(new_track_id);
319
320                        let line = iter::repeat("|")
321                            .take(tracks.len())
322                            .collect::<Vec<_>>()
323                            .join(" ");
324
325                        writeln!(&mut writer, "{}", line)?;
326                    }
327                }
328            }
329
330            &JoinTrack(from_track_id, to_track_id) => {
331                let from_track_index = tracks.iter().position(|&id| id == from_track_id);
332
333                if from_track_id == to_track_id {
334                    stop_track(&mut writer, &mut tracks, from_track_id)?;
335                    continue;
336                }
337
338                if let Some(from_track_index) = from_track_index {
339                    let to_track_index = tracks.iter().position(|&id| id == to_track_id);
340
341                    if let Some(to_track_index) = to_track_index {
342                        let left_index = from_track_index.min(to_track_index);
343                        let right_index = from_track_index.max(to_track_index);
344
345                        if (right_index - left_index) == 1 {
346                            let line = (0..tracks.len())
347                                .filter_map(|i| {
348                                    if i > right_index {
349                                        Some("/")
350                                    } else if i == left_index {
351                                        Some("|/")
352                                    } else if i != right_index {
353                                        Some("|")
354                                    } else {
355                                        None
356                                    }
357                                })
358                                .collect::<Vec<_>>()
359                                .join(" ");
360
361                            writeln!(&mut writer, "{}", line)?;
362                        } else {
363                            let line = (0..tracks.len())
364                                .filter_map(|i| {
365                                    if i > right_index {
366                                        Some(" /")
367                                    } else if i == right_index {
368                                        None
369                                    } else if i >= (right_index - 1) {
370                                        Some("|/")
371                                    } else if i > left_index {
372                                        Some("|_")
373                                    } else {
374                                        Some("| ")
375                                    }
376                                })
377                                .collect::<Vec<_>>()
378                                .concat();
379
380                            writeln!(&mut writer, "{}", line)?;
381
382                            let track_count = tracks.len() - 1;
383                            let line = (0..track_count)
384                                .map(|i| {
385                                    if i == left_index {
386                                        "|/"
387                                    } else if i == (track_count - 1) {
388                                        "|"
389                                    } else {
390                                        "| "
391                                    }
392                                })
393                                .collect::<Vec<_>>()
394                                .concat();
395
396                            writeln!(&mut writer, "{}", line)?;
397                        }
398
399                        tracks.remove(from_track_index);
400                    } else {
401                        stop_track(&mut writer, &mut tracks, from_track_id)?;
402                    }
403                }
404            }
405
406            NoEvent => {
407                let line = iter::repeat("|")
408                    .take(tracks.len())
409                    .collect::<Vec<_>>()
410                    .join(" ");
411
412                writeln!(&mut writer, "{}", line)?;
413            }
414        }
415    }
416
417    Ok(())
418}
419
420fn stop_track<W: Write>(
421    mut writer: W,
422    tracks: &mut Vec<usize>,
423    track_id: usize,
424) -> Result<(), Error> {
425    if let Some(index) = tracks.iter().position(|&id| id == track_id) {
426        let line = (0..tracks.len())
427            .map(|i| if i == index { "\"" } else { "|" })
428            .collect::<Vec<_>>()
429            .join(" ");
430
431        writeln!(&mut writer, "{}", line)?;
432
433        if index != (tracks.len() - 1) {
434            let line = (0..tracks.len())
435                .map(|i| {
436                    use std::cmp::Ordering::*;
437                    match i.cmp(&index) {
438                        Greater => "/",
439                        Equal => "",
440                        Less => "|",
441                    }
442                })
443                .collect::<Vec<_>>()
444                .join(" ");
445
446            writeln!(&mut writer, "{}", line)?;
447        }
448
449        tracks.remove(index);
450    }
451
452    Ok(())
453}
454
455/// Write `&[`[`Event`]`]` to [`Vec<u8>`].
456/// Defines a default track with `track_id` of `0`.
457///
458/// *[See also `Metro::to_vec`.][`Metro::to_vec`]*
459///
460/// *See also [`to_string`] and [`to_writer`].*
461///
462/// [`to_writer`]: fn.to_writer.html
463/// [`to_string`]: fn.to_string.html
464///
465/// [`Event`]: enum.Event.html
466///
467/// [`Metro::to_vec`]: struct.Metro.html#method.to_vec
468///
469/// [`Vec<u8>`]: https://doc.rust-lang.org/stable/std/vec/struct.Vec.html
470#[inline]
471pub fn to_vec(events: &[Event]) -> Result<Vec<u8>, Error> {
472    let mut vec = Vec::new();
473    to_writer(&mut vec, events)?;
474    Ok(vec)
475}
476
477/// Write `&[`[`Event`]`]` to [`String`].
478/// Defines a default track with `track_id` of `0`.
479///
480/// *[See also `Metro::to_string`.][`Metro::to_string`]*
481///
482/// *See also [`to_vec`] and [`to_writer`].*
483///
484/// [`to_writer`]: fn.to_writer.html
485/// [`to_vec`]: fn.to_vec.html
486///
487/// [`Event`]: enum.Event.html
488///
489/// [`Metro::to_string`]: struct.Metro.html#method.to_string
490///
491/// [`String`]: https://doc.rust-lang.org/stable/std/string/struct.String.html
492#[inline]
493pub fn to_string(events: &[Event]) -> Result<String, Error> {
494    let vec = to_vec(events)?;
495    Ok(String::from_utf8(vec)?)
496}
497
498/// `Error` is an error that can be returned by
499/// [`to_string`], [`to_vec`], and [`to_writer`].
500///
501/// [`to_writer`]: fn.to_writer.html
502/// [`to_vec`]: fn.to_vec.html
503/// [`to_string`]: fn.to_string.html
504#[derive(Debug)]
505pub enum Error {
506    /// [See `std::io::Error`][io::Error].
507    ///
508    /// [io::Error]: https://doc.rust-lang.org/std/io/struct.Error.html
509    IoError(io::Error),
510
511    /// [See `std::string::FromUtf8Error`][FromUtf8Error].
512    ///
513    /// [FromUtf8Error]: https://doc.rust-lang.org/std/string/struct.FromUtf8Error.html
514    FromUtf8Error(FromUtf8Error),
515}
516
517impl fmt::Display for Error {
518    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
519        use Error::*;
520        match self {
521            IoError(err) => err.fmt(fmt),
522            FromUtf8Error(err) => err.fmt(fmt),
523        }
524    }
525}
526
527impl error::Error for Error {
528    #[inline]
529    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
530        use Error::*;
531        match self {
532            IoError(ref err) => Some(err),
533            FromUtf8Error(ref err) => Some(err),
534        }
535    }
536}
537
538impl From<io::Error> for Error {
539    #[inline]
540    fn from(err: io::Error) -> Self {
541        Self::IoError(err)
542    }
543}
544
545impl From<FromUtf8Error> for Error {
546    #[inline]
547    fn from(err: FromUtf8Error) -> Self {
548        Self::FromUtf8Error(err)
549    }
550}
551
552#[cfg(test)]
553mod tests {
554    use super::to_string;
555    use super::Event::*;
556
557    #[test]
558    fn start_track() {
559        let events = [StartTrack(1)];
560        let string = to_string(&events).unwrap();
561
562        assert_eq!(string, "| |\n");
563    }
564
565    #[test]
566    fn start_track_already_exists() {
567        let events = [StartTrack(0)];
568        let string = to_string(&events).unwrap();
569
570        assert_eq!(string, "");
571    }
572
573    #[test]
574    fn start_track_already_exists2() {
575        #[rustfmt::skip]
576        let events = [
577            StartTrack(1),
578            StartTrack(2),
579            StartTrack(1),
580            StartTrack(2),
581        ];
582        let string = to_string(&events).unwrap();
583
584        assert_eq!(string, "| |\n| | |\n");
585    }
586
587    #[test]
588    fn start_track_default() {
589        let events = [StartTrack(0)];
590        let string = to_string(&events).unwrap();
591
592        assert_eq!(string, "");
593    }
594
595    #[test]
596    fn event_start_track_default2() {
597        let events = [StartTrack(1)];
598        let string = to_string(&events).unwrap();
599
600        assert_eq!(string, "| |\n");
601    }
602
603    #[test]
604    fn event_start_tracks() {
605        let events = [StartTracks(&[1, 2, 3])];
606        let string = to_string(&events).unwrap();
607
608        assert_eq!(string, "| | | |\n");
609    }
610
611    #[test]
612    fn start_tracks_some_already_exist() {
613        let events = [StartTracks(&[0, 1, 2])];
614        let string = to_string(&events).unwrap();
615
616        assert_eq!(string, "| | |\n");
617    }
618
619    #[test]
620    fn start_tracks_all_already_exist() {
621        let events = [StartTracks(&[0, 0, 0])];
622        let string = to_string(&events).unwrap();
623
624        assert_eq!(string, "");
625    }
626
627    #[test]
628    fn stop_track() {
629        let events = [StopTrack(0)];
630        let string = to_string(&events).unwrap();
631
632        assert_eq!(string, "\"\n");
633    }
634
635    #[test]
636    fn stop_track_does_not_exist() {
637        let events = [StopTrack(1)];
638        let string = to_string(&events).unwrap();
639
640        assert_eq!(string, "");
641    }
642
643    #[test]
644    fn stop_track_left() {
645        #[rustfmt::skip]
646        let events = [
647            StartTracks(&[0, 1, 2, 3, 4]),
648            StopTrack(0),
649            NoEvent,
650        ];
651        let string = to_string(&events).unwrap();
652
653        assert_eq!(
654            string,
655            r#"| | | | |
656" | | | |
657 / / / /
658| | | |
659"#
660        );
661    }
662
663    #[test]
664    fn stop_track_middle() {
665        #[rustfmt::skip]
666        let events = [
667            StartTracks(&[0, 1, 2, 3, 4]),
668            StopTrack(2),
669            NoEvent,
670        ];
671        let string = to_string(&events).unwrap();
672
673        assert_eq!(
674            string,
675            r#"| | | | |
676| | " | |
677| |  / /
678| | | |
679"#
680        );
681    }
682
683    #[test]
684    fn stop_track_right() {
685        #[rustfmt::skip]
686        let events = [
687            StartTracks(&[0, 1, 2, 3, 4]),
688            StopTrack(4),
689            NoEvent,
690        ];
691        let string = to_string(&events).unwrap();
692
693        assert_eq!(
694            string,
695            r#"| | | | |
696| | | | "
697| | | |
698"#
699        );
700    }
701
702    #[test]
703    fn station() {
704        let events = [
705            StartTracks(&[0, 1, 2]),
706            Station(0, "Station 1"),
707            Station(1, "Station 2"),
708            Station(2, "Station 3"),
709            Station(0, "Station 4"),
710            Station(1, "Station 5"),
711            Station(2, "Station 6"),
712        ];
713        let string = to_string(&events).unwrap();
714
715        assert_eq!(
716            string,
717            r#"| | |
718* | | Station 1
719| * | Station 2
720| | * Station 3
721* | | Station 4
722| * | Station 5
723| | * Station 6
724"#
725        );
726    }
727
728    #[test]
729    fn station_non_existing_track() {
730        let events = [
731            StartTracks(&[0, 1, 2]),
732            Station(0, "Station 1"),
733            Station(1, "Station 2"),
734            Station(2, "Station 3"),
735            Station(3, "Station 4"),
736            Station(4, "Station 5"),
737            Station(5, "Station 6"),
738            Station(std::usize::MAX, "Station 7"),
739        ];
740        let string = to_string(&events).unwrap();
741
742        assert_eq!(
743            string,
744            r#"| | |
745* | | Station 1
746| * | Station 2
747| | * Station 3
748| | | Station 4
749| | | Station 5
750| | | Station 6
751| | | Station 7
752"#
753        );
754    }
755
756    #[test]
757    fn split_track() {
758        let events = [
759            SplitTrack(0, 1),
760            NoEvent,
761            SplitTrack(0, 2),
762            SplitTrack(1, 3),
763            SplitTrack(3, 4),
764        ];
765        let string = to_string(&events).unwrap();
766
767        assert_eq!(
768            string,
769            r#"|\
770| |
771|\ \
772| | |\
773| | | |\
774"#
775        );
776    }
777
778    #[test]
779    fn split_track_non_existing_from_track() {
780        let events1 = [
781            SplitTrack(1, 2),
782            SplitTrack(3, 4),
783            Station(2, "2"),
784            Station(4, "4"),
785        ];
786        let events2 = [
787            StartTrack(2),
788            StartTrack(4),
789            Station(2, "2"),
790            Station(4, "4"),
791        ];
792
793        let string1 = to_string(&events1).unwrap();
794        let string2 = to_string(&events2).unwrap();
795
796        assert_eq!(string1, string2);
797    }
798
799    #[test]
800    fn split_track_already_existing_new_track() {
801        let events1 = [
802            StartTracks(&[0, 1, 2]),
803            SplitTrack(0, 1),
804            SplitTrack(0, 2),
805            SplitTrack(3, 4),
806            Station(0, "0"),
807            Station(1, "1"),
808            Station(2, "2"),
809            Station(3, "3"),
810            Station(4, "4"),
811        ];
812        let events2 = [
813            StartTracks(&[0, 1, 2]),
814            StartTrack(4),
815            Station(0, "0"),
816            Station(1, "1"),
817            Station(2, "2"),
818            Station(3, "3"),
819            Station(4, "4"),
820        ];
821
822        let string1 = to_string(&events1).unwrap();
823        let string2 = to_string(&events2).unwrap();
824
825        assert_eq!(string1, string2);
826    }
827
828    #[test]
829    fn split_track_same_from_and_new_track() {
830        let events = [SplitTrack(0, 0)];
831        let string = to_string(&events).unwrap();
832
833        assert_eq!(string, "");
834
835        #[rustfmt::skip]
836        let events1 = [
837            StartTracks(&[0, 1, 2]),
838            SplitTrack(1, 1),
839            SplitTrack(0, 2),
840        ];
841        let events2 = [StartTracks(&[0, 1, 2])];
842
843        let string1 = to_string(&events1).unwrap();
844        let string2 = to_string(&events2).unwrap();
845
846        assert_eq!(string1, string2);
847    }
848
849    #[test]
850    fn join_track_zero_between() {
851        #[rustfmt::skip]
852        let events = [
853            StartTracks(&[0, 1, 2]),
854            JoinTrack(1, 0),
855            NoEvent,
856        ];
857        let string = to_string(&events).unwrap();
858
859        assert_eq!(
860            string,
861            r#"| | |
862|/ /
863| |
864"#
865        );
866    }
867
868    #[test]
869    fn join_track_one_between() {
870        #[rustfmt::skip]
871        let events = [
872            StartTracks(&[0, 1, 2]),
873            JoinTrack(2, 0),
874            NoEvent,
875        ];
876        let string = to_string(&events).unwrap();
877
878        assert_eq!(
879            string,
880            r#"| | |
881| |/
882|/|
883| |
884"#
885        );
886    }
887
888    #[test]
889    fn join_track_many_between() {
890        #[rustfmt::skip]
891        let events = [
892            StartTracks(&[0, 1, 2, 3, 4]),
893            JoinTrack(4, 0),
894            NoEvent,
895        ];
896        let string = to_string(&events).unwrap();
897
898        assert_eq!(
899            string,
900            r#"| | | | |
901| |_|_|/
902|/| | |
903| | | |
904"#
905        );
906    }
907
908    #[test]
909    fn join_track_always_leftmost() {
910        let events1 = [
911            StartTracks(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
912            JoinTrack(4, 1),
913            JoinTrack(5, 0),
914            JoinTrack(8, 7),
915            JoinTrack(9, 6),
916            NoEvent,
917        ];
918
919        let events2 = events1
920            .iter()
921            .cloned()
922            .map(|event| match event {
923                JoinTrack(from, to) => JoinTrack(to, from),
924                _ => event,
925            })
926            .collect::<Vec<_>>();
927
928        let string1 = to_string(&events1).unwrap();
929        let string2 = to_string(&events2).unwrap();
930
931        // Note these only visually looks the same,
932        // the actual deleted track is not the same.
933        assert_eq!(string1, string2);
934    }
935
936    // The above `join_track` tests are all `existing_from_track_existing_to_track`
937
938    #[test]
939    fn join_track_non_existing_from_track_existing_to_track() {
940        let events1 = [
941            StartTracks(&[0, 1, 2, 3, 4]),
942            JoinTrack(5, 0),
943            JoinTrack(10, 1),
944        ];
945        let events2 = [StartTracks(&[0, 1, 2, 3, 4])];
946
947        let string1 = to_string(&events1).unwrap();
948        let string2 = to_string(&events2).unwrap();
949
950        assert_eq!(string1, string2);
951    }
952
953    #[test]
954    fn join_track_non_existing_from_track_non_existing_to_track() {
955        let events1 = [
956            StartTracks(&[0, 1, 2, 3, 4]),
957            JoinTrack(5, 6),
958            JoinTrack(10, 11),
959        ];
960        let events2 = [StartTracks(&[0, 1, 2, 3, 4])];
961
962        let string1 = to_string(&events1).unwrap();
963        let string2 = to_string(&events2).unwrap();
964
965        assert_eq!(string1, string2);
966    }
967
968    #[test]
969    fn join_track_existing_from_track_non_existing_to_track() {
970        let events1 = [
971            StartTracks(&[0, 1, 2, 3, 4]),
972            JoinTrack(0, 5),
973            JoinTrack(2, 10),
974        ];
975        #[rustfmt::skip]
976        let events2 = [
977            StartTracks(&[0, 1, 2, 3, 4]),
978            StopTrack(0),
979            StopTrack(2),
980        ];
981
982        let string1 = to_string(&events1).unwrap();
983        let string2 = to_string(&events2).unwrap();
984
985        assert_eq!(string1, string2);
986    }
987
988    #[test]
989    fn join_track_same_from_and_to_track() {
990        let events1 = [
991            StartTracks(&[0, 1, 2, 3, 4]),
992            JoinTrack(0, 0),
993            JoinTrack(2, 2),
994            JoinTrack(10, 10),
995        ];
996        #[rustfmt::skip]
997        let events2 = [
998            StartTracks(&[0, 1, 2, 3, 4]),
999            StopTrack(0),
1000            StopTrack(2),
1001        ];
1002
1003        let string1 = to_string(&events1).unwrap();
1004        let string2 = to_string(&events2).unwrap();
1005
1006        assert_eq!(string1, string2);
1007    }
1008
1009    #[test]
1010    fn no_event() {
1011        let events = [NoEvent, NoEvent, NoEvent];
1012        let string = to_string(&events).unwrap();
1013
1014        assert_eq!(string, "|\n|\n|\n");
1015
1016        #[rustfmt::skip]
1017        let events = [
1018            StartTracks(&[0, 1, 2]),
1019            NoEvent,
1020            NoEvent,
1021            NoEvent,
1022        ];
1023        let string = to_string(&events).unwrap();
1024
1025        assert_eq!(string, "| | |\n| | |\n| | |\n| | |\n");
1026    }
1027}