use std::fmt;
pub struct SnapshotPrettyPrinter {
config: SnapshotConfig,
}
impl SnapshotPrettyPrinter {
pub fn new(config: SnapshotConfig) -> Self {
SnapshotPrettyPrinter { config }
}
pub fn render(&self, value: &dyn std::fmt::Display) -> String {
if self.config.pretty_print {
self.format_pretty(value)
} else {
value.to_string()
}
}
fn format_pretty(&self, value: &dyn std::fmt::Display) -> String {
let str_value: _ = value.to_string();
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(&str_value) {
return serde_json::to_string_pretty(&json_value).unwrap_or(str_value);
}
str_value
}
pub fn render_inline(&self, value: &dyn std::fmt::Display) -> String {
let rendered: _ = self.render(value);
format!("Snapshot({})", rendered)
}
pub fn render_diff(&self, old: &str, new: &str) -> String {
let mut diff = String::new();
diff.push_str("Expected:\n");
diff.push_str(&self.render(&old));
diff.push_str("\n\nReceived:\n");
diff.push_str(&self.render(&new));
diff
}
pub fn render_comparison(&self, comparison: &SnapshotComparison) -> String {
if comparison.matches {
format!(
"✓ Snapshot matched for '{}'",
comparison.name
)
} else {
let mut result = format!("✗ Snapshot mismatch for '{}'\n", comparison.name));
result.push_str("\nExpected:\n");
if let Some(expected) = &comparison.expected {
result.push_str(&self.render(&expected));
} else {
result.push_str("(no snapshot found)");
}
result.push_str("\n\nReceived:\n");
result.push_str(&self.render(&comparison.received));
result.push('\n');
if let Some(diff) = &comparison.diff {
result.push_str("\nDiff:\n");
result.push_str(diff);
}
result
}
}
pub fn render_metadata(&self, metadata: &SnapshotMetadata) -> String {
let mut result = String::new();
result.push_str(&format!("Snapshot: {}\n", metadata.name));
result.push_str(&format!("Version: {}\n", metadata.version));
result.push_str(&format!("Created: {}\n", metadata.created_at));
result.push_str(&format!("Updated: {}\n", metadata.updated_at));
result.push_str(&format!("Lines: {}\n", metadata.line_count));
result.push_str(&format!("Size: {} bytes\n", metadata.size_bytes));
result
}
}
#[derive(Debug, Clone)]
pub struct SnapshotFormatOptions {
pub pretty: bool,
pub inline: bool,
pub show_diff: bool,
pub show_metadata: bool,
pub theme: SnapshotTheme,
}
#[derive(Debug, Clone)]
pub enum SnapshotTheme {
Plain,
Colored,
Json,
}
impl Default for SnapshotFormatOptions {
fn default() -> Self {
SnapshotFormatOptions {
pretty: true,
inline: false,
show_diff: false,
show_metadata: false,
theme: SnapshotTheme::Plain,
}
}
}
pub struct SnapshotRenderer {
config: SnapshotConfig,
printer: SnapshotPrettyPrinter,
}
impl SnapshotRenderer {
pub fn new(config: SnapshotConfig) -> Self {
SnapshotRenderer {
printer: SnapshotPrettyPrinter::new(config.clone()),
config,
}
}
pub fn render_test_result(
&self,
name: &str,
received: &dyn std::fmt::Display,
comparison: &SnapshotComparison,
) -> String {
let mut result = String::new();
if comparison.matches {
result.push_str("✓ ");
} else {
result.push_str("✗ ");
}
result.push_str(name);
result.push('\n');
result.push_str(&self.printer.render_comparison(comparison));
result.push('\n');
result
}
pub fn render_summary(&self, comparisons: &[SnapshotComparison]) -> String {
let mut result = String::new();
result.push_str("Snapshot Test Summary\n");
result.push_str("===================\n\n");
let matched: _ = comparisons.iter().filter(|c| c.matches).count();
let mismatched: _ = comparisons.len() - matched;
result.push_str(&format!("Total: {} snapshots\n", comparisons.len());
result.push_str(&format!("Matched: {}\n", matched));
result.push_str(&format!("Mismatched: {}\n", mismatched));
if mismatched > 0 {
result.push_str("\nMismatched snapshots:\n");
for comparison in comparisons.iter().filter(|c| !c.matches) {
result.push_str(&format!(" - {}\n", comparison.name));
}
}
result
}
pub fn render_update_notice(&self) -> String {
let mut result = String::new();
result.push_str("⚠ Snapshot update required\n");
result.push_str("Run tests with --update-snapshots to update snapshots\n");
result
}
pub fn render_help(&self) -> String {
let mut result = String::new();
result.push_str("Snapshot Testing\n");
result.push_str("================\n\n");
result.push_str("Snapshot tests compare serialized values to stored snapshots.\n\n");
result.push_str("Usage:\n");
result.push_str(" - Run tests: amber test\n");
result.push_str(" - Update snapshots: amber test --update-snapshots\n");
result.push_str(" - Inline snapshots: amber test --inline-snapshots\n\n");
result.push_str("Configuration:\n");
result.push_str(" - Set SNAPSHOT_UPDATE=true to auto-update\n");
result.push_str(" - Set SNAPSHOT_INLINE=true for inline snapshots\n");
result.push_str(" - Customize serializer in code\n");
result
}
}
#[cfg(test)]
mod tests {
use std::collections::{HashMap, BTreeMap};
#[test]
fn test_pretty_print_json() {
let config: _ = SnapshotConfig::default();
let printer: _ = SnapshotPrettyPrinter::new(config);
let value: _ = r#"{"name":"test","value":42}"#;
let rendered: _ = printer.render(&value);
assert!(rendered.contains("\"name\": \"test\""));
}
#[test]
fn test_render_comparison_match() {
let config: _ = SnapshotConfig::default();
let renderer: _ = SnapshotRenderer::new(config);
let comparison: _ = SnapshotComparison::new_match("test".to_string(), "value".to_string());
let rendered: _ = renderer.render_test_result("test", &"value", &comparison);
assert!(rendered.contains("✓"));
assert!(rendered.contains("test"));
}
#[test]
fn test_render_comparison_mismatch() {
let config: _ = SnapshotConfig::default();
let renderer: _ = SnapshotRenderer::new(config);
let comparison: _ = SnapshotComparison::new_mismatch(
"test".to_string(),
"new_value".to_string(),
"old_value".to_string(),
);
let rendered: _ = renderer.render_test_result("test", &"new_value", &comparison);
assert!(rendered.contains("✗"));
assert!(rendered.contains("Expected:"));
assert!(rendered.contains("Received:"));
}
#[test]
fn test_render_summary() {
let config: _ = SnapshotConfig::default();
let renderer: _ = SnapshotRenderer::new(config);
let comparisons: _ = vec![
SnapshotComparison::new_match("test1".to_string(), "value1".to_string()),
SnapshotComparison::new_match("test2".to_string(), "value2".to_string()),
SnapshotComparison::new_mismatch(
"test3".to_string(),
"new_value".to_string(),
"old_value".to_string(),
),
];
let rendered: _ = renderer.render_summary(&comparisons);
assert!(rendered.contains("Total: 3"));
assert!(rendered.contains("Matched: 2"));
assert!(rendered.contains("Mismatched: 1"));
assert!(rendered.contains("test3"));
}
#[test]
fn test_render_metadata() {
let config: _ = SnapshotConfig::default();
let printer: _ = SnapshotPrettyPrinter::new(config);
let metadata: _ = SnapshotMetadata {
name: "test".to_string(),
version: "1".to_string(),
created_at: "1234567890".to_string(),
updated_at: "1234567890".to_string(),
line_count: 10,
size_bytes: 100,
};
let rendered: _ = printer.render_metadata(&metadata);
assert!(rendered.contains("Snapshot: test"));
assert!(rendered.contains("Lines: 10"));
assert!(rendered.contains("Size: 100 bytes"));
}
}