mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-25 18:40:06 -04:00
Render unknown URLs as a 404 page inside the app layout (#805)
An unknown URL (e.g. /archive instead of /archives) had no matching route, so react-router rendered the root errorElement in place of Root. That put PageNotFound outside ThemeProvider, and its Mantine Box threw "MantineProvider was not found", leaving a blank page. - A catch-all route renders PageNotFound inside Root, with the navigation bar, footer and theme. - The root errorElement brings its own ThemeProvider, so it can render when Root itself fails. - App exports its routes so a test can mount them in a memory router. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d75be8e1a4
commit
bb8945e8ad
2 changed files with 61 additions and 3 deletions
|
|
@ -87,10 +87,12 @@ function Root() {
|
|||
</QueryProvider>
|
||||
}
|
||||
|
||||
const router = createBrowserRouter(createRoutesFromElements(<Route
|
||||
export const routes = createRoutesFromElements(<Route
|
||||
path='/'
|
||||
element={<Root/>}
|
||||
errorElement={<PageNotFound/>}
|
||||
// Root (and its ThemeProvider) is not rendered when Root itself fails, so the error page
|
||||
// brings its own theme; Mantine components cannot render without one.
|
||||
errorElement={<ThemeProvider><PageNotFound/></ThemeProvider>}
|
||||
>
|
||||
<Route index element={<ErrorBoundary><DashboardPage/></ErrorBoundary>}/>
|
||||
<Route path='search/*' element={<ErrorBoundary><DashboardPage/></ErrorBoundary>}/>
|
||||
|
|
@ -121,7 +123,11 @@ const router = createBrowserRouter(createRoutesFromElements(<Route
|
|||
<Route path='repos/*' element={<ErrorBoundary><ReposRoute/></ErrorBoundary>}/>
|
||||
<Route path='files/*' element={<ErrorBoundary><FilesRoute/></ErrorBoundary>}/>
|
||||
<Route path='flasher/*' element={<ErrorBoundary><FlasherRoute/></ErrorBoundary>}/>
|
||||
</Route>));
|
||||
{/* Unknown URLs render inside Root, with the navigation bar and theme. */}
|
||||
<Route path='*' element={<PageNotFound/>}/>
|
||||
</Route>);
|
||||
|
||||
const router = createBrowserRouter(routes);
|
||||
|
||||
export default function App() {
|
||||
useEventsInterval();
|
||||
|
|
|
|||
52
app/src/App.test.js
Normal file
52
app/src/App.test.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import React from 'react';
|
||||
import {render, screen} from '@testing-library/react';
|
||||
import {createMemoryRouter, RouterProvider} from 'react-router';
|
||||
import {routes} from './App';
|
||||
import {StatusProvider} from './hooks/customHooks';
|
||||
import {FileWorkerStatusProvider} from './contexts/FileWorkerStatusContext';
|
||||
import {MediaContextProvider} from './contexts/contexts';
|
||||
|
||||
// Jest does not transform CSS inside node_modules (see transformIgnorePatterns).
|
||||
jest.mock('@mantine/core/styles.css', () => ({}));
|
||||
jest.mock('@mantine/notifications/styles.css', () => ({}));
|
||||
// Routes this test does not visit, whose libraries jsdom/Jest cannot load (maplibre needs
|
||||
// browser APIs; esptool-js is ESM-only).
|
||||
jest.mock('./components/Map', () => ({MapRoute: () => null}));
|
||||
jest.mock('./components/Flasher', () => ({FlasherRoute: () => null}));
|
||||
|
||||
describe('unknown routes', () => {
|
||||
beforeEach(() => {
|
||||
// resetMocks clears setupTests' implementation; the theme and media providers read it.
|
||||
window.matchMedia.mockImplementation(query => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
}));
|
||||
global.fetch = jest.fn(() => Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
headers: {get: () => 'application/json'},
|
||||
json: () => Promise.resolve({tags: []}),
|
||||
text: () => Promise.resolve('{}'),
|
||||
}));
|
||||
});
|
||||
|
||||
it('renders Page Not Found inside the app layout', async () => {
|
||||
const router = createMemoryRouter(routes, {initialEntries: ['/no-such-page']});
|
||||
// The providers App wraps around the router.
|
||||
render(<StatusProvider>
|
||||
<FileWorkerStatusProvider>
|
||||
<MediaContextProvider>
|
||||
<RouterProvider router={router}/>
|
||||
</MediaContextProvider>
|
||||
</FileWorkerStatusProvider>
|
||||
</StatusProvider>);
|
||||
|
||||
expect(await screen.findByText('Page Not Found!')).toBeInTheDocument();
|
||||
// Rendered inside Root's layout (the footer comes from Root), not as a bare error page.
|
||||
expect(screen.getByText('Donate')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue