aboutsummaryrefslogtreecommitdiffstats
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/src/App.test.tsx4
-rw-r--r--frontend/src/App.tsx8
-rw-r--r--frontend/src/Navigation.test.tsx45
-rw-r--r--frontend/src/components/FeedList.test.tsx3
-rw-r--r--frontend/src/components/FeedList.tsx2
5 files changed, 37 insertions, 25 deletions
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 1ef9763..27b9da2 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -16,7 +16,7 @@ describe('App', () => {
} as Response);
window.history.pushState({}, 'Test page', '/v2/login');
render(<App />);
- expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument();
+ expect(await screen.findByRole('button', { name: /login/i })).toBeInTheDocument();
});
it('renders dashboard when authenticated', async () => {
@@ -54,7 +54,7 @@ describe('App', () => {
'/api/logout',
expect.objectContaining({ method: 'POST' })
);
- expect(window.location.href).toBe('/v2/login');
+ expect(window.location.href).toBe('/v2/#/login');
});
});
});
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 478444c..cc45949 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
-import { BrowserRouter, Routes, Route, Navigate, useLocation } from 'react-router-dom';
+import { HashRouter, Routes, Route, Navigate, useLocation } from 'react-router-dom';
import Login from './components/Login';
import './App.css';
import { apiFetch } from './utils';
@@ -113,10 +113,10 @@ function App() {
localStorage.setItem('neko-font-theme', newFontTheme);
};
- const basename = window.location.pathname.startsWith('/v2') ? '/v2' : '/';
+
return (
- <BrowserRouter basename={basename}>
+ <HashRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route
@@ -133,7 +133,7 @@ function App() {
}
/>
</Routes>
- </BrowserRouter>
+ </HashRouter>
);
}
diff --git a/frontend/src/Navigation.test.tsx b/frontend/src/Navigation.test.tsx
index 7d73249..b0bae86 100644
--- a/frontend/src/Navigation.test.tsx
+++ b/frontend/src/Navigation.test.tsx
@@ -27,7 +27,7 @@ describe('Navigation and Filtering', () => {
it('preserves "all" filter when clicking a feed', async () => {
Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: 1024 });
- window.history.pushState({}, '', '/');
+ window.history.pushState({}, '', '/#/');
render(<App />);
// Wait for sidebar to load and feeds section to be visible
@@ -47,7 +47,9 @@ describe('Navigation and Filtering', () => {
fireEvent.click(allFilter);
// Verify URL has filter=all
- expect(window.location.search).toContain('filter=all');
+ await waitFor(() => {
+ expect(window.location.hash).toContain('filter=all');
+ });
// Click Feed 1
const feed1Link = screen.getByText('Feed 1');
@@ -55,8 +57,8 @@ describe('Navigation and Filtering', () => {
// Verify URL is /feed/1?filter=all (or similar)
await waitFor(() => {
- expect(window.location.pathname).toContain('/feed/1');
- expect(window.location.search).toContain('filter=all');
+ expect(window.location.hash).toContain('/feed/1');
+ expect(window.location.hash).toContain('filter=all');
});
// Click Feed 2
@@ -65,48 +67,57 @@ describe('Navigation and Filtering', () => {
// Verify URL is /feed/2?filter=all
await waitFor(() => {
- expect(window.location.pathname).toContain('/feed/2');
- expect(window.location.search).toContain('filter=all');
+ expect(window.location.hash).toContain('/feed/2');
+ expect(window.location.hash).toContain('filter=all');
});
});
it('highlights the correct filter link', async () => {
- window.history.pushState({}, '', '/');
+ window.history.pushState({}, '', '/#/');
render(<App />);
await waitFor(() => {
- expect(screen.getByText('unread')).toHaveClass('active');
+ const unreadLink = screen.getByText('unread');
+ expect(unreadLink.className).toContain('active');
});
fireEvent.click(screen.getByText('all'));
await waitFor(() => {
- expect(screen.getByText('all')).toHaveClass('active');
- expect(screen.getByText('unread')).not.toHaveClass('active');
+ const allLink = screen.getByText('all');
+ const unreadLink = screen.getByText('unread');
+ expect(allLink.className).toContain('active');
+ expect(unreadLink.className).not.toContain('active');
});
});
it('highlights "unread" as active even when on a feed page without filter param', async () => {
- window.history.pushState({}, '', '/feed/1');
+ window.history.pushState({}, '', '/#/feed/1');
render(<App />);
await waitFor(() => {
- expect(screen.getByText('unread')).toHaveClass('active');
+ const unreadLink = screen.getByText('unread');
+ expect(unreadLink.className).toContain('active');
});
});
it('preserves search query when clicking a feed', async () => {
- window.history.pushState({}, '', '/?q=linux');
+ window.history.pushState({}, '', '/#/?q=linux');
render(<App />);
- await screen.findByRole('heading', { name: /Feeds/i, level: 4 });
- fireEvent.click(screen.getByRole('heading', { name: /Feeds/i, level: 4 }));
+ // Wait for load
+ await waitFor(() => {
+ expect(screen.queryByText(/Loading feeds/i)).not.toBeInTheDocument();
+ });
+
+ const feedsHeader = await screen.findByRole('heading', { name: /Feeds/i, level: 4 });
+ fireEvent.click(feedsHeader);
await screen.findByText('Feed 1');
fireEvent.click(screen.getByText('Feed 1'));
await waitFor(() => {
- expect(window.location.pathname).toContain('/feed/1');
- expect(window.location.search).toContain('q=linux');
+ expect(window.location.hash).toContain('/feed/1');
+ expect(window.location.hash).toContain('q=linux');
});
});
});
diff --git a/frontend/src/components/FeedList.test.tsx b/frontend/src/components/FeedList.test.tsx
index d3d3a57..d4e72cc 100644
--- a/frontend/src/components/FeedList.test.tsx
+++ b/frontend/src/components/FeedList.test.tsx
@@ -202,8 +202,9 @@ describe('FeedList Component', () => {
await waitFor(() => {
expect(global.fetch).toHaveBeenCalledWith('/api/logout', expect.any(Object));
- expect(window.location.href).toContain('/v2/login');
+ expect(window.location.href).toContain('/v2/#/login');
});
+ // @ts-expect-error - restoring window.location
window.location = originalLocation;
});
diff --git a/frontend/src/components/FeedList.tsx b/frontend/src/components/FeedList.tsx
index 556ce6e..ce83333 100644
--- a/frontend/src/components/FeedList.tsx
+++ b/frontend/src/components/FeedList.tsx
@@ -111,7 +111,7 @@ export default function FeedList({
if (error) return <div className="feed-list-error">Error: {error}</div>;
const handleLogout = () => {
- apiFetch('/api/logout', { method: 'POST' }).then(() => (window.location.href = '/v2/login'));
+ apiFetch('/api/logout', { method: 'POST' }).then(() => (window.location.href = '/v2/#/login'));
};
return (