From f8fcc0be57fa7f471ffd22d4b9559cb6d0ff20bf Mon Sep 17 00:00:00 2001 From: Adam Mathes Date: Fri, 13 Feb 2026 11:45:02 -0800 Subject: Implement infinite scroll for feed items view (NK-5ocxgm) --- frontend/src/components/FeedItems.test.tsx | 55 ++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 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 00118fa..ea68a7c 100644 --- a/frontend/src/components/FeedItems.test.tsx +++ b/frontend/src/components/FeedItems.test.tsx @@ -53,8 +53,6 @@ describe('FeedItems Component', () => { await waitFor(() => { expect(screen.getByText('Item One')).toBeInTheDocument(); - // Title should now be "Feed Items" based on logic - expect(screen.getByText('Feed Items')).toBeInTheDocument(); }); const params = new URLSearchParams(); @@ -171,4 +169,57 @@ describe('FeedItems Component', () => { })); }); }); + + it('loads more items when sentinel becomes visible', async () => { + const initialItems = [{ _id: 101, title: 'Item 1', url: 'u1', read: true, starred: false }]; + const moreItems = [{ _id: 100, title: 'Item 0', url: 'u0', read: true, starred: false }]; + + (global.fetch as any) + .mockResolvedValueOnce({ ok: true, json: async () => initialItems }) + .mockResolvedValueOnce({ ok: true, json: async () => moreItems }); + + let observerCallback: IntersectionObserverCallback = () => { }; + class MockIntersectionObserver { + constructor(callback: IntersectionObserverCallback) { + observerCallback = callback; + } + observe = vi.fn(); + unobserve = vi.fn(); + disconnect = vi.fn(); + } + window.IntersectionObserver = MockIntersectionObserver as any; + + render( + + + + ); + + await waitFor(() => { + expect(screen.getByText('Item 1')).toBeInTheDocument(); + }); + + // Simulate sentinel becoming visible + const entry = { + isIntersecting: true, + target: { id: 'load-more-sentinel' } as unknown as Element, + boundingClientRect: {} as DOMRectReadOnly, + intersectionRatio: 1, + time: 0, + rootBounds: null, + intersectionRect: {} as DOMRectReadOnly, + } as IntersectionObserverEntry; + + act(() => { + observerCallback([entry], {} as IntersectionObserver); + }); + + await waitFor(() => { + expect(screen.getByText('Item 0')).toBeInTheDocument(); + const params = new URLSearchParams(); + params.append('max_id', '101'); + params.append('read_filter', 'unread'); + expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`); + }); + }); }); -- cgit v1.2.3