diff --git a/frontend/src/app/forbidden/page.tsx b/frontend/src/app/forbidden/page.tsx index 32bf022..bcbb208 100644 --- a/frontend/src/app/forbidden/page.tsx +++ b/frontend/src/app/forbidden/page.tsx @@ -9,8 +9,7 @@ import Link from 'next/link'; import { ShieldAlert } from 'lucide-react'; import { Button } from '@/components/ui/button'; -/* istanbul ignore next - Next.js metadata, not executable code */ -export const metadata: Metadata = { +export const metadata: Metadata = /* istanbul ignore next */ { title: '403 - Forbidden', description: 'You do not have permission to access this resource', }; diff --git a/frontend/tests/app/demos/page.test.tsx b/frontend/tests/app/demos/page.test.tsx new file mode 100644 index 0000000..e6e3178 --- /dev/null +++ b/frontend/tests/app/demos/page.test.tsx @@ -0,0 +1,274 @@ +/** + * Tests for Demo Tour Page + */ + +import { render, screen, within } from '@testing-library/react'; +import DemoTourPage from '@/app/demos/page'; + +// Mock Next.js Link +jest.mock('next/link', () => ({ + __esModule: true, + default: ({ children, href, ...props }: any) => { + return ( + + {children} + + ); + }, +})); + +describe('DemoTourPage', () => { + describe('Page Structure', () => { + it('renders without crashing', () => { + render(); + expect(screen.getByRole('banner')).toBeInTheDocument(); // header + expect(screen.getByRole('main')).toBeInTheDocument(); + }); + + it('renders header with navigation', () => { + render(); + const header = screen.getByRole('banner'); + expect(within(header).getByText('Demo Tour')).toBeInTheDocument(); + expect(within(header).getByRole('link', { name: /home/i })).toHaveAttribute('href', '/'); + expect(within(header).getByRole('link', { name: /start exploring/i })).toHaveAttribute('href', '/login'); + }); + }); + + describe('Hero Section', () => { + it('renders hero heading and description', () => { + render(); + expect(screen.getByText('Explore All Features')).toBeInTheDocument(); + expect(screen.getByText(/Try everything with our pre-configured demo accounts/i)).toBeInTheDocument(); + }); + + it('renders Interactive Demo badge', () => { + render(); + expect(screen.getByText('Interactive Demo')).toBeInTheDocument(); + }); + }); + + describe('Quick Start Guide', () => { + it('renders quick start heading', () => { + render(); + expect(screen.getByText('Quick Start Guide')).toBeInTheDocument(); + expect(screen.getByText('Follow these simple steps to get started')).toBeInTheDocument(); + }); + + it('renders all 3 quick start steps', () => { + render(); + expect(screen.getByText('Choose a Demo')).toBeInTheDocument(); + expect(screen.getByText(/Browse the demo categories below/i)).toBeInTheDocument(); + + expect(screen.getByText('Use Credentials')).toBeInTheDocument(); + expect(screen.getByText(/Copy the demo credentials and login/i)).toBeInTheDocument(); + + expect(screen.getByText('Explore Freely')).toBeInTheDocument(); + expect(screen.getByText(/Test all features - everything resets automatically/i)).toBeInTheDocument(); + }); + }); + + describe('Demo Categories', () => { + it('renders demo categories heading', () => { + render(); + expect(screen.getByText('Demo Categories')).toBeInTheDocument(); + expect(screen.getByText('Explore different areas of the template')).toBeInTheDocument(); + }); + + it('renders Design System Hub category', () => { + render(); + expect(screen.getByText('Design System Hub')).toBeInTheDocument(); + expect(screen.getByText(/Browse components, layouts, spacing, and forms/i)).toBeInTheDocument(); + expect(screen.getByText('All UI components')).toBeInTheDocument(); + expect(screen.getByText('Layout patterns')).toBeInTheDocument(); + expect(screen.getByText('Spacing philosophy')).toBeInTheDocument(); + expect(screen.getByText('Form implementations')).toBeInTheDocument(); + }); + + it('renders Authentication System category with credentials', () => { + render(); + expect(screen.getByText('Authentication System')).toBeInTheDocument(); + expect(screen.getByText(/Test login, registration, password reset/i)).toBeInTheDocument(); + expect(screen.getByText('Login & logout')).toBeInTheDocument(); + expect(screen.getByText('Registration')).toBeInTheDocument(); + expect(screen.getByText('Password reset')).toBeInTheDocument(); + expect(screen.getByText('Session tokens')).toBeInTheDocument(); + + // Check for credentials + const authCards = screen.getAllByText(/demo@example\.com/i); + expect(authCards.length).toBeGreaterThan(0); + const demo123 = screen.getAllByText(/Demo123!/i); + expect(demo123.length).toBeGreaterThan(0); + }); + + it('renders User Features category with credentials', () => { + render(); + expect(screen.getByText('User Features')).toBeInTheDocument(); + expect(screen.getByText(/Experience user settings, profile management/i)).toBeInTheDocument(); + expect(screen.getByText('Profile editing')).toBeInTheDocument(); + expect(screen.getByText('Password changes')).toBeInTheDocument(); + expect(screen.getByText('Active sessions')).toBeInTheDocument(); + expect(screen.getByText('Preferences')).toBeInTheDocument(); + }); + + it('renders Admin Dashboard category with credentials', () => { + render(); + expect(screen.getByText('Admin Dashboard')).toBeInTheDocument(); + expect(screen.getByText(/Explore admin panel with user management/i)).toBeInTheDocument(); + expect(screen.getByText('User management')).toBeInTheDocument(); + expect(screen.getByText('Analytics charts')).toBeInTheDocument(); + expect(screen.getByText('Bulk operations')).toBeInTheDocument(); + expect(screen.getByText('Organization control')).toBeInTheDocument(); + + // Check for admin credentials + expect(screen.getByText(/admin@example\.com/i)).toBeInTheDocument(); + expect(screen.getByText(/Admin123!/i)).toBeInTheDocument(); + }); + + it('shows Login Required badge for authenticated demos', () => { + render(); + const loginRequiredBadges = screen.getAllByText('Login Required'); + // Should be 3: Auth, User Features, Admin Dashboard + expect(loginRequiredBadges.length).toBe(3); + }); + + it('has working links to each demo category', () => { + render(); + + // Design System Hub + const exploreLinks = screen.getAllByRole('link', { name: /explore/i }); + expect(exploreLinks.some(link => link.getAttribute('href') === '/dev')).toBe(true); + + // Authentication System + const tryNowLinks = screen.getAllByRole('link', { name: /try now/i }); + expect(tryNowLinks.length).toBeGreaterThan(0); + expect(tryNowLinks.some(link => link.getAttribute('href') === '/login')).toBe(true); + expect(tryNowLinks.some(link => link.getAttribute('href') === '/settings')).toBe(true); + expect(tryNowLinks.some(link => link.getAttribute('href') === '/admin')).toBe(true); + }); + }); + + describe('Suggested Exploration Paths', () => { + it('renders exploration paths heading', () => { + render(); + expect(screen.getByText('Suggested Exploration Paths')).toBeInTheDocument(); + expect(screen.getByText('Choose your adventure based on available time')).toBeInTheDocument(); + }); + + it('renders Quick Tour path', () => { + render(); + expect(screen.getByText('Quick Tour (5 min)')).toBeInTheDocument(); + expect(screen.getByText('Browse Design System components')).toBeInTheDocument(); + expect(screen.getByText('Test login flow')).toBeInTheDocument(); + expect(screen.getByText('View user settings')).toBeInTheDocument(); + }); + + it('renders Full Experience path', () => { + render(); + expect(screen.getByText('Full Experience (15 min)')).toBeInTheDocument(); + expect(screen.getByText('Explore all design system pages')).toBeInTheDocument(); + expect(screen.getByText('Try complete auth flow')).toBeInTheDocument(); + expect(screen.getByText('Update profile and password')).toBeInTheDocument(); + expect(screen.getByText('Check active sessions')).toBeInTheDocument(); + expect(screen.getByText('Login as admin and manage users')).toBeInTheDocument(); + expect(screen.getByText('View analytics dashboard')).toBeInTheDocument(); + }); + + it('exploration path items have links', () => { + render(); + + // Quick Tour links + const devLinks = screen.getAllByRole('link'); + const hasDevLink = devLinks.some(link => link.getAttribute('href') === '/dev'); + expect(hasDevLink).toBe(true); + + const hasLoginLink = devLinks.some(link => link.getAttribute('href') === '/login'); + expect(hasLoginLink).toBe(true); + + const hasSettingsLink = devLinks.some(link => link.getAttribute('href') === '/settings'); + expect(hasSettingsLink).toBe(true); + }); + }); + + describe('Feature Checklist', () => { + it('renders checklist heading', () => { + render(); + expect(screen.getByText('What to Try')).toBeInTheDocument(); + expect(screen.getByText(/Complete checklist of features to explore/i)).toBeInTheDocument(); + }); + + it('renders Feature Checklist card', () => { + render(); + expect(screen.getByText('Feature Checklist')).toBeInTheDocument(); + expect(screen.getByText(/Try these features to experience the full power/i)).toBeInTheDocument(); + }); + + it('renders all checklist items', () => { + render(); + + expect(screen.getByText('Browse design system components')).toBeInTheDocument(); + expect(screen.getByText('Test login/logout flow')).toBeInTheDocument(); + expect(screen.getByText('Register a new account')).toBeInTheDocument(); + expect(screen.getByText('Reset password')).toBeInTheDocument(); + expect(screen.getByText('Update user profile')).toBeInTheDocument(); + expect(screen.getByText('Change password')).toBeInTheDocument(); + expect(screen.getByText('View active sessions')).toBeInTheDocument(); + expect(screen.getByText('Login as admin')).toBeInTheDocument(); + expect(screen.getByText('Manage users (admin)')).toBeInTheDocument(); + expect(screen.getByText('View analytics (admin)')).toBeInTheDocument(); + expect(screen.getByText('Perform bulk operations (admin)')).toBeInTheDocument(); + expect(screen.getByText('Explore organizations (admin)')).toBeInTheDocument(); + }); + }); + + describe('CTA Section', () => { + it('renders final CTA heading', () => { + render(); + expect(screen.getByText('Ready to Start?')).toBeInTheDocument(); + expect(screen.getByText(/Pick a demo category above or jump right into the action/i)).toBeInTheDocument(); + }); + + it('has CTA buttons', () => { + render(); + + const tryAuthLinks = screen.getAllByRole('link', { name: /try authentication flow/i }); + expect(tryAuthLinks.some(link => link.getAttribute('href') === '/login')).toBe(true); + + const browseDesignLinks = screen.getAllByRole('link', { name: /browse design system/i }); + expect(browseDesignLinks.some(link => link.getAttribute('href') === '/dev')).toBe(true); + }); + }); + + describe('Accessibility', () => { + it('has proper heading hierarchy', () => { + render(); + const main = screen.getByRole('main'); + const headings = within(main).getAllByRole('heading'); + expect(headings.length).toBeGreaterThan(0); + }); + + it('has proper role attributes', () => { + render(); + expect(screen.getByRole('banner')).toBeInTheDocument(); + expect(screen.getByRole('main')).toBeInTheDocument(); + }); + }); + + describe('Navigation Links', () => { + it('has multiple navigation links throughout the page', () => { + render(); + + const allLinks = screen.getAllByRole('link'); + + // Should have links to: /, /dev, /login, /settings, /admin, and various /settings/* and /admin/* paths + expect(allLinks.length).toBeGreaterThan(10); + + // Verify key destination paths exist + const hrefs = allLinks.map(link => link.getAttribute('href')); + expect(hrefs).toContain('/'); + expect(hrefs).toContain('/dev'); + expect(hrefs).toContain('/login'); + expect(hrefs).toContain('/settings'); + expect(hrefs).toContain('/admin'); + }); + }); +}); diff --git a/frontend/tests/app/forbidden/page.test.tsx b/frontend/tests/app/forbidden/page.test.tsx index dc440b7..59e4cde 100644 --- a/frontend/tests/app/forbidden/page.test.tsx +++ b/frontend/tests/app/forbidden/page.test.tsx @@ -4,9 +4,15 @@ */ import { render, screen } from '@testing-library/react'; -import ForbiddenPage from '@/app/forbidden/page'; +import ForbiddenPage, { metadata } from '@/app/forbidden/page'; describe('ForbiddenPage', () => { + it('has correct metadata', () => { + expect(metadata).toBeDefined(); + expect(metadata.title).toBe('403 - Forbidden'); + expect(metadata.description).toBe('You do not have permission to access this resource'); + }); + it('renders page heading', () => { render(); diff --git a/frontend/tests/app/page.test.tsx b/frontend/tests/app/page.test.tsx index fec9b67..9f6b443 100644 --- a/frontend/tests/app/page.test.tsx +++ b/frontend/tests/app/page.test.tsx @@ -3,7 +3,7 @@ * Tests for the new FastNext Template landing page */ -import { render, screen, within } from '@testing-library/react'; +import { render, screen, within, fireEvent } from '@testing-library/react'; import Home from '@/app/page'; // Mock Next.js components @@ -244,6 +244,36 @@ describe('HomePage', () => { }); }); + describe('Demo Modal', () => { + it('demo modal is initially closed', () => { + render(); + expect(screen.queryByTestId('demo-modal')).not.toBeInTheDocument(); + }); + + it('opens demo modal when Try Demo button is clicked in header', () => { + render(); + const tryDemoButtons = screen.getAllByRole('button', { name: /try demo/i }); + // Click the first Try Demo button (from header) + fireEvent.click(tryDemoButtons[0]); + expect(screen.getByTestId('demo-modal')).toBeInTheDocument(); + }); + + it('closes demo modal when close button is clicked', () => { + render(); + // Open the modal + const tryDemoButtons = screen.getAllByRole('button', { name: /try demo/i }); + fireEvent.click(tryDemoButtons[0]); + expect(screen.getByTestId('demo-modal')).toBeInTheDocument(); + + // Close the modal + const closeButtons = screen.getAllByRole('button', { name: /close/i }); + const modalCloseButton = closeButtons.find(btn => btn.textContent === 'Close'); + if (modalCloseButton) { + fireEvent.click(modalCloseButton); + } + }); + }); + describe('Accessibility', () => { it('has proper heading hierarchy', () => { render(); diff --git a/frontend/tests/components/home/Header.test.tsx b/frontend/tests/components/home/Header.test.tsx index 15ad0cb..56c694e 100644 --- a/frontend/tests/components/home/Header.test.tsx +++ b/frontend/tests/components/home/Header.test.tsx @@ -171,6 +171,70 @@ describe('Header', () => { const githubLinks = screen.getAllByRole('link', { name: /github/i }); expect(githubLinks.length).toBeGreaterThan(0); }); + + it('clicking mobile menu navigation links works', () => { + render( +
+ ); + + const designSystemLinks = screen.getAllByRole('link', { name: /Design System/i }); + // Click the mobile menu link (there should be 2: desktop + mobile) + if (designSystemLinks.length > 1) { + fireEvent.click(designSystemLinks[1]); + expect(designSystemLinks[1]).toHaveAttribute('href', '/dev'); + } + }); + + it('clicking mobile menu GitHub link works', () => { + render( +
+ ); + + const githubLinks = screen.getAllByRole('link', { name: /github/i }); + // Find the mobile menu GitHub link (second one) + if (githubLinks.length > 1) { + const mobileGithubLink = githubLinks[1]; + fireEvent.click(mobileGithubLink); + expect(mobileGithubLink).toHaveAttribute('href', expect.stringContaining('github.com')); + } + }); + + it('mobile menu Try Demo button calls onOpenDemoModal', () => { + const mockOnOpenDemoModal = jest.fn(); + render(
); + + const tryDemoButtons = screen.getAllByRole('button', { name: /try demo/i }); + // Click the mobile menu button (there should be 2: desktop + mobile) + if (tryDemoButtons.length > 1) { + fireEvent.click(tryDemoButtons[1]); + expect(mockOnOpenDemoModal).toHaveBeenCalled(); + } + }); + + it('mobile menu Login link works', () => { + render( +
+ ); + + const loginLinks = screen.getAllByRole('link', { name: /login/i }); + // Click the mobile menu login link (there should be 2: desktop + mobile) + if (loginLinks.length > 1) { + fireEvent.click(loginLinks[1]); + expect(loginLinks[1]).toHaveAttribute('href', '/login'); + } + }); }); describe('Accessibility', () => {