From a4997a5fbc65913b55f2215eb3b868693bd76c51 Mon Sep 17 00:00:00 2001 From: Adam Mathes Date: Sat, 14 Feb 2026 10:03:35 -0800 Subject: test: increase frontend coverage for Settings and improve FeedItem css --- .../coverage/src/components/FeedItems.tsx.html | 597 +++++++++++---------- 1 file changed, 315 insertions(+), 282 deletions(-) (limited to 'frontend/coverage/src/components/FeedItems.tsx.html') diff --git a/frontend/coverage/src/components/FeedItems.tsx.html b/frontend/coverage/src/components/FeedItems.tsx.html index e57acf9..f6b7493 100644 --- a/frontend/coverage/src/components/FeedItems.tsx.html +++ b/frontend/coverage/src/components/FeedItems.tsx.html @@ -1,64 +1,68 @@ + - + + Code coverage report for src/components/FeedItems.tsx - - - - -
-
-

- All files / - src/components FeedItems.tsx -

-
-
- 89.34% - Statements - 109/122 -
- -
- 77.21% - Branches - 61/79 -
- -
- 86.2% - Functions - 25/29 -
- -
- 89.09% - Lines - 98/110 -
+ + + +
+
+

All files / src/components FeedItems.tsx

+
+ +
+ 88.97% + Statements + 113/127 +
+ + +
+ 75.3% + Branches + 61/81 +
+ + +
+ 86.2% + Functions + 25/29 +
+ + +
+ 88.69% + Lines + 102/115 +
+ +

- Press n or j to go to the next uncovered block, b, - p or k for the previous block. + Press n or j to go to the next uncovered block, b, p or k for the previous block.

-
-
-

+    
+    
+
1 2 3 @@ -282,7 +286,18 @@ 221 222 223 -224  +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234  +        @@ -298,6 +313,7 @@ 27x 27x 27x +27x   27x 8x @@ -323,6 +339,11 @@     8x +8x +  +  +  +8x   8x   @@ -330,6 +351,8 @@     8x +8x +      8x @@ -363,77 +386,79 @@   27x 7x +7x     -27x   27x -23x -3x -  -3x -2x 2x 2x 2x -2x -1x   +  +  +27x 2x   +3x +  2x   -1x -  -  -  -  +        -1x -1x -1x -1x   +27x 1x   +2x   +1x     -23x -23x +  +      27x +23x +3x +  +3x 2x 2x 2x -  -  -  -27x 2x -  -3x +2x +1x   2x   +2x   +1x +  +  +  +        +1x +1x +1x +1x   -27x 1x   -2x   -1x     +23x +23x +    -      27x @@ -477,7 +502,6 @@ 27x 14x   -  13x     @@ -510,242 +534,251 @@ import { useParams, useSearchParams } from 'react-router-dom'; import type { Item } from '../types'; import FeedItem from './FeedItem'; import './FeedItems.css'; +import { apiFetch } from '../utils';   export default function FeedItems() { - const { feedId, tagName } = useParams<{ feedId: string; tagName: string }>(); - const [searchParams] = useSearchParams(); - const filterFn = searchParams.get('filter') || 'unread'; + const { feedId, tagName } = useParams<{ feedId: string; tagName: string }>(); + const [searchParams] = useSearchParams(); + const filterFn = searchParams.get('filter') || 'unread';   - const [items, setItems] = useState<Item[]>([]); - const [loading, setLoading] = useState(true); - const [loadingMore, setLoadingMore] = useState(false); - const [hasMore, setHasMore] = useState(true); - const [error, setError] = useState(''); + const [items, setItems] = useState<Item[]>([]); + const [loading, setLoading] = useState(true); + const [loadingMore, setLoadingMore] = useState(false); + const [hasMore, setHasMore] = useState(true); + const [error, setError] = useState(''); + const [selectedIndex, setSelectedIndex] = useState(-1);   - const fetchItems = (maxId?: string) => { - if (maxId) { - setLoadingMore(true); - } else { - setLoading(true); - setItems([]); - } - setError(''); + const fetchItems = (maxId?: string) => { + if (maxId) { + setLoadingMore(true); + } else { + setLoading(true); + setItems([]); + } + setError('');   - let url = '/api/stream'; - const params = new URLSearchParams(); + let url = '/api/stream'; + const params = new URLSearchParams();   - if (feedId) { - params.append('feed_id', feedId); - } else if (tagName) { - params.append('tag', tagName); - } + if (feedId) { + params.append('feed_id', feedId); + } else if (tagName) { + params.append('tag', tagName); + }   - if (maxId) { - params.append('max_id', maxId); - } + if (maxId) { + params.append('max_id', maxId); + } +  + // Apply filters + const searchQuery = searchParams.get('q'); + Iif (searchQuery) { + params.append('q', searchQuery); + } +  + Iif (filterFn === 'all') { + params.append('read_filter', 'all'); + I} else if (filterFn === 'starred') { + params.append('starred', 'true'); + params.append('read_filter', 'all'); + } else { + // default to unread + Eif (!searchQuery) { + params.append('read_filter', 'unread'); + } + } +  + const queryString = params.toString(); + Eif (queryString) { + url += `?${queryString}`; + }   - // Apply filters - Iif (filterFn === 'all') { - params.append('read_filter', 'all'); - I} else if (filterFn === 'starred') { - params.append('starred', 'true'); - params.append('read_filter', 'all'); + apiFetch(url) + .then((res) => { + Iif (!res.ok) { + throw new Error('Failed to fetch items'); + } + return res.json(); + }) + .then((data) => { + if (maxId) { + setItems((prev) => [...prev, ...data]); } else { - // default to unread - params.append('read_filter', 'unread'); + setItems(data); } + setHasMore(data.length > 0); + setLoading(false); + setLoadingMore(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + setLoadingMore(false); + }); + };   - const queryString = params.toString(); - Eif (queryString) { - url += `?${queryString}`; - } + useEffect(() => { + fetchItems(); + setSelectedIndex(-1); + }, [feedId, tagName, filterFn, searchParams]);   - fetch(url) - .then((res) => { - Iif (!res.ok) { - throw new Error('Failed to fetch items'); - } - return res.json(); - }) - .then((data) => { - if (maxId) { - setItems((prev) => [...prev, ...data]); - } else { - setItems(data); - } - setHasMore(data.length > 0); - setLoading(false); - setLoadingMore(false); - }) - .catch((err) => { - setError(err.message); - setLoading(false); - setLoadingMore(false); - }); - };   - useEffect(() => { - fetchItems(); - }, [feedId, tagName, filterFn]); + const scrollToItem = (index: number) => { + const element = document.getElementById(`item-${index}`); + Eif (element) { + element.scrollIntoView({ behavior: 'auto', block: 'start' }); + } + };   - const [selectedIndex, setSelectedIndex] = useState(-1); + const markAsRead = (item: Item) => { + const updatedItem = { ...item, read: true }; + // Optimistic update + setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i)));   - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - Iif (items.length === 0) return; + apiFetch(`/api/item/${item._id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ read: true, starred: item.starred }), + }).catch((err) => console.error('Failed to mark read', err)); + };   - if (e.key === 'j') { - setSelectedIndex((prev) => { - const nextIndex = Math.min(prev + 1, items.length - 1); - Eif (nextIndex !== prev) { - const item = items[nextIndex]; - if (!item.read) { - markAsRead(item); - } - scrollToItem(nextIndex); - } - return nextIndex; - }); - I} else if (e.key === 'k') { - setSelectedIndex((prev) => { - const nextIndex = Math.max(prev - 1, 0); - if (nextIndex !== prev) { - scrollToItem(nextIndex); - } - return nextIndex; - }); - E} else if (e.key === 's') { - setSelectedIndex((currentIndex) => { - Eif (currentIndex >= 0 && currentIndex < items.length) { - toggleStar(items[currentIndex]); - } - return currentIndex; - }); - } - }; + const toggleStar = (item: Item) => { + const updatedItem = { ...item, starred: !item.starred }; + // Optimistic update + setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i)));   - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [items]); + apiFetch(`/api/item/${item._id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ read: item.read, starred: !item.starred }), + }).catch((err) => console.error('Failed to toggle star', err)); + };   - const scrollToItem = (index: number) => { - const element = document.getElementById(`item-${index}`); - Eif (element) { - element.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } - }; + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + Iif (items.length === 0) return;   - const markAsRead = (item: Item) => { - const updatedItem = { ...item, read: true }; - // Optimistic update - setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i))); -  - fetch(`/api/item/${item._id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ read: true, starred: item.starred }), - }).catch((err) => console.error('Failed to mark read', err)); + if (e.key === 'j') { + setSelectedIndex((prev) => { + const nextIndex = Math.min(prev + 1, items.length - 1); + Eif (nextIndex !== prev) { + const item = items[nextIndex]; + if (!item.read) { + markAsRead(item); + } + scrollToItem(nextIndex); + } + return nextIndex; + }); + I} else if (e.key === 'k') { + setSelectedIndex((prev) => { + const nextIndex = Math.max(prev - 1, 0); + if (nextIndex !== prev) { + scrollToItem(nextIndex); + } + return nextIndex; + }); + E} else if (e.key === 's') { + setSelectedIndex((currentIndex) => { + Eif (currentIndex >= 0 && currentIndex < items.length) { + toggleStar(items[currentIndex]); + } + return currentIndex; + }); + } };   - const toggleStar = (item: Item) => { - const updatedItem = { ...item, starred: !item.starred }; - // Optimistic update - setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i))); + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [items]);   - fetch(`/api/item/${item._id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ read: item.read, starred: !item.starred }), - }).catch((err) => console.error('Failed to toggle star', err)); - };   - useEffect(() => { - const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - // Infinity scroll sentinel - if (entry.target.id === 'load-more-sentinel') { - Eif (entry.isIntersecting && !loadingMore && hasMore && items.length > 0) { - fetchItems(String(items[items.length - 1]._id)); - } - return; - }   - // If item is not intersecting and is above the viewport, it's been scrolled past - Eif (!entry.isIntersecting && entry.boundingClientRect.top < 0) { - const index = Number(entry.target.getAttribute('data-index')); - Eif (!isNaN(index) && index >= 0 && index < items.length) { - const item = items[index]; - Eif (!item.read) { - markAsRead(item); - } - } - } - }); - }, - { root: null, threshold: 0 } - ); + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + // Infinity scroll sentinel + if (entry.target.id === 'load-more-sentinel') { + Eif (entry.isIntersecting && !loadingMore && hasMore && items.length > 0) { + fetchItems(String(items[items.length - 1]._id)); + } + return; + }   - items.forEach((_, index) => { - const el = document.getElementById(`item-${index}`); - Eif (el) observer.observe(el); + // If item is not intersecting and is above the viewport, it's been scrolled past + Eif (!entry.isIntersecting && entry.boundingClientRect.top < 0) { + const index = Number(entry.target.getAttribute('data-index')); + Eif (!isNaN(index) && index >= 0 && index < items.length) { + const item = items[index]; + Eif (!item.read) { + markAsRead(item); + } + } + } }); + }, + { root: null, threshold: 0 } + );   - const sentinel = document.getElementById('load-more-sentinel'); - if (sentinel) observer.observe(sentinel); + items.forEach((_, index) => { + const el = document.getElementById(`item-${index}`); + Eif (el) observer.observe(el); + });   - return () => observer.disconnect(); - }, [items, loadingMore, hasMore]); + const sentinel = document.getElementById('load-more-sentinel'); + if (sentinel) observer.observe(sentinel);   - if (loading) return <div className="feed-items-loading">Loading items...</div>; - if (error) return <div className="feed-items-error">Error: {error}</div>; + return () => observer.disconnect(); + }, [items, loadingMore, hasMore]);   + if (loading) return <div className="feed-items-loading">Loading items...</div>; + if (error) return <div className="feed-items-error">Error: {error}</div>;   - return ( - <div className="feed-items"> - {items.length === 0 ? ( - <p>No items found.</p> - ) : ( - <ul className="item-list"> - {items.map((item, index) => ( - <div - id={`item-${index}`} - key={item._id} - data-index={index} - className={index === selectedIndex ? 'selected-item-container' : ''} - onClick={() => setSelectedIndex(index)} - > - <FeedItem item={item} /> - </div> - ))} - {hasMore && ( - <div id="load-more-sentinel" className="loading-more"> - {loadingMore ? 'Loading more...' : ''} - </div> - )} - </ul> - )} - </div> - ); + return ( + <div className="feed-items"> + {items.length === 0 ? ( + <p>No items found.</p> + ) : ( + <ul className="item-list"> + {items.map((item, index) => ( + <div + id={`item-${index}`} + key={item._id} + data-index={index} + data-selected={index === selectedIndex} + onClick={() => setSelectedIndex(index)} + > + <FeedItem item={item} /> + </div> + ))} + {hasMore && ( + <div id="load-more-sentinel" className="loading-more"> + {loadingMore ? 'Loading more...' : ''} + </div> + )} + </ul> + )} + </div> + ); }  
-
- -
- - - - - - - +
+
+ + + + + + + \ No newline at end of file -- cgit v1.2.3