aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src/components/FeedItems.test.tsx
diff options
context:
space:
mode:
authorAdam Mathes <adam@adammathes.com>2026-02-16 19:37:50 -0800
committerAdam Mathes <adam@adammathes.com>2026-02-16 19:40:10 -0800
commitcba29e6aae637b04ff6eaf28f74bc15b6242b9ea (patch)
tree226753a3fcd18a6d45089dafcb1ee72557140aa8 /frontend/src/components/FeedItems.test.tsx
parentcb6d0c9e330c27ff85ff065c2ea6dd1a756cbf6d (diff)
downloadneko-cba29e6aae637b04ff6eaf28f74bc15b6242b9ea.tar.gz
neko-cba29e6aae637b04ff6eaf28f74bc15b6242b9ea.tar.bz2
neko-cba29e6aae637b04ff6eaf28f74bc15b6242b9ea.zip
Remove legacy V2 React frontend and update build/test scripts to focus on Vanilla JS (V3)
Diffstat (limited to 'frontend/src/components/FeedItems.test.tsx')
-rw-r--r--frontend/src/components/FeedItems.test.tsx250
1 files changed, 0 insertions, 250 deletions
diff --git a/frontend/src/components/FeedItems.test.tsx b/frontend/src/components/FeedItems.test.tsx
deleted file mode 100644
index 1a002d8..0000000
--- a/frontend/src/components/FeedItems.test.tsx
+++ /dev/null
@@ -1,250 +0,0 @@
-import React from 'react';
-import '@testing-library/jest-dom';
-import { render, screen, waitFor, fireEvent, act } from '@testing-library/react';
-import { MemoryRouter, Route, Routes } from 'react-router-dom';
-import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
-import FeedItems from './FeedItems';
-
-describe('FeedItems Component', () => {
- beforeEach(() => {
- vi.resetAllMocks();
- global.fetch = vi.fn();
- window.HTMLElement.prototype.scrollIntoView = vi.fn();
-
- // Mock IntersectionObserver
- class MockIntersectionObserver {
- observe = vi.fn();
- unobserve = vi.fn();
- disconnect = vi.fn();
- }
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- window.IntersectionObserver = MockIntersectionObserver as any;
- });
-
- it('renders loading state', () => {
- vi.mocked(global.fetch).mockImplementation(() => new Promise(() => { }));
- render(
- <MemoryRouter initialEntries={['/feed/1']}>
- <Routes>
- <Route path="/feed/:feedId" element={<FeedItems />} />
- </Routes>
- </MemoryRouter>
- );
- expect(screen.getByText(/loading items/i)).toBeInTheDocument();
- });
-
- it('renders items for a feed', async () => {
- const mockItems = [
- {
- _id: 101,
- title: 'Item One',
- url: 'http://example.com/1',
- publish_date: '2023-01-01',
- read: false,
- },
- {
- _id: 102,
- title: 'Item Two',
- url: 'http://example.com/2',
- publish_date: '2023-01-02',
- read: true,
- },
- ];
-
- vi.mocked(global.fetch).mockResolvedValueOnce({
- ok: true,
- json: async () => mockItems,
- } as Response);
-
- render(
- <MemoryRouter initialEntries={['/feed/1']}>
- <Routes>
- <Route path="/feed/:feedId" element={<FeedItems />} />
- </Routes>
- </MemoryRouter>
- );
-
- await waitFor(() => {
- expect(screen.getByText('Item One')).toBeInTheDocument();
- });
-
- const params = new URLSearchParams();
- params.append('feed_id', '1');
- params.append('read_filter', 'unread');
- expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`, expect.anything());
- });
-
-
-
- afterEach(() => {
- vi.useRealTimers();
- vi.restoreAllMocks();
- });
-
- it('marks items as read when scrolled past', async () => {
- const mockItems = [{ _id: 101, title: 'Item 1', url: 'u1', read: false, starred: false }];
- vi.mocked(global.fetch).mockResolvedValue({
- ok: true,
- json: async () => mockItems,
- } as Response);
-
- // Mock getBoundingClientRect
- const getBoundingClientRectMock = vi.spyOn(Element.prototype, 'getBoundingClientRect');
- getBoundingClientRectMock.mockImplementation(function (this: Element) {
- if (this.classList && this.classList.contains('dashboard-main')) {
- return {
- top: 0, bottom: 500, height: 500, left: 0, right: 1000, width: 1000, x: 0, y: 0,
- toJSON: () => { }
- } as DOMRect;
- }
- if (this.id && this.id.startsWith('item-')) {
- // Item top is -50 (above container top 0)
- return {
- top: -150, bottom: -50, height: 100, left: 0, right: 1000, width: 1000, x: 0, y: 0,
- toJSON: () => { }
- } as DOMRect;
- }
- return {
- top: 0, bottom: 0, height: 0, left: 0, right: 0, width: 0, x: 0, y: 0,
- toJSON: () => { }
- } as DOMRect;
- });
-
- render(
- <MemoryRouter>
- <div className="dashboard-main">
- <FeedItems />
- </div>
- </MemoryRouter>
- );
-
- // Initial load fetch
- await waitFor(() => {
- expect(screen.getByText('Item 1')).toBeVisible();
- });
-
- // Trigger scroll
- const container = document.querySelector('.dashboard-main');
- expect(container).not.toBeNull();
-
- act(() => {
- // Dispatch scroll event
- fireEvent.scroll(container!);
- });
-
- // Wait for throttle (500ms) + buffer
- await new Promise(r => setTimeout(r, 600));
-
- await waitFor(() => {
- expect(global.fetch).toHaveBeenCalledWith(
- '/api/item/101',
- expect.objectContaining({
- method: 'PUT',
- body: JSON.stringify({ read: true, starred: false }),
- })
- );
- });
- });
-
- 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 }];
-
- vi.mocked(global.fetch)
- .mockResolvedValueOnce({ ok: true, json: async () => initialItems } as Response)
- .mockResolvedValueOnce({ ok: true, json: async () => moreItems } as Response);
-
- const observerCallbacks: IntersectionObserverCallback[] = [];
- class MockIntersectionObserver {
- constructor(callback: IntersectionObserverCallback) {
- observerCallbacks.push(callback);
- }
- observe = vi.fn();
- unobserve = vi.fn();
- disconnect = vi.fn();
- }
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- window.IntersectionObserver = MockIntersectionObserver as any;
-
- render(
- <MemoryRouter>
- <FeedItems />
- </MemoryRouter>
- );
-
- await waitFor(() => {
- expect(screen.getByText('Item 1')).toBeInTheDocument();
- });
-
- 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(() => {
- // Trigger all observers
- observerCallbacks.forEach(cb => cb([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');
- // Verify the second fetch call content
- expect(global.fetch).toHaveBeenCalledWith(
- expect.stringContaining('max_id=101'),
- 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 },
- ];
-
- vi.mocked(global.fetch)
- .mockResolvedValueOnce({ ok: true, json: async () => initialItems } as Response)
- .mockResolvedValueOnce({ ok: true, json: async () => moreItems } as Response);
-
- render(
- <MemoryRouter>
- <FeedItems />
- </MemoryRouter>
- );
-
- await waitFor(() => {
- expect(screen.getByText('Item 1')).toBeInTheDocument();
- });
-
- fireEvent.keyDown(window, { key: 'j' }); // index 0
- await waitFor(() => expect(document.getElementById('item-0')).toHaveAttribute('data-selected', 'true'));
-
- fireEvent.keyDown(window, { key: 'j' }); // index 1
- await waitFor(() => expect(document.getElementById('item-1')).toHaveAttribute('data-selected', 'true'));
-
- fireEvent.keyDown(window, { key: 'j' }); // index 2 (last item)
- await waitFor(() => expect(document.getElementById('item-2')).toHaveAttribute('data-selected', 'true'));
-
- await waitFor(() => {
- expect(screen.getByText('Item 0')).toBeInTheDocument();
- });
-
- // Check fetch call
- expect(global.fetch).toHaveBeenCalledWith(
- expect.stringContaining('max_id=101'),
- expect.anything()
- );
- });
-});