From 4cd15bb8a04bf8df3fb292796a8f32d7533cacdc Mon Sep 17 00:00:00 2001
From: Adam Mathes <adam@adammathes.com>
Date: Sun, 15 Feb 2026 15:57:54 -0800
Subject: Optimize frontend with memoized FeedItem and efficient
 IntersectionObserver

---
 frontend/src/components/FeedItem.test.tsx | 48 ++++++++++++-------------------
 1 file changed, 18 insertions(+), 30 deletions(-)

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

diff --git a/frontend/src/components/FeedItem.test.tsx b/frontend/src/components/FeedItem.test.tsx
index 1c51dc3..ab2ca45 100644
--- a/frontend/src/components/FeedItem.test.tsx
+++ b/frontend/src/components/FeedItem.test.tsx
@@ -27,66 +27,54 @@ describe('FeedItem Component', () => {
     render(<FeedItem item={mockItem} />);
     expect(screen.getByText('Test Item')).toBeInTheDocument();
     expect(screen.getByText(/Test Feed/)).toBeInTheDocument();
-    // Check for relative time or date formatting? For now just check it renders
   });
 
-  it('toggles star status', async () => {
-    vi.mocked(global.fetch).mockResolvedValueOnce({ ok: true, json: async () => ({}) } as Response);
-
-    render(<FeedItem item={mockItem} />);
+  it('calls onToggleStar when star clicked', () => {
+    const onToggleStar = vi.fn();
+    render(<FeedItem item={mockItem} onToggleStar={onToggleStar} />);
 
     const starBtn = screen.getByTitle('Star');
-    expect(starBtn).toHaveTextContent('★');
     fireEvent.click(starBtn);
 
-    // Optimistic update
-    expect(await screen.findByTitle('Unstar')).toHaveTextContent('★');
-
-    expect(global.fetch).toHaveBeenCalledWith(
-      '/api/item/1',
-      expect.objectContaining({
-        method: 'PUT',
-        body: JSON.stringify({
-          _id: 1,
-          read: false,
-          starred: true,
-        }),
-      })
-    );
+    expect(onToggleStar).toHaveBeenCalledWith(mockItem);
   });
 
   it('updates styling when read state changes', () => {
     const { rerender } = render(<FeedItem item={{ ...mockItem, read: false }} />);
     const link = screen.getByText('Test Item');
-    // Initial state: unread (bold)
-    // Note: checking computed style might be flaky in jsdom, but we can check the class on the parent
     const listItem = link.closest('li');
     expect(listItem).toHaveClass('unread');
     expect(listItem).not.toHaveClass('read');
 
-    // Update prop to read
     rerender(<FeedItem item={{ ...mockItem, read: true }} />);
-
-    // Should now be read
     expect(listItem).toHaveClass('read');
     expect(listItem).not.toHaveClass('unread');
   });
 
-  it('loads full content', async () => {
+  it('loads full content and calls onUpdate', async () => {
+    const onUpdate = vi.fn();
     vi.mocked(global.fetch).mockResolvedValueOnce({
       ok: true,
-      json: async () => ({ ...mockItem, full_content: '<p>Full Content Loaded</p>' }),
+      json: async () => ({ full_content: '<p>Full Content Loaded</p>' }),
     } as Response);
 
-    render(<FeedItem item={mockItem} />);
+    const { rerender } = render(<FeedItem item={mockItem} onUpdate={onUpdate} />);
 
     const scrapeBtn = screen.getByTitle('Load Full Content');
     fireEvent.click(scrapeBtn);
 
     await waitFor(() => {
-      expect(screen.getByText('Full Content Loaded')).toBeInTheDocument();
+      expect(global.fetch).toHaveBeenCalledWith('/api/item/1', expect.anything());
+    });
+
+    await waitFor(() => {
+      expect(onUpdate).toHaveBeenCalledWith(expect.objectContaining({
+        full_content: '<p>Full Content Loaded</p>'
+      }));
     });
 
-    expect(global.fetch).toHaveBeenCalledWith('/api/item/1', expect.anything());
+    // Simulate parent updating prop
+    rerender(<FeedItem item={{ ...mockItem, full_content: '<p>Full Content Loaded</p>' }} onUpdate={onUpdate} />);
+    expect(screen.getByText('Full Content Loaded')).toBeInTheDocument();
   });
 });
-- 
cgit v1.2.3

