feat: Add comprehensive test suite and misc improvements

- Add frontend unit tests with Vitest for components, hooks, pages, and utilities
- Add backend tests for webhooks, notifications, middleware, and edge cases
- Add ForgotPassword, NotFound, and ResetPassword pages
- Add migration for orphaned staff resources conversion
- Add coverage directory to gitignore (generated reports)
- Various bug fixes and improvements from previous work

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-08 02:36:46 -05:00
parent c220612214
commit 8dc2248f1f
145 changed files with 77947 additions and 1048 deletions

View File

@@ -0,0 +1,687 @@
/**
* Unit tests for AboutPage component
*
* Tests cover:
* - Component rendering with all sections
* - Header section with title and subtitle
* - Story section with timeline
* - Mission section content
* - Values section with all value cards
* - CTA section integration
* - Internationalization (i18n)
* - Accessibility attributes
* - Responsive design elements
* - Dark mode support
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import React from 'react';
import AboutPage from '../AboutPage';
// Mock react-i18next
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => {
// Return mock translations based on key
const translations: Record<string, string> = {
// Header section
'marketing.about.title': 'About Smooth Schedule',
'marketing.about.subtitle': "We're on a mission to simplify scheduling for businesses everywhere.",
// Story section
'marketing.about.story.title': 'Our Story',
'marketing.about.story.content': 'We started creating bespoke custom scheduling and payment solutions in 2017. Through that work, we became convinced that we had a better way of doing things than other scheduling services out there.',
'marketing.about.story.content2': "Along the way, we discovered features and options that customers love, capabilities that nobody else offers. That's when we decided to change our model so we could help more businesses. SmoothSchedule was born from years of hands-on experience building what businesses actually need.",
'marketing.about.story.founded': 'Building scheduling solutions',
'marketing.about.story.timeline.experience': '8+ years building scheduling solutions',
'marketing.about.story.timeline.battleTested': 'Battle-tested with real businesses',
'marketing.about.story.timeline.feedback': 'Features born from customer feedback',
'marketing.about.story.timeline.available': 'Now available to everyone',
// Mission section
'marketing.about.mission.title': 'Our Mission',
'marketing.about.mission.content': 'To empower service businesses with the tools they need to grow, while giving their customers a seamless booking experience.',
// Values section
'marketing.about.values.title': 'Our Values',
'marketing.about.values.simplicity.title': 'Simplicity',
'marketing.about.values.simplicity.description': 'We believe powerful software can still be simple to use.',
'marketing.about.values.reliability.title': 'Reliability',
'marketing.about.values.reliability.description': 'Your business depends on us, so we never compromise on uptime.',
'marketing.about.values.transparency.title': 'Transparency',
'marketing.about.values.transparency.description': 'No hidden fees, no surprises. What you see is what you get.',
'marketing.about.values.support.title': 'Support',
'marketing.about.values.support.description': "We're here to help you succeed, every step of the way.",
};
return translations[key] || key;
},
}),
}));
// Mock CTASection component
vi.mock('../../../components/marketing/CTASection', () => ({
default: ({ variant }: { variant?: string }) => (
<div data-testid="cta-section" data-variant={variant}>
CTA Section
</div>
),
}));
// Test wrapper with Router
const createWrapper = () => {
return ({ children }: { children: React.ReactNode }) => (
<BrowserRouter>{children}</BrowserRouter>
);
};
describe('AboutPage', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('Component Rendering', () => {
it('should render the about page', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const title = screen.getByText(/About Smooth Schedule/i);
expect(title).toBeInTheDocument();
});
it('should render without crashing', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
expect(container).toBeTruthy();
});
it('should render all major sections', () => {
render(<AboutPage />, { wrapper: createWrapper() });
// Header
expect(screen.getByText(/About Smooth Schedule/i)).toBeInTheDocument();
// Story
expect(screen.getByText(/Our Story/i)).toBeInTheDocument();
// Mission
expect(screen.getByText(/Our Mission/i)).toBeInTheDocument();
// Values
expect(screen.getByText(/Our Values/i)).toBeInTheDocument();
// CTA
expect(screen.getByTestId('cta-section')).toBeInTheDocument();
});
});
describe('Header Section', () => {
it('should render page title', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const title = screen.getByText(/About Smooth Schedule/i);
expect(title).toBeInTheDocument();
});
it('should render page subtitle', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const subtitle = screen.getByText(/We're on a mission to simplify scheduling/i);
expect(subtitle).toBeInTheDocument();
});
it('should render title as h1 element', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const heading = screen.getByRole('heading', { level: 1 });
expect(heading).toHaveTextContent(/About Smooth Schedule/i);
});
it('should apply proper styling to title', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const heading = screen.getByRole('heading', { level: 1 });
expect(heading).toHaveClass('text-4xl');
expect(heading).toHaveClass('sm:text-5xl');
expect(heading).toHaveClass('font-bold');
});
it('should apply proper styling to subtitle', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const subtitle = screen.getByText(/We're on a mission to simplify scheduling/i);
expect(subtitle).toHaveClass('text-xl');
expect(subtitle.tagName).toBe('P');
});
it('should have gradient background', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const gradientSection = container.querySelector('.bg-gradient-to-br');
expect(gradientSection).toBeInTheDocument();
});
it('should center align header text', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const headerContainer = container.querySelector('.text-center');
expect(headerContainer).toBeInTheDocument();
});
});
describe('Story Section', () => {
it('should render story title', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const heading = screen.getByRole('heading', { level: 2, name: /Our Story/i });
expect(heading).toBeInTheDocument();
});
it('should render first story paragraph', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const paragraph = screen.getByText(/We started creating bespoke custom scheduling/i);
expect(paragraph).toBeInTheDocument();
});
it('should render second story paragraph', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const paragraph = screen.getByText(/Along the way, we discovered features/i);
expect(paragraph).toBeInTheDocument();
});
it('should render founding year 2017', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const year = screen.getByText(/2017/i);
expect(year).toBeInTheDocument();
});
it('should render founding description', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const description = screen.getByText(/Building scheduling solutions/i);
expect(description).toBeInTheDocument();
});
it('should render all timeline items', () => {
render(<AboutPage />, { wrapper: createWrapper() });
expect(screen.getByText(/8\+ years building scheduling solutions/i)).toBeInTheDocument();
expect(screen.getByText(/Battle-tested with real businesses/i)).toBeInTheDocument();
expect(screen.getByText(/Features born from customer feedback/i)).toBeInTheDocument();
expect(screen.getByText(/Now available to everyone/i)).toBeInTheDocument();
});
it('should have grid layout for story content', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const gridElement = container.querySelector('.grid.md\\:grid-cols-2');
expect(gridElement).toBeInTheDocument();
});
it('should style founding year prominently', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const year = screen.getByText(/2017/i);
expect(year).toHaveClass('text-6xl');
expect(year).toHaveClass('font-bold');
});
it('should have brand gradient background for timeline card', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const gradientCard = container.querySelector('.bg-gradient-to-br.from-brand-500');
expect(gradientCard).toBeInTheDocument();
});
});
describe('Mission Section', () => {
it('should render mission title', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const heading = screen.getByRole('heading', { level: 2, name: /Our Mission/i });
expect(heading).toBeInTheDocument();
});
it('should render mission content', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const content = screen.getByText(/To empower service businesses with the tools they need to grow/i);
expect(content).toBeInTheDocument();
});
it('should apply proper styling to mission title', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const heading = screen.getByRole('heading', { level: 2, name: /Our Mission/i });
expect(heading).toHaveClass('text-3xl');
expect(heading).toHaveClass('font-bold');
});
it('should apply proper styling to mission content', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const content = screen.getByText(/To empower service businesses/i);
expect(content).toHaveClass('text-xl');
expect(content.tagName).toBe('P');
});
it('should center align mission section', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const missionSection = screen.getByText(/Our Mission/i).closest('div')?.parentElement;
expect(missionSection).toHaveClass('text-center');
});
it('should have gray background', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const missionSection = container.querySelector('.bg-gray-50');
expect(missionSection).toBeInTheDocument();
});
});
describe('Values Section', () => {
it('should render values title', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const heading = screen.getByRole('heading', { level: 2, name: /Our Values/i });
expect(heading).toBeInTheDocument();
});
it('should render all four value cards', () => {
render(<AboutPage />, { wrapper: createWrapper() });
expect(screen.getByText(/Simplicity/i)).toBeInTheDocument();
expect(screen.getByText(/Reliability/i)).toBeInTheDocument();
expect(screen.getByText(/Transparency/i)).toBeInTheDocument();
expect(screen.getByText(/Support/i)).toBeInTheDocument();
});
it('should render Simplicity value description', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const description = screen.getByText(/We believe powerful software can still be simple to use/i);
expect(description).toBeInTheDocument();
});
it('should render Reliability value description', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const description = screen.getByText(/Your business depends on us, so we never compromise on uptime/i);
expect(description).toBeInTheDocument();
});
it('should render Transparency value description', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const description = screen.getByText(/No hidden fees, no surprises/i);
expect(description).toBeInTheDocument();
});
it('should render Support value description', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const description = screen.getByText(/We're here to help you succeed/i);
expect(description).toBeInTheDocument();
});
it('should have grid layout for value cards', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const gridElement = container.querySelector('.grid.md\\:grid-cols-2.lg\\:grid-cols-4');
expect(gridElement).toBeInTheDocument();
});
it('should render value icons', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
// Each value card should have an icon
const icons = container.querySelectorAll('svg');
// Should have at least 4 icons (one for each value)
expect(icons.length).toBeGreaterThanOrEqual(4);
});
it('should apply color-coded icon backgrounds', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
// Check for different colored backgrounds
expect(container.querySelector('.bg-brand-100')).toBeInTheDocument();
expect(container.querySelector('.bg-green-100')).toBeInTheDocument();
expect(container.querySelector('.bg-purple-100')).toBeInTheDocument();
expect(container.querySelector('.bg-orange-100')).toBeInTheDocument();
});
it('should center align value cards', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const valueCards = container.querySelectorAll('.text-center');
expect(valueCards.length).toBeGreaterThan(0);
});
it('should render value titles as h3 elements', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const simplicityHeading = screen.getByRole('heading', { level: 3, name: /Simplicity/i });
expect(simplicityHeading).toBeInTheDocument();
});
});
describe('CTA Section', () => {
it('should render CTA section', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const ctaSection = screen.getByTestId('cta-section');
expect(ctaSection).toBeInTheDocument();
});
it('should render CTA section with minimal variant', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const ctaSection = screen.getByTestId('cta-section');
expect(ctaSection).toHaveAttribute('data-variant', 'minimal');
});
});
describe('Internationalization', () => {
it('should use translations for header', () => {
render(<AboutPage />, { wrapper: createWrapper() });
expect(screen.getByText(/About Smooth Schedule/i)).toBeInTheDocument();
expect(screen.getByText(/We're on a mission to simplify scheduling/i)).toBeInTheDocument();
});
it('should use translations for story section', () => {
render(<AboutPage />, { wrapper: createWrapper() });
expect(screen.getByText(/Our Story/i)).toBeInTheDocument();
expect(screen.getByText(/We started creating bespoke custom scheduling/i)).toBeInTheDocument();
});
it('should use translations for mission section', () => {
render(<AboutPage />, { wrapper: createWrapper() });
expect(screen.getByText(/Our Mission/i)).toBeInTheDocument();
expect(screen.getByText(/To empower service businesses/i)).toBeInTheDocument();
});
it('should use translations for values section', () => {
render(<AboutPage />, { wrapper: createWrapper() });
expect(screen.getByText(/Our Values/i)).toBeInTheDocument();
expect(screen.getByText(/Simplicity/i)).toBeInTheDocument();
expect(screen.getByText(/Reliability/i)).toBeInTheDocument();
expect(screen.getByText(/Transparency/i)).toBeInTheDocument();
expect(screen.getByText(/Support/i)).toBeInTheDocument();
});
it('should use translations for timeline items', () => {
render(<AboutPage />, { wrapper: createWrapper() });
expect(screen.getByText(/8\+ years building scheduling solutions/i)).toBeInTheDocument();
expect(screen.getByText(/Battle-tested with real businesses/i)).toBeInTheDocument();
expect(screen.getByText(/Features born from customer feedback/i)).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have accessible heading hierarchy', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const h1 = screen.getByRole('heading', { level: 1 });
const h2Elements = screen.getAllByRole('heading', { level: 2 });
const h3Elements = screen.getAllByRole('heading', { level: 3 });
expect(h1).toBeInTheDocument();
expect(h2Elements.length).toBeGreaterThan(0);
expect(h3Elements.length).toBeGreaterThan(0);
});
it('should have proper heading structure (h1 -> h2 -> h3)', () => {
render(<AboutPage />, { wrapper: createWrapper() });
// Should have exactly one h1
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
// Should have multiple h2 sections
const h2Elements = screen.getAllByRole('heading', { level: 2 });
expect(h2Elements.length).toBe(3); // Story, Mission, Values
// Should have h3 for value titles
const h3Elements = screen.getAllByRole('heading', { level: 3 });
expect(h3Elements.length).toBe(4); // Four values
});
it('should use semantic HTML elements', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const sections = container.querySelectorAll('section');
expect(sections.length).toBeGreaterThan(0);
});
it('should have accessible text contrast', () => {
render(<AboutPage />, { wrapper: createWrapper() });
// Light mode text should use gray-900
const heading = screen.getByRole('heading', { level: 1 });
expect(heading).toHaveClass('text-gray-900');
});
});
describe('Responsive Design', () => {
it('should have responsive heading sizes', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toHaveClass('text-4xl');
expect(h1).toHaveClass('sm:text-5xl');
});
it('should have responsive grid for story section', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const storyGrid = container.querySelector('.md\\:grid-cols-2');
expect(storyGrid).toBeInTheDocument();
});
it('should have responsive grid for values section', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const valuesGrid = container.querySelector('.md\\:grid-cols-2.lg\\:grid-cols-4');
expect(valuesGrid).toBeInTheDocument();
});
it('should have responsive padding', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const sections = container.querySelectorAll('.py-20');
expect(sections.length).toBeGreaterThan(0);
});
it('should have responsive container width', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const maxWidthContainers = container.querySelectorAll('[class*="max-w-"]');
expect(maxWidthContainers.length).toBeGreaterThan(0);
});
});
describe('Dark Mode Support', () => {
it('should have dark mode classes for headings', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const heading = screen.getByRole('heading', { level: 1 });
expect(heading).toHaveClass('dark:text-white');
});
it('should have dark mode classes for text elements', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const subtitle = screen.getByText(/We're on a mission to simplify scheduling/i);
expect(subtitle).toHaveClass('dark:text-gray-400');
});
it('should have dark mode background classes', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const darkBg = container.querySelector('.dark\\:bg-gray-900');
expect(darkBg).toBeInTheDocument();
});
it('should have dark mode gradient classes', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const darkGradient = container.querySelector('.dark\\:from-gray-900');
expect(darkGradient).toBeInTheDocument();
});
it('should have dark mode icon background classes', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const darkIconBg = container.querySelector('.dark\\:bg-brand-900\\/30');
expect(darkIconBg).toBeInTheDocument();
});
});
describe('Layout and Spacing', () => {
it('should have proper section padding', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const sections = container.querySelectorAll('.py-20');
expect(sections.length).toBeGreaterThan(0);
});
it('should have responsive section padding', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const responsivePadding = container.querySelector('.lg\\:py-28');
expect(responsivePadding).toBeInTheDocument();
});
it('should constrain content width', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
expect(container.querySelector('.max-w-4xl')).toBeInTheDocument();
expect(container.querySelector('.max-w-7xl')).toBeInTheDocument();
});
it('should have proper margins between elements', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const heading = screen.getByRole('heading', { level: 1 });
expect(heading).toHaveClass('mb-6');
});
it('should have gap between grid items', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const gridsWithGap = container.querySelectorAll('[class*="gap-"]');
expect(gridsWithGap.length).toBeGreaterThan(0);
});
});
describe('Visual Elements', () => {
it('should render timeline bullets', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const bullets = container.querySelectorAll('.rounded-full');
// Should have bullet points for timeline items
expect(bullets.length).toBeGreaterThanOrEqual(4);
});
it('should apply rounded corners to cards', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const roundedElements = container.querySelectorAll('.rounded-2xl');
expect(roundedElements.length).toBeGreaterThan(0);
});
it('should have icon containers with proper styling', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const iconContainer = container.querySelector('.inline-flex.p-4.rounded-2xl');
expect(iconContainer).toBeInTheDocument();
});
});
describe('Integration Tests', () => {
it('should render complete page structure', () => {
render(<AboutPage />, { wrapper: createWrapper() });
// Header
expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument();
// Story
expect(screen.getByText(/2017/i)).toBeInTheDocument();
expect(screen.getByText(/8\+ years building scheduling solutions/i)).toBeInTheDocument();
// Mission
expect(screen.getByText(/To empower service businesses/i)).toBeInTheDocument();
// Values
expect(screen.getByText(/Simplicity/i)).toBeInTheDocument();
expect(screen.getByText(/Reliability/i)).toBeInTheDocument();
expect(screen.getByText(/Transparency/i)).toBeInTheDocument();
expect(screen.getByText(/Support/i)).toBeInTheDocument();
// CTA
expect(screen.getByTestId('cta-section')).toBeInTheDocument();
});
it('should have all sections in correct order', () => {
const { container } = render(<AboutPage />, { wrapper: createWrapper() });
const sections = container.querySelectorAll('section');
expect(sections.length).toBe(5); // Header, Story, Mission, Values, CTA (in div)
});
it('should maintain proper visual hierarchy', () => {
render(<AboutPage />, { wrapper: createWrapper() });
// h1 for main title
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toHaveTextContent(/About Smooth Schedule/i);
// h2 for section titles
const h2Elements = screen.getAllByRole('heading', { level: 2 });
expect(h2Elements).toHaveLength(3);
// h3 for value titles
const h3Elements = screen.getAllByRole('heading', { level: 3 });
expect(h3Elements).toHaveLength(4);
});
it('should render all timeline items', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const timelineItems = [
/8\+ years building scheduling solutions/i,
/Battle-tested with real businesses/i,
/Features born from customer feedback/i,
/Now available to everyone/i,
];
timelineItems.forEach(item => {
expect(screen.getByText(item)).toBeInTheDocument();
});
});
it('should render all value cards with descriptions', () => {
render(<AboutPage />, { wrapper: createWrapper() });
const values = [
{ title: /Simplicity/i, description: /powerful software can still be simple/i },
{ title: /Reliability/i, description: /never compromise on uptime/i },
{ title: /Transparency/i, description: /No hidden fees/i },
{ title: /Support/i, description: /help you succeed/i },
];
values.forEach(value => {
expect(screen.getByText(value.title)).toBeInTheDocument();
expect(screen.getByText(value.description)).toBeInTheDocument();
});
});
});
});

View File

@@ -0,0 +1,655 @@
/**
* Comprehensive unit tests for HomePage component
*
* Tests the marketing HomePage including:
* - Hero section rendering
* - Feature sections display
* - CTA buttons presence
* - Navigation links work
* - Marketing components integration
* - Translations
* - Accessibility
*/
import { describe, it, expect, vi } from 'vitest';
import { render, screen, within } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import HomePage from '../HomePage';
// Mock child components to isolate HomePage testing
vi.mock('../../../components/marketing/Hero', () => ({
default: () => <div data-testid="hero-section">Hero Component</div>,
}));
vi.mock('../../../components/marketing/FeatureCard', () => ({
default: ({ title, description, icon: Icon }: any) => (
<div data-testid="feature-card">
<Icon data-testid="feature-icon" />
<h3>{title}</h3>
<p>{description}</p>
</div>
),
}));
vi.mock('../../../components/marketing/PluginShowcase', () => ({
default: () => <div data-testid="plugin-showcase">Plugin Showcase Component</div>,
}));
vi.mock('../../../components/marketing/BenefitsSection', () => ({
default: () => <div data-testid="benefits-section">Benefits Section Component</div>,
}));
vi.mock('../../../components/marketing/TestimonialCard', () => ({
default: ({ quote, author, role, company, rating }: any) => (
<div data-testid="testimonial-card">
<p>{quote}</p>
<div>{author}</div>
<div>{role}</div>
<div>{company}</div>
<div>{rating} stars</div>
</div>
),
}));
vi.mock('../../../components/marketing/CTASection', () => ({
default: () => <div data-testid="cta-section">CTA Section Component</div>,
}));
// Mock useTranslation hook
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => {
const translations: Record<string, string> = {
// Features section
'marketing.home.featuresSection.title': 'Powerful Features',
'marketing.home.featuresSection.subtitle': 'Everything you need to manage your business',
// Features
'marketing.home.features.intelligentScheduling.title': 'Intelligent Scheduling',
'marketing.home.features.intelligentScheduling.description': 'Smart scheduling that works for you',
'marketing.home.features.automationEngine.title': 'Automation Engine',
'marketing.home.features.automationEngine.description': 'Automate repetitive tasks',
'marketing.home.features.multiTenant.title': 'Multi-Tenant',
'marketing.home.features.multiTenant.description': 'Manage multiple businesses',
'marketing.home.features.integratedPayments.title': 'Integrated Payments',
'marketing.home.features.integratedPayments.description': 'Accept payments easily',
'marketing.home.features.customerManagement.title': 'Customer Management',
'marketing.home.features.customerManagement.description': 'Manage your customers',
'marketing.home.features.advancedAnalytics.title': 'Advanced Analytics',
'marketing.home.features.advancedAnalytics.description': 'Track your performance',
'marketing.home.features.digitalContracts.title': 'Digital Contracts',
'marketing.home.features.digitalContracts.description': 'Sign contracts digitally',
// Testimonials section
'marketing.home.testimonialsSection.title': 'What Our Customers Say',
'marketing.home.testimonialsSection.subtitle': 'Join thousands of happy customers',
// Testimonials
'marketing.home.testimonials.winBack.quote': 'SmoothSchedule helped us win back customers',
'marketing.home.testimonials.winBack.author': 'John Doe',
'marketing.home.testimonials.winBack.role': 'CEO',
'marketing.home.testimonials.winBack.company': 'Acme Corp',
'marketing.home.testimonials.resources.quote': 'Resource management is a breeze',
'marketing.home.testimonials.resources.author': 'Jane Smith',
'marketing.home.testimonials.resources.role': 'Operations Manager',
'marketing.home.testimonials.resources.company': 'Tech Solutions',
'marketing.home.testimonials.whiteLabel.quote': 'White label features are amazing',
'marketing.home.testimonials.whiteLabel.author': 'Bob Johnson',
'marketing.home.testimonials.whiteLabel.role': 'Founder',
'marketing.home.testimonials.whiteLabel.company': 'StartupXYZ',
};
return translations[key] || key;
},
}),
}));
const renderHomePage = () => {
return render(
<BrowserRouter>
<HomePage />
</BrowserRouter>
);
};
describe('HomePage', () => {
describe('Hero Section', () => {
it('should render the hero section', () => {
renderHomePage();
const heroSection = screen.getByTestId('hero-section');
expect(heroSection).toBeInTheDocument();
expect(heroSection).toHaveTextContent('Hero Component');
});
it('should render hero section as the first major section', () => {
const { container } = renderHomePage();
const heroSection = screen.getByTestId('hero-section');
const allSections = container.querySelectorAll('[data-testid]');
// Hero should be one of the first sections
expect(allSections[0]).toBe(heroSection);
});
});
describe('Features Section', () => {
it('should render features section heading', () => {
renderHomePage();
const heading = screen.getByText('Powerful Features');
expect(heading).toBeInTheDocument();
expect(heading.tagName).toBe('H2');
});
it('should render features section subtitle', () => {
renderHomePage();
const subtitle = screen.getByText('Everything you need to manage your business');
expect(subtitle).toBeInTheDocument();
});
it('should display all 7 feature cards', () => {
renderHomePage();
const featureCards = screen.getAllByTestId('feature-card');
expect(featureCards).toHaveLength(7);
});
it('should render intelligent scheduling feature', () => {
renderHomePage();
expect(screen.getByText('Intelligent Scheduling')).toBeInTheDocument();
expect(screen.getByText('Smart scheduling that works for you')).toBeInTheDocument();
});
it('should render automation engine feature', () => {
renderHomePage();
expect(screen.getByText('Automation Engine')).toBeInTheDocument();
expect(screen.getByText('Automate repetitive tasks')).toBeInTheDocument();
});
it('should render multi-tenant feature', () => {
renderHomePage();
expect(screen.getByText('Multi-Tenant')).toBeInTheDocument();
expect(screen.getByText('Manage multiple businesses')).toBeInTheDocument();
});
it('should render integrated payments feature', () => {
renderHomePage();
expect(screen.getByText('Integrated Payments')).toBeInTheDocument();
expect(screen.getByText('Accept payments easily')).toBeInTheDocument();
});
it('should render customer management feature', () => {
renderHomePage();
expect(screen.getByText('Customer Management')).toBeInTheDocument();
expect(screen.getByText('Manage your customers')).toBeInTheDocument();
});
it('should render advanced analytics feature', () => {
renderHomePage();
expect(screen.getByText('Advanced Analytics')).toBeInTheDocument();
expect(screen.getByText('Track your performance')).toBeInTheDocument();
});
it('should render digital contracts feature', () => {
renderHomePage();
expect(screen.getByText('Digital Contracts')).toBeInTheDocument();
expect(screen.getByText('Sign contracts digitally')).toBeInTheDocument();
});
it('should render all feature icons', () => {
renderHomePage();
const featureIcons = screen.getAllByTestId('feature-icon');
expect(featureIcons).toHaveLength(7);
});
it('should apply correct styling to features section', () => {
const { container } = renderHomePage();
// Find the features section by looking for the section with feature cards
const sections = container.querySelectorAll('section');
const featuresSection = Array.from(sections).find(section =>
section.querySelector('[data-testid="feature-card"]')
);
expect(featuresSection).toBeInTheDocument();
expect(featuresSection).toHaveClass('bg-white', 'dark:bg-gray-900');
});
});
describe('Plugin Showcase Section', () => {
it('should render the plugin showcase section', () => {
renderHomePage();
const pluginShowcase = screen.getByTestId('plugin-showcase');
expect(pluginShowcase).toBeInTheDocument();
expect(pluginShowcase).toHaveTextContent('Plugin Showcase Component');
});
});
describe('Benefits Section', () => {
it('should render the benefits section', () => {
renderHomePage();
const benefitsSection = screen.getByTestId('benefits-section');
expect(benefitsSection).toBeInTheDocument();
expect(benefitsSection).toHaveTextContent('Benefits Section Component');
});
});
describe('Testimonials Section', () => {
it('should render testimonials section heading', () => {
renderHomePage();
const heading = screen.getByText('What Our Customers Say');
expect(heading).toBeInTheDocument();
expect(heading.tagName).toBe('H2');
});
it('should render testimonials section subtitle', () => {
renderHomePage();
const subtitle = screen.getByText('Join thousands of happy customers');
expect(subtitle).toBeInTheDocument();
});
it('should display all 3 testimonial cards', () => {
renderHomePage();
const testimonialCards = screen.getAllByTestId('testimonial-card');
expect(testimonialCards).toHaveLength(3);
});
it('should render win-back testimonial', () => {
renderHomePage();
expect(screen.getByText('SmoothSchedule helped us win back customers')).toBeInTheDocument();
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('CEO')).toBeInTheDocument();
expect(screen.getByText('Acme Corp')).toBeInTheDocument();
});
it('should render resources testimonial', () => {
renderHomePage();
expect(screen.getByText('Resource management is a breeze')).toBeInTheDocument();
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
expect(screen.getByText('Operations Manager')).toBeInTheDocument();
expect(screen.getByText('Tech Solutions')).toBeInTheDocument();
});
it('should render white-label testimonial', () => {
renderHomePage();
expect(screen.getByText('White label features are amazing')).toBeInTheDocument();
expect(screen.getByText('Bob Johnson')).toBeInTheDocument();
expect(screen.getByText('Founder')).toBeInTheDocument();
expect(screen.getByText('StartupXYZ')).toBeInTheDocument();
});
it('should render all testimonials with 5-star ratings', () => {
renderHomePage();
const ratingElements = screen.getAllByText('5 stars');
expect(ratingElements).toHaveLength(3);
});
it('should apply correct styling to testimonials section', () => {
const { container } = renderHomePage();
// Find the testimonials section by looking for the section with testimonial cards
const sections = container.querySelectorAll('section');
const testimonialsSection = Array.from(sections).find(section =>
section.querySelector('[data-testid="testimonial-card"]')
);
expect(testimonialsSection).toBeInTheDocument();
expect(testimonialsSection).toHaveClass('bg-gray-50', 'dark:bg-gray-800/50');
});
});
describe('CTA Section', () => {
it('should render the CTA section', () => {
renderHomePage();
const ctaSection = screen.getByTestId('cta-section');
expect(ctaSection).toBeInTheDocument();
expect(ctaSection).toHaveTextContent('CTA Section Component');
});
it('should render CTA section as the last section', () => {
renderHomePage();
const ctaSection = screen.getByTestId('cta-section');
const allSections = screen.getAllByTestId(/section/);
// CTA should be the last section
expect(allSections[allSections.length - 1]).toBe(ctaSection);
});
});
describe('Page Structure', () => {
it('should render all main sections in correct order', () => {
renderHomePage();
const hero = screen.getByTestId('hero-section');
const pluginShowcase = screen.getByTestId('plugin-showcase');
const benefits = screen.getByTestId('benefits-section');
const cta = screen.getByTestId('cta-section');
// All sections should be present
expect(hero).toBeInTheDocument();
expect(pluginShowcase).toBeInTheDocument();
expect(benefits).toBeInTheDocument();
expect(cta).toBeInTheDocument();
});
it('should have proper semantic structure', () => {
const { container } = renderHomePage();
// Should have multiple section elements
const sections = container.querySelectorAll('section');
expect(sections.length).toBeGreaterThan(0);
// Should have proper heading hierarchy
const h2Headings = container.querySelectorAll('h2');
expect(h2Headings.length).toBeGreaterThan(0);
});
it('should render within a container div', () => {
const { container } = renderHomePage();
const rootDiv = container.firstChild;
expect(rootDiv).toBeInstanceOf(HTMLDivElement);
});
});
describe('Responsive Design', () => {
it('should apply responsive padding classes to features section', () => {
const { container } = renderHomePage();
const sections = container.querySelectorAll('section');
const featuresSection = Array.from(sections).find(section =>
section.querySelector('[data-testid="feature-card"]')
);
expect(featuresSection).toHaveClass('py-20', 'lg:py-28');
});
it('should apply responsive grid layout to features', () => {
const { container } = renderHomePage();
// Find the grid container
const gridContainer = container.querySelector('.grid.md\\:grid-cols-2.lg\\:grid-cols-3');
expect(gridContainer).toBeInTheDocument();
});
it('should apply responsive grid layout to testimonials', () => {
const { container } = renderHomePage();
// Find all grid containers
const gridContainers = container.querySelectorAll('.grid.md\\:grid-cols-2.lg\\:grid-cols-3');
// There should be at least 2 grid containers (features and testimonials)
expect(gridContainers.length).toBeGreaterThanOrEqual(2);
});
it('should have max-width container for content', () => {
const { container } = renderHomePage();
const maxWidthContainers = container.querySelectorAll('.max-w-7xl');
expect(maxWidthContainers.length).toBeGreaterThan(0);
});
it('should apply responsive padding to containers', () => {
const { container } = renderHomePage();
const paddedContainers = container.querySelectorAll('.px-4.sm\\:px-6.lg\\:px-8');
expect(paddedContainers.length).toBeGreaterThan(0);
});
});
describe('Dark Mode Support', () => {
it('should include dark mode classes for sections', () => {
const { container } = renderHomePage();
// Features section should have dark mode background
const sections = container.querySelectorAll('section');
const hasFeatureSection = Array.from(sections).some(section =>
section.classList.contains('dark:bg-gray-900')
);
expect(hasFeatureSection).toBe(true);
});
it('should include dark mode classes for testimonials section', () => {
const { container } = renderHomePage();
const sections = container.querySelectorAll('section');
const hasTestimonialSection = Array.from(sections).some(section =>
section.classList.contains('dark:bg-gray-800/50')
);
expect(hasTestimonialSection).toBe(true);
});
it('should include dark mode classes for headings', () => {
renderHomePage();
const heading = screen.getByText('Powerful Features');
expect(heading).toHaveClass('dark:text-white');
});
it('should include dark mode classes for descriptions', () => {
renderHomePage();
const subtitle = screen.getByText('Everything you need to manage your business');
expect(subtitle).toHaveClass('dark:text-gray-400');
});
});
describe('Accessibility', () => {
it('should use semantic heading elements', () => {
renderHomePage();
const h2Headings = screen.getAllByRole('heading', { level: 2 });
expect(h2Headings.length).toBeGreaterThan(0);
});
it('should have proper heading hierarchy for features section', () => {
renderHomePage();
const featuresHeading = screen.getByText('Powerful Features');
expect(featuresHeading).toHaveClass('text-3xl', 'sm:text-4xl', 'font-bold');
});
it('should have proper heading hierarchy for testimonials section', () => {
renderHomePage();
const testimonialsHeading = screen.getByText('What Our Customers Say');
expect(testimonialsHeading).toHaveClass('text-3xl', 'sm:text-4xl', 'font-bold');
});
it('should maintain readable text contrast', () => {
renderHomePage();
const heading = screen.getByText('Powerful Features');
expect(heading).toHaveClass('text-gray-900', 'dark:text-white');
const subtitle = screen.getByText('Everything you need to manage your business');
expect(subtitle).toHaveClass('text-gray-600', 'dark:text-gray-400');
});
it('should use semantic section elements', () => {
const { container } = renderHomePage();
const sections = container.querySelectorAll('section');
expect(sections.length).toBeGreaterThan(0);
});
});
describe('Translation Integration', () => {
it('should use translations for features section title', () => {
renderHomePage();
expect(screen.getByText('Powerful Features')).toBeInTheDocument();
});
it('should use translations for features section subtitle', () => {
renderHomePage();
expect(screen.getByText('Everything you need to manage your business')).toBeInTheDocument();
});
it('should use translations for all feature titles', () => {
renderHomePage();
expect(screen.getByText('Intelligent Scheduling')).toBeInTheDocument();
expect(screen.getByText('Automation Engine')).toBeInTheDocument();
expect(screen.getByText('Multi-Tenant')).toBeInTheDocument();
expect(screen.getByText('Integrated Payments')).toBeInTheDocument();
expect(screen.getByText('Customer Management')).toBeInTheDocument();
expect(screen.getByText('Advanced Analytics')).toBeInTheDocument();
expect(screen.getByText('Digital Contracts')).toBeInTheDocument();
});
it('should use translations for testimonials section', () => {
renderHomePage();
expect(screen.getByText('What Our Customers Say')).toBeInTheDocument();
expect(screen.getByText('Join thousands of happy customers')).toBeInTheDocument();
});
});
describe('Component Integration', () => {
it('should integrate Hero component', () => {
renderHomePage();
expect(screen.getByTestId('hero-section')).toBeInTheDocument();
});
it('should integrate FeatureCard components', () => {
renderHomePage();
const featureCards = screen.getAllByTestId('feature-card');
expect(featureCards).toHaveLength(7);
});
it('should integrate PluginShowcase component', () => {
renderHomePage();
expect(screen.getByTestId('plugin-showcase')).toBeInTheDocument();
});
it('should integrate BenefitsSection component', () => {
renderHomePage();
expect(screen.getByTestId('benefits-section')).toBeInTheDocument();
});
it('should integrate TestimonialCard components', () => {
renderHomePage();
const testimonialCards = screen.getAllByTestId('testimonial-card');
expect(testimonialCards).toHaveLength(3);
});
it('should integrate CTASection component', () => {
renderHomePage();
expect(screen.getByTestId('cta-section')).toBeInTheDocument();
});
});
describe('Content Sections', () => {
it('should have distinct background colors for sections', () => {
const { container } = renderHomePage();
const sections = container.querySelectorAll('section');
// Features section - white background
const featuresSection = Array.from(sections).find(section =>
section.classList.contains('bg-white')
);
expect(featuresSection).toBeInTheDocument();
// Testimonials section - gray background
const testimonialsSection = Array.from(sections).find(section =>
section.classList.contains('bg-gray-50')
);
expect(testimonialsSection).toBeInTheDocument();
});
it('should center content with max-width containers', () => {
const { container } = renderHomePage();
const maxWidthContainers = container.querySelectorAll('.max-w-7xl.mx-auto');
expect(maxWidthContainers.length).toBeGreaterThan(0);
});
it('should apply consistent spacing between sections', () => {
const { container } = renderHomePage();
const sections = container.querySelectorAll('section');
const hasPaddedSections = Array.from(sections).some(section =>
section.classList.contains('py-20') || section.classList.contains('lg:py-28')
);
expect(hasPaddedSections).toBe(true);
});
});
describe('Feature Card Configuration', () => {
it('should pass correct props to feature cards', () => {
renderHomePage();
// Check that feature cards receive title and description
const featureCards = screen.getAllByTestId('feature-card');
featureCards.forEach(card => {
// Each card should have an h3 (title) and p (description)
const title = within(card).getByRole('heading', { level: 3 });
const description = within(card).getByText(/.+/);
expect(title).toBeInTheDocument();
expect(description).toBeInTheDocument();
});
});
});
describe('Testimonial Configuration', () => {
it('should render testimonials with all required fields', () => {
renderHomePage();
const testimonialCards = screen.getAllByTestId('testimonial-card');
testimonialCards.forEach(card => {
// Each testimonial should have quote, author, role, company, and rating
expect(card.textContent).toMatch(/.+/); // Has content
});
});
it('should render testimonials with proper author information', () => {
renderHomePage();
// Check all authors are displayed
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
expect(screen.getByText('Bob Johnson')).toBeInTheDocument();
});
it('should render testimonials with proper company information', () => {
renderHomePage();
// Check all companies are displayed
expect(screen.getByText('Acme Corp')).toBeInTheDocument();
expect(screen.getByText('Tech Solutions')).toBeInTheDocument();
expect(screen.getByText('StartupXYZ')).toBeInTheDocument();
});
});
});

View File

@@ -0,0 +1,593 @@
/**
* Unit tests for TermsOfServicePage component
*
* Tests the Terms of Service page including:
* - Header rendering with title and last updated date
* - All 16 sections are present with correct headings
* - Section content rendering
* - List items in sections with requirements/prohibitions/terms
* - Contact information rendering
* - Translation keys usage
* - Semantic HTML structure
* - Styling and CSS classes
*/
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { I18nextProvider } from 'react-i18next';
import i18n from '../../../i18n';
import TermsOfServicePage from '../TermsOfServicePage';
// Helper to render with i18n provider
const renderWithI18n = (component: React.ReactElement) => {
return render(<I18nextProvider i18n={i18n}>{component}</I18nextProvider>);
};
describe('TermsOfServicePage', () => {
describe('Header Section', () => {
it('should render the main title', () => {
renderWithI18n(<TermsOfServicePage />);
const title = screen.getByRole('heading', { level: 1, name: /terms of service/i });
expect(title).toBeInTheDocument();
});
it('should display the last updated date', () => {
renderWithI18n(<TermsOfServicePage />);
expect(screen.getByText(/last updated/i)).toBeInTheDocument();
});
it('should apply correct header styling', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const headerSection = container.querySelector('.py-20');
expect(headerSection).toBeInTheDocument();
expect(headerSection).toHaveClass('lg:py-28');
expect(headerSection).toHaveClass('bg-gradient-to-br');
});
it('should use semantic heading hierarchy', () => {
renderWithI18n(<TermsOfServicePage />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1.textContent).toContain('Terms of Service');
});
});
describe('Content Sections - All 16 Sections Present', () => {
it('should render section 1: Acceptance of Terms', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /1\.\s*acceptance of terms/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/by accessing and using smoothschedule/i)).toBeInTheDocument();
});
it('should render section 2: Description of Service', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /2\.\s*description of service/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/smoothschedule is a scheduling platform/i)).toBeInTheDocument();
});
it('should render section 3: User Accounts', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /3\.\s*user accounts/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/to use the service, you must:/i)).toBeInTheDocument();
});
it('should render section 4: Acceptable Use', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /4\.\s*acceptable use/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/you agree not to use the service to:/i)).toBeInTheDocument();
});
it('should render section 5: Subscriptions and Payments', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /5\.\s*subscriptions and payments/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/subscription terms:/i)).toBeInTheDocument();
});
it('should render section 6: Trial Period', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /6\.\s*trial period/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/we may offer a free trial period/i)).toBeInTheDocument();
});
it('should render section 7: Data and Privacy', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /7\.\s*data and privacy/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/your use of the service is also governed by our privacy policy/i)).toBeInTheDocument();
});
it('should render section 8: Service Availability', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /8\.\s*service availability/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/while we strive for 99\.9% uptime/i)).toBeInTheDocument();
});
it('should render section 9: Intellectual Property', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /9\.\s*intellectual property/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/the service, including all software, designs/i)).toBeInTheDocument();
});
it('should render section 10: Termination', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /10\.\s*termination/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/we may terminate or suspend your account/i)).toBeInTheDocument();
});
it('should render section 11: Limitation of Liability', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /11\.\s*limitation of liability/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/to the maximum extent permitted by law/i)).toBeInTheDocument();
});
it('should render section 12: Warranty Disclaimer', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /12\.\s*warranty disclaimer/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/the service is provided "as is" and "as available"/i)).toBeInTheDocument();
});
it('should render section 13: Indemnification', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /13\.\s*indemnification/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/you agree to indemnify and hold harmless/i)).toBeInTheDocument();
});
it('should render section 14: Changes to Terms', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /14\.\s*changes to terms/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/we reserve the right to modify these terms/i)).toBeInTheDocument();
});
it('should render section 15: Governing Law', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /15\.\s*governing law/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/these terms shall be governed by and construed/i)).toBeInTheDocument();
});
it('should render section 16: Contact Us', () => {
renderWithI18n(<TermsOfServicePage />);
const heading = screen.getByRole('heading', { name: /16\.\s*contact us/i });
expect(heading).toBeInTheDocument();
expect(screen.getByText(/if you have any questions about these terms/i)).toBeInTheDocument();
});
});
describe('Section Content - User Accounts Requirements', () => {
it('should render all four user account requirements', () => {
renderWithI18n(<TermsOfServicePage />);
expect(screen.getByText(/create an account with accurate and complete information/i)).toBeInTheDocument();
expect(screen.getByText(/maintain the security of your account credentials/i)).toBeInTheDocument();
expect(screen.getByText(/notify us immediately of any unauthorized access/i)).toBeInTheDocument();
expect(screen.getByText(/be responsible for all activities under your account/i)).toBeInTheDocument();
});
it('should render user accounts section with a list', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const lists = container.querySelectorAll('ul');
const userAccountsList = Array.from(lists).find(list =>
list.textContent?.includes('accurate and complete information')
);
expect(userAccountsList).toBeInTheDocument();
expect(userAccountsList?.querySelectorAll('li')).toHaveLength(4);
});
});
describe('Section Content - Acceptable Use Prohibitions', () => {
it('should render all five acceptable use prohibitions', () => {
renderWithI18n(<TermsOfServicePage />);
expect(screen.getByText(/violate any applicable laws or regulations/i)).toBeInTheDocument();
expect(screen.getByText(/infringe on intellectual property rights/i)).toBeInTheDocument();
expect(screen.getByText(/transmit malicious code or interfere with the service/i)).toBeInTheDocument();
expect(screen.getByText(/attempt to gain unauthorized access/i)).toBeInTheDocument();
expect(screen.getByText(/use the service for any fraudulent or illegal purpose/i)).toBeInTheDocument();
});
it('should render acceptable use section with a list', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const lists = container.querySelectorAll('ul');
const acceptableUseList = Array.from(lists).find(list =>
list.textContent?.includes('Violate any applicable laws')
);
expect(acceptableUseList).toBeInTheDocument();
expect(acceptableUseList?.querySelectorAll('li')).toHaveLength(5);
});
});
describe('Section Content - Subscriptions and Payments Terms', () => {
it('should render all five subscription payment terms', () => {
renderWithI18n(<TermsOfServicePage />);
expect(screen.getByText(/subscriptions are billed in advance on a recurring basis/i)).toBeInTheDocument();
expect(screen.getByText(/you may cancel your subscription at any time/i)).toBeInTheDocument();
expect(screen.getByText(/no refunds are provided for partial subscription periods/i)).toBeInTheDocument();
expect(screen.getByText(/we reserve the right to change pricing with 30 days notice/i)).toBeInTheDocument();
expect(screen.getByText(/failed payments may result in service suspension/i)).toBeInTheDocument();
});
it('should render subscriptions and payments section with a list', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const lists = container.querySelectorAll('ul');
const subscriptionsList = Array.from(lists).find(list =>
list.textContent?.includes('billed in advance')
);
expect(subscriptionsList).toBeInTheDocument();
expect(subscriptionsList?.querySelectorAll('li')).toHaveLength(5);
});
});
describe('Contact Information Section', () => {
it('should render contact email label and address', () => {
renderWithI18n(<TermsOfServicePage />);
expect(screen.getByText(/email:/i)).toBeInTheDocument();
expect(screen.getByText(/legal@smoothschedule\.com/i)).toBeInTheDocument();
});
it('should render contact website label and URL', () => {
renderWithI18n(<TermsOfServicePage />);
expect(screen.getByText(/website:/i)).toBeInTheDocument();
expect(screen.getByText(/https:\/\/smoothschedule\.com\/contact/i)).toBeInTheDocument();
});
it('should display contact information with bold labels', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const strongElements = container.querySelectorAll('strong');
const emailLabel = Array.from(strongElements).find(el => el.textContent === 'Email:');
const websiteLabel = Array.from(strongElements).find(el => el.textContent === 'Website:');
expect(emailLabel).toBeInTheDocument();
expect(websiteLabel).toBeInTheDocument();
});
});
describe('Semantic HTML Structure', () => {
it('should use h1 for main title', () => {
renderWithI18n(<TermsOfServicePage />);
const h1Elements = screen.getAllByRole('heading', { level: 1 });
expect(h1Elements).toHaveLength(1);
expect(h1Elements[0].textContent).toContain('Terms of Service');
});
it('should use h2 for all section headings', () => {
renderWithI18n(<TermsOfServicePage />);
const h2Elements = screen.getAllByRole('heading', { level: 2 });
// Should have 16 section headings
expect(h2Elements.length).toBeGreaterThanOrEqual(16);
});
it('should use proper list elements for requirements and prohibitions', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const ulElements = container.querySelectorAll('ul');
// Should have 3 lists: user accounts, acceptable use, subscriptions
expect(ulElements.length).toBe(3);
ulElements.forEach(ul => {
expect(ul).toHaveClass('list-disc');
});
});
it('should use section elements for major content areas', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const sections = container.querySelectorAll('section');
// Should have at least header and content sections
expect(sections.length).toBeGreaterThanOrEqual(2);
});
it('should use paragraph elements for content text', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const paragraphs = container.querySelectorAll('p');
// Should have many paragraphs for all the content
expect(paragraphs.length).toBeGreaterThan(15);
});
});
describe('Styling and CSS Classes', () => {
it('should apply gradient background to header section', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const headerSection = container.querySelector('section');
expect(headerSection).toHaveClass('bg-gradient-to-br');
expect(headerSection).toHaveClass('from-white');
expect(headerSection).toHaveClass('via-brand-50/30');
});
it('should apply dark mode classes to header section', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const headerSection = container.querySelector('section');
expect(headerSection).toHaveClass('dark:from-gray-900');
expect(headerSection).toHaveClass('dark:via-gray-800');
expect(headerSection).toHaveClass('dark:to-gray-900');
});
it('should apply content section background colors', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const sections = container.querySelectorAll('section');
const contentSection = sections[1]; // Second section is content
expect(contentSection).toHaveClass('bg-white');
expect(contentSection).toHaveClass('dark:bg-gray-900');
});
it('should apply prose classes to content container', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const proseContainer = container.querySelector('.prose');
expect(proseContainer).toBeInTheDocument();
expect(proseContainer).toHaveClass('prose-lg');
expect(proseContainer).toHaveClass('dark:prose-invert');
expect(proseContainer).toHaveClass('max-w-none');
});
it('should apply heading styling classes', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const h2Elements = container.querySelectorAll('h2');
h2Elements.forEach(heading => {
expect(heading).toHaveClass('text-2xl');
expect(heading).toHaveClass('font-bold');
expect(heading).toHaveClass('text-gray-900');
expect(heading).toHaveClass('dark:text-white');
});
});
it('should apply paragraph text color classes', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const paragraphs = container.querySelectorAll('.text-gray-600');
expect(paragraphs.length).toBeGreaterThan(0);
paragraphs.forEach(p => {
expect(p).toHaveClass('dark:text-gray-400');
});
});
it('should apply list styling classes', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const lists = container.querySelectorAll('ul');
lists.forEach(ul => {
expect(ul).toHaveClass('list-disc');
expect(ul).toHaveClass('pl-6');
expect(ul).toHaveClass('text-gray-600');
expect(ul).toHaveClass('dark:text-gray-400');
});
});
it('should apply spacing classes to sections', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const h2Elements = container.querySelectorAll('h2');
h2Elements.forEach(heading => {
expect(heading).toHaveClass('mt-8');
expect(heading).toHaveClass('mb-4');
});
});
});
describe('Dark Mode Support', () => {
it('should include dark mode classes for title', () => {
renderWithI18n(<TermsOfServicePage />);
const title = screen.getByRole('heading', { level: 1 });
expect(title).toHaveClass('dark:text-white');
});
it('should include dark mode classes for subtitle/date', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const lastUpdated = container.querySelector('.text-xl');
expect(lastUpdated).toHaveClass('dark:text-gray-400');
});
it('should include dark mode classes for section headings', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const headings = container.querySelectorAll('h2');
headings.forEach(heading => {
expect(heading).toHaveClass('dark:text-white');
});
});
it('should include dark mode classes for content text', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const contentParagraphs = container.querySelectorAll('.text-gray-600');
contentParagraphs.forEach(p => {
expect(p).toHaveClass('dark:text-gray-400');
});
});
it('should include dark mode classes for lists', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const lists = container.querySelectorAll('ul');
lists.forEach(ul => {
expect(ul).toHaveClass('dark:text-gray-400');
});
});
});
describe('Responsive Design', () => {
it('should apply responsive padding to header section', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const headerSection = container.querySelector('section');
expect(headerSection).toHaveClass('py-20');
expect(headerSection).toHaveClass('lg:py-28');
});
it('should apply responsive title sizing', () => {
renderWithI18n(<TermsOfServicePage />);
const title = screen.getByRole('heading', { level: 1 });
expect(title).toHaveClass('text-4xl');
expect(title).toHaveClass('sm:text-5xl');
});
it('should apply responsive max-width constraints', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const maxWidthContainers = container.querySelectorAll('.max-w-4xl');
expect(maxWidthContainers.length).toBeGreaterThanOrEqual(2);
});
it('should apply responsive padding to containers', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const paddedContainers = container.querySelectorAll('.px-4');
expect(paddedContainers.length).toBeGreaterThan(0);
paddedContainers.forEach(div => {
expect(div).toHaveClass('sm:px-6');
expect(div).toHaveClass('lg:px-8');
});
});
});
describe('Content Completeness', () => {
it('should render all sections in correct order', () => {
renderWithI18n(<TermsOfServicePage />);
const headings = screen.getAllByRole('heading', { level: 2 });
// Verify the order by checking for section numbers
expect(headings[0].textContent).toMatch(/1\./);
expect(headings[1].textContent).toMatch(/2\./);
expect(headings[2].textContent).toMatch(/3\./);
expect(headings[3].textContent).toMatch(/4\./);
expect(headings[4].textContent).toMatch(/5\./);
});
it('should have substantial content in each section', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
// Check that there are multiple paragraphs with substantial text
const paragraphs = container.querySelectorAll('p');
const substantialParagraphs = Array.from(paragraphs).filter(
p => (p.textContent?.length ?? 0) > 50
);
expect(substantialParagraphs.length).toBeGreaterThan(10);
});
it('should render page without errors', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
expect(container).toBeInTheDocument();
expect(container.querySelector('section')).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have proper heading hierarchy', () => {
renderWithI18n(<TermsOfServicePage />);
// Should have exactly one h1
const h1Elements = screen.getAllByRole('heading', { level: 1 });
expect(h1Elements).toHaveLength(1);
// Should have multiple h2 elements for sections
const h2Elements = screen.getAllByRole('heading', { level: 2 });
expect(h2Elements.length).toBeGreaterThanOrEqual(16);
});
it('should maintain readable text contrast in light mode', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const title = container.querySelector('h1');
expect(title).toHaveClass('text-gray-900');
const paragraphs = container.querySelectorAll('p.text-gray-600');
expect(paragraphs.length).toBeGreaterThan(0);
});
it('should use semantic list markup', () => {
const { container } = renderWithI18n(<TermsOfServicePage />);
const lists = container.querySelectorAll('ul');
lists.forEach(ul => {
const listItems = ul.querySelectorAll('li');
expect(listItems.length).toBeGreaterThan(0);
});
});
it('should not have any empty headings', () => {
renderWithI18n(<TermsOfServicePage />);
const allHeadings = screen.getAllByRole('heading');
allHeadings.forEach(heading => {
expect(heading.textContent).toBeTruthy();
expect(heading.textContent?.trim().length).toBeGreaterThan(0);
});
});
});
describe('Translation Integration', () => {
it('should use translation keys for all content', () => {
// This is verified by the fact that content renders correctly through i18n
renderWithI18n(<TermsOfServicePage />);
// Main title should be translated
expect(screen.getByRole('heading', { name: /terms of service/i })).toBeInTheDocument();
// All 16 sections should be present (implies translations are working)
const h2Elements = screen.getAllByRole('heading', { level: 2 });
expect(h2Elements.length).toBeGreaterThanOrEqual(16);
});
it('should render without i18n errors', () => {
// Should not throw when rendering with i18n
expect(() => renderWithI18n(<TermsOfServicePage />)).not.toThrow();
});
});
});