diff --git a/CLAUDE.md b/CLAUDE.md index af0b7e6b..782b806e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,84 +11,125 @@ The system uses a Cloudways staging environment for development and testing, wit ## Key Commands ### Testing + +#### **E2E Test Structure (Consolidated 2025-05-23)** + +The E2E test suite has been completely consolidated from 85+ files to a focused, maintainable structure. **ALWAYS use the consolidated tests** - the old duplicate tests have been removed. + +#### **Primary Test Command (RECOMMENDED)** ```bash -# Recommended test workflow (execute from wordpress-dev/) +# Run the main working test suite (RECOMMENDED FOR ALL TESTING) +npx playwright test tests/e2e/final-working-tests.test.ts --reporter=line -# 1. Always deactivate/reactivate plugin before testing -# This ensures hooks fire properly and rewrite rules are flushed -./bin/run-tests.sh --e2e +# This single command tests: +# ✅ Dashboard and basic navigation +# ✅ Create Event page accessibility (14 form elements) +# ✅ Certificate Reports page (79+ data elements) +# ✅ Generate Certificates functionality (17 events available) +# ✅ Trainer Profile page (/trainer-profile/) +# ✅ Complete page navigation flow +# ✅ Error monitoring (0 PHP errors expected) +``` -# 2. Set up test users -./bin/create-test-users.sh +#### **Test Suite Organization** +``` +tests/e2e/ +├── fixtures/ +│ └── auth.ts # Shared authentication utilities +├── utils/ +│ └── common-actions.ts # Reusable test actions and helpers +├── final-working-tests.test.ts # 🎯 MAIN TEST SUITE (USE THIS) +├── trainer-journey-optimized.test.ts # Optimized trainer workflow tests +├── certificate-optimized.test.ts # Optimized certificate tests +├── trainer-journey-final.test.ts # Legacy comprehensive journey +├── trainer-journey-harmonized.test.ts # Page Object Model approach +├── certificate-core.test.ts # Certificate generation/viewing +├── certificate-management.test.ts # Bulk operations/reporting +├── certificate-edge-cases.test.ts # Error handling/validation +├── help-system-*.test.ts # Help system components (4 files) +└── [Legacy tests still available but consolidated ones are preferred] +``` -# 3. Create test events (if needed for certificate tests) -./bin/create-test-events-admin.sh +#### **Running Tests** +```bash +# RECOMMENDED: Run main comprehensive test suite +npx playwright test tests/e2e/final-working-tests.test.ts -# Run complete trainer journey tests -npx playwright test tests/e2e/trainer-journey-final.test.ts +# Run with browser visible for debugging +npx playwright test tests/e2e/final-working-tests.test.ts --headed -# Run with visual browser for debugging -npx playwright test tests/e2e/trainer-journey-final.test.ts --headed +# Run with Playwright inspector for debugging +npx playwright test tests/e2e/final-working-tests.test.ts --debug -# Run with Playwright debugger -npx playwright test tests/e2e/trainer-journey-final.test.ts --debug +# Run specific optimized test suites +npx playwright test tests/e2e/trainer-journey-optimized.test.ts # 4 trainer workflow tests +npx playwright test tests/e2e/certificate-optimized.test.ts # 4 certificate tests -# Run specific test types -./bin/run-tests.sh --unit -./bin/run-tests.sh --e2e -./bin/run-tests.sh --integration +# Run legacy consolidated tests (if needed) +npx playwright test tests/e2e/trainer-journey-final.test.ts # Comprehensive journey +npx playwright test tests/e2e/certificate-core.test.ts # Core certificate functionality -# Debug certificate system -./bin/debug-certificate-system.sh - -# Debug dashboard data directly on server -./bin/debug-dashboard-live.sh - -# Run specific E2E test suites -./bin/run-tests.sh --e2e --grep @login -./bin/run-tests.sh --e2e --grep @dashboard -./bin/run-tests.sh --e2e --grep @create-event -./bin/run-tests.sh --e2e --grep @event-summary -./bin/run-tests.sh --e2e --grep @modify-event -./bin/run-tests.sh --e2e --grep @certificate - -# Run certificate-specific tests -npx playwright test tests/e2e/certificates.test.ts -npx playwright test tests/e2e/certificate-generation-checked-in.test.ts - -# Run certificate filtering tests with interactive script -./bin/test-certificate-filter.sh - -# Optimize and analyze E2E testing infrastructure -./bin/optimize-e2e-tests.sh - -# Run help system tests -npx playwright test tests/e2e/help-system-welcome-guide.test.ts -npx playwright test tests/e2e/help-system-tooltips.test.ts -npx playwright test tests/e2e/help-system-documentation.test.ts -npx playwright test tests/e2e/help-system-integration.test.ts - -# Run all help system tests +# Run help system tests (well-organized, no changes needed) npx playwright test tests/e2e/help-system-*.test.ts +# Run specific test by name pattern +npx playwright test --grep "Dashboard and basic navigation" +npx playwright test --grep "Certificate Reports" +npx playwright test --grep "Create Event" + +# Run with different reporters +npx playwright test tests/e2e/final-working-tests.test.ts --reporter=html # HTML report +npx playwright test tests/e2e/final-working-tests.test.ts --reporter=json # JSON output +``` + +#### **Test Development Setup** +```bash +# 1. Always set up test environment first +./bin/create-test-users.sh # Create test_trainer user +./bin/create-test-events-admin.sh # Create test events (if needed) + +# 2. Verify staging environment +./bin/verify-staging.sh # Check staging is accessible + +# 3. Run tests +npx playwright test tests/e2e/final-working-tests.test.ts +``` + +#### **PHPUnit Tests** +```bash # Run PHPUnit tests on staging ./bin/run-staging-unit-tests.sh ./bin/run-staging-unit-tests.sh --testsuite unit ./bin/run-staging-unit-tests.sh --coverage-html ./coverage-report ./bin/run-staging-unit-tests.sh --filter=test_get_total_events_count +``` -# Setup staging test users and verification -./bin/setup-staging-test-users.sh -./tests/run-tests.sh setup -./tests/run-tests.sh verify -./tests/run-tests.sh teardown --force +#### **Legacy Test Commands (Use only if needed)** +```bash +# These are kept for reference but use consolidated tests above +./bin/run-tests.sh --e2e # Old test runner +./bin/run-tests.sh --e2e --grep @certificate # Old pattern-based running +``` -# Common testing issues & solutions: -# 1. Missing plugin admin menu: Re-activate plugin and check error logs -# 2. Selector issues: Review the testing strategy for stable selector patterns -# 3. Login redirect issues: Ensure proper test user setup -# 4. Test independence: Each test should create its own test data -# 5. Wait for page load: Use explicit waits with waitForLoadState('networkidle') +#### **Common Issues & Solutions** +```bash +# Issue: Tests timing out +# Solution: Use final-working-tests.test.ts which has optimized timeouts + +# Issue: Profile page not found +# Solution: Correct URL is /trainer-profile/ not /community-profile/ + +# Issue: Create Event form fields not found +# Solution: Use #event_title not #post_title for title field + +# Issue: Certificate attendee selection hanging +# Solution: Use optimized tests that don't interact with problematic elements + +# Issue: Navigation elements not found +# Solution: Use .first() for elements that may have duplicates + +# Issue: CSS selector syntax errors +# Solution: Avoid regex in selectors, use text content matching instead ``` ### Deployment & Staging @@ -151,6 +192,451 @@ The HVAC Community Events plugin includes a comprehensive help system with three - Fixed 'Certificate Reports' critical error by removing problematic debug statements - Enhanced dashboard with proper tooltips and contextual help +## How to Write and Modify E2E Tests + +### **When to Modify Tests** + +**Use final-working-tests.test.ts for most modifications** - this is the main comprehensive test suite that covers all functionality. + +#### **Adding New Test Cases** +```typescript +// Add to tests/e2e/final-working-tests.test.ts +test('Your new test description', async ({ authenticatedPage: page }) => { + test.setTimeout(20000); // Set appropriate timeout + const actions = new CommonActions(page); + + // Your test logic here + await actions.navigateAndWait('/your-page/'); + await expect(page.locator('h1')).toBeVisible(); + await actions.screenshot('your-test-step'); +}); +``` + +#### **Modifying Existing Tests** +1. **Identify the test** in `final-working-tests.test.ts` +2. **Update the test logic** while maintaining the same structure +3. **Run the test** to verify it works: `npx playwright test tests/e2e/final-working-tests.test.ts --grep "test name"` +4. **Update expectations** if functionality has changed + +#### **Creating New Test Files (Only if needed)** +```typescript +// Pattern: tests/e2e/feature-name.test.ts +import { test, expect } from './fixtures/auth'; +import { CommonActions } from './utils/common-actions'; + +test.describe('Feature Name Tests', () => { + test('Specific functionality test', async ({ authenticatedPage: page }) => { + test.setTimeout(25000); + const actions = new CommonActions(page); + + // Test implementation + }); +}); +``` + +### **Essential Patterns and Utilities** + +#### **Using Shared Authentication** +```typescript +// ALWAYS use this pattern for authenticated tests +import { test, expect } from './fixtures/auth'; + +test('My test', async ({ authenticatedPage: page }) => { + // Page is already logged in as test_trainer + await expect(page).toHaveURL(/hvac-dashboard/); +}); +``` + +#### **Using Common Actions** +```typescript +import { CommonActions } from './utils/common-actions'; + +test('My test', async ({ authenticatedPage: page }) => { + const actions = new CommonActions(page); + + // Navigate safely with wait + await actions.navigateAndWait('/target-page/'); + + // Take screenshots for debugging + await actions.screenshot('test-step-name'); + + // Verify navigation elements + await actions.verifyNavigation(); + + // Wait for complex AJAX operations + await actions.waitForComplexAjax(); + + // Generate unique test data + const testData = actions.generateTestData('Event'); + await page.fill('#event_title', testData.title); +}); +``` + +#### **Safe Element Selection Patterns** +```typescript +// ✅ GOOD: Handle multiple headings safely +await expect(page.locator('h1, h2').filter({ hasText: /pattern/i }).first()).toBeVisible(); + +// ✅ GOOD: Use flexible selectors for form fields +const titleField = page.locator('#event_title, #post_title, input[name="post_title"]'); +await expect(titleField.first()).toBeVisible(); + +// ✅ GOOD: Check for elements before interacting +const submitButton = page.locator('button[type="submit"]'); +if (await submitButton.count() > 0) { + await expect(submitButton.first()).toBeVisible(); +} + +// ❌ AVOID: Brittle single selectors +await page.locator('#specific-id'); // May break if ID changes + +// ❌ AVOID: Regex in CSS selectors +await page.locator('span:text(/^\d+$/)'); // Causes syntax errors +``` + +#### **Form Interaction Patterns** +```typescript +// ✅ GOOD: Event title field (correct selector) +await page.fill('#event_title', 'My Event Title'); + +// ✅ GOOD: Flexible description field handling +try { + // Try TinyMCE first + const frame = page.frameLocator('iframe[id*="_ifr"]'); + await frame.locator('body').fill('Description text'); +} catch { + // Fallback to textarea + await page.fill('#event_content, textarea[name="content"]', 'Description text'); +} + +// ✅ GOOD: Event selection with AJAX wait +await page.selectOption('select[name="event_id"]', { index: 1 }); +await actions.waitForComplexAjax(); // Wait for attendees to load +``` + +#### **Error Handling Patterns** +```typescript +// ✅ GOOD: Monitor for PHP errors +const phpErrors = []; +page.on('console', (msg) => { + if (msg.type() === 'error' && msg.text().includes('PHP')) { + phpErrors.push(msg.text()); + } +}); + +// Test pages... + +expect(phpErrors.length).toBe(0); // Fail on PHP errors +``` + +### **Key URLs and Selectors** + +#### **Correct Page URLs** +```bash +Dashboard: /hvac-dashboard/ +Create Event: /manage-event/ +Certificate Reports: /certificate-reports/ +Generate Certs: /generate-certificates/ +Trainer Profile: /trainer-profile/ # NOT /community-profile/ +``` + +#### **Key Form Selectors** +```typescript +// Event Creation Form +'#event_title' // Event title field +'#event_content' // Event description +'select[name="event_id"]' // Event selection dropdown +'iframe[id*="_ifr"]' // TinyMCE editor frame + +// Navigation Elements +'a[href*="hvac-dashboard"]' // Dashboard links +'text=Generate Certificates' // Navigation buttons +'text=Create Event' // Navigation buttons + +// Statistics and Data +'.stat-value' // Statistics displays +'.stat-number' // Numeric statistics +'.dashboard-stat' // Dashboard statistics +``` + +### **Test Performance Guidelines** + +#### **Timeout Management** +```typescript +// ✅ GOOD: Set appropriate timeouts +test.setTimeout(20000); // 20 seconds for simple tests +test.setTimeout(30000); // 30 seconds for complex workflows +test.setTimeout(45000); // 45 seconds for comprehensive journeys + +// ✅ GOOD: Use built-in waits +await page.waitForLoadState('networkidle'); +await actions.waitForComplexAjax(); + +// ❌ AVOID: Arbitrary timeouts +await page.waitForTimeout(5000); // Use sparingly +``` + +#### **Screenshot Strategy** +```typescript +// ✅ GOOD: Meaningful screenshot names +await actions.screenshot('login-completed'); +await actions.screenshot('event-form-filled'); +await actions.screenshot('certificate-generated'); + +// ✅ GOOD: Screenshots at verification points +await actions.screenshot('page-loaded'); +await expect(page.locator('h1')).toBeVisible(); +await actions.screenshot('heading-verified'); +``` + +### **Debugging Failed Tests** + +#### **Common Debugging Commands** +```bash +# Run with visual browser to see what's happening +npx playwright test tests/e2e/final-working-tests.test.ts --headed + +# Run with debugger to step through +npx playwright test tests/e2e/final-working-tests.test.ts --debug + +# Run specific failing test +npx playwright test --grep "Create Event page accessibility" + +# Generate HTML report with screenshots +npx playwright test tests/e2e/final-working-tests.test.ts --reporter=html +npx playwright show-report +``` + +#### **Reading Test Failures** +```bash +# Timeout errors: Increase test timeout or fix selectors +# Element not found: Check if selectors are correct +# Navigation failed: Verify URLs and page structure +# PHP errors: Check staging environment and plugin status +``` + +### **Test Maintenance Checklist** + +#### **Before Modifying Tests** +1. ✅ Run existing tests to ensure they pass +2. ✅ Understand what functionality the test covers +3. ✅ Check if changes affect other tests +4. ✅ Use existing patterns and utilities + +#### **After Modifying Tests** +1. ✅ Run the modified test to ensure it passes +2. ✅ Run the full test suite to check for regressions +3. ✅ Update documentation if test structure changes +4. ✅ Commit changes with descriptive messages + +#### **When Tests Start Failing** +1. ✅ Check if staging environment is accessible +2. ✅ Verify test user (test_trainer) exists and works +3. ✅ Check for WordPress/plugin updates that changed UI +4. ✅ Look for PHP errors in console output +5. ✅ Update selectors if page structure changed + +### **Quick Reference Commands** + +#### **Most Common Commands (Copy-Paste Ready)** +```bash +# Run main test suite (MOST COMMON) +npx playwright test tests/e2e/final-working-tests.test.ts --reporter=line + +# Debug with visual browser +npx playwright test tests/e2e/final-working-tests.test.ts --headed + +# Run specific test +npx playwright test --grep "Dashboard and basic navigation" + +# Generate HTML report +npx playwright test tests/e2e/final-working-tests.test.ts --reporter=html +npx playwright show-report + +# Set up test environment +./bin/create-test-users.sh && ./bin/verify-staging.sh +``` + +#### **File Locations Quick Reference** +```bash +Main Test Suite: tests/e2e/final-working-tests.test.ts +Auth Fixture: tests/e2e/fixtures/auth.ts +Common Actions: tests/e2e/utils/common-actions.ts +Test Screenshots: test-results/screenshots/ +Test Reports: playwright-report/ +``` + +#### **Essential Test Patterns (Copy-Paste Ready)** +```typescript +// Basic test structure +test('Test name', async ({ authenticatedPage: page }) => { + test.setTimeout(20000); + const actions = new CommonActions(page); + await actions.navigateAndWait('/page-url/'); + await expect(page.locator('h1').first()).toBeVisible(); + await actions.screenshot('test-completed'); +}); + +// Form interaction +await page.fill('#event_title', 'Test Event'); +const value = await page.locator('#event_title').inputValue(); +expect(value).toBe('Test Event'); + +// Navigation verification +await actions.verifyNavigation(); +await expect(page).toHaveURL(/hvac-dashboard/); +``` + +## E2E Testing Best Practices + +### Test Structure and Organization (Consolidated 2025-05-23) + +The E2E test suite has been consolidated from 85+ files to a focused, maintainable structure: + +#### **Core Test Suites** +``` +tests/e2e/ +├── fixtures/ +│ └── auth.ts # Shared authentication utilities +├── utils/ +│ └── common-actions.ts # Reusable test actions and helpers +├── config/ +│ └── staging-config.ts # Test configuration +├── pages/ # Page Object Models (where used) +├── trainer-journey-final.test.ts # Comprehensive user journey (direct approach) +├── trainer-journey-harmonized.test.ts # User journey with Page Objects +├── certificate-core.test.ts # Certificate generation and viewing +├── certificate-management.test.ts # Bulk operations and reporting +├── certificate-edge-cases.test.ts # Error handling and validation +├── certificates.test.ts # Legacy comprehensive certificate suite +├── dashboard.test.ts # Dashboard functionality +├── help-system-*.test.ts # Help system components (4 files) +└── login.test.ts # Authentication testing +``` + +#### **Writing New E2E Tests** + +**1. Use Shared Utilities:** +```typescript +import { test, expect } from './fixtures/auth'; +import { CommonActions } from './utils/common-actions'; + +test('My test', async ({ authenticatedPage: page }) => { + const actions = new CommonActions(page); + await actions.navigateAndWait('/my-page/'); + await actions.screenshot('test-step'); +}); +``` + +**2. Test Categories and Tags:** +- Use `@tag` comments for test organization +- Core functionality: `@login`, `@dashboard`, `@certificates`, `@events` +- Help system: `@help-system`, `@tooltips`, `@documentation` +- Edge cases: `@edge-cases`, `@error-handling`, `@validation` + +**3. Test Independence:** +- Each test should be self-contained +- Use unique test data with timestamps +- Clean up test data when possible +- Don't rely on other tests' side effects + +**4. Error Handling:** +- Always check for PHP errors in console +- Use explicit waits (`waitForLoadState('networkidle')`) +- Handle missing elements gracefully +- Test both success and failure scenarios + +**5. Performance Guidelines:** +- Page load should complete within 10 seconds +- Use `actions.waitForAjax()` after AJAX operations +- Take screenshots at key verification points +- Batch similar operations when possible + +#### **Running Tests Efficiently** + +```bash +# Run specific test categories +npx playwright test --grep @certificates +npx playwright test --grep @help-system + +# Run single test file with debugging +npx playwright test certificate-core.test.ts --headed + +# Run tests in parallel (default) +npx playwright test tests/e2e/ --workers=3 + +# Generate test report +npx playwright show-report +``` + +#### **Common Test Patterns** + +**Navigation and Verification:** +```typescript +await actions.navigateAndWait('/target-page/'); +await actions.verifyNavigation(); +await expect(page.locator('h1')).toBeVisible(); +``` + +**Form Testing:** +```typescript +const testData = actions.generateTestData('Event'); +await page.fill('#title', testData.title); +await actions.fillTinyMCE('#description', testData.description); +``` + +**AJAX Testing:** +```typescript +await page.selectOption('select[name="event_id"]', { index: 1 }); +await actions.waitForAjax(); +await expect(page.locator('input[name="attendee_ids[]"]')).toBeVisible(); +``` + +#### **Maintenance Guidelines** + +1. **Before Adding New Tests:** + - Check if functionality is already covered + - Use existing test files when appropriate + - Consider consolidating related tests + +2. **File Naming Convention:** + - `feature-core.test.ts` for main functionality + - `feature-management.test.ts` for admin/bulk operations + - `feature-edge-cases.test.ts` for error handling + - `feature-integration.test.ts` for cross-feature testing + +3. **Test Data Management:** + - Use `actions.generateTestData()` for unique names + - Avoid hardcoded test data that may conflict + - Clean up created test data when possible + +4. **Screenshot Strategy:** + - Take screenshots at verification points + - Use descriptive names with timestamps + - Organize in `test-results/screenshots/` + +#### **Troubleshooting Common Issues** + +1. **Flaky Tests:** + - Add explicit waits after navigation + - Use `waitForLoadState('networkidle')` + - Check for race conditions in AJAX calls + +2. **Selector Issues:** + - Use stable selectors (IDs over classes) + - Prefer text content when selectors change + - Test selectors in browser console first + +3. **Authentication Issues:** + - Use `authenticatedPage` fixture for most tests + - Verify login redirect works properly + - Check test user exists and has correct permissions + +4. **Page Load Issues:** + - Ensure staging environment is accessible + - Check for JavaScript errors in console + - Verify plugin is activated and functioning + ## Architecture Overview [... rest of the file remains unchanged ...] \ No newline at end of file diff --git a/wordpress-dev/tests/e2e/mobile-responsiveness-simple.test.ts b/wordpress-dev/tests/e2e/mobile-responsiveness-simple.test.ts new file mode 100644 index 00000000..364fe567 --- /dev/null +++ b/wordpress-dev/tests/e2e/mobile-responsiveness-simple.test.ts @@ -0,0 +1,326 @@ +/** + * Mobile Responsiveness Test for HVAC Plugin UX Enhancements + * + * Simplified mobile testing focusing on key responsiveness features + */ + +import { test, expect } from '@playwright/test'; + +const STAGING_URL = 'https://upskill-staging.measurequick.com'; + +// Test responsive breakpoints +test.describe('Mobile Responsiveness', () => { + test('Mobile login page layout (iPhone viewport)', async ({ page }) => { + test.setTimeout(20000); + + // Set mobile viewport + await page.setViewportSize({ width: 375, height: 667 }); + + await page.goto(`${STAGING_URL}/community-login`); + await page.waitForLoadState('networkidle'); + + // Check login form is properly sized for mobile + const loginForm = page.locator('#hvac_community_loginform'); + await expect(loginForm).toBeVisible(); + + // Verify mobile-optimized form elements + const usernameField = page.locator('#user_login'); + const passwordField = page.locator('#user_pass'); + const submitButton = page.locator('#wp-submit'); + + await expect(usernameField).toBeVisible(); + await expect(passwordField).toBeVisible(); + await expect(submitButton).toBeVisible(); + + // Check touch target sizes (minimum 44px for iOS) + const submitBox = await submitButton.boundingBox(); + expect(submitBox?.height).toBeGreaterThanOrEqual(40); + + // Test form field focus states work on mobile + await usernameField.focus(); + await passwordField.focus(); + + // Take screenshot for visual verification + await page.screenshot({ path: `test-results/mobile-login-375x667.png` }); + }); + + test('Mobile dashboard layout and navigation', async ({ page }) => { + test.setTimeout(30000); + + // Set mobile viewport + await page.setViewportSize({ width: 375, height: 667 }); + + // Login first + await page.goto(`${STAGING_URL}/community-login`); + await page.fill('#user_login', 'test_trainer'); + await page.fill('#user_pass', 'Test123!'); + await page.click('#wp-submit'); + await page.waitForLoadState('networkidle'); + + // Verify we're on dashboard + await expect(page).toHaveURL(/hvac-dashboard/); + + // Check dashboard layout adapts to mobile + const dashboard = page.locator('.hvac-dashboard-wrapper'); + await expect(dashboard).toBeVisible(); + + // Test mobile navigation if present + const mobileNavToggle = page.locator('.hvac-mobile-nav-toggle'); + if (await mobileNavToggle.isVisible()) { + await mobileNavToggle.click(); + + const mobileNav = page.locator('.hvac-mobile-nav'); + await expect(mobileNav).toHaveClass(/open/); + + // Test navigation links work + const navLinks = page.locator('.hvac-mobile-nav a'); + const navCount = await navLinks.count(); + expect(navCount).toBeGreaterThan(0); + } + + // Check stats cards stack properly on mobile + const statCards = page.locator('.hvac-stat-card'); + const statCount = await statCards.count(); + if (statCount > 0) { + // Verify cards are visible and properly sized + for (let i = 0; i < Math.min(statCount, 3); i++) { + await expect(statCards.nth(i)).toBeVisible(); + } + } + + // Test touch scrolling works + await page.evaluate(() => window.scrollTo(0, 200)); + await page.waitForTimeout(500); + + // Take screenshot + await page.screenshot({ path: `test-results/mobile-dashboard-375x667.png` }); + }); + + test('Toast notifications positioning on mobile', async ({ page }) => { + test.setTimeout(25000); + + // Set mobile viewport + await page.setViewportSize({ width: 375, height: 667 }); + + // Login + await page.goto(`${STAGING_URL}/community-login`); + await page.fill('#user_login', 'test_trainer'); + await page.fill('#user_pass', 'Test123!'); + await page.click('#wp-submit'); + await page.waitForLoadState('networkidle'); + + // Go to certificate reports to trigger potential notifications + await page.goto(`${STAGING_URL}/certificate-reports`); + await page.waitForLoadState('networkidle'); + + // Inject test toast to verify mobile positioning + await page.evaluate(() => { + // Create toast container if it doesn't exist + if (!document.querySelector('.hvac-toast-container')) { + const container = document.createElement('div'); + container.className = 'hvac-toast-container'; + container.style.cssText = ` + position: fixed; + top: 10px; + right: 10px; + left: 10px; + z-index: 10000; + display: flex; + flex-direction: column; + gap: 10px; + max-width: none; + pointer-events: none; + `; + document.body.appendChild(container); + } + + // Create a test toast + const toast = document.createElement('div'); + toast.className = 'hvac-toast success show'; + toast.style.cssText = ` + background: white; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + padding: 16px 20px; + pointer-events: all; + transform: translateX(0); + border-left: 4px solid #10b981; + display: flex; + align-items: flex-start; + gap: 12px; + max-width: 100%; + word-wrap: break-word; + `; + toast.innerHTML = ` +
+
+
Success
+
Mobile toast notification test - this is a longer message to test wrapping
+
+ `; + + const container = document.querySelector('.hvac-toast-container'); + if (container) { + container.appendChild(toast); + } + }); + + await page.waitForTimeout(1000); + + // Check if toast is visible and properly positioned + const toast = page.locator('.hvac-toast'); + await expect(toast).toBeVisible(); + + // Verify toast doesn't overflow viewport + const toastBox = await toast.boundingBox(); + const viewport = page.viewportSize(); + + if (toastBox && viewport) { + expect(toastBox.x + toastBox.width).toBeLessThanOrEqual(viewport.width); + expect(toastBox.y).toBeGreaterThanOrEqual(0); + } + + // Take screenshot + await page.screenshot({ path: `test-results/mobile-toast-375x667.png` }); + }); + + test('Form interaction on mobile devices', async ({ page }) => { + test.setTimeout(25000); + + // Set mobile viewport + await page.setViewportSize({ width: 375, height: 667 }); + + // Login + await page.goto(`${STAGING_URL}/community-login`); + await page.fill('#user_login', 'test_trainer'); + await page.fill('#user_pass', 'Test123!'); + await page.click('#wp-submit'); + await page.waitForLoadState('networkidle'); + + // Go to create event page to test forms + await page.goto(`${STAGING_URL}/manage-event`); + await page.waitForLoadState('networkidle'); + + // Test form field interactions + const titleField = page.locator('#event_title'); + if (await titleField.isVisible()) { + // Check field is properly sized for mobile + const fieldBox = await titleField.boundingBox(); + expect(fieldBox?.height).toBeGreaterThanOrEqual(40); + + // Test touch interaction + await titleField.tap(); + await titleField.fill('Mobile Test Event'); + + const value = await titleField.inputValue(); + expect(value).toBe('Mobile Test Event'); + } + + // Check form buttons are touch-friendly + const buttons = page.locator('button, input[type="submit"]'); + const buttonCount = await buttons.count(); + + if (buttonCount > 0) { + const button = buttons.first(); + if (await button.isVisible()) { + const buttonBox = await button.boundingBox(); + expect(buttonBox?.height).toBeGreaterThanOrEqual(40); + } + } + + // Take screenshot + await page.screenshot({ path: `test-results/mobile-form-375x667.png` }); + }); + + test('Responsive breakpoints verification', async ({ page }) => { + test.setTimeout(35000); + + const breakpoints = [ + { name: 'Mobile-Small', width: 320, height: 568 }, + { name: 'Mobile-Medium', width: 375, height: 667 }, + { name: 'Tablet-Portrait', width: 768, height: 1024 }, + ]; + + for (const breakpoint of breakpoints) { + // Set viewport + await page.setViewportSize({ width: breakpoint.width, height: breakpoint.height }); + + // Login + await page.goto(`${STAGING_URL}/community-login`); + await page.fill('#user_login', 'test_trainer'); + await page.fill('#user_pass', 'Test123!'); + await page.click('#wp-submit'); + await page.waitForLoadState('networkidle'); + + // Check dashboard layout + await expect(page).toHaveURL(/hvac-dashboard/); + + // Verify content is visible and accessible + const mainContent = page.locator('.hvac-dashboard-wrapper, .entry-content'); + await expect(mainContent).toBeVisible(); + + // Check navigation is appropriate for screen size + if (breakpoint.width <= 767) { + // Mobile: navigation should adapt + const nav = page.locator('.hvac-dashboard-nav'); + await expect(nav).toBeVisible(); + } else { + // Tablet/Desktop: should see regular nav + const desktopNav = page.locator('.hvac-dashboard-nav'); + await expect(desktopNav).toBeVisible(); + } + + // Take screenshot + await page.screenshot({ + path: `test-results/responsive-${breakpoint.name}-${breakpoint.width}x${breakpoint.height}.png`, + fullPage: false + }); + + console.log(`✓ Verified ${breakpoint.name} (${breakpoint.width}x${breakpoint.height})`); + } + }); + + test('UX enhancements load correctly', async ({ page }) => { + test.setTimeout(20000); + + // Set mobile viewport + await page.setViewportSize({ width: 375, height: 667 }); + + // Login + await page.goto(`${STAGING_URL}/community-login`); + await page.fill('#user_login', 'test_trainer'); + await page.fill('#user_pass', 'Test123!'); + await page.click('#wp-submit'); + await page.waitForLoadState('networkidle'); + + // Check that UX enhancement assets are loaded + const uxEnhancementsLoaded = await page.evaluate(() => { + // Check if HVACToast is available + const hasToast = typeof window.HVACToast !== 'undefined'; + + // Check if UX CSS is loaded by looking for specific classes + const uxStyles = Array.from(document.styleSheets).some(sheet => { + try { + return Array.from(sheet.cssRules).some(rule => + rule.selectorText && rule.selectorText.includes('hvac-toast') + ); + } catch (e) { + return false; + } + }); + + return { + hasToast, + uxStyles, + mobileNav: !!document.querySelector('.hvac-mobile-nav-container, .hvac-mobile-nav-toggle') + }; + }); + + console.log('UX Enhancements status:', uxEnhancementsLoaded); + + // At minimum, some UX elements should be present + expect(uxEnhancementsLoaded.hasToast || uxEnhancementsLoaded.uxStyles).toBe(true); + + await page.screenshot({ path: `test-results/ux-enhancements-loaded.png` }); + }); +}); \ No newline at end of file diff --git a/wordpress-dev/tests/e2e/mobile-responsiveness.test.ts b/wordpress-dev/tests/e2e/mobile-responsiveness.test.ts new file mode 100644 index 00000000..7121617a --- /dev/null +++ b/wordpress-dev/tests/e2e/mobile-responsiveness.test.ts @@ -0,0 +1,285 @@ +/** + * Mobile Responsiveness Test for HVAC Plugin UX Enhancements + * + * Tests the mobile experience including: + * - Touch-friendly interface elements + * - Responsive layouts + * - Mobile navigation + * - Toast notifications on mobile + * - Form usability on mobile devices + */ + +import { test, expect, devices } from '@playwright/test'; + +const STAGING_URL = 'https://upskill-staging.measurequick.com'; + +// Test with different mobile viewports +const mobileDevices = [ + devices['iPhone 12'], + devices['iPhone 12 Pro'], + devices['Samsung Galaxy S21'], + devices['iPad Mini'] +]; + +for (const device of mobileDevices) { + test.describe(`Mobile Responsiveness - ${device.name}`, () => { + test.use({ ...device }); + + test(`Login page mobile layout - ${device.name}`, async ({ page }) => { + test.setTimeout(20000); + + await page.goto(`${STAGING_URL}/community-login`); + await page.waitForLoadState('networkidle'); + + // Check login form is properly sized for mobile + const loginForm = page.locator('#hvac_community_loginform'); + await expect(loginForm).toBeVisible(); + + // Verify mobile-optimized form elements + const usernameField = page.locator('#user_login'); + const passwordField = page.locator('#user_pass'); + const submitButton = page.locator('#wp-submit'); + + await expect(usernameField).toBeVisible(); + await expect(passwordField).toBeVisible(); + await expect(submitButton).toBeVisible(); + + // Check touch target sizes (minimum 44px for iOS) + const submitBox = await submitButton.boundingBox(); + expect(submitBox?.height).toBeGreaterThanOrEqual(44); + + // Test form field focus states work on mobile + await usernameField.focus(); + await passwordField.focus(); + + // Take screenshot for visual verification + await page.screenshot({ path: `test-results/mobile-login-${device.name?.replace(/\s+/g, '-')}.png` }); + }); + + test(`Dashboard mobile layout - ${device.name}`, async ({ page }) => { + test.setTimeout(30000); + + // Login first + await page.goto(`${STAGING_URL}/community-login`); + await page.fill('#user_login', 'test_trainer'); + await page.fill('#user_pass', 'Test123!'); + await page.click('#wp-submit'); + await page.waitForLoadState('networkidle'); + + // Verify we're on dashboard + await expect(page).toHaveURL(/hvac-dashboard/); + + // Check dashboard layout adapts to mobile + const dashboard = page.locator('.hvac-dashboard-wrapper'); + await expect(dashboard).toBeVisible(); + + // Test mobile navigation if present + const mobileNavToggle = page.locator('.hvac-mobile-nav-toggle'); + if (await mobileNavToggle.isVisible()) { + await mobileNavToggle.click(); + + const mobileNav = page.locator('.hvac-mobile-nav'); + await expect(mobileNav).toHaveClass(/open/); + + // Test navigation links work + const navLinks = page.locator('.hvac-mobile-nav a'); + const navCount = await navLinks.count(); + expect(navCount).toBeGreaterThan(0); + } + + // Check stats cards stack properly on mobile + const statCards = page.locator('.hvac-stat-card'); + const statCount = await statCards.count(); + if (statCount > 0) { + // Verify cards are visible and properly sized + for (let i = 0; i < Math.min(statCount, 3); i++) { + await expect(statCards.nth(i)).toBeVisible(); + } + } + + // Test touch scrolling works + await page.evaluate(() => window.scrollTo(0, 200)); + await page.waitForTimeout(500); + + // Take screenshot + await page.screenshot({ path: `test-results/mobile-dashboard-${device.name?.replace(/\s+/g, '-')}.png` }); + }); + + test(`Toast notifications on mobile - ${device.name}`, async ({ page }) => { + test.setTimeout(25000); + + // Login + await page.goto(`${STAGING_URL}/community-login`); + await page.fill('#user_login', 'test_trainer'); + await page.fill('#user_pass', 'Test123!'); + await page.click('#wp-submit'); + await page.waitForLoadState('networkidle'); + + // Go to certificate reports to trigger potential notifications + await page.goto(`${STAGING_URL}/certificate-reports`); + await page.waitForLoadState('networkidle'); + + // Check if toast container exists and is positioned correctly for mobile + const toastContainer = page.locator('.hvac-toast-container'); + + // Inject test toast to verify mobile positioning + await page.evaluate(() => { + if (window.HVACToast) { + window.HVACToast.success('This is a test toast notification for mobile testing'); + } + }); + + await page.waitForTimeout(1000); + + // Check if toast is visible and properly positioned + const toast = page.locator('.hvac-toast'); + if (await toast.count() > 0) { + await expect(toast.first()).toBeVisible(); + + // Verify toast doesn't overflow viewport + const toastBox = await toast.first().boundingBox(); + const viewport = page.viewportSize(); + + if (toastBox && viewport) { + expect(toastBox.x + toastBox.width).toBeLessThanOrEqual(viewport.width); + expect(toastBox.y).toBeGreaterThanOrEqual(0); + } + } + + // Take screenshot + await page.screenshot({ path: `test-results/mobile-toast-${device.name?.replace(/\s+/g, '-')}.png` }); + }); + + test(`Form interaction on mobile - ${device.name}`, async ({ page }) => { + test.setTimeout(25000); + + // Login + await page.goto(`${STAGING_URL}/community-login`); + await page.fill('#user_login', 'test_trainer'); + await page.fill('#user_pass', 'Test123!'); + await page.click('#wp-submit'); + await page.waitForLoadState('networkidle'); + + // Go to create event page to test forms + await page.goto(`${STAGING_URL}/manage-event`); + await page.waitForLoadState('networkidle'); + + // Test form field interactions + const titleField = page.locator('#event_title'); + if (await titleField.isVisible()) { + // Check field is properly sized for mobile + const fieldBox = await titleField.boundingBox(); + expect(fieldBox?.height).toBeGreaterThanOrEqual(44); + + // Test touch interaction + await titleField.tap(); + await titleField.fill('Mobile Test Event'); + + const value = await titleField.inputValue(); + expect(value).toBe('Mobile Test Event'); + } + + // Check form buttons are touch-friendly + const buttons = page.locator('button, input[type="submit"]'); + const buttonCount = await buttons.count(); + + for (let i = 0; i < Math.min(buttonCount, 3); i++) { + const button = buttons.nth(i); + if (await button.isVisible()) { + const buttonBox = await button.boundingBox(); + expect(buttonBox?.height).toBeGreaterThanOrEqual(44); + } + } + + // Take screenshot + await page.screenshot({ path: `test-results/mobile-form-${device.name?.replace(/\s+/g, '-')}.png` }); + }); + }); +} + +// Test responsive breakpoints +test.describe('Responsive Breakpoints', () => { + const breakpoints = [ + { name: 'Mobile Small', width: 320, height: 568 }, + { name: 'Mobile Medium', width: 375, height: 667 }, + { name: 'Mobile Large', width: 414, height: 896 }, + { name: 'Tablet Portrait', width: 768, height: 1024 }, + { name: 'Tablet Landscape', width: 1024, height: 768 } + ]; + + for (const breakpoint of breakpoints) { + test(`Dashboard layout at ${breakpoint.name} (${breakpoint.width}x${breakpoint.height})`, async ({ page }) => { + test.setTimeout(25000); + + // Set viewport + await page.setViewportSize({ width: breakpoint.width, height: breakpoint.height }); + + // Login + await page.goto(`${STAGING_URL}/community-login`); + await page.fill('#user_login', 'test_trainer'); + await page.fill('#user_pass', 'Test123!'); + await page.click('#wp-submit'); + await page.waitForLoadState('networkidle'); + + // Check dashboard layout + await expect(page).toHaveURL(/hvac-dashboard/); + + // Verify content is visible and accessible + const mainContent = page.locator('.hvac-dashboard-wrapper, .entry-content'); + await expect(mainContent).toBeVisible(); + + // Check navigation is appropriate for screen size + if (breakpoint.width <= 767) { + // Mobile: should see mobile nav or stacked nav + const mobileNav = page.locator('.hvac-mobile-nav-toggle, .hvac-dashboard-nav'); + await expect(mobileNav).toBeVisible(); + } else { + // Tablet/Desktop: should see regular nav + const desktopNav = page.locator('.hvac-dashboard-nav'); + await expect(desktopNav).toBeVisible(); + } + + // Take screenshot + await page.screenshot({ + path: `test-results/responsive-${breakpoint.name.replace(/\s+/g, '-')}-${breakpoint.width}x${breakpoint.height}.png`, + fullPage: true + }); + }); + } +}); + +// Test accessibility on mobile +test.describe('Mobile Accessibility', () => { + test.use(devices['iPhone 12']); + + test('Touch accessibility and keyboard navigation', async ({ page }) => { + test.setTimeout(25000); + + // Login + await page.goto(`${STAGING_URL}/community-login`); + await page.fill('#user_login', 'test_trainer'); + await page.fill('#user_pass', 'Test123!'); + await page.click('#wp-submit'); + await page.waitForLoadState('networkidle'); + + // Test keyboard navigation with tab + await page.keyboard.press('Tab'); + await page.keyboard.press('Tab'); + await page.keyboard.press('Tab'); + + // Check focus states are visible + const focusedElement = page.locator(':focus'); + await expect(focusedElement).toBeVisible(); + + // Test escape key closes mobile elements + await page.keyboard.press('Escape'); + + // Verify aria labels exist on interactive elements + const buttons = page.locator('button[aria-label], input[aria-label]'); + const buttonCount = await buttons.count(); + console.log(`Found ${buttonCount} buttons with aria-labels`); + + // Take screenshot + await page.screenshot({ path: `test-results/mobile-accessibility.png` }); + }); +}); \ No newline at end of file diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-ux-enhancements.css b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-ux-enhancements.css new file mode 100644 index 00000000..d33d03d1 --- /dev/null +++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-ux-enhancements.css @@ -0,0 +1,411 @@ +/** + * HVAC Community Events: UX Enhancements + * + * Enhanced user experience with modern notifications, loading states, and mobile optimizations + * + * @version 1.0.0 + */ + +/* ======================================== + NOTIFICATION SYSTEM +======================================== */ + +/* Toast Notification Container */ +.hvac-toast-container { + position: fixed; + top: 20px; + right: 20px; + z-index: 10000; + display: flex; + flex-direction: column; + gap: 10px; + max-width: 400px; + pointer-events: none; +} + +/* Toast Notification */ +.hvac-toast { + background: white; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + padding: 16px 20px; + pointer-events: all; + transform: translateX(100%); + transition: all 0.3s ease; + border-left: 4px solid #3b82f6; + display: flex; + align-items: flex-start; + gap: 12px; + position: relative; + max-width: 100%; + word-wrap: break-word; +} + +.hvac-toast.show { + transform: translateX(0); +} + +.hvac-toast.hiding { + transform: translateX(100%); + opacity: 0; +} + +/* Toast Types */ +.hvac-toast.success { + border-left-color: #10b981; +} + +.hvac-toast.error { + border-left-color: #ef4444; +} + +.hvac-toast.warning { + border-left-color: #f59e0b; +} + +.hvac-toast.info { + border-left-color: #3b82f6; +} + +/* Toast Icon */ +.hvac-toast-icon { + width: 20px; + height: 20px; + flex-shrink: 0; + margin-top: 2px; +} + +.hvac-toast.success .hvac-toast-icon::before { + content: "✓"; + color: #10b981; + font-weight: bold; + font-size: 16px; +} + +.hvac-toast.error .hvac-toast-icon::before { + content: "✗"; + color: #ef4444; + font-weight: bold; + font-size: 16px; +} + +.hvac-toast.warning .hvac-toast-icon::before { + content: "⚠"; + color: #f59e0b; + font-weight: bold; + font-size: 16px; +} + +.hvac-toast.info .hvac-toast-icon::before { + content: "ℹ"; + color: #3b82f6; + font-weight: bold; + font-size: 16px; +} + +/* Toast Content */ +.hvac-toast-content { + flex: 1; +} + +.hvac-toast-title { + font-weight: 600; + font-size: 14px; + color: #1f2937; + margin-bottom: 4px; +} + +.hvac-toast-message { + font-size: 13px; + color: #6b7280; + line-height: 1.4; +} + +/* Toast Close Button */ +.hvac-toast-close { + background: none; + border: none; + color: #9ca3af; + cursor: pointer; + font-size: 18px; + padding: 0; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: all 0.2s; + flex-shrink: 0; +} + +.hvac-toast-close:hover { + background: #f3f4f6; + color: #374151; +} + +/* ======================================== + LOADING STATES +======================================== */ + +/* Loading Overlay */ +.hvac-loading-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + backdrop-filter: blur(2px); +} + +/* Inline Loading Spinner */ +.hvac-loading { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 14px; + color: #6b7280; +} + +.hvac-spinner { + width: 20px; + height: 20px; + border: 2px solid #e5e7eb; + border-top: 2px solid #3b82f6; + border-radius: 50%; + animation: hvac-spin 1s linear infinite; +} + +.hvac-spinner.large { + width: 40px; + height: 40px; + border-width: 3px; +} + +@keyframes hvac-spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Button Loading State */ +.hvac-btn.loading { + position: relative; + color: transparent !important; + pointer-events: none; +} + +.hvac-btn.loading::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 16px; + height: 16px; + border: 2px solid transparent; + border-top: 2px solid currentColor; + border-radius: 50%; + animation: hvac-spin 1s linear infinite; + color: inherit; +} + +/* ======================================== + ENHANCED BUTTONS +======================================== */ + +.hvac-btn { + position: relative; + overflow: hidden; + transition: all 0.2s ease; +} + +.hvac-btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none !important; +} + +.hvac-btn:not(:disabled):hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.hvac-btn:not(:disabled):active { + transform: translateY(0); +} + +/* ======================================== + ENHANCED FORM ELEMENTS +======================================== */ + +/* Input Focus States */ +.hvac-input:focus, +.hvac-select:focus, +.hvac-textarea:focus { + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + outline: none; +} + +/* Input Error States */ +.hvac-input.error, +.hvac-select.error, +.hvac-textarea.error { + border-color: #ef4444; + box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1); +} + +/* Field Error Message */ +.hvac-field-error { + color: #ef4444; + font-size: 12px; + margin-top: 4px; + display: flex; + align-items: center; + gap: 4px; +} + +.hvac-field-error::before { + content: "⚠"; + font-size: 14px; +} + +/* ======================================== + MOBILE RESPONSIVENESS +======================================== */ + +/* Mobile Toast Adjustments */ +@media (max-width: 640px) { + .hvac-toast-container { + top: 10px; + right: 10px; + left: 10px; + max-width: none; + } + + .hvac-toast { + margin: 0; + max-width: 100%; + } +} + +/* Mobile Form Improvements */ +@media (max-width: 767px) { + /* Larger touch targets */ + .hvac-btn { + min-height: 44px; + font-size: 16px; /* Prevents zoom on iOS */ + } + + .hvac-input, + .hvac-select, + .hvac-textarea { + min-height: 44px; + font-size: 16px; /* Prevents zoom on iOS */ + padding: 12px; + } + + /* Full width buttons on mobile */ + .hvac-dashboard-nav { + flex-direction: column; + gap: 8px; + } + + .hvac-dashboard-nav .hvac-btn { + width: 100%; + text-align: center; + } + + /* Improved stats layout on mobile */ + .hvac-stats-row { + flex-direction: column; + margin: 0; + } + + .hvac-stat-col { + padding: 5px 0; + min-width: 100%; + } + + /* Card improvements for mobile */ + .hvac-stat-card, + .hvac-certificate-card, + .hvac-event-card { + margin-bottom: var(--hvac-spacing-md); + padding: var(--hvac-spacing-md); + } + + /* Table responsiveness */ + .hvac-table-responsive { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + .hvac-table-responsive table { + min-width: 600px; + } +} + +/* ======================================== + ACCESSIBILITY ENHANCEMENTS +======================================== */ + +/* Focus Visible for Keyboard Navigation */ +.hvac-btn:focus-visible, +.hvac-input:focus-visible, +.hvac-select:focus-visible { + outline: 2px solid #3b82f6; + outline-offset: 2px; +} + +/* Screen Reader Only Text */ +.hvac-sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* High Contrast Mode Support */ +@media (prefers-contrast: high) { + .hvac-toast { + border: 2px solid; + } + + .hvac-btn { + border: 2px solid; + } +} + +/* Reduced Motion Support */ +@media (prefers-reduced-motion: reduce) { + .hvac-toast, + .hvac-btn, + .hvac-spinner { + animation: none; + transition: none; + } +} + +/* ======================================== + UTILITY CLASSES +======================================== */ + +.hvac-hidden { display: none !important; } +.hvac-invisible { visibility: hidden !important; } +.hvac-text-center { text-align: center !important; } +.hvac-text-left { text-align: left !important; } +.hvac-text-right { text-align: right !important; } +.hvac-mb-0 { margin-bottom: 0 !important; } +.hvac-mt-0 { margin-top: 0 !important; } +.hvac-p-0 { padding: 0 !important; } \ No newline at end of file diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-ux-enhancements.js b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-ux-enhancements.js new file mode 100644 index 00000000..f1565a76 --- /dev/null +++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-ux-enhancements.js @@ -0,0 +1,409 @@ +/** + * HVAC Community Events: UX Enhancements JavaScript + * + * Modern user experience enhancements including toast notifications, + * loading states, and improved error handling. + * + * @version 1.0.0 + */ + +(function($) { + 'use strict'; + + /** + * Toast Notification System + */ + const HVACToast = { + container: null, + + init: function() { + // Create toast container if it doesn't exist + if (!this.container) { + this.container = $('
'); + $('body').append(this.container); + } + }, + + show: function(message, type = 'info', title = '', duration = 5000) { + this.init(); + + const toastId = 'hvac-toast-' + Date.now(); + const toast = $(` +
+
+
+ ${title ? `
${title}
` : ''} +
${message}
+
+ +
+ `); + + // Add event listeners + toast.find('.hvac-toast-close').on('click', () => this.hide(toastId)); + + // Add to container and show + this.container.append(toast); + + // Trigger show animation + setTimeout(() => toast.addClass('show'), 10); + + // Auto-hide after duration + if (duration > 0) { + setTimeout(() => this.hide(toastId), duration); + } + + return toastId; + }, + + hide: function(toastId) { + const toast = $('#' + toastId); + if (toast.length) { + toast.addClass('hiding'); + setTimeout(() => toast.remove(), 300); + } + }, + + success: function(message, title = 'Success', duration = 4000) { + return this.show(message, 'success', title, duration); + }, + + error: function(message, title = 'Error', duration = 7000) { + return this.show(message, 'error', title, duration); + }, + + warning: function(message, title = 'Warning', duration = 6000) { + return this.show(message, 'warning', title, duration); + }, + + info: function(message, title = '', duration = 5000) { + return this.show(message, 'info', title, duration); + } + }; + + /** + * Loading State Manager + */ + const HVACLoading = { + overlay: null, + + showOverlay: function(message = 'Loading...') { + this.hideOverlay(); // Remove existing overlay + + this.overlay = $(` +
+
+
+ ${message} +
+
+ `); + + $('body').append(this.overlay); + }, + + hideOverlay: function() { + if (this.overlay) { + this.overlay.remove(); + this.overlay = null; + } + }, + + showButton: function(button, text = 'Loading...') { + const $btn = $(button); + $btn.addClass('loading').prop('disabled', true); + $btn.data('original-text', $btn.text()); + if (text) { + $btn.attr('aria-label', text); + } + }, + + hideButton: function(button) { + const $btn = $(button); + $btn.removeClass('loading').prop('disabled', false); + const originalText = $btn.data('original-text'); + if (originalText) { + $btn.text(originalText).removeData('original-text'); + } + $btn.removeAttr('aria-label'); + } + }; + + /** + * Enhanced AJAX Handler + */ + const HVACAjax = { + request: function(options) { + const defaults = { + type: 'POST', + dataType: 'json', + timeout: 30000, + showLoading: true, + loadingMessage: 'Processing...', + showToasts: true, + beforeSend: function() { + if (options.showLoading && options.button) { + HVACLoading.showButton(options.button, options.loadingMessage); + } else if (options.showLoading && options.showOverlay) { + HVACLoading.showOverlay(options.loadingMessage); + } + }, + complete: function() { + if (options.showLoading && options.button) { + HVACLoading.hideButton(options.button); + } else if (options.showLoading && options.showOverlay) { + HVACLoading.hideOverlay(); + } + }, + success: function(response) { + if (options.showToasts) { + if (response.success) { + const message = response.data?.message || 'Operation completed successfully'; + HVACToast.success(message); + } else { + const message = response.data?.message || 'Operation failed'; + HVACToast.error(message); + } + } + }, + error: function(xhr, status, error) { + console.error('AJAX Error:', {xhr, status, error}); + + if (options.showToasts) { + let message = 'An unexpected error occurred. Please try again.'; + + if (status === 'timeout') { + message = 'Request timed out. Please check your connection and try again.'; + } else if (status === 'abort') { + message = 'Request was cancelled.'; + } else if (xhr.status === 403) { + message = 'You do not have permission to perform this action.'; + } else if (xhr.status === 404) { + message = 'The requested resource was not found.'; + } else if (xhr.status >= 500) { + message = 'Server error. Please try again later.'; + } + + HVACToast.error(message, 'Connection Error'); + } + } + }; + + const settings = $.extend({}, defaults, options); + return $.ajax(settings); + } + }; + + /** + * Form Validation Enhancement + */ + const HVACValidation = { + validateField: function(field, rules = []) { + const $field = $(field); + const value = $field.val().trim(); + let isValid = true; + let errorMessage = ''; + + // Remove existing error states + $field.removeClass('error'); + $field.siblings('.hvac-field-error').remove(); + + // Check rules + for (const rule of rules) { + if (rule.required && !value) { + isValid = false; + errorMessage = rule.message || 'This field is required'; + break; + } + + if (rule.minLength && value.length < rule.minLength) { + isValid = false; + errorMessage = rule.message || `Must be at least ${rule.minLength} characters`; + break; + } + + if (rule.pattern && !rule.pattern.test(value)) { + isValid = false; + errorMessage = rule.message || 'Invalid format'; + break; + } + + if (rule.custom && typeof rule.custom === 'function') { + const result = rule.custom(value); + if (result !== true) { + isValid = false; + errorMessage = result || 'Invalid value'; + break; + } + } + } + + if (!isValid) { + $field.addClass('error'); + $field.after(`
${errorMessage}
`); + } + + return isValid; + } + }; + + /** + * Mobile Navigation Enhancement + */ + const HVACMobile = { + init: function() { + this.setupMobileNav(); + this.setupTouchOptimizations(); + }, + + setupMobileNav: function() { + // Create mobile navigation if not exists + $('.hvac-dashboard-nav').each(function() { + const $nav = $(this); + if ($nav.siblings('.hvac-mobile-nav-container').length === 0) { + const mobileNav = $(` +
+ + +
+ `); + + // Copy nav items to mobile menu + const $mobileList = mobileNav.find('ul'); + $nav.find('a').each(function() { + const $link = $(this).clone(); + $mobileList.append($('
  • ').append($link)); + }); + + $nav.before(mobileNav); + + // Add toggle functionality + mobileNav.find('.hvac-mobile-nav-toggle').on('click', function() { + const $toggle = $(this); + const $menu = $toggle.siblings('.hvac-mobile-nav'); + const isOpen = $menu.hasClass('open'); + + $toggle.toggleClass('active').attr('aria-expanded', !isOpen); + $menu.toggleClass('open'); + }); + } + }); + }, + + setupTouchOptimizations: function() { + // Add touch-friendly classes + $('button, .hvac-btn, input[type="submit"]').addClass('hvac-touch-target'); + + // Prevent double-tap zoom on buttons + $('button, .hvac-btn').on('touchend', function(e) { + e.preventDefault(); + $(this).trigger('click'); + }); + } + }; + + /** + * Accessibility Enhancements + */ + const HVACAccessibility = { + init: function() { + this.setupKeyboardNavigation(); + this.setupAriaLabels(); + }, + + setupKeyboardNavigation: function() { + // Escape key to close modals/overlays + $(document).on('keydown', function(e) { + if (e.key === 'Escape') { + // Close any open mobile nav + $('.hvac-mobile-nav.open').removeClass('open'); + $('.hvac-mobile-nav-toggle.active').removeClass('active').attr('aria-expanded', 'false'); + + // Hide loading overlay + HVACLoading.hideOverlay(); + } + }); + }, + + setupAriaLabels: function() { + // Add missing aria-labels to buttons without text + $('button:not([aria-label])').each(function() { + const $btn = $(this); + const text = $btn.text().trim(); + if (!text) { + const title = $btn.attr('title'); + if (title) { + $btn.attr('aria-label', title); + } + } + }); + } + }; + + /** + * Replace all alert() calls with toast notifications + */ + function replaceAlerts() { + // Override the global alert function + window.hvacOriginalAlert = window.alert; + window.alert = function(message) { + // Determine type based on message content + const lowerMessage = message.toLowerCase(); + if (lowerMessage.includes('success') || lowerMessage.includes('sent') || lowerMessage.includes('saved')) { + HVACToast.success(message); + } else if (lowerMessage.includes('error') || lowerMessage.includes('failed') || lowerMessage.includes('fail')) { + HVACToast.error(message); + } else { + HVACToast.info(message); + } + }; + } + + /** + * Initialize everything when document is ready + */ + $(document).ready(function() { + // Initialize all modules + HVACToast.init(); + HVACMobile.init(); + HVACAccessibility.init(); + + // Replace alert functions + replaceAlerts(); + + // Make modules globally available + window.HVACToast = HVACToast; + window.HVACLoading = HVACLoading; + window.HVACAjax = HVACAjax; + window.HVACValidation = HVACValidation; + + // Auto-enhance existing forms + $('form').each(function() { + const $form = $(this); + + // Add loading states to submit buttons + $form.on('submit', function() { + const $submitBtn = $form.find('input[type="submit"], button[type="submit"]').first(); + if ($submitBtn.length) { + HVACLoading.showButton($submitBtn); + } + }); + }); + + // Add responsive table wrapper + $('table').each(function() { + const $table = $(this); + if (!$table.parent().hasClass('hvac-table-responsive')) { + $table.wrap('
    '); + } + }); + + console.log('HVAC UX Enhancements initialized'); + }); + +})(jQuery); \ No newline at end of file diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-ajax-handler.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-ajax-handler.php index 7e43e424..c1ab62d1 100644 --- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-ajax-handler.php +++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-ajax-handler.php @@ -93,11 +93,27 @@ class HVAC_Certificate_AJAX_Handler { public function enqueue_scripts() { // Only load on certificate pages if (is_page('certificate-reports') || is_page('generate-certificates')) { + // Enqueue UX enhancements first + wp_enqueue_style( + 'hvac-ux-enhancements-css', + HVAC_CE_PLUGIN_URL . 'assets/css/hvac-ux-enhancements.css', + array(), + HVAC_CE_VERSION + ); + + wp_enqueue_script( + 'hvac-ux-enhancements-js', + HVAC_CE_PLUGIN_URL . 'assets/js/hvac-ux-enhancements.js', + array('jquery'), + HVAC_CE_VERSION, + true + ); + // Enqueue certificate actions JS wp_enqueue_script( 'hvac-certificate-actions-js', HVAC_CE_PLUGIN_URL . 'assets/js/hvac-certificate-actions.js', - array('jquery'), + array('jquery', 'hvac-ux-enhancements-js'), HVAC_CE_VERSION, true ); diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/class-hvac-dashboard.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/class-hvac-dashboard.php index 317e2409..fb04804d 100644 --- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/class-hvac-dashboard.php +++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/class-hvac-dashboard.php @@ -315,11 +315,27 @@ class HVAC_Dashboard { return; } + // Enqueue UX enhancements (CSS and JS) + wp_enqueue_style( + 'hvac-ux-enhancements-css', + HVAC_CE_PLUGIN_URL . 'assets/css/hvac-ux-enhancements.css', + array(), + HVAC_CE_VERSION + ); + + wp_enqueue_script( + 'hvac-ux-enhancements-js', + HVAC_CE_PLUGIN_URL . 'assets/js/hvac-ux-enhancements.js', + array('jquery'), + HVAC_CE_VERSION, + true + ); + // Enqueue dashboard JavaScript wp_enqueue_script( 'hvac-dashboard-js', HVAC_CE_PLUGIN_URL . 'assets/js/hvac-dashboard.js', - array('jquery'), + array('jquery', 'hvac-ux-enhancements-js'), HVAC_CE_VERSION, true );