mod common;
use common::*;
use std::sync::Arc;
#[tokio::test]
async fn test_scroll_live_to_oldest_and_back() -> 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(
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?;
r.up_no_render(500, 1030, 1039).await;
r.scroll_up_and_expect(
500, 50, 1000..=1049, Some(1029),
false, true, false, 1020, 1029, 1000,
Some("TRUE AND \"timestamp\" <= 1049 ORDER BY timestamp DESC LIMIT 51"),
).await?;
r.up_no_render(500, 1010, 1019).await;
r.up_no_render(500, 1000, 1009).await;
assert_eq!(r.scroll_offset, 0);
r.down_no_render(500, 1010, 1019).await;
r.down_no_render(500, 1020, 1029).await;
r.scroll_down_and_expect(
500, 50, 1010..=1059, Some(1030),
true, false, true, 1050, 1059, 2000,
Some("TRUE AND \"timestamp\" >= 1010 ORDER BY timestamp ASC LIMIT 51"),
).await?;
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Live);
Ok(())
}
#[tokio::test]
async fn test_scroll_to_absolute_oldest() -> 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?;
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?;
r.up_no_render(500, 1030, 1039).await;
let vs = r.scroll_up_and_expect(
500, 50, 1000..=1049, Some(1029),
false, true, false, 1020, 1029, 1000,
Some("TRUE AND \"timestamp\" <= 1049 ORDER BY timestamp DESC LIMIT 51"),
).await?;
assert!(!vs.has_more_preceding, "should have no more preceding items at oldest edge");
Ok(())
}
#[tokio::test]
async fn test_small_dataset_no_pagination() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..15).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, 15, 1000..=1014, None, false, false, true, 1005, 1014);
r.scroll_up_and_expect(
200, 15, 1000..=1014, None, false, false, false, 1001, 1010, 50,
None,
).await?;
r.up_no_render(50, 1000, 1009).await;
assert_eq!(r.scroll_offset, 0);
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Backward);
Ok(())
}
#[tokio::test]
async fn test_one_more_than_live_window() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..31).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, 1001..=1030, None, true, false, true, 1021, 1030);
r.scroll_up_and_expect(
400, 30, 1001..=1030, None,
true, false, false, 1013, 1022, 600,
None,
).await?;
let vs = r.scroll_up_and_expect(
100, 31, 1000..=1030, Some(1020),
false, true, false, 1011, 1020, 550,
Some("TRUE AND \"timestamp\" <= 1030 ORDER BY timestamp DESC LIMIT 51"),
).await?;
assert!(!vs.has_more_preceding);
Ok(())
}
#[tokio::test]
async fn test_debounce_rapid_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);
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?;
r.up_no_render(50, 1039, 1048).await;
r.up_no_render(50, 1038, 1047).await;
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Backward);
Ok(())
}
#[tokio::test]
async fn test_variable_item_heights() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
let heights = [50, 75, 100];
create_messages(
&ctx,
(0..60).map(|i| (1000 + i, heights[i as usize % 3])),
).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);
assert!(vs.has_more_preceding);
let expected_content_height: i32 = (0..30).map(|i| heights[(30 + i) % 3]).sum();
assert_eq!(r.content_height(), expected_content_height);
Ok(())
}
#[tokio::test]
async fn test_empty_dataset() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().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(), 0);
assert!(!vs.has_more_following);
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Live);
Ok(())
}
#[tokio::test]
async fn test_single_item() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, [(1000, 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(), 1);
assert!(!vs.has_more_preceding);
assert!(!vs.has_more_following);
assert!(vs.should_auto_scroll);
Ok(())
}
#[tokio::test]
async fn test_ascending_order() -> 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 ASC", 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?;
let ts = timestamps(&vs);
assert_eq!(ts.len(), 30);
assert_eq!(*ts.first().unwrap(), 1000);
assert_eq!(*ts.last().unwrap(), 1029);
Ok(())
}
#[tokio::test]
async fn test_large_viewport() -> 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,
1000,
)?);
let mut r = MockRenderer::new(sm.clone(), 1000);
tokio::spawn({
let sm = sm.clone();
async move { sm.start().await }
});
let vs = r.next_render().await?;
assert_eq!(vs.items.len(), 60);
assert!(!vs.has_more_following);
Ok(())
}
#[tokio::test]
async fn test_two_items() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, [(1000, 50), (1001, 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(), 2);
assert!(!vs.has_more_preceding);
assert!(!vs.has_more_following);
assert!(vs.should_auto_scroll);
let ts = timestamps(&vs);
assert_eq!(ts, vec![1000, 1001]);
Ok(())
}
#[tokio::test]
async fn test_initial_auto_scroll() -> 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);
let ts = timestamps(&vs);
assert!(ts.contains(&1059)); assert!(ts.contains(&1030));
assert_eq!(r.scroll_offset, 1000);
Ok(())
}
#[tokio::test]
async fn test_mode_transitions() -> 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 _ = r.next_render().await?;
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_exactly_live_window_items() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..30).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 ts = timestamps(&vs);
assert_eq!(*ts.first().unwrap(), 1000);
assert_eq!(*ts.last().unwrap(), 1029);
r.scroll_up_and_expect(
500, 30, 1000..=1029, None,
true, false, false, 1010, 1019, 500,
Some("TRUE AND \"timestamp\" <= 1029 ORDER BY timestamp DESC LIMIT 51"),
).await?;
r.up_no_render(500, 1000, 1009).await;
assert_eq!(r.scroll_offset, 0);
Ok(())
}
#[tokio::test]
async fn test_at_newest_edge() -> 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.has_more_following, "should have no more following items at newest edge");
assert!(vs.has_more_preceding, "should have more preceding items (older)");
let ts = timestamps(&vs);
assert!(ts.contains(&1059), "should contain newest item");
Ok(())
}
#[tokio::test]
async fn test_debounce_escape() -> 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 _ = r.next_render().await?;
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?;
r.up_no_render(50, 1039, 1048).await; r.up_no_render(50, 1038, 1047).await; r.up_no_render(50, 1037, 1046).await;
assert_eq!(sm.mode(), ankurah_virtual_scroll::ScrollMode::Backward);
r.scroll_up_and_expect(
850, 50, 1000..=1049, Some(1029),
false, true, false, 1020, 1029, 1000,
Some("TRUE AND \"timestamp\" <= 1049 ORDER BY timestamp DESC LIMIT 51"),
).await?;
Ok(())
}
#[tokio::test]
async fn test_immediate_pagination_trigger() -> 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 _ = r.next_render().await?;
r.scroll_up_and_expect(
500, 30, 1030..=1059, None, true, false, false,
1040, 1049, 500,
None,
).await?;
let _ = r.next_render().await?;
Ok(())
}
#[tokio::test]
async fn test_exactly_full_window_items() -> Result<(), anyhow::Error> {
let ctx = durable_sled_setup().await?;
create_messages(&ctx, (0..50).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);
assert!(vs.has_more_preceding);
r.scroll_up_and_expect(
400, 30, 1020..=1049, None,
true, false, false, 1032, 1041, 600,
None,
).await?;
let vs = r.scroll_up_and_expect(
100, 50, 1000..=1049, Some(1039),
false, true, false, 1030, 1039, 1500,
Some("TRUE AND \"timestamp\" <= 1049 ORDER BY timestamp DESC LIMIT 51"),
).await?;
assert_eq!(vs.items.len(), 50);
assert!(!vs.has_more_preceding);
Ok(())
}