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:
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
98
frontend/src/pages/__tests__/GuidePage.test.tsx
Normal file
98
frontend/src/pages/__tests__/GuidePage.test.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user