use tinyagents::{Diagnostic, Severity, SourceFile, SourceId, SourceMap, Span, TinyAgentsError};
#[test]
fn crlf_line_text_strips_the_trailing_carriage_return() {
let file = SourceFile::new("crlf.rag", "ab\r\ncd");
assert_eq!(file.line_count(), 2);
assert_eq!(file.line_text(1), Some("ab"));
assert_eq!(file.line_text(2), Some("cd"));
assert_eq!(file.line_text(3), None);
assert_eq!(file.line_range(3), None);
}
#[test]
fn location_columns_count_unicode_scalars_not_bytes() {
let file = SourceFile::new("u.rag", "αβ x");
let x_offset = file.text().find('x').unwrap();
assert_eq!(x_offset, 5);
assert_eq!(file.location(x_offset), (1, 4));
}
#[test]
fn snippet_clamps_a_span_that_points_past_the_end() {
let file = SourceFile::new("short.rag", "abc");
let past_end = Span::at(100, 200, 1, 50);
assert_eq!(file.snippet(past_end), "");
assert_eq!(file.location(999), (1, 4));
}
#[test]
fn span_merge_is_order_independent() {
let early = Span::at(2, 5, 1, 3);
let late = Span::at(10, 14, 2, 1);
let forward = early.merge(late);
let backward = late.merge(early);
assert_eq!(forward.start, 2);
assert_eq!(forward.end, 14);
assert_eq!(forward.line, 1);
assert_eq!(forward.column, 3);
assert_eq!(forward, backward);
}
#[test]
fn a_line_column_only_span_is_empty_and_has_no_length() {
let span = Span::new(4, 2);
assert!(span.is_empty());
assert_eq!(span.len(), 0);
assert_eq!((span.line, span.column), (4, 2));
}
#[test]
fn render_draws_a_block_for_the_primary_and_each_secondary_label() {
let source = "graph g {\n start missing\n}\n";
let file = SourceFile::new("flow.rag", source);
let start = source.find("missing").unwrap();
let primary = Span::at(start, start + "missing".len(), 2, 9);
let secondary = Span::at(0, 5, 1, 1);
let rendered = Diagnostic::error("unknown start node", primary)
.with_code("E-rag-start")
.with_primary_label("not declared")
.with_label(secondary, "graph begins here")
.render(&file);
assert_eq!(rendered.matches("-->").count(), 2, "{rendered}");
assert!(rendered.contains("not declared"), "{rendered}");
assert!(rendered.contains("graph begins here"), "{rendered}");
assert!(rendered.contains("error[E-rag-start]"), "{rendered}");
}
#[test]
fn rendering_a_span_past_the_end_of_the_source_does_not_panic() {
let file = SourceFile::new("tiny.rag", "graph g {}");
let rendered = Diagnostic::error("out of range", Span::at(500, 600, 9, 9))
.with_primary_label("here")
.render(&file);
assert!(rendered.contains("out of range"), "{rendered}");
}
#[test]
fn into_parse_error_resolves_line_and_column_from_a_byte_offset() {
let file = SourceFile::new("f.rag", "ab\ncd\n");
let c_offset = file.text().find('c').unwrap();
let span = Span::at(c_offset, c_offset + 1, 99, 99);
let err = Diagnostic::error("boom", span)
.with_primary_label("here")
.into_parse_error(Some(&file));
match err {
TinyAgentsError::Parse { line, column, .. } => assert_eq!((line, column), (2, 1)),
other => panic!("expected parse error, got {other:?}"),
}
}
#[test]
fn into_parse_error_uses_a_line_column_only_span_verbatim() {
let file = SourceFile::new("f.rag", "graph g {}");
let err = Diagnostic::error("headline", Span::new(3, 7))
.with_help("try this")
.into_parse_error(Some(&file));
match err {
TinyAgentsError::Parse {
line,
column,
message,
} => {
assert_eq!((line, column), (3, 7));
assert!(message.contains("--> 3:7"), "{message}");
assert!(message.contains("help: try this"), "{message}");
}
other => panic!("expected parse error, got {other:?}"),
}
}
#[test]
fn severity_labels_are_stable() {
assert_eq!(Severity::Error.label(), "error");
assert_eq!(Severity::Warning.label(), "warning");
assert_eq!(Severity::Note.label(), "note");
}
#[test]
fn a_source_map_assigns_sequential_ids_and_looks_files_up() {
let mut map = SourceMap::new();
assert!(map.is_empty());
let a = map.add("a.rag", "graph a {}");
let b = map.add("b.rag", "graph b {}");
assert_eq!(a, SourceId(0));
assert_eq!(b, SourceId(1));
assert_eq!(map.len(), 2);
assert_eq!(map.get(a).unwrap().name(), "a.rag");
assert_eq!(map.get(b).unwrap().text(), "graph b {}");
assert!(map.get(SourceId(5)).is_none());
assert_eq!(
map.files().map(SourceFile::name).collect::<Vec<_>>(),
vec!["a.rag", "b.rag"]
);
}