diff options
| author | Adam Mathes <adam@adammathes.com> | 2026-02-14 12:36:04 -0800 |
|---|---|---|
| committer | Adam Mathes <adam@adammathes.com> | 2026-02-14 12:36:04 -0800 |
| commit | cb6e71d75e526f207b0c4a8f08d660fdde3e7e98 (patch) | |
| tree | ba23c7c63648bdf1b03ec4fd649a5b37e5f68e13 /frontend/src/components | |
| parent | 4d55202300f9648bdcf9be14aeb2b8034ca37fc3 (diff) | |
| download | neko-cb6e71d75e526f207b0c4a8f08d660fdde3e7e98.tar.gz neko-cb6e71d75e526f207b0c4a8f08d660fdde3e7e98.tar.bz2 neko-cb6e71d75e526f207b0c4a8f08d660fdde3e7e98.zip | |
fix: auto-load more items when pressing 'j' on last item\n\nPreviously, if you were focused on the last loaded item and that item was\nvery long (extending past the viewport), pressing 'j' would do nothing\nbecause there were no more items loaded yet.\n\nNow, when the user presses 'j' and lands on the last item, we automatically\ntrigger loading more items (if available), ensuring that the next 'j' press\nwill work as expected.\n\nAdded test to verify this behavior works correctly.
Diffstat (limited to 'frontend/src/components')
| -rw-r--r-- | frontend/src/components/FeedItems.test.tsx | 39 | ||||
| -rw-r--r-- | frontend/src/components/FeedItems.tsx | 9 |
2 files changed, 47 insertions, 1 deletions
diff --git a/frontend/src/components/FeedItems.test.tsx b/frontend/src/components/FeedItems.test.tsx index 555d8a2..cf1e708 100644 --- a/frontend/src/components/FeedItems.test.tsx +++ b/frontend/src/components/FeedItems.test.tsx @@ -256,4 +256,43 @@ describe('FeedItems Component', () => { expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`, expect.anything()); }); }); + + it('loads more items when pressing j on last item', async () => { + const initialItems = [ + { _id: 103, title: 'Item 3', url: 'u3', read: true, starred: false }, + { _id: 102, title: 'Item 2', url: 'u2', read: true, starred: false }, + { _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 }); + + render( + <MemoryRouter> + <FeedItems /> + </MemoryRouter> + ); + + await waitFor(() => { + expect(screen.getByText('Item 1')).toBeInTheDocument(); + }); + + // Navigate to the last item by pressing 'j' multiple times + fireEvent.keyDown(window, { key: 'j' }); // index 0 + fireEvent.keyDown(window, { key: 'j' }); // index 1 + fireEvent.keyDown(window, { key: 'j' }); // index 2 (last item) + + // Pressing 'j' on the last item should trigger loading more + 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()}`, expect.anything()); + }); + }); }); diff --git a/frontend/src/components/FeedItems.tsx b/frontend/src/components/FeedItems.tsx index a058b70..f43852b 100644 --- a/frontend/src/components/FeedItems.tsx +++ b/frontend/src/components/FeedItems.tsx @@ -137,6 +137,13 @@ export default function FeedItems() { } scrollToItem(nextIndex); } + + // If we're now on the last item and there are more items to load, + // trigger loading them so the next 'j' press will work + if (nextIndex === items.length - 1 && hasMore && !loadingMore) { + fetchItems(String(items[items.length - 1]._id)); + } + return nextIndex; }); } else if (e.key === 'k') { @@ -159,7 +166,7 @@ export default function FeedItems() { window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); - }, [items]); + }, [items, hasMore, loadingMore]); |
