boxworks_testing/
lib.rs

1//! Testing utilities for Boxworks-based code.
2//!
3//! This crate makes writing tests for Boxworks code easier.
4//! It provides the [`assert_box_eq`] macro,
5//!     which validates that two Boxworks data structures are the same.
6//! The data structures can be provided as Rust values from [`boxworks::ds`],
7//!     or as Boxworks language strings.
8//! If there is a mismatch, the diff is printed nicely.
9//!
10//! ## Example
11//!
12//! ```rust
13//! # use boxworks::ds;
14//! # use boxworks_testing::assert_box_eq;
15//! let v_box = ds::VBox {
16//!     list: vec![ds::HBox {
17//!         list: vec![
18//!             ds::Char { char: 'A', font: 33 }.into(),
19//!             ds::Char { char: 'Z', font: 33 }.into(),
20//!         ],
21//!         ..Default::default()
22//!     }.into()],
23//!     ..Default::default()
24//! };
25//! assert_box_eq!(
26//!     v_box,
27//!     r#"
28//!     vbox(
29//!       content=[
30//!         hbox(
31//!           content=[
32//!             chars("AZ", 33)
33//!           ]
34//!         )
35//!       ]
36//!     )
37//!     "#,
38//! );
39//! ```
40
41use boxworks::ds;
42use boxworks::lang;
43use boxworks::lang::convert::ToBoxLang;
44
45#[macro_export]
46macro_rules! assert_box_eq {
47    ($left:expr, $right:expr$(,)?) => {{
48        let lhs: boxworks_testing::Value = $left.into();
49        let rhs: boxworks_testing::Value = $right.into();
50        boxworks_testing::assert_eq(lhs, rhs);
51    }};
52}
53
54pub enum Value {
55    String(String),
56    Box(Vec<ds::Horizontal>),
57}
58
59impl From<&str> for Value {
60    fn from(value: &str) -> Self {
61        Value::String(value.into())
62    }
63}
64
65impl From<String> for Value {
66    fn from(value: String) -> Self {
67        Value::String(value)
68    }
69}
70
71impl From<ds::VBox> for Value {
72    fn from(value: ds::VBox) -> Self {
73        Value::Box(vec![ds::Horizontal::VBox(value)])
74    }
75}
76
77impl From<ds::HBox> for Value {
78    fn from(value: ds::HBox) -> Self {
79        Value::Box(vec![ds::Horizontal::HBox(value)])
80    }
81}
82
83impl From<Vec<ds::Horizontal>> for Value {
84    fn from(value: Vec<ds::Horizontal>) -> Self {
85        Value::Box(value)
86    }
87}
88
89pub fn assert_eq(lhs: Value, rhs: Value) {
90    let (lhs_list, lhs_s) = normalize(lhs, "lhs.box");
91    let (rhs_list, rhs_s) = normalize(rhs, "rhs.box");
92    // We first diff the boxlang representation because this is clearer.
93    use pretty_assertions::assert_eq;
94    assert_eq!(lhs_s, rhs_s);
95    // But we also diff the data structure, in case the ds to lang process is lossy
96    // and causes different lists be the same.
97    assert_eq!(rhs_list, lhs_list);
98}
99
100fn normalize(val: Value, side: &str) -> (Vec<ds::Horizontal>, String) {
101    let list = match val {
102        Value::String(s) => match lang::parse_horizontal_list(&s.clone()) {
103            Ok(v) => v,
104            Err(err) => {
105                let source = ariadne::Source::from(s);
106                let cache: (&str, _) = (side, source);
107                for err in err {
108                    err.ariadne_report(side).eprint(cache.clone()).unwrap();
109                }
110                panic!("failed to parse boxlang input; errors printed above.")
111            }
112        },
113        Value::Box(v) => v,
114    };
115    let mut s = String::new();
116    for elem in &list {
117        use std::fmt::Write;
118        write!(&mut s, "{}", elem.to_box_lang()).unwrap();
119    }
120    (list, s)
121}