use std::collections::VecDeque;
pub(crate) fn enqueue_later(queue: &mut VecDeque<String>, text: String) -> Option<usize> {
if text.trim().is_empty() {
return None;
}
queue.push_back(text);
Some(queue.len())
}
pub(crate) fn pop_later(queue: &mut VecDeque<String>) -> Option<String> {
queue.pop_back()
}
pub(crate) fn clear_later(queue: &mut VecDeque<String>) -> usize {
let n = queue.len();
queue.clear();
n
}
pub(crate) fn drain_later_as_batch(queue: &mut VecDeque<String>) -> Option<(String, usize)> {
if queue.is_empty() {
return None;
}
let items: Vec<String> = queue.drain(..).collect();
let count = items.len();
Some((items.join("\n\n"), count))
}
#[cfg(test)]
mod tests {
use super::*;
fn empty() -> VecDeque<String> {
VecDeque::new()
}
fn seeded(items: &[&str]) -> VecDeque<String> {
items.iter().map(|s| s.to_string()).collect()
}
#[test]
fn enqueue_later_pushes_text_and_returns_new_count() {
let mut q = empty();
assert_eq!(enqueue_later(&mut q, "first".into()), Some(1));
assert_eq!(enqueue_later(&mut q, "second".into()), Some(2));
assert_eq!(enqueue_later(&mut q, "third".into()), Some(3));
assert_eq!(q.len(), 3);
}
#[test]
fn enqueue_later_ignores_empty_string() {
let mut q = empty();
assert_eq!(enqueue_later(&mut q, String::new()), None);
assert!(q.is_empty(), "empty input must not be queued");
}
#[test]
fn enqueue_later_ignores_whitespace_only_string() {
let mut q = empty();
assert_eq!(enqueue_later(&mut q, " \n\t \n".into()), None);
assert!(q.is_empty(), "whitespace-only input must not be queued");
}
#[test]
fn enqueue_later_preserves_text_with_internal_whitespace() {
let mut q = empty();
assert_eq!(enqueue_later(&mut q, " hello world ".into()), Some(1));
assert_eq!(q.front().unwrap(), " hello world ");
}
#[test]
fn pop_later_returns_most_recently_pushed_item() {
let mut q = seeded(&["alpha", "beta", "gamma"]);
assert_eq!(pop_later(&mut q), Some("gamma".into()));
assert_eq!(pop_later(&mut q), Some("beta".into()));
assert_eq!(pop_later(&mut q), Some("alpha".into()));
assert_eq!(pop_later(&mut q), None);
}
#[test]
fn pop_later_on_empty_queue_returns_none() {
let mut q = empty();
assert_eq!(pop_later(&mut q), None);
}
#[test]
fn clear_later_drains_queue_and_returns_count() {
let mut q = seeded(&["a", "b", "c", "d"]);
let dropped = clear_later(&mut q);
assert_eq!(dropped, 4);
assert!(q.is_empty(), "queue must be empty after clear");
}
#[test]
fn clear_later_on_empty_returns_zero() {
let mut q = empty();
assert_eq!(clear_later(&mut q), 0);
}
#[test]
fn drain_as_batch_returns_none_when_empty() {
let mut q = empty();
assert!(drain_later_as_batch(&mut q).is_none());
}
#[test]
fn drain_as_batch_preserves_fifo_order() {
let mut q = seeded(&["one", "two", "three", "four"]);
let (joined, count) = drain_later_as_batch(&mut q).unwrap();
assert_eq!(count, 4);
assert_eq!(joined, "one\n\ntwo\n\nthree\n\nfour");
assert!(q.is_empty(), "drain must empty the queue");
}
#[test]
fn drain_as_batch_single_item_has_no_separator() {
let mut q = seeded(&["only"]);
let (joined, count) = drain_later_as_batch(&mut q).unwrap();
assert_eq!(count, 1);
assert_eq!(
joined, "only",
"single item must not gain a trailing \\n\\n"
);
}
#[test]
fn drain_as_batch_uses_double_newline_separator() {
let mut q = seeded(&["a", "b"]);
let (joined, _) = drain_later_as_batch(&mut q).unwrap();
assert!(
joined.contains("\n\n"),
"batch separator must be a blank line: got {joined:?}"
);
assert!(
!joined.contains("\n\n\n"),
"no triple newlines: got {joined:?}"
);
}
#[test]
fn push_to_later_queue_during_inference_enqueues() {
let mut q = empty();
enqueue_later(&mut q, "deferred 1".into()).unwrap();
enqueue_later(&mut q, "deferred 2".into()).unwrap();
assert_eq!(q.len(), 2);
assert_eq!(q.front().unwrap(), "deferred 1");
assert_eq!(q.back().unwrap(), "deferred 2");
}
#[test]
fn up_arrow_pops_from_later_queue() {
let mut q = empty();
enqueue_later(&mut q, "first".into());
enqueue_later(&mut q, "second".into());
assert_eq!(pop_later(&mut q), Some("second".into()));
assert_eq!(q.len(), 1);
}
#[test]
fn ctrl_u_clears_later_queue() {
let mut q = seeded(&["one", "two", "three"]);
let n = clear_later(&mut q);
assert_eq!(n, 3);
assert!(q.is_empty());
}
#[test]
fn queue_empty_after_all_items_popped() {
let mut q = seeded(&["x", "y"]);
pop_later(&mut q);
pop_later(&mut q);
assert!(q.is_empty());
assert_eq!(pop_later(&mut q), None);
}
#[test]
fn queue_items_sent_in_fifo_order_after_inference() {
let mut q = empty();
enqueue_later(&mut q, "step 1".into());
enqueue_later(&mut q, "step 2".into());
enqueue_later(&mut q, "step 3".into());
let (batched, _) = drain_later_as_batch(&mut q).unwrap();
let s1 = batched.find("step 1").expect("step 1 in batch");
let s2 = batched.find("step 2").expect("step 2 in batch");
let s3 = batched.find("step 3").expect("step 3 in batch");
assert!(s1 < s2 && s2 < s3, "FIFO order required: got {batched:?}");
}
#[test]
fn full_lifecycle_push_pop_clear_drain() {
let mut q = empty();
enqueue_later(&mut q, "a".into());
enqueue_later(&mut q, "b".into());
enqueue_later(&mut q, "c".into());
assert_eq!(pop_later(&mut q), Some("c".into()));
assert_eq!(clear_later(&mut q), 2);
assert!(drain_later_as_batch(&mut q).is_none());
enqueue_later(&mut q, "fresh".into());
let (batched, count) = drain_later_as_batch(&mut q).unwrap();
assert_eq!(count, 1);
assert_eq!(batched, "fresh");
}
}