use perl_position_tracking::{
ByteSpan, LineIndex, LineStartsCache, Position, PositionMapper, Range, WirePosition, WireRange,
};
#[test]
fn byte_span_hash_is_consistent() {
use std::collections::HashSet;
let a = ByteSpan::new(0, 5);
let b = ByteSpan::new(0, 5);
let c = ByteSpan::new(1, 5);
let mut set = HashSet::new();
set.insert(a);
set.insert(b);
set.insert(c);
assert_eq!(set.len(), 2);
}
#[test]
fn byte_span_serde_round_trip_non_zero() -> Result<(), serde_json::Error> {
let span = ByteSpan::new(10, 20);
let json = serde_json::to_string(&span)?;
let back: ByteSpan = serde_json::from_str(&json)?;
assert_eq!(span, back);
Ok(())
}
#[test]
fn byte_span_try_slice_oob_at_start_returns_none() {
let span = ByteSpan::new(5, 50); assert!(span.try_slice("short").is_none());
}
#[test]
fn position_advance_char_multibyte_increments_byte_and_column() {
let mut pos = Position::new(0, 1, 1);
pos.advance_char('日'); assert_eq!(pos.byte, 3);
assert_eq!(pos.line, 1);
assert_eq!(pos.column, 2);
}
#[test]
fn position_advance_char_emoji_increments_byte_by_four() {
let mut pos = Position::new(0, 1, 1);
pos.advance_char('💖'); assert_eq!(pos.byte, 4);
assert_eq!(pos.line, 1);
assert_eq!(pos.column, 2);
}
#[test]
fn range_is_empty_false_for_non_empty() {
let start = Position::new(0, 1, 1);
let end = Position::new(5, 1, 6);
let r = Range::new(start, end);
assert!(!r.is_empty());
}
#[test]
fn range_is_empty_true_when_bytes_equal() {
let p = Position::new(7, 2, 3);
let r = Range::empty(p);
assert!(r.is_empty());
}
#[test]
fn range_len_returns_byte_difference() {
let start = Position::new(3, 1, 4);
let end = Position::new(10, 1, 11);
let r = Range::new(start, end);
assert_eq!(r.len(), 7);
}
#[test]
fn range_len_empty_is_zero() {
let p = Position::new(5, 1, 6);
let r = Range::empty(p);
assert_eq!(r.len(), 0);
}
#[test]
fn range_contains_position_inside_returns_true() {
let start = Position::new(0, 1, 1);
let end = Position::new(10, 1, 11);
let r = Range::new(start, end);
let mid = Position::new(5, 1, 6);
assert!(r.contains(mid));
}
#[test]
fn range_contains_position_at_end_returns_false() {
let start = Position::new(0, 1, 1);
let end = Position::new(10, 1, 11);
let r = Range::new(start, end);
assert!(!r.contains(end));
}
#[test]
fn line_starts_cache_cr_only_position_round_trip() {
let src = "abc\rdef\rghi";
let cache = LineStartsCache::new(src);
assert_eq!(cache.offset_to_position(src, 4), (1, 0));
assert_eq!(cache.offset_to_position(src, 8), (2, 0));
assert_eq!(cache.position_to_offset(src, 1, 0), 4);
assert_eq!(cache.position_to_offset(src, 2, 0), 8);
}
#[test]
fn line_starts_cache_position_to_offset_strips_cr_from_line_end() {
let src = "ab\rcd";
let cache = LineStartsCache::new(src);
let off = cache.position_to_offset(src, 1, 0);
assert_eq!(off, 3);
let off2 = cache.position_to_offset(src, 1, 1);
assert_eq!(off2, 4);
}
#[test]
fn line_index_position_to_offset_last_line_no_newline() {
let idx = LineIndex::new("abc".to_string());
assert_eq!(idx.position_to_offset(0, 2), Some(2));
assert_eq!(idx.position_to_offset(0, 3), Some(3)); }
#[test]
fn line_index_position_to_offset_crlf_text() {
let idx = LineIndex::new("ab\r\ncd".to_string());
assert_eq!(idx.position_to_offset(1, 0), Some(4));
assert_eq!(idx.position_to_offset(1, 1), Some(5));
}
#[test]
fn mapper_byte_to_lsp_pos_mid_multibyte_char_snaps_to_char_start() {
let mapper = PositionMapper::new("a😀b");
let pos2 = mapper.byte_to_lsp_pos(2);
let pos3 = mapper.byte_to_lsp_pos(3);
let pos4 = mapper.byte_to_lsp_pos(4);
assert_eq!(pos2.line, 0);
assert_eq!(pos2.character, 1);
assert_eq!(pos3.line, 0);
assert_eq!(pos3.character, 1);
assert_eq!(pos4.line, 0);
assert_eq!(pos4.character, 1);
}
#[test]
fn mapper_lsp_pos_to_byte_line_beyond_end_returns_none() {
let mapper = PositionMapper::new("hello");
assert!(mapper.lsp_pos_to_byte(WirePosition { line: 99, character: 0 }).is_none());
}
#[test]
fn mapper_slice_empty_range_returns_empty() {
let mapper = PositionMapper::new("hello world");
let s = mapper.slice(3, 3);
assert!(s.is_empty());
}
#[test]
fn mapper_slice_past_end_clamps() {
let mapper = PositionMapper::new("abc");
let s = mapper.slice(5, 5);
assert!(s.is_empty());
}
#[test]
fn wire_position_new_sets_fields() {
let wp = WirePosition::new(3, 7);
assert_eq!(wp.line, 3);
assert_eq!(wp.character, 7);
}
#[test]
fn wire_position_default_is_origin() {
let wp = WirePosition::default();
assert_eq!(wp.line, 0);
assert_eq!(wp.character, 0);
}
#[test]
fn wire_range_whole_document_multiline_text() {
let src = "abc\ndef";
let wr = WireRange::whole_document(src);
assert_eq!(wr.start.line, 0);
assert_eq!(wr.start.character, 0);
assert_eq!(wr.end.line, 1);
assert_eq!(wr.end.character, 3); }
#[test]
fn offset_to_utf16_line_col_empty_text_returns_origin() {
let (line, col) = perl_position_tracking::offset_to_utf16_line_col("", 0);
assert_eq!((line, col), (0, 0));
}
#[test]
fn offset_to_utf16_line_col_beyond_empty_text_clamps() {
let (line, col) = perl_position_tracking::offset_to_utf16_line_col("", 99);
assert_eq!((line, col), (0, 0));
}
#[test]
fn utf16_line_col_to_offset_col_zero_returns_line_start() {
let text = "hello\nworld\nfoo";
assert_eq!(perl_position_tracking::utf16_line_col_to_offset(text, 0, 0), 0);
assert_eq!(perl_position_tracking::utf16_line_col_to_offset(text, 1, 0), 6);
assert_eq!(perl_position_tracking::utf16_line_col_to_offset(text, 2, 0), 12);
}
#[test]
fn line_starts_cache_new_rope_single_line() {
let rope = ropey::Rope::from_str("hello");
let cache = LineStartsCache::new_rope(&rope);
let (line, col) = cache.offset_to_position_rope(&rope, 3);
assert_eq!(line, 0);
assert_eq!(col, 3);
}
#[test]
fn line_starts_cache_new_rope_multi_line() {
let rope = ropey::Rope::from_str("abc\ndef\n");
let cache = LineStartsCache::new_rope(&rope);
let (line, col) = cache.offset_to_position_rope(&rope, 4);
assert_eq!(line, 1);
assert_eq!(col, 0);
}