Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub use backend::MockLLMBackend;
pub use bus::MockAgentBus;
pub use clock::{Clock, MockClock, SystemClock};
pub use report::{
JsonFormatter, ReportFormatter, TestCaseResult, TestReport, TestReportBuilder, TestStatus,
TextFormatter,
JsonFormatter, MarkdownFormatter, ReportFormatter, TestCaseResult, TestReport,
TestReportBuilder, TestStatus, TextFormatter,
};
pub use tools::MockTool;
53 changes: 53 additions & 0 deletions tests/src/report/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,56 @@ impl ReportFormatter for TextFormatter {
buf
}
}

/// Renders a report as a Markdown summary.
pub struct MarkdownFormatter;

impl ReportFormatter for MarkdownFormatter {
fn format(&self, report: &TestReport) -> String {
let mut buf = String::new();
buf.push_str(&format!("# Test Report: {}\n\n", report.suite_name));
buf.push_str("## Summary\n\n");
buf.push_str("| Total | Passed | Failed | Skipped | Pass Rate | Duration (ms) |\n");
buf.push_str("| --- | --- | --- | --- | --- | --- |\n");
buf.push_str(&format!(
"| {} | {} | {} | {} | {:.1}% | {} |\n\n",
report.total(),
report.passed(),
report.failed(),
report.skipped(),
report.pass_rate() * 100.0,
report.total_duration.as_millis()
));

buf.push_str("## Results\n\n");
buf.push_str("| Status | Test Case | Duration (ms) | Error |\n");
buf.push_str("| --- | --- | --- | --- |\n");

for result in &report.results {
let status = match result.status {
TestStatus::Passed => "passed",
TestStatus::Failed => "failed",
TestStatus::Skipped => "skipped",
};
let error = result
.error
.as_deref()
.map(escape_markdown_cell)
.unwrap_or_else(|| "-".to_string());

buf.push_str(&format!(
"| {} | {} | {} | {} |\n",
status,
escape_markdown_cell(&result.name),
result.duration.as_millis(),
error
));
}

buf
}
}

fn escape_markdown_cell(input: &str) -> String {
input.replace('|', "\\|").replace('\n', "<br/>")
}
2 changes: 1 addition & 1 deletion tests/src/report/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ mod format;
mod types;

pub use builder::TestReportBuilder;
pub use format::{JsonFormatter, ReportFormatter, TextFormatter};
pub use format::{JsonFormatter, MarkdownFormatter, ReportFormatter, TextFormatter};
pub use types::{TestCaseResult, TestReport, TestStatus};
56 changes: 54 additions & 2 deletions tests/tests/report_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use std::sync::Arc;
use std::time::Duration;

use mofa_testing::{
JsonFormatter, MockClock, ReportFormatter, TestCaseResult, TestReport, TestReportBuilder,
TestStatus, TextFormatter,
JsonFormatter, MarkdownFormatter, MockClock, ReportFormatter, TestCaseResult, TestReport,
TestReportBuilder, TestStatus, TextFormatter,
};

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -273,6 +273,58 @@ fn json_formatter_includes_metadata() {
assert_eq!(parsed["results"][0]["metadata"]["key"], "val");
}

// ===========================================================================
// MarkdownFormatter
// ===========================================================================

#[test]
fn markdown_formatter_contains_summary_and_results_table() {
let r = mixed_report();
let output = MarkdownFormatter.format(&r);

assert!(output.contains("# Test Report: mixed"));
assert!(output.contains("## Summary"));
assert!(output.contains("| Total | Passed | Failed | Skipped | Pass Rate | Duration (ms) |"));
assert!(output.contains("| 5 | 2 | 2 | 1 | 40.0% | 110 |"));
assert!(output.contains("## Results"));
assert!(output.contains("| Status | Test Case | Duration (ms) | Error |"));
assert!(output.contains("| failed | b | 50 | boom |"));
assert!(output.contains("| skipped | d | 0 | - |"));
}

#[test]
fn markdown_formatter_escapes_special_cells() {
let report = TestReport {
suite_name: "pipes".into(),
results: vec![make_result(
"test | case",
TestStatus::Failed,
12,
Some("line1\nline2 | detail"),
)],
total_duration: Duration::from_millis(12),
timestamp: 0,
};

let output = MarkdownFormatter.format(&report);
assert!(output.contains("test \\| case"));
assert!(output.contains("line1<br/>line2 \\| detail"));
}

#[test]
fn markdown_formatter_empty_report_still_renders_tables() {
let r = TestReport {
suite_name: "empty".into(),
results: vec![],
total_duration: Duration::ZERO,
timestamp: 0,
};
let output = MarkdownFormatter.format(&r);
assert!(output.contains("# Test Report: empty"));
assert!(output.contains("| 0 | 0 | 0 | 0 | 100.0% | 0 |"));
assert!(output.contains("## Results"));
}

// ===========================================================================
// TextFormatter
// ===========================================================================
Expand Down
Loading