feat: add guide navigation integration and tests (refs #203)

- Add Guide link to public nav bar (desktop + mobile) in HomePage
- Add Guide link to authenticated sidebar in Layout.tsx
- Add Guide link to HamburgerDrawer with window.location guard
- Add GuidePage integration tests (6 test scenarios)
- Remove old PDF user guide at public/docs/v2026-01-03.pdf

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-02-15 17:19:40 -06:00
parent 6196ebfc91
commit 197aeda2ef
5 changed files with 120 additions and 150 deletions

View File

@@ -14,6 +14,7 @@ import BuildRoundedIcon from '@mui/icons-material/BuildRounded';
import PlaceRoundedIcon from '@mui/icons-material/PlaceRounded';
import SettingsRoundedIcon from '@mui/icons-material/SettingsRounded';
import DescriptionRoundedIcon from '@mui/icons-material/DescriptionRounded';
import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded';
import MenuIcon from '@mui/icons-material/Menu';
import ChevronLeftRoundedIcon from '@mui/icons-material/ChevronLeftRounded';
import ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded';
@@ -51,6 +52,7 @@ export const Layout: React.FC<LayoutProps> = ({ children, mobileMode = false })
{ name: 'Gas Stations', href: '/garage/stations', icon: <PlaceRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Documents', href: '/garage/documents', icon: <DescriptionRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Settings', href: '/garage/settings', icon: <SettingsRoundedIcon sx={{ fontSize: 20 }} /> },
{ name: 'Guide', href: '/guide', icon: <HelpOutlineRoundedIcon sx={{ fontSize: 20 }} /> },
];
const sidebarWidth = sidebarCollapsed ? 64 : 256;

View File

@@ -89,6 +89,12 @@ export const HomePage = () => {
<a href="#about" className="text-white/75 hover:text-white transition-colors">
About
</a>
<a
href="/guide"
className="text-white/75 hover:text-white transition-colors"
>
Guide
</a>
<button
onClick={handleSignup}
className="border border-primary-500/90 text-primary-500 hover:bg-primary-500/10 hover:border-primary-500 font-semibold py-2 px-6 rounded-lg transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-primary-500/50"
@@ -160,6 +166,12 @@ export const HomePage = () => {
>
About
</a>
<a
href="/guide"
className="block text-white/75 hover:text-white transition-colors py-2"
>
Guide
</a>
<button
onClick={handleSignup}
className="w-full border border-primary-500/90 text-primary-500 hover:bg-primary-500/10 font-semibold py-2 px-6 rounded-lg transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-primary-500/50"

View File

@@ -0,0 +1,98 @@
import { render, screen } from '@testing-library/react';
jest.mock('@auth0/auth0-react', () => ({
useAuth0: () => ({
isAuthenticated: false,
loginWithRedirect: jest.fn(),
}),
}));
jest.mock('react-router-dom', () => ({
useNavigate: () => jest.fn(),
}));
// Mock MUI Accordion to avoid jsdom layout issues
jest.mock('@mui/material', () => {
const actual = jest.requireActual('@mui/material');
return {
...actual,
Accordion: ({ children, expanded }: any) => (
<div data-testid="accordion" data-expanded={expanded}>
{children}
</div>
),
AccordionSummary: ({ children }: any) => <div>{children}</div>,
AccordionDetails: ({ children }: any) => <div>{children}</div>,
};
});
jest.mock('@mui/icons-material/ExpandMore', () => ({
__esModule: true,
default: () => <span data-testid="expand-icon" />,
}));
import { GuidePage } from '../GuidePage/GuidePage';
import { guideSections } from '../GuidePage/guideTypes';
describe('GuidePage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders page heading and subheading', () => {
render(<GuidePage />);
expect(screen.getByText('User Guide')).toBeInTheDocument();
expect(
screen.getByText(/Precision Vehicle Management/)
).toBeInTheDocument();
});
it('renders all 10 section headings', () => {
render(<GuidePage />);
const expectedHeadings = [
'1. Getting Started',
'2. Dashboard',
'3. Vehicles',
'4. Fuel Logs',
'5. Maintenance',
'6. Gas Stations',
'7. Documents',
'8. Settings',
'9. Subscription Tiers and Pro Features',
'10. Mobile Experience',
];
expectedHeadings.forEach((heading) => {
expect(screen.getByRole('heading', { name: heading })).toBeInTheDocument();
});
});
it('renders TOC with correct section titles', () => {
render(<GuidePage />);
guideSections.forEach((section) => {
const matches = screen.getAllByText(section.title);
expect(matches.length).toBeGreaterThanOrEqual(1);
});
});
it('renders navigation bar with Guide link highlighted', () => {
render(<GuidePage />);
const guideLinks = screen.getAllByText('Guide');
expect(guideLinks.length).toBeGreaterThanOrEqual(1);
});
it('renders GuideScreenshot components with loading="lazy"', () => {
render(<GuidePage />);
const images = document.querySelectorAll('img[loading="lazy"]');
expect(images.length).toBeGreaterThan(0);
});
it('renders footer with copyright', () => {
render(<GuidePage />);
expect(screen.getByText(/FB Technologies LLC/)).toBeInTheDocument();
});
});

View File

@@ -20,6 +20,7 @@ import LocalGasStationRoundedIcon from '@mui/icons-material/LocalGasStationRound
import DescriptionRoundedIcon from '@mui/icons-material/DescriptionRounded';
import BuildRoundedIcon from '@mui/icons-material/BuildRounded';
import SettingsRoundedIcon from '@mui/icons-material/SettingsRounded';
import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded';
import { MobileScreen } from '../../../core/store/navigation';
// iOS swipeable drawer configuration
@@ -41,6 +42,7 @@ interface MenuItem {
// Menu items from bottom to top (reversed order in array for rendering)
const menuItems: MenuItem[] = [
{ screen: 'Settings', label: 'Settings', icon: <SettingsRoundedIcon /> },
{ screen: 'Guide' as MobileScreen, label: 'Guide', icon: <HelpOutlineRoundedIcon /> },
{ screen: 'Documents', label: 'Documents', icon: <DescriptionRoundedIcon /> },
{ screen: 'Maintenance', label: 'Maintenance', icon: <BuildRoundedIcon /> },
{ screen: 'Log Fuel', label: 'Log Fuel', icon: <LocalGasStationRoundedIcon /> },
@@ -57,6 +59,12 @@ export const HamburgerDrawer: React.FC<HamburgerDrawerProps> = ({
const theme = useTheme();
const handleNavigate = (screen: MobileScreen) => {
// Guide is a public route outside /garage/* shell
if (screen === ('Guide' as MobileScreen)) {
window.location.href = '/guide';
onClose();
return;
}
onNavigate(screen);
onClose();
};