Skip to main content

grammar_kit/
testing.rs

1//! Utilities for testing parsers generated by `syn-grammar`.
2//!
3//! This module provides a fluent API for testing parsing results,
4//! asserting success/failure, and checking error messages.
5
6use std::fmt::{Debug, Display};
7
8// A wrapper around Result to write fluent tests.
9pub struct TestResult<T, E> {
10    inner: Result<T, E>,
11    context: Option<String>,
12}
13
14impl<T: Debug, E: Display + Debug> TestResult<T, E> {
15    pub fn new(result: Result<T, E>) -> Self {
16        Self {
17            inner: result,
18            context: None,
19        }
20    }
21
22    pub fn with_context(mut self, context: &str) -> Self {
23        self.context = Some(context.to_string());
24        self
25    }
26
27    fn format_context(&self) -> String {
28        self.context
29            .as_ref()
30            .map(|c| format!("\nContext:  {}", c))
31            .unwrap_or_default()
32    }
33
34    // 1. Asserts success and returns the value.
35    pub fn assert_success(self) -> T {
36        let ctx = self.format_context();
37        match self.inner {
38            Ok(val) => val,
39            Err(e) => {
40                panic!(
41                    "\n🔴 TEST FAILED (Expected Success, but got Error):{}\nMessage:  {}\nError Debug: {:?}\n", 
42                    ctx, e, e
43                );
44            }
45        }
46    }
47
48    // 2. Asserts success AND checks the value directly.
49    // Returns a nice diff output if values do not match.
50    pub fn assert_success_is<Exp>(self, expected: Exp) -> T
51    where
52        T: PartialEq<Exp>,
53        Exp: Debug,
54    {
55        let ctx = self.format_context();
56        let val = self.assert_success();
57        if val != expected {
58            panic!(
59                "\n🔴 TEST FAILED (Value Mismatch):{}\nExpected: {:?}\nGot:      {:?}\n",
60                ctx, expected, val
61            );
62        }
63        val
64    }
65
66    // 3. Asserts success AND checks the value using a closure.
67    // Useful for complex assertions or when PartialEq is not implemented.
68    pub fn assert_success_with<F>(self, f: F) -> T
69    where
70        F: FnOnce(&T),
71    {
72        let val = self.assert_success();
73        f(&val);
74        val
75    }
76
77    // 4. Asserts success AND checks the Debug representation matches.
78    // Useful for syn types where PartialEq is often missing or complicated by Spans.
79    pub fn assert_success_debug(self, expected_debug: &str) -> T {
80        let ctx = self.format_context();
81        let val = self.assert_success();
82        let actual_debug = format!("{:?}", val);
83        if actual_debug != expected_debug {
84            panic!(
85                "\n🔴 TEST FAILED (Debug Mismatch):{}\nExpected: {:?}\nGot:      {:?}\n",
86                ctx, expected_debug, actual_debug
87            );
88        }
89        val
90    }
91
92    // 5. Asserts failure and returns the error.
93    pub fn assert_failure(self) -> E {
94        let ctx = self.format_context();
95        match self.inner {
96            Ok(val) => {
97                panic!(
98                    "\n🔴 TEST FAILED (Expected Failure, but got Success):{}\nParsed Value: {:?}\n",
99                    ctx, val
100                );
101            }
102            Err(e) => e,
103        }
104    }
105
106    // 6. Asserts failure AND checks if the message contains a specific text.
107    pub fn assert_failure_contains(self, expected_msg_part: &str) {
108        let ctx = self.format_context();
109        let err = self.assert_failure();
110        let actual_msg = err.to_string();
111        if !actual_msg.contains(expected_msg_part) {
112            panic!(
113                "\n🔴 TEST FAILED (Error Message Mismatch):{}\nExpected part: {:?}\nActual msg:    {:?}\nError Debug:   {:?}\n", 
114                ctx, expected_msg_part, actual_msg, err
115            );
116        }
117    }
118
119    // 7. Asserts success AND checks if the string representation contains a specific substring.
120    pub fn assert_success_contains(self, expected_part: &str) -> T
121    where
122        T: Display,
123    {
124        let ctx = self.format_context();
125        let val = self.assert_success();
126        let val_str = val.to_string();
127        if !val_str.contains(expected_part) {
128            panic!(
129                "\n🔴 TEST FAILED (Content Mismatch):{}\nExpected to contain: {:?}\nGot:                 {:?}\n",
130                ctx, expected_part, val_str
131            );
132        }
133        val
134    }
135}
136
137pub trait Testable<T, E> {
138    fn test(self) -> TestResult<T, E>;
139}
140
141#[cfg(feature = "syn")]
142impl<T: Debug> Testable<T, syn::Error> for syn::Result<T> {
143    fn test(self) -> TestResult<T, syn::Error> {
144        TestResult::new(self)
145    }
146}