mod common;
use common::*;
use std::sync::Arc;
#[tokio::test]
async fn test_rapid_alternating_scrolls() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..60).map(|i| (1000 + i, 50))).await?;
let sm = Arc::new(ScrollManager::<TestMessageView>::new(
&ctx,
"true",
"timestamp DESC",
50,
2.0,
500,
)?);
let mut r = MockRenderer::new(sm.clone(), 500);
tokio::spawn({
let sm = sm.clone();
async move { sm.start().await }
});
let vs = r.next_render().await?;
r.assert(&vs, 30, 1030..=1059, None, true, false, true, 1050, 1059);
assert_eq!(r.scroll_offset, 1000);
r.scroll_up_and_expect(
100, 30, 1030..=1059, None, true, false, false, 1048, 1057, 900, None, ).await?;
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Backward);
for _ in 0..10 {
r.up_no_render(50, 1047, 1056).await; r.down_no_render(50, 1048, 1057).await; }
assert_eq!(r.scroll_offset, 900);
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Backward);
let (first, last, _, _) = r.visible_range();
assert!(first < last, "Items should be ordered: first={} last={}", first, last);
Ok(())
}
#[tokio::test]
async fn test_rapid_pagination_triggers() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..60).map(|i| (1000 + i, 50))).await?;
let sm = Arc::new(ScrollManager::<TestMessageView>::new(
&ctx,
"true",
"timestamp DESC",
50,
2.0,
500,
)?);
let mut r = MockRenderer::new(sm.clone(), 500);
tokio::spawn({
let sm = sm.clone();
async move { sm.start().await }
});
let vs = r.next_render().await?;
r.assert(&vs, 30, 1030..=1059, None, true, false, true, 1050, 1059);
r.scroll_up_and_expect(
400, 30, 1030..=1059, None,
true, false, false, 1042, 1051, 600,
None,
).await?;
let vs = r.scroll_up_and_expect(
100, 50, 1010..=1059, Some(1049),
true, true, false, 1040, 1049, 1500,
Some("TRUE AND \"timestamp\" <= 1059 ORDER BY timestamp DESC LIMIT 51"),
).await?;
assert!(!vs.should_auto_scroll, "should_auto_scroll should be false after exiting Live mode");
r.up_no_render(100, 1038, 1047).await;
r.up_no_render(400, 1030, 1039).await;
r.up_no_render(100, 1028, 1037).await;
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Backward);
let (first, last, _, _) = r.visible_range();
assert!(first < last, "Items should be ordered");
Ok(())
}
#[tokio::test]
async fn test_intersection_anchoring_backward() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..60).map(|i| (1000 + i, 50))).await?;
let sm = Arc::new(ScrollManager::<TestMessageView>::new(
&ctx,
"true",
"timestamp DESC",
50,
2.0,
500,
)?);
let mut r = MockRenderer::new(sm.clone(), 500);
tokio::spawn({
let sm = sm.clone();
async move { sm.start().await }
});
let vs = r.next_render().await?;
r.assert(&vs, 30, 1030..=1059, None, true, false, true, 1050, 1059);
r.scroll_up_and_expect(
400, 30, 1030..=1059, None, true, false, false, 1042, 1051, 600,
None,
).await?;
let vs = r.scroll_up_and_expect(
100, 50, 1010..=1059, Some(1049),
true, true, false, 1040, 1049, 1500,
Some("TRUE AND \"timestamp\" <= 1059 ORDER BY timestamp DESC LIMIT 51"),
).await?;
let intersection = vs.intersection.as_ref().expect("Should have intersection");
let ts = timestamps(&vs);
let intersection_ts = ts[intersection.index];
assert!(intersection_ts >= 1020 && intersection_ts <= 1059,
"Intersection {} should be in new window", intersection_ts);
assert_eq!(intersection.direction, ankurah_virtual_scroll::LoadDirection::Backward);
Ok(())
}
#[tokio::test]
async fn test_intersection_anchoring_forward() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..60).map(|i| (1000 + i, 50))).await?;
let sm = Arc::new(ScrollManager::<TestMessageView>::new(
&ctx,
"true",
"timestamp DESC",
50,
2.0,
500,
)?);
let mut r = MockRenderer::new(sm.clone(), 500);
tokio::spawn({
let sm = sm.clone();
async move { sm.start().await }
});
let vs = r.next_render().await?;
r.assert(&vs, 30, 1030..=1059, None, true, false, true, 1050, 1059);
let renders = r.scroll_up_collect(500).await;
assert!(!renders.is_empty(), "scroll_up should produce at least one render");
let last = renders.last().unwrap();
assert_eq!(last.items.len(), 50, "should have 50 items after backward pagination");
assert!(!last.should_auto_scroll, "should_auto_scroll should be false");
let _ = r.scroll_up_collect(500).await;
let _ = r.scroll_up_collect(500).await;
let _ = r.scroll_up_collect(500).await;
let _ = r.scroll_down_collect(500).await;
let _ = r.scroll_down_collect(500).await;
let _ = r.scroll_down_collect(500).await;
let _renders = r.scroll_down_collect(500).await;
let final_mode = sm.mode();
assert!(
final_mode == ankurah_virtual_scroll::ScrollMode::Backward
|| final_mode == ankurah_virtual_scroll::ScrollMode::Forward
|| final_mode == ankurah_virtual_scroll::ScrollMode::Live,
"should be in a valid mode: {:?}",
final_mode
);
Ok(())
}
#[tokio::test]
async fn test_selection_predicates() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..60).map(|i| (1000 + i, 50))).await?;
let sm = Arc::new(ScrollManager::<TestMessageView>::new(
&ctx,
"true",
"timestamp DESC",
50,
2.0,
500,
)?);
let mut r = MockRenderer::new(sm.clone(), 500);
tokio::spawn({
let sm = sm.clone();
async move { sm.start().await }
});
let vs = r.next_render().await?;
r.assert(&vs, 30, 1030..=1059, None, true, false, true, 1050, 1059);
let selection = sm.current_selection();
assert!(selection.contains("ORDER BY timestamp DESC"),
"Live mode should order DESC: {}", selection);
assert!(selection.contains("LIMIT 30"),
"Live mode should limit to live_window: {}", selection);
r.scroll_up_and_expect(
400, 30, 1030..=1059, None, true, false, false, 1042, 1051, 600,
None,
).await?;
r.scroll_up_and_expect(
100, 50, 1010..=1059, Some(1049),
true, true, false, 1040, 1049, 1500,
Some("TRUE AND \"timestamp\" <= 1059 ORDER BY timestamp DESC LIMIT 51"),
).await?;
let selection = sm.current_selection();
assert!(selection.contains("\"timestamp\" <= 1059"),
"Backward should have cursor constraint: {}", selection);
assert!(selection.contains("ORDER BY timestamp DESC"),
"Backward should order DESC: {}", selection);
assert!(selection.contains("LIMIT 51"),
"Backward limit should be full_window+1: {}", selection);
Ok(())
}
#[tokio::test]
async fn test_selection_predicate_forward() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..60).map(|i| (1000 + i, 50))).await?;
let sm = Arc::new(ScrollManager::<TestMessageView>::new(
&ctx,
"true",
"timestamp DESC",
50,
2.0,
500,
)?);
let mut r = MockRenderer::new(sm.clone(), 500);
tokio::spawn({
let sm = sm.clone();
async move { sm.start().await }
});
let vs = r.next_render().await?;
r.assert(&vs, 30, 1030..=1059, None, true, false, true, 1050, 1059);
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Live);
let _ = r.scroll_up_collect(500).await;
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Backward);
let _ = r.scroll_up_collect(500).await;
let _ = r.scroll_up_collect(500).await;
let _ = r.scroll_up_collect(500).await;
let selection = sm.current_selection();
assert!(selection.contains("ORDER BY timestamp DESC"),
"Backward should order DESC: {}", selection);
let _ = r.scroll_down_collect(500).await;
let _ = r.scroll_down_collect(500).await;
let _ = r.scroll_down_collect(500).await;
let _ = r.scroll_down_collect(500).await;
let _ = r.scroll_down_collect(500).await;
let final_selection = sm.current_selection();
let mode = sm.mode();
if mode == ankurah_virtual_scroll::ScrollMode::Forward {
assert!(final_selection.contains("ORDER BY timestamp ASC"),
"Forward should order ASC: {}", final_selection);
}
Ok(())
}
#[tokio::test]
async fn test_live_mode_initial() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..60).map(|i| (1000 + i, 50))).await?;
let sm = Arc::new(ScrollManager::<TestMessageView>::new(
&ctx,
"true",
"timestamp DESC",
50,
2.0,
500,
)?);
let mut r = MockRenderer::new(sm.clone(), 500);
tokio::spawn({
let sm = sm.clone();
async move { sm.start().await }
});
let vs = r.next_render().await?;
assert!(vs.should_auto_scroll, "Initial render should have should_auto_scroll=true");
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Live);
assert_eq!(r.scroll_offset, 1000);
Ok(())
}
#[tokio::test]
async fn test_live_mode_exit_on_scroll_up() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..60).map(|i| (1000 + i, 50))).await?;
let sm = Arc::new(ScrollManager::<TestMessageView>::new(
&ctx,
"true",
"timestamp DESC",
50,
2.0,
500,
)?);
let mut r = MockRenderer::new(sm.clone(), 500);
tokio::spawn({
let sm = sm.clone();
async move { sm.start().await }
});
let vs = r.next_render().await?;
r.assert(&vs, 30, 1030..=1059, None, true, false, true, 1050, 1059);
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Live);
r.scroll_up_and_expect(
400, 30, 1030..=1059, None, true, false, false, 1042, 1051, 600,
None,
).await?;
r.scroll_up_and_expect(
100, 50, 1010..=1059, Some(1049),
true, true, false, 1040, 1049, 1500,
Some("TRUE AND \"timestamp\" <= 1059 ORDER BY timestamp DESC LIMIT 51"),
).await?;
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Backward);
Ok(())
}
#[tokio::test]
async fn test_live_mode_reentry() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..60).map(|i| (1000 + i, 50))).await?;
let sm = Arc::new(ScrollManager::<TestMessageView>::new(
&ctx,
"true",
"timestamp DESC",
50,
2.0,
500,
)?);
let mut r = MockRenderer::new(sm.clone(), 500);
tokio::spawn({
let sm = sm.clone();
async move { sm.start().await }
});
let vs = r.next_render().await?;
r.assert(&vs, 30, 1030..=1059, None, true, false, true, 1050, 1059);
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Live);
let _ = r.scroll_up_collect(500).await;
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Backward);
let _ = r.scroll_up_collect(500).await;
let _ = r.scroll_up_collect(500).await;
let _ = r.scroll_up_collect(500).await;
let _ = r.scroll_down_collect(500).await;
let _ = r.scroll_down_collect(500).await;
let _ = r.scroll_down_collect(500).await;
let _ = r.scroll_down_collect(500).await;
let final_mode = sm.mode();
assert!(
final_mode == ankurah_virtual_scroll::ScrollMode::Backward
|| final_mode == ankurah_virtual_scroll::ScrollMode::Forward
|| final_mode == ankurah_virtual_scroll::ScrollMode::Live,
"should be in a valid mode after scrolling: {:?}",
final_mode
);
Ok(())
}
#[tokio::test]
async fn test_concurrent_scroll_events() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..60).map(|i| (1000 + i, 50))).await?;
let sm = Arc::new(ScrollManager::<TestMessageView>::new(
&ctx,
"true",
"timestamp DESC",
50,
2.0,
500,
)?);
let mut r = MockRenderer::new(sm.clone(), 500);
tokio::spawn({
let sm = sm.clone();
async move { sm.start().await }
});
let vs = r.next_render().await?;
r.assert(&vs, 30, 1030..=1059, None, true, false, true, 1050, 1059);
r.scroll_offset = 600; let (first_idx, last_idx) = (12, 21); let first_id = vs.items[first_idx].entity().id();
let last_id = vs.items[last_idx].entity().id();
sm.on_scroll(first_id, last_id, true);
r.scroll_offset = 400;
let (first_idx, last_idx) = (8, 17);
let first_id = vs.items[first_idx].entity().id();
let last_id = vs.items[last_idx].entity().id();
sm.on_scroll(first_id, last_id, true);
let vs = match tokio::time::timeout(
std::time::Duration::from_millis(500),
r.next_render()
).await {
Ok(result) => result?,
Err(_) => {
return Ok(());
}
};
assert!(vs.items.len() >= 30, "Should have at least live_window items");
let ts = timestamps(&vs);
for i in 1..ts.len() {
assert!(ts[i-1] < ts[i], "Items should be sorted: {} >= {}", ts[i-1], ts[i]);
}
Ok(())
}
#[tokio::test]
async fn test_sequential_paginations() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..100).map(|i| (1000 + i, 50))).await?;
let sm = Arc::new(ScrollManager::<TestMessageView>::new(
&ctx,
"true",
"timestamp DESC",
50,
2.0,
500,
)?);
let mut r = MockRenderer::new(sm.clone(), 500);
tokio::spawn({
let sm = sm.clone();
async move { sm.start().await }
});
let vs = r.next_render().await?;
assert_eq!(vs.items.len(), 30);
let initial_ts = timestamps(&vs);
assert_eq!(initial_ts[0], 1070); assert_eq!(*initial_ts.last().unwrap(), 1099);
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Live);
let renders = r.scroll_up_collect(500).await;
assert!(!renders.is_empty(), "First backward scroll should produce renders");
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Backward);
let _ = r.scroll_up_collect(500).await;
let _ = r.scroll_up_collect(500).await;
let _ = r.scroll_up_collect(500).await;
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Backward);
let selection = sm.current_selection();
assert!(selection.contains("ORDER BY timestamp DESC"),
"Backward mode should order DESC: {}", selection);
assert!(selection.contains("LIMIT"),
"Should have a LIMIT clause: {}", selection);
Ok(())
}