From 4cd15bb8a04bf8df3fb292796a8f32d7533cacdc Mon Sep 17 00:00:00 2001 From: Adam Mathes 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(); 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(); + it('calls onToggleStar when star clicked', () => { + const onToggleStar = vi.fn(); + render(); 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(); 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(); - - // 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: '

Full Content Loaded

' }), + json: async () => ({ full_content: '

Full Content Loaded

' }), } as Response); - render(); + const { rerender } = render(); 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: '

Full Content Loaded

' + })); }); - expect(global.fetch).toHaveBeenCalledWith('/api/item/1', expect.anything()); + // Simulate parent updating prop + rerender(Full Content Loaded

' }} onUpdate={onUpdate} />); + expect(screen.getByText('Full Content Loaded')).toBeInTheDocument(); }); }); -- cgit v1.2.3