From 419d84d2a8e84d028b145df57d1584a3fe163b37 Mon Sep 17 00:00:00 2001
From: Adam Mathes <adam@adammathes.com>
Date: Sat, 14 Feb 2026 10:44:18 -0800
Subject: fix: make infinite scroll less aggressive by using threshold 1.0 for
 sentinel observer

---
 frontend/src/components/FeedItems.test.tsx | 33 ++++++++++++++++++++----------
 1 file changed, 22 insertions(+), 11 deletions(-)

(limited to 'frontend/src/components/FeedItems.test.tsx')

diff --git a/frontend/src/components/FeedItems.test.tsx b/frontend/src/components/FeedItems.test.tsx
index ca0dc98..6ffd026 100644
--- a/frontend/src/components/FeedItems.test.tsx
+++ b/frontend/src/components/FeedItems.test.tsx
@@ -137,13 +137,13 @@ describe('FeedItems Component', () => {
       json: async () => mockItems,
     });
 
-    // Capture the callback
-    let observerCallback: IntersectionObserverCallback = () => { };
+    // Capture both callbacks
+    const observerCallbacks: IntersectionObserverCallback[] = [];
 
-    // Override the mock to capture callback
+    // Override the mock to capture both callbacks
     class MockIntersectionObserver {
       constructor(callback: IntersectionObserverCallback) {
-        observerCallback = callback;
+        observerCallbacks.push(callback);
       }
       observe = vi.fn();
       unobserve = vi.fn();
@@ -161,6 +161,11 @@ describe('FeedItems Component', () => {
       expect(screen.getByText('Item 1')).toBeVisible();
     });
 
+    // Wait for observers to be created
+    await waitFor(() => {
+      expect(observerCallbacks.length).toBeGreaterThan(0);
+    });
+
     // Simulate item leaving viewport at the top
     // Element index is 0
     const entry = {
@@ -173,11 +178,10 @@ describe('FeedItems Component', () => {
       intersectionRect: {} as DOMRectReadOnly,
     } as IntersectionObserverEntry;
 
-    // Use vi.waitUntil to wait for callback to be assigned if needed,
-    // though strictly synchronous render + effect should do it.
-    // Direct call:
+    // Call the last itemObserver (second-to-last in the array, since sentinelObserver is last)
     act(() => {
-      observerCallback([entry], {} as IntersectionObserver);
+      const lastItemObserver = observerCallbacks[observerCallbacks.length - 2];
+      lastItemObserver([entry], {} as IntersectionObserver);
     });
 
     await waitFor(() => {
@@ -199,10 +203,10 @@ describe('FeedItems Component', () => {
       .mockResolvedValueOnce({ ok: true, json: async () => initialItems })
       .mockResolvedValueOnce({ ok: true, json: async () => moreItems });
 
-    let observerCallback: IntersectionObserverCallback = () => { };
+    const observerCallbacks: IntersectionObserverCallback[] = [];
     class MockIntersectionObserver {
       constructor(callback: IntersectionObserverCallback) {
-        observerCallback = callback;
+        observerCallbacks.push(callback);
       }
       observe = vi.fn();
       unobserve = vi.fn();
@@ -220,6 +224,11 @@ describe('FeedItems Component', () => {
       expect(screen.getByText('Item 1')).toBeInTheDocument();
     });
 
+    // Wait for observers to be created (effect runs multiple times)
+    await waitFor(() => {
+      expect(observerCallbacks.length).toBeGreaterThan(0);
+    });
+
     // Simulate sentinel becoming visible
     const entry = {
       isIntersecting: true,
@@ -231,8 +240,10 @@ describe('FeedItems Component', () => {
       intersectionRect: {} as DOMRectReadOnly,
     } as IntersectionObserverEntry;
 
+    // Call the last sentinelObserver (second of the last pair created)
     act(() => {
-      observerCallback([entry], {} as IntersectionObserver);
+      const lastSentinelObserver = observerCallbacks[observerCallbacks.length - 1];
+      lastSentinelObserver([entry], {} as IntersectionObserver);
     });
 
     await waitFor(() => {
-- 
cgit v1.2.3

