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 --- frontend/coverage/src/components/Settings.tsx.html | 524 ++++++++++++++------- 1 file changed, 352 insertions(+), 172 deletions(-) (limited to 'frontend/coverage/src/components/Settings.tsx.html') diff --git a/frontend/coverage/src/components/Settings.tsx.html b/frontend/coverage/src/components/Settings.tsx.html index df6d027..3d8d219 100644 --- a/frontend/coverage/src/components/Settings.tsx.html +++ b/frontend/coverage/src/components/Settings.tsx.html @@ -1,64 +1,68 @@ + - + + Code coverage report for src/components/Settings.tsx - - - - -
-
-

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

-
-
- 75.55% - Statements - 34/45 -
- -
- 56.25% - Branches - 9/16 -
- -
- 82.35% - Functions - 14/17 -
- -
- 84.61% - Lines - 33/39 -
+ + + +
+
+

All files / src/components Settings.tsx

+
+ +
+ 56.25% + Statements + 36/64 +
+ + +
+ 41.66% + Branches + 10/24 +
+ + +
+ 63.63% + Functions + 14/22 +
+ + +
+ 62.5% + Lines + 35/56 +
+ +

- 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 @@ -180,7 +184,67 @@ 119 120 121 -122  +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181  +        @@ -191,8 +255,6 @@ 14x   14x -3x -    14x 4x @@ -212,6 +274,10 @@     14x +3x +  +  +14x 1x 1x   @@ -254,6 +320,34 @@     14x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +14x       @@ -281,6 +375,34 @@       +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  5x     @@ -304,142 +426,200 @@  
import React, { useEffect, useState } from 'react';
 import type { Feed } from '../types';
 import './Settings.css';
+import { apiFetch } from '../utils';
  
 export default function Settings() {
-    const [feeds, setFeeds] = useState<Feed[]>([]);
-    const [newFeedUrl, setNewFeedUrl] = useState('');
-    const [loading, setLoading] = useState(false);
-    const [error, setError] = useState<string | null>(null);
+  const [feeds, setFeeds] = useState<Feed[]>([]);
+  const [newFeedUrl, setNewFeedUrl] = useState('');
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState<string | null>(null);
  
-    useEffect(() => {
+  const [importFile, setImportFile] = useState<File | null>(null);
+ 
+  const fetchFeeds = () => {
+    setLoading(true);
+    apiFetch('/api/feed/')
+      .then((res) => {
+        Iif (!res.ok) throw new Error('Failed to fetch feeds');
+        return res.json();
+      })
+      .then((data) => {
+        setFeeds(data);
+        setLoading(false);
+      })
+      .catch((err) => {
+        setError(err.message);
+        setLoading(false);
+      });
+  };
+ 
+  useEffect(() => {
+    fetchFeeds();
+  }, []);
+ 
+  const handleAddFeed = (e: React.FormEvent) => {
+    e.preventDefault();
+    Iif (!newFeedUrl) return;
+ 
+    setLoading(true);
+    apiFetch('/api/feed/', {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({ url: newFeedUrl }),
+    })
+      .then((res) => {
+        Iif (!res.ok) throw new Error('Failed to add feed');
+        return res.json();
+      })
+      .then(() => {
+        setNewFeedUrl('');
         fetchFeeds();
-    }, []);
+      })
+      .catch((err) => {
+        setError(err.message);
+        setLoading(false);
+      });
+  };
+ 
+  const handleDeleteFeed = (id: number) => {
+    Iif (!globalThis.confirm('Are you sure you want to delete this feed?')) return;
  
-    const fetchFeeds = () => {
-        setLoading(true);
-        fetch('/api/feed/')
-            .then((res) => {
-                Iif (!res.ok) throw new Error('Failed to fetch feeds');
-                return res.json();
-            })
-            .then((data) => {
-                setFeeds(data);
-                setLoading(false);
-            })
-            .catch((err) => {
-                setError(err.message);
-                setLoading(false);
-            });
-    };
+    setLoading(true);
+    apiFetch(`/api/feed/${id}`, {
+      method: 'DELETE',
+    })
+      .then((res) => {
+        Iif (!res.ok) throw new Error('Failed to delete feed');
+        setFeeds(feeds.filter((f) => f._id !== id));
+        setLoading(false);
+      })
+      .catch((err) => {
+        setError(err.message);
+        setLoading(false);
+      });
+  };
  
-    const handleAddFeed = (e: React.FormEvent) => {
-        e.preventDefault();
-        Iif (!newFeedUrl) return;
+  const handleImport = (e: React.FormEvent) => {
+    e.preventDefault();
+    if (!importFile) return;
  
-        setLoading(true);
-        fetch('/api/feed/', {
-            method: 'POST',
-            headers: { 'Content-Type': 'application/json' },
-            body: JSON.stringify({ url: newFeedUrl }),
-        })
-            .then((res) => {
-                Iif (!res.ok) throw new Error('Failed to add feed');
-                return res.json();
-            })
-            .then(() => {
-                setNewFeedUrl('');
-                fetchFeeds(); // Refresh list (or we could append if server returns full feed object)
-            })
-            .catch((err) => {
-                setError(err.message);
-                setLoading(false);
-            });
-    };
+    setLoading(true);
+    const formData = new FormData();
+    formData.append('file', importFile);
+    formData.append('format', 'opml');
  
-    const handleDeleteFeed = (id: number) => {
-        Iif (!globalThis.confirm('Are you sure you want to delete this feed?')) return;
+    apiFetch('/api/import', {
+      method: 'POST',
+      body: formData,
+    })
+      .then((res) => {
+        if (!res.ok) throw new Error('Failed to import feeds');
+        return res.json();
+      })
+      .then(() => {
+        setImportFile(null);
+        fetchFeeds();
+        alert('Import successful!');
+      })
+      .catch((err) => {
+        setError(err.message);
+        setLoading(false);
+      });
+  };
  
-        setLoading(true);
-        fetch(`/api/feed/${id}`, {
-            method: 'DELETE',
-        })
-            .then((res) => {
-                Iif (!res.ok) throw new Error('Failed to delete feed');
-                setFeeds(feeds.filter((f) => f._id !== id));
-                setLoading(false);
-            })
-            .catch((err) => {
-                setError(err.message);
-                setLoading(false);
-            });
-    };
+  return (
+    <div className="settings-page">
+      <h2>Settings</h2>
  
-    return (
-        <div className="settings-page">
-            <h2>Settings</h2>
+      <div className="add-feed-section">
+        <h3>Add New Feed</h3>
+        <form onSubmit={handleAddFeed} className="add-feed-form">
+          <input
+            type="url"
+            value={newFeedUrl}
+            onChange={(e) => setNewFeedUrl(e.target.value)}
+            placeholder="https://example.com/feed.xml"
+            required
+            className="feed-input"
+            disabled={loading}
+          />
+          <button type="submit" disabled={loading}>
+            Add Feed
+          </button>
+        </form>
+      </div>
  
-            <div className="add-feed-section">
-                <h3>Add New Feed</h3>
-                <form onSubmit={handleAddFeed} className="add-feed-form">
-                    <input
-                        type="url"
-                        value={newFeedUrl}
-                        onChange={(e) => setNewFeedUrl(e.target.value)}
-                        placeholder="https://example.com/feed.xml"
-                        required
-                        className="feed-input"
-                        disabled={loading}
-                    />
-                    <button type="submit" disabled={loading}>
-                        Add Feed
-                    </button>
-                </form>
-                {error && <p className="error-message">{error}</p>}
-            </div>
+      <div className="import-export-section">
+        <div className="import-section">
+          <h3>Import Feeds (OPML)</h3>
+          <form onSubmit={handleImport} className="import-form">
+            <input
+              type="file"
+              accept=".opml,.xml,.txt"
+              onChange={(e) => setImportFile(e.target.files?.[0] || null)}
+              className="file-input"
+              disabled={loading}
+            />
+            <button type="submit" disabled={!importFile || loading}>
+              Import
+            </button>
+          </form>
+        </div>
  
-            <div className="feed-list-section">
-                <h3>Manage Feeds</h3>
-                {loading && <p>Loading...</p>}
-                <ul className="settings-feed-list">
-                    {feeds.map((feed) => (
-                        <li key={feed._id} className="settings-feed-item">
-                            <div className="feed-info">
-                                <span className="feed-title">{feed.title || '(No Title)'}</span>
-                                <span className="feed-url">{feed.url}</span>
-                            </div>
-                            <button
-                                onClick={() => handleDeleteFeed(feed._id)}
-                                className="delete-btn"
-                                disabled={loading}
-                                title="Delete Feed"
-                            >
-                                Delete
-                            </button>
-                        </li>
-                    ))}
-                </ul>
-            </div>
+        <div className="export-section">
+          <h3>Export Feeds</h3>
+          <div className="export-buttons">
+            <a href="/api/export/opml" className="export-btn">OPML</a>
+            <a href="/api/export/text" className="export-btn">Text</a>
+            <a href="/api/export/json" className="export-btn">JSON</a>
+          </div>
         </div>
-    );
+      </div>
+ 
+      {error && <p className="error-message">{error}</p>}
+ 
+      <div className="feed-list-section">
+        <h3>Manage Feeds</h3>
+        {loading && <p>Loading...</p>}
+        <ul className="settings-feed-list">
+          {feeds.map((feed) => (
+            <li key={feed._id} className="settings-feed-item">
+              <div className="feed-info">
+                <span className="feed-title">{feed.title || '(No Title)'}</span>
+                <span className="feed-url">{feed.url}</span>
+              </div>
+              <button
+                onClick={() => handleDeleteFeed(feed._id)}
+                className="delete-btn"
+                disabled={loading}
+                title="Delete Feed"
+              >
+                Delete
+              </button>
+            </li>
+          ))}
+        </ul>
+      </div>
+    </div>
+  );
 }
  
-
- -
- - - - - - - +
+
+ + + + + + + \ No newline at end of file -- cgit v1.2.3