diff options
| -rw-r--r-- | .thicket/tickets.jsonl | 6 | ||||
| -rw-r--r-- | frontend/src/components/Login.test.tsx | 12 | ||||
| -rw-r--r-- | frontend/src/components/Login.tsx | 11 | ||||
| -rw-r--r-- | frontend/tests/auth.spec.ts | 11 |
4 files changed, 34 insertions, 6 deletions
diff --git a/.thicket/tickets.jsonl b/.thicket/tickets.jsonl index 6b4a77d..09f36e1 100644 --- a/.thicket/tickets.jsonl +++ b/.thicket/tickets.jsonl @@ -30,6 +30,7 @@ {"id":"NK-8hu7z1","title":"scrape full text button","description":"add this feature back in to the v2 ui and verify it is still working (not sure if we have any tests)","type":"feature","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-14T17:27:49.815938946Z","updated":"2026-02-14T17:58:19.083695149Z"} {"id":"NK-8rhpp3","title":"v2 frontend: when selected, don't change style of feed items","description":"Just leave them the same when j/k \"selects\" an item. No blue side thing, no change in background, it's distracting. Just scroll it to the right place.","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T00:39:50.01934312Z","updated":"2026-02-14T01:02:54.204739756Z"} {"id":"NK-8s75ec","title":"page size and performance","description":"Do some analysis of page size (css/html/javascript) on the legacy version vs. new version and give me a report. We want it to be small and fast! If the new version is much worse file some tickets to investigate further.","type":"task","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-13T20:16:13.898081788Z","updated":"2026-02-13T21:50:12.004391671Z"} +{"id":"NK-8u3uo3","title":"Add configurable username support","description":"Currently login accepts a username field but ignores it. We should allow configuring a username (defaulting to 'neko') and validate it during login.","type":"task","status":"open","priority":2,"labels":null,"assignee":"","created":"2026-02-15T05:16:55.257993616Z","updated":"2026-02-15T05:16:55.257993616Z"} {"id":"NK-9hx0y7","title":"Implement Frontend Login","description":"Create login page and auth logic in the new React frontend. Port functionality from legacy login.html.","type":"","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-13T05:44:01.546395342Z","updated":"2026-02-13T05:50:33.877452063Z"} {"id":"NK-9pgjph","title":"v2 ui - font size 18px","description":"Compare your font sizes with the legacy version -- I think they're a little too small (16 vs 18 baseline)","type":"bug","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T03:21:48.453217898Z","updated":"2026-02-14T03:24:25.316927886Z"} {"id":"NK-a217qm","title":"font styles","description":"Switch the default font stack and size to match the legacy UI","type":"feature","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-14T00:59:37.686539676Z","updated":"2026-02-14T01:25:03.119825567Z"} @@ -37,6 +38,7 @@ {"id":"NK-acq08a","title":"update Makefile","description":"Ensure the Makefile builds things and works\nTest it by running it regularly before checking in!","type":"task","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T00:55:40.127322076Z","updated":"2026-02-14T01:26:31.564799193Z"} {"id":"NK-ahzf5c","title":"drop \"mark read\" button","description":"there's no mark read/unread buttons, it's just by scrolling!","type":"task","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-13T19:28:20.708443259Z","updated":"2026-02-13T20:26:43.029168286Z"} {"id":"NK-aiaza3","title":"clean up root directory of project","description":"There are some scripts in the root directory like run_e2e.sh that probably should be in a subdirectory -- look into it and make things a little tidier where approopriate.","type":"task","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-14T17:42:14.8736959Z","updated":"2026-02-14T23:44:07.081167865Z"} +{"id":"NK-arckp3","title":"Install golangci-lint in dev environment","description":"Local Makefile uses 'go vet' because 'golangci-lint' is missing. CI uses golangci-lint. We should install it locally for consistency.","type":"task","status":"open","priority":1,"labels":null,"assignee":"","created":"2026-02-15T05:14:44.840444844Z","updated":"2026-02-15T05:14:44.840444844Z"} {"id":"NK-bsdwqz","title":"terminal UI","description":"once there is good test coverage and a clean backend API, work on a nice efficient TUI with https://github.com/charmbracelet/bubbletea","type":"task","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-13T01:54:02.285738454Z","updated":"2026-02-13T04:42:09.824268427Z"} {"id":"NK-ca9t70","title":"Vanilla JS: Add Feed UI","description":"Add UI to add a new feed by URL in vanilla JS prototype.","type":"feature","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-14T04:47:41.764330544Z","updated":"2026-02-14T04:47:41.764330544Z"} {"id":"NK-chns2b","title":"reach parity between vanilla js and react v2 ui","description":"Continue implementing the vanilla js one with minimal overhad/depdnencies to be fast and lean. Make sure there are tests and rely on the v2 ui and legacy version as references.","type":"epic","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-14T04:45:06.813453353Z","updated":"2026-02-14T04:45:06.813453353Z"} @@ -91,7 +93,7 @@ {"id":"NK-pwogze","title":"Crawler testing","description":"The general usage of neko is to run it and have it crawl feeds in the background after X minutes\n\nDo we have a test that can verify that's happening","type":"task","status":"closed","priority":0,"labels":null,"assignee":"","created":"2026-02-14T18:43:04.957220621Z","updated":"2026-02-14T20:44:09.76303647Z"} {"id":"NK-qwef98","title":"UI Styling: Controls \u0026 Header","description":"","type":"","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-13T18:05:18.450759919Z","updated":"2026-02-13T18:11:46.291830432Z"} {"id":"NK-r1aqiw","title":"Implement Subscription List and UserInfo endpoints","description":"","type":"task","status":"closed","priority":2,"labels":null,"assignee":"","created":"2026-02-15T00:21:51.619650383Z","updated":"2026-02-15T00:44:41.428045944Z"} -{"id":"NK-r39tqq","title":"username + password","description":"it's too weird to have just a password -- in the old UI i had a username but it was just ignored. but password managers get confused by this new no username thing.\n\nlet's make it so you can enter a username and password. to start, just let that be a no-op (it ignores the username and just pays attention to the password.)\n\nwe can consider later on if we want to make the username real and definable too.","type":"bug","status":"open","priority":1,"labels":null,"assignee":"","created":"2026-02-15T02:56:35.68970604Z","updated":"2026-02-15T02:56:35.68970604Z"} +{"id":"NK-r39tqq","title":"username + password","description":"it's too weird to have just a password -- in the old UI i had a username but it was just ignored. but password managers get confused by this new no username thing.\n\nlet's make it so you can enter a username and password. to start, just let that be a no-op (it ignores the username and just pays attention to the password.)\n\nwe can consider later on if we want to make the username real and definable too.","type":"bug","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-15T02:56:35.68970604Z","updated":"2026-02-15T05:16:49.35160585Z"} {"id":"NK-r6nhj0","title":"import/export","description":"Import/Export has only ever been partially implemented. Let's finish it up across OPML (de facto standard) but also simple txt line oriented input/output. We may need to file a ticket to deal with the async crawling as part of this.","type":"feature","status":"closed","priority":1,"labels":null,"assignee":"","created":"2026-02-14T16:45:04.739162003Z","updated":"2026-02-14T17:42:20.713094047Z"} {"id":"NK-rhelrq","title":"Add end-to-end integration test for complete crawl cycle","description":"Create an integration test that verifies the complete crawl workflow: start server with background crawling enabled, add a feed via API, wait for background crawl to execute, verify items are fetched and stored. This would require mocking or using a test RSS feed and potentially adjusting timing for faster test execution.","type":"task","status":"open","priority":3,"labels":null,"assignee":"","created":"2026-02-14T20:44:31.052207214Z","updated":"2026-02-14T20:44:31.052207214Z"} {"id":"NK-ric1zs","title":"Migrate frontend to /api/ endpoints","description":"The backend now provides a clean REST API at /api/. Update the frontend UI to use these new endpoints instead of the legacy backward-compatibility routes (/stream/, /feed/, etc.). This will allow for cleaner separation and better utilization of proper REST patterns.","type":"cleanup","status":"closed","priority":3,"labels":null,"assignee":"","created":"2026-02-13T04:26:55.864725765Z","updated":"2026-02-13T04:26:55.864725765Z"} @@ -128,6 +130,7 @@ {"id":"NK-d3uzxij","from_ticket_id":"NK-g818qn","to_ticket_id":"NK-2tcnmq","type":"created_from","created":"2026-02-14T22:46:32.880451865Z"} {"id":"NK-d50pbhs","from_ticket_id":"NK-zt4e32","to_ticket_id":"NK-t0nmbj","type":"created_from","created":"2026-02-13T05:44:01.598803513Z"} {"id":"NK-d58l5y5","from_ticket_id":"NK-ojdcmq","to_ticket_id":"NK-5ocxgm","type":"created_from","created":"2026-02-13T19:45:07.395109046Z"} +{"id":"NK-d7gsxp6","from_ticket_id":"NK-arckp3","to_ticket_id":"NK-tgmc9s","type":"created_from","created":"2026-02-15T05:14:44.866754938Z"} {"id":"NK-d7tr9po","from_ticket_id":"NK-8d1uzw","to_ticket_id":"NK-3g1ouf","type":"created_from","created":"2026-02-15T02:24:06.423302632Z"} {"id":"NK-d7zgu6w","from_ticket_id":"NK-fm15vq","to_ticket_id":"NK-ymf1jb","type":"created_from","created":"2026-02-13T20:27:36.795511524Z"} {"id":"NK-d86tgcs","from_ticket_id":"NK-ed1iah","to_ticket_id":"NK-1phdpf","type":"created_from","created":"2026-02-13T04:26:55.917754798Z"} @@ -157,6 +160,7 @@ {"id":"NK-dwav3hh","from_ticket_id":"NK-6q9nyg","to_ticket_id":"NK-x924bu","type":"created_from","created":"2026-02-13T03:54:37.639569082Z"} {"id":"NK-dwoby5c","from_ticket_id":"NK-5zgzee","to_ticket_id":"NK-d4c8jv","type":"created_from","created":"2026-02-14T04:47:43.008840145Z"} {"id":"NK-dy64l7j","from_ticket_id":"NK-m8bya7","to_ticket_id":"NK-mbuw7q","type":"created_from","created":"2026-02-14T03:08:18.518727956Z"} +{"id":"NK-dyjzq6a","from_ticket_id":"NK-8u3uo3","to_ticket_id":"NK-r39tqq","type":"created_from","created":"2026-02-15T05:16:55.281950619Z"} {"id":"NK-dz8ehu0","from_ticket_id":"NK-zs9we8","to_ticket_id":"NK-mwf9q2","type":"created_from","created":"2026-02-13T18:05:16.20323827Z"} {"id":"NK-dzv8knd","from_ticket_id":"NK-2ypbgd","to_ticket_id":"NK-d4c8jv","type":"created_from","created":"2026-02-14T04:47:39.432806599Z"} {"id":"NK-dzzbbxf","from_ticket_id":"NK-fnaohu","to_ticket_id":"NK-mwf9q2","type":"created_from","created":"2026-02-13T18:05:19.608013948Z"} diff --git a/frontend/src/components/Login.test.tsx b/frontend/src/components/Login.test.tsx index cf69eb1..47f37e3 100644 --- a/frontend/src/components/Login.test.tsx +++ b/frontend/src/components/Login.test.tsx @@ -23,6 +23,7 @@ describe('Login Component', () => { it('renders login form', () => { renderLogin(); + expect(screen.getByLabelText(/username/i)).toBeInTheDocument(); expect(screen.getByLabelText(/password/i)).toBeInTheDocument(); expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument(); }); @@ -34,6 +35,7 @@ describe('Login Component', () => { renderLogin(); + fireEvent.change(screen.getByLabelText(/username/i), { target: { value: 'testuser' } }); fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'secret' } }); fireEvent.click(screen.getByRole('button', { name: /login/i })); @@ -42,9 +44,17 @@ describe('Login Component', () => { '/api/login', expect.objectContaining({ method: 'POST', + body: expect.any(URLSearchParams), }) ); }); + + // Check if params contained username and password + const callArgs = vi.mocked(global.fetch).mock.calls[0][1]; + const body = callArgs?.body as URLSearchParams; + expect(body.get('username')).toBe('testuser'); + expect(body.get('password')).toBe('secret'); + // Navigation assertion is tricky without mocking useNavigate, // but if no error is shown, we assume success path was taken expect(screen.queryByText(/login failed/i)).not.toBeInTheDocument(); @@ -58,6 +68,7 @@ describe('Login Component', () => { renderLogin(); + fireEvent.change(screen.getByLabelText(/username/i), { target: { value: 'testuser' } }); fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'wrong' } }); fireEvent.click(screen.getByRole('button', { name: /login/i })); @@ -71,6 +82,7 @@ describe('Login Component', () => { renderLogin(); + fireEvent.change(screen.getByLabelText(/username/i), { target: { value: 'testuser' } }); fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'secret' } }); fireEvent.click(screen.getByRole('button', { name: /login/i })); diff --git a/frontend/src/components/Login.tsx b/frontend/src/components/Login.tsx index b62acea..87694cb 100644 --- a/frontend/src/components/Login.tsx +++ b/frontend/src/components/Login.tsx @@ -5,6 +5,7 @@ import './Login.css'; import { apiFetch } from '../utils'; export default function Login() { + const [username, setUsername] = useState('neko'); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const navigate = useNavigate(); @@ -16,6 +17,7 @@ export default function Login() { try { // Use URLSearchParams to send as form-urlencoded, matching backend expectation const params = new URLSearchParams(); + params.append('username', username); params.append('password', password); const res = await apiFetch('/api/login', { @@ -39,6 +41,15 @@ export default function Login() { <form onSubmit={handleSubmit} className="login-form"> <h1>neko rss mode</h1> <div className="form-group"> + <label htmlFor="username">username</label> + <input + id="username" + type="text" + value={username} + onChange={(e) => setUsername(e.target.value)} + /> + </div> + <div className="form-group"> <label htmlFor="password">password</label> <input id="password" diff --git a/frontend/tests/auth.spec.ts b/frontend/tests/auth.spec.ts index 4161a83..33ada85 100644 --- a/frontend/tests/auth.spec.ts +++ b/frontend/tests/auth.spec.ts @@ -26,11 +26,8 @@ test.describe('Authentication - No Password Required', () => { // Visit login page await page.goto('/v2/login'); - // Submit with empty password - const passwordInput = page.getByLabel(/password/i); - await expect(passwordInput).toBeVisible(); - - // Leave password empty and submit + // Fill username and submit with empty password + await page.fill('input[id="username"]', 'neko'); await page.click('button[type="submit"]'); // Should redirect to dashboard @@ -68,6 +65,7 @@ test.describe('Authentication - Password Required', () => { await page.goto('/v2/login'); // Enter wrong password + await page.fill('input[id="username"]', 'neko'); await page.fill('input[type="password"]', 'wrongpassword'); await page.click('button[type="submit"]'); @@ -82,6 +80,7 @@ test.describe('Authentication - Password Required', () => { await page.goto('/v2/login'); // Enter correct password (must match what the server was started with) + await page.fill('input[id="username"]', 'neko'); await page.fill('input[type="password"]', 'testpass'); await page.click('button[type="submit"]'); @@ -94,6 +93,7 @@ test.describe('Authentication - Password Required', () => { test.skip('should persist authentication across page reloads', async ({ page }) => { // Login first await page.goto('/v2/login'); + await page.fill('input[id="username"]', 'neko'); await page.fill('input[type="password"]', 'testpass'); await page.click('button[type="submit"]'); await expect(page).toHaveURL(/.*\/v2\/?$/); @@ -109,6 +109,7 @@ test.describe('Authentication - Password Required', () => { test.skip('should logout and redirect to login page', async ({ page }) => { // Login first await page.goto('/v2/login'); + await page.fill('input[id="username"]', 'neko'); await page.fill('input[type="password"]', 'testpass'); await page.click('button[type="submit"]'); await expect(page).toHaveURL(/.*\/v2\/?$/); |
