aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/coverage/src/components/FeedItems.tsx.html
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/coverage/src/components/FeedItems.tsx.html')
-rw-r--r--frontend/coverage/src/components/FeedItems.tsx.html597
1 files changed, 315 insertions, 282 deletions
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 @@
+
<!doctype html>
<html lang="en">
- <head>
+
+<head>
<title>Code coverage report for src/components/FeedItems.tsx</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../../prettify.css" />
<link rel="stylesheet" href="../../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
- <style type="text/css">
- .coverage-summary .sorter {
- background-image: url(../../sort-arrow-sprite.png);
- }
+ <style type='text/css'>
+ .coverage-summary .sorter {
+ background-image: url(../../sort-arrow-sprite.png);
+ }
</style>
- </head>
-
- <body>
- <div class="wrapper">
- <div class="pad1">
- <h1>
- <a href="../../index.html">All files</a> /
- <a href="index.html">src/components</a> FeedItems.tsx
- </h1>
- <div class="clearfix">
- <div class="fl pad1y space-right2">
- <span class="strong">89.34% </span>
- <span class="quiet">Statements</span>
- <span class="fraction">109/122</span>
- </div>
-
- <div class="fl pad1y space-right2">
- <span class="strong">77.21% </span>
- <span class="quiet">Branches</span>
- <span class="fraction">61/79</span>
- </div>
-
- <div class="fl pad1y space-right2">
- <span class="strong">86.2% </span>
- <span class="quiet">Functions</span>
- <span class="fraction">25/29</span>
- </div>
-
- <div class="fl pad1y space-right2">
- <span class="strong">89.09% </span>
- <span class="quiet">Lines</span>
- <span class="fraction">98/110</span>
- </div>
+</head>
+
+<body>
+<div class='wrapper'>
+ <div class='pad1'>
+ <h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> FeedItems.tsx</h1>
+ <div class='clearfix'>
+
+ <div class='fl pad1y space-right2'>
+ <span class="strong">88.97% </span>
+ <span class="quiet">Statements</span>
+ <span class='fraction'>113/127</span>
+ </div>
+
+
+ <div class='fl pad1y space-right2'>
+ <span class="strong">75.3% </span>
+ <span class="quiet">Branches</span>
+ <span class='fraction'>61/81</span>
+ </div>
+
+
+ <div class='fl pad1y space-right2'>
+ <span class="strong">86.2% </span>
+ <span class="quiet">Functions</span>
+ <span class='fraction'>25/29</span>
+ </div>
+
+
+ <div class='fl pad1y space-right2'>
+ <span class="strong">88.69% </span>
+ <span class="quiet">Lines</span>
+ <span class='fraction'>102/115</span>
+ </div>
+
+
</div>
<p class="quiet">
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>,
- <em>p</em> or <em>k</em> for the previous block.
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
- <div class="quiet">
- Filter:
- <input type="search" id="fileSearch" />
- </div>
+ <div class="quiet">
+ Filter:
+ <input type="search" id="fileSearch">
+ </div>
</template>
- </div>
- <div class="status-line high"></div>
- <pre><table class="coverage">
+ </div>
+ <div class='status-line high'></div>
+ <pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
@@ -282,7 +286,18 @@
<a name='L221'></a><a href='#L221'>221</a>
<a name='L222'></a><a href='#L222'>222</a>
<a name='L223'></a><a href='#L223'>223</a>
-<a name='L224'></a><a href='#L224'>224</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
+<a name='L224'></a><a href='#L224'>224</a>
+<a name='L225'></a><a href='#L225'>225</a>
+<a name='L226'></a><a href='#L226'>226</a>
+<a name='L227'></a><a href='#L227'>227</a>
+<a name='L228'></a><a href='#L228'>228</a>
+<a name='L229'></a><a href='#L229'>229</a>
+<a name='L230'></a><a href='#L230'>230</a>
+<a name='L231'></a><a href='#L231'>231</a>
+<a name='L232'></a><a href='#L232'>232</a>
+<a name='L233'></a><a href='#L233'>233</a>
+<a name='L234'></a><a href='#L234'>234</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
@@ -298,6 +313,7 @@
<span class="cline-any cline-yes">27x</span>
<span class="cline-any cline-yes">27x</span>
<span class="cline-any cline-yes">27x</span>
+<span class="cline-any cline-yes">27x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">27x</span>
<span class="cline-any cline-yes">8x</span>
@@ -323,6 +339,11 @@
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">8x</span>
+<span class="cline-any cline-yes">8x</span>
+<span class="cline-any cline-no">&nbsp;</span>
+<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-no">&nbsp;</span>
@@ -330,6 +351,8 @@
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">8x</span>
+<span class="cline-any cline-yes">8x</span>
+<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">8x</span>
@@ -363,77 +386,79 @@
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">27x</span>
<span class="cline-any cline-yes">7x</span>
+<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">27x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">27x</span>
-<span class="cline-any cline-yes">23x</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">3x</span>
-<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-yes">27x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-yes">3x</span>
+<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
+<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
-<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-yes">27x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">23x</span>
-<span class="cline-any cline-yes">23x</span>
+<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">27x</span>
+<span class="cline-any cline-yes">23x</span>
+<span class="cline-any cline-yes">3x</span>
+<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">27x</span>
<span class="cline-any cline-yes">2x</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">3x</span>
+<span class="cline-any cline-yes">2x</span>
+<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-yes">1x</span>
+<span class="cline-any cline-no">&nbsp;</span>
+<span class="cline-any cline-no">&nbsp;</span>
+<span class="cline-any cline-no">&nbsp;</span>
+<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-yes">1x</span>
+<span class="cline-any cline-yes">1x</span>
+<span class="cline-any cline-yes">1x</span>
+<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">27x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
+<span class="cline-any cline-yes">23x</span>
+<span class="cline-any cline-yes">23x</span>
+<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">27x</span>
@@ -477,7 +502,6 @@
<span class="cline-any cline-yes">27x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
-<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
@@ -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';
&nbsp;
export default function FeedItems() {
- const { feedId, tagName } = useParams&lt;{ feedId: string; tagName: string }&gt;();
- const [searchParams] = useSearchParams();
- const filterFn = searchParams.get('filter') || 'unread';
+ const { feedId, tagName } = useParams&lt;{ feedId: string; tagName: string }&gt;();
+ const [searchParams] = useSearchParams();
+ const filterFn = searchParams.get('filter') || 'unread';
&nbsp;
- const [items, setItems] = useState&lt;Item[]&gt;([]);
- const [loading, setLoading] = useState(true);
- const [loadingMore, setLoadingMore] = useState(false);
- const [hasMore, setHasMore] = useState(true);
- const [error, setError] = useState('');
+ const [items, setItems] = useState&lt;Item[]&gt;([]);
+ const [loading, setLoading] = useState(true);
+ const [loadingMore, setLoadingMore] = useState(false);
+ const [hasMore, setHasMore] = useState(true);
+ const [error, setError] = useState('');
+ const [selectedIndex, setSelectedIndex] = useState(-1);
&nbsp;
- const fetchItems = (maxId?: string) =&gt; {
- if (maxId) {
- setLoadingMore(true);
- } else {
- setLoading(true);
- setItems([]);
- }
- setError('');
+ const fetchItems = (maxId?: string) =&gt; {
+ if (maxId) {
+ setLoadingMore(true);
+ } else {
+ setLoading(true);
+ setItems([]);
+ }
+ setError('');
&nbsp;
- let url = '/api/stream';
- const params = new URLSearchParams();
+ let url = '/api/stream';
+ const params = new URLSearchParams();
&nbsp;
- 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);
+ }
&nbsp;
- if (maxId) {
- params.append('max_id', maxId);
- }
+ if (maxId) {
+ params.append('max_id', maxId);
+ }
+&nbsp;
+ // Apply filters
+ const searchQuery = searchParams.get('q');
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (searchQuery) {
+<span class="cstat-no" title="statement not covered" > params.append('q', searchQuery);</span>
+ }
+&nbsp;
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (filterFn === 'all') {
+<span class="cstat-no" title="statement not covered" > params.append('read_filter', 'all');</span>
+ <span class="missing-if-branch" title="if path not taken" >I</span>} else if (filterFn === 'starred') {
+<span class="cstat-no" title="statement not covered" > params.append('starred', 'true');</span>
+<span class="cstat-no" title="statement not covered" > params.append('read_filter', 'all');</span>
+ } else {
+ // default to unread
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (!searchQuery) {
+ params.append('read_filter', 'unread');
+ }
+ }
+&nbsp;
+ const queryString = params.toString();
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (queryString) {
+ url += `?${queryString}`;
+ }
&nbsp;
- // Apply filters
- <span class="missing-if-branch" title="if path not taken" >I</span>if (filterFn === 'all') {
-<span class="cstat-no" title="statement not covered" > params.append('read_filter', 'all');</span>
- <span class="missing-if-branch" title="if path not taken" >I</span>} else if (filterFn === 'starred') {
-<span class="cstat-no" title="statement not covered" > params.append('starred', 'true');</span>
-<span class="cstat-no" title="statement not covered" > params.append('read_filter', 'all');</span>
+ apiFetch(url)
+ .then((res) =&gt; {
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) {
+<span class="cstat-no" title="statement not covered" > throw new Error('Failed to fetch items');</span>
+ }
+ return res.json();
+ })
+ .then((data) =&gt; {
+ if (maxId) {
+ setItems((prev) =&gt; [...prev, ...data]);
} else {
- // default to unread
- params.append('read_filter', 'unread');
+ setItems(data);
}
+ setHasMore(data.length &gt; 0);
+ setLoading(false);
+ setLoadingMore(false);
+ })
+ .catch((err) =&gt; {
+ setError(err.message);
+ setLoading(false);
+ setLoadingMore(false);
+ });
+ };
&nbsp;
- const queryString = params.toString();
- <span class="missing-if-branch" title="else path not taken" >E</span>if (queryString) {
- url += `?${queryString}`;
- }
+ useEffect(() =&gt; {
+ fetchItems();
+ setSelectedIndex(-1);
+ }, [feedId, tagName, filterFn, searchParams]);
&nbsp;
- fetch(url)
- .then((res) =&gt; {
- <span class="missing-if-branch" title="if path not taken" >I</span>if (!res.ok) {
-<span class="cstat-no" title="statement not covered" > throw new Error('Failed to fetch items');</span>
- }
- return res.json();
- })
- .then((data) =&gt; {
- if (maxId) {
- setItems((prev) =&gt; [...prev, ...data]);
- } else {
- setItems(data);
- }
- setHasMore(data.length &gt; 0);
- setLoading(false);
- setLoadingMore(false);
- })
- .catch((err) =&gt; {
- setError(err.message);
- setLoading(false);
- setLoadingMore(false);
- });
- };
&nbsp;
- useEffect(() =&gt; {
- fetchItems();
- }, [feedId, tagName, filterFn]);
+ const scrollToItem = (index: number) =&gt; {
+ const element = document.getElementById(`item-${index}`);
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (element) {
+ element.scrollIntoView({ behavior: 'auto', block: 'start' });
+ }
+ };
&nbsp;
- const [selectedIndex, setSelectedIndex] = useState(-1);
+ const markAsRead = (item: Item) =&gt; {
+ const updatedItem = { ...item, read: true };
+ // Optimistic update
+ setItems((prevItems) =&gt; prevItems.map((i) =&gt; (i._id === item._id ? updatedItem : i)));
&nbsp;
- useEffect(() =&gt; {
- const handleKeyDown = (e: KeyboardEvent) =&gt; {
- <span class="missing-if-branch" title="if path not taken" >I</span>if (items.length === 0) <span class="cstat-no" title="statement not covered" >return;</span>
+ apiFetch(`/api/item/${item._id}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ read: true, starred: item.starred }),
+ }).catch(<span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; <span class="cstat-no" title="statement not covered" >console.error('Failed to mark read', err))</span>;
+ };
&nbsp;
- if (e.key === 'j') {
- setSelectedIndex((prev) =&gt; {
- const nextIndex = Math.min(prev + 1, items.length - 1);
- <span class="missing-if-branch" title="else path not taken" >E</span>if (nextIndex !== prev) {
- const item = items[nextIndex];
- if (!item.read) {
- markAsRead(item);
- }
- scrollToItem(nextIndex);
- }
- return nextIndex;
- });
- <span class="missing-if-branch" title="if path not taken" >I</span>} else if (e.key === 'k') {
-<span class="cstat-no" title="statement not covered" > setSelectedIndex(<span class="fstat-no" title="function not covered" >(p</span>rev) =&gt; {</span>
- const nextIndex = <span class="cstat-no" title="statement not covered" >Math.max(prev - 1, 0);</span>
-<span class="cstat-no" title="statement not covered" > if (nextIndex !== prev) {</span>
-<span class="cstat-no" title="statement not covered" > scrollToItem(nextIndex);</span>
- }
-<span class="cstat-no" title="statement not covered" > return nextIndex;</span>
- });
- <span class="missing-if-branch" title="else path not taken" >E</span>} else if (e.key === 's') {
- setSelectedIndex((currentIndex) =&gt; {
- <span class="missing-if-branch" title="else path not taken" >E</span>if (currentIndex &gt;= 0 &amp;&amp; currentIndex &lt; items.length) {
- toggleStar(items[currentIndex]);
- }
- return currentIndex;
- });
- }
- };
+ const toggleStar = (item: Item) =&gt; {
+ const updatedItem = { ...item, starred: !item.starred };
+ // Optimistic update
+ setItems((prevItems) =&gt; prevItems.map((i) =&gt; (i._id === item._id ? updatedItem : i)));
&nbsp;
- window.addEventListener('keydown', handleKeyDown);
- return () =&gt; 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(<span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; <span class="cstat-no" title="statement not covered" >console.error('Failed to toggle star', err))</span>;
+ };
&nbsp;
- const scrollToItem = (index: number) =&gt; {
- const element = document.getElementById(`item-${index}`);
- <span class="missing-if-branch" title="else path not taken" >E</span>if (element) {
- element.scrollIntoView({ behavior: 'smooth', block: 'start' });
- }
- };
+ useEffect(() =&gt; {
+ const handleKeyDown = (e: KeyboardEvent) =&gt; {
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (items.length === 0) <span class="cstat-no" title="statement not covered" >return;</span>
&nbsp;
- const markAsRead = (item: Item) =&gt; {
- const updatedItem = { ...item, read: true };
- // Optimistic update
- setItems((prevItems) =&gt; prevItems.map((i) =&gt; (i._id === item._id ? updatedItem : i)));
-&nbsp;
- fetch(`/api/item/${item._id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ read: true, starred: item.starred }),
- }).catch(<span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; <span class="cstat-no" title="statement not covered" >console.error('Failed to mark read', err))</span>;
+ if (e.key === 'j') {
+ setSelectedIndex((prev) =&gt; {
+ const nextIndex = Math.min(prev + 1, items.length - 1);
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (nextIndex !== prev) {
+ const item = items[nextIndex];
+ if (!item.read) {
+ markAsRead(item);
+ }
+ scrollToItem(nextIndex);
+ }
+ return nextIndex;
+ });
+ <span class="missing-if-branch" title="if path not taken" >I</span>} else if (e.key === 'k') {
+<span class="cstat-no" title="statement not covered" > setSelectedIndex(<span class="fstat-no" title="function not covered" >(p</span>rev) =&gt; {</span>
+ const nextIndex = <span class="cstat-no" title="statement not covered" >Math.max(prev - 1, 0);</span>
+<span class="cstat-no" title="statement not covered" > if (nextIndex !== prev) {</span>
+<span class="cstat-no" title="statement not covered" > scrollToItem(nextIndex);</span>
+ }
+<span class="cstat-no" title="statement not covered" > return nextIndex;</span>
+ });
+ <span class="missing-if-branch" title="else path not taken" >E</span>} else if (e.key === 's') {
+ setSelectedIndex((currentIndex) =&gt; {
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (currentIndex &gt;= 0 &amp;&amp; currentIndex &lt; items.length) {
+ toggleStar(items[currentIndex]);
+ }
+ return currentIndex;
+ });
+ }
};
&nbsp;
- const toggleStar = (item: Item) =&gt; {
- const updatedItem = { ...item, starred: !item.starred };
- // Optimistic update
- setItems((prevItems) =&gt; prevItems.map((i) =&gt; (i._id === item._id ? updatedItem : i)));
+ window.addEventListener('keydown', handleKeyDown);
+ return () =&gt; window.removeEventListener('keydown', handleKeyDown);
+ }, [items]);
&nbsp;
- fetch(`/api/item/${item._id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ read: item.read, starred: !item.starred }),
- }).catch(<span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; <span class="cstat-no" title="statement not covered" >console.error('Failed to toggle star', err))</span>;
- };
&nbsp;
- useEffect(() =&gt; {
- const observer = new IntersectionObserver(
- (entries) =&gt; {
- entries.forEach((entry) =&gt; {
- // Infinity scroll sentinel
- if (entry.target.id === 'load-more-sentinel') {
- <span class="missing-if-branch" title="else path not taken" >E</span>if (entry.isIntersecting &amp;&amp; !loadingMore &amp;&amp; hasMore &amp;&amp; items.length &gt; 0) {
- fetchItems(String(items[items.length - 1]._id));
- }
- return;
- }
&nbsp;
- // If item is not intersecting and is above the viewport, it's been scrolled past
- <span class="missing-if-branch" title="else path not taken" >E</span>if (!entry.isIntersecting &amp;&amp; entry.boundingClientRect.top &lt; 0) {
- const index = Number(entry.target.getAttribute('data-index'));
- <span class="missing-if-branch" title="else path not taken" >E</span>if (!isNaN(index) &amp;&amp; index &gt;= 0 &amp;&amp; index &lt; items.length) {
- const item = items[index];
- <span class="missing-if-branch" title="else path not taken" >E</span>if (!item.read) {
- markAsRead(item);
- }
- }
- }
- });
- },
- { root: null, threshold: 0 }
- );
+ useEffect(() =&gt; {
+ const observer = new IntersectionObserver(
+ (entries) =&gt; {
+ entries.forEach((entry) =&gt; {
+ // Infinity scroll sentinel
+ if (entry.target.id === 'load-more-sentinel') {
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (entry.isIntersecting &amp;&amp; !loadingMore &amp;&amp; hasMore &amp;&amp; items.length &gt; 0) {
+ fetchItems(String(items[items.length - 1]._id));
+ }
+ return;
+ }
&nbsp;
- items.forEach((_, index) =&gt; {
- const el = document.getElementById(`item-${index}`);
- <span class="missing-if-branch" title="else path not taken" >E</span>if (el) observer.observe(el);
+ // If item is not intersecting and is above the viewport, it's been scrolled past
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (!entry.isIntersecting &amp;&amp; entry.boundingClientRect.top &lt; 0) {
+ const index = Number(entry.target.getAttribute('data-index'));
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (!isNaN(index) &amp;&amp; index &gt;= 0 &amp;&amp; index &lt; items.length) {
+ const item = items[index];
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (!item.read) {
+ markAsRead(item);
+ }
+ }
+ }
});
+ },
+ { root: null, threshold: 0 }
+ );
&nbsp;
- const sentinel = document.getElementById('load-more-sentinel');
- if (sentinel) observer.observe(sentinel);
+ items.forEach((_, index) =&gt; {
+ const el = document.getElementById(`item-${index}`);
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (el) observer.observe(el);
+ });
&nbsp;
- return () =&gt; observer.disconnect();
- }, [items, loadingMore, hasMore]);
+ const sentinel = document.getElementById('load-more-sentinel');
+ if (sentinel) observer.observe(sentinel);
&nbsp;
- if (loading) return &lt;div className="feed-items-loading"&gt;Loading items...&lt;/div&gt;;
- if (error) return &lt;div className="feed-items-error"&gt;Error: {error}&lt;/div&gt;;
+ return () =&gt; observer.disconnect();
+ }, [items, loadingMore, hasMore]);
&nbsp;
+ if (loading) return &lt;div className="feed-items-loading"&gt;Loading items...&lt;/div&gt;;
+ if (error) return &lt;div className="feed-items-error"&gt;Error: {error}&lt;/div&gt;;
&nbsp;
- return (
- &lt;div className="feed-items"&gt;
- {items.length === 0 ? (
-<span class="branch-0 cbranch-no" title="branch not covered" > &lt;p&gt;No items found.&lt;/p&gt;</span>
- ) : (
- &lt;ul className="item-list"&gt;
- {items.map((item, index) =&gt; (
- &lt;div
- id={`item-${index}`}
- key={item._id}
- data-index={index}
- className={index === selectedIndex ? 'selected-item-container' : ''}
- onClick={<span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >s</span>etSelectedIndex(index)}</span>
- &gt;
- &lt;FeedItem item={item} /&gt;
- &lt;/div&gt;
- ))}
- {hasMore &amp;&amp; (
- &lt;div id="load-more-sentinel" className="loading-more"&gt;
- {loadingMore ? 'Loading more...' : ''}
- &lt;/div&gt;
- )}
- &lt;/ul&gt;
- )}
- &lt;/div&gt;
- );
+ return (
+ &lt;div className="feed-items"&gt;
+ {items.length === 0 ? (
+<span class="branch-0 cbranch-no" title="branch not covered" > &lt;p&gt;No items found.&lt;/p&gt;</span>
+ ) : (
+ &lt;ul className="item-list"&gt;
+ {items.map((item, index) =&gt; (
+ &lt;div
+ id={`item-${index}`}
+ key={item._id}
+ data-index={index}
+ data-selected={index === selectedIndex}
+ onClick={<span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >s</span>etSelectedIndex(index)}</span>
+ &gt;
+ &lt;FeedItem item={item} /&gt;
+ &lt;/div&gt;
+ ))}
+ {hasMore &amp;&amp; (
+ &lt;div id="load-more-sentinel" className="loading-more"&gt;
+ {loadingMore ? 'Loading more...' : ''}
+ &lt;/div&gt;
+ )}
+ &lt;/ul&gt;
+ )}
+ &lt;/div&gt;
+ );
}
&nbsp;</pre></td></tr></table></pre>
- <div class="push"></div>
- <!-- for sticky footer -->
- </div>
- <!-- /wrapper -->
- <div class="footer quiet pad2 space-top1 center small">
- Code coverage generated by
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
- at 2026-02-13T21:49:58.924Z
- </div>
- <script src="../../prettify.js"></script>
- <script>
- window.onload = function () {
- prettyPrint();
- };
- </script>
- <script src="../../sorter.js"></script>
- <script src="../../block-navigation.js"></script>
- </body>
+ <div class='push'></div><!-- for sticky footer -->
+ </div><!-- /wrapper -->
+ <div class='footer quiet pad2 space-top1 center small'>
+ Code coverage generated by
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
+ at 2026-02-14T18:02:09.004Z
+ </div>
+ <script src="../../prettify.js"></script>
+ <script>
+ window.onload = function () {
+ prettyPrint();
+ };
+ </script>
+ <script src="../../sorter.js"></script>
+ <script src="../../block-navigation.js"></script>
+ </body>
</html>
+ \ No newline at end of file