Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { cleanup, render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { Shell } from './App';
import { SettingsProvider } from './context/SettingsContext';

function renderShell(path: string) {
return render(
<MemoryRouter initialEntries={[path]}>
<SettingsProvider>
<Shell />
</SettingsProvider>
</MemoryRouter>
);
}

beforeEach(() => {
localStorage.clear();
vi.stubGlobal(
'fetch',
vi.fn(async () => ({ ok: true, status: 200, json: async () => [] }) as unknown as Response)
);
});

afterEach(() => {
cleanup();
vi.unstubAllGlobals();
});

describe('App shell', () => {
it('redirects the root path to the first news page', async () => {
renderShell('/');

await waitFor(() => expect(fetch).toHaveBeenCalled());
expect((fetch as unknown as ReturnType<typeof vi.fn>).mock.calls[0][0]).toBe(
'https://node-hnapi.herokuapp.com/news?page=1'
);
});

it('renders the chrome around the routed content', () => {
const { container } = renderShell('/news/1');

expect(container.querySelector('.wrapper')).toBeTruthy();
expect(screen.getByText('new')).toBeTruthy();
expect(screen.getByText('GitHub')).toBeTruthy();
});

it('sends unknown feed types back to the news feed', async () => {
renderShell('/nonsense/1');

await waitFor(() => expect(fetch).toHaveBeenCalled());
expect((fetch as unknown as ReturnType<typeof vi.fn>).mock.calls[0][0]).toContain('/news?page=1');
});

it('applies the persisted theme as the outer wrapper class', () => {
localStorage.setItem('theme', 'amoledblack');
const { container } = renderShell('/news/1');

expect(container.firstElementChild?.className).toBe('amoledblack');
});

it('routes item and user paths to their pages', async () => {
vi.stubGlobal(
'fetch',
vi.fn(async () => ({ ok: true, status: 200, json: async () => ({ id: 'ashwin', created: '', karma: 1 }) }) as unknown as Response)
);
renderShell('/user/ashwin');

await waitFor(() => expect(screen.getByText('ashwin')).toBeTruthy());
});
});
54 changes: 50 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,54 @@
export default function App() {
import { BrowserRouter, Navigate, Route, Routes, useParams } from 'react-router-dom';

import Footer from './components/Footer';
import Header from './components/Header';
import { SettingsProvider, useSettings } from './context/SettingsContext';
import Feed from './pages/Feed';
import ItemDetails from './pages/ItemDetails';
import User from './pages/User';

import './App.scss';

export const FEED_TYPES = ['news', 'newest', 'show', 'ask', 'jobs'];

/** Angular listed one route per feed; here a single param route rejects unknown feeds. */
function FeedRoute() {
const { feedType } = useParams<{ feedType: string }>();

if (!feedType || !FEED_TYPES.includes(feedType)) {
return <Navigate to="/news/1" replace />;
}

return <Feed />;
}

export function Shell() {
const { settings } = useSettings();

return (
<div className="wrapper">
<h1>Angular 2 HN</h1>
<p>React + TypeScript + Vite scaffolding is up. UI migration lands in the following phases.</p>
<div className={settings.theme}>
<div className="body-cover"></div>
<div className="wrapper">
<Header />
<Routes>
<Route path="/" element={<Navigate to="/news/1" replace />} />
<Route path="/item/:id" element={<ItemDetails />} />
<Route path="/user/:id" element={<User />} />
<Route path="/:feedType/:page" element={<FeedRoute />} />
<Route path="*" element={<Navigate to="/news/1" replace />} />
</Routes>
<Footer />
</div>
</div>
);
}

export default function App() {
return (
<SettingsProvider>
<BrowserRouter>
<Shell />
</BrowserRouter>
</SettingsProvider>
);
}