/** * Event Manager Consolidation Test Suite * * Tests the new unified HVAC_Event_Manager that replaces 8+ fragmented implementations: * - Event CRUD operations through single API * - Template routing and loading * - Form submission workflows * - TEC integration validation * - Security and role validation * - Memory-efficient data loading * * @package HVAC_Community_Events * @version 3.0.0 * @created 2025-08-20 */ const { test, expect, authHelpers, LoginPage } = require('../helpers/auth-fixtures'); const path = require('path'); // Test configuration const BASE_URL = process.env.UPSKILL_STAGING_URL || 'https://upskill-staging.measurequick.com'; const TEST_TIMEOUT = 90000; // Test event data const TEST_EVENT_DATA = { title: `Unified Event Manager Test ${Date.now()}`, description: 'Test event created through unified HVAC_Event_Manager system', startDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), endDate: new Date(Date.now() + 8 * 24 * 60 * 60 * 1000), venue: 'Test Training Facility', organizer: 'Test Training Org', category: 'HVAC Training', cost: '299.00' }; // Helper functions - using new authentication system async function loginAsUser(page, userType = 'trainer') { await authHelpers.loginAs(page, userType); } async function createTestEvent(page, eventData = TEST_EVENT_DATA) { await page.goto(`${BASE_URL}/trainer/events/create/`); // Wait for form to load (could be iframe or direct form) await page.waitForSelector('.hvac-event-form-wrapper, iframe#tec-create-frame, form', { timeout: 10000 }); // Determine form context (iframe vs direct) const iframe = await page.$('iframe#tec-create-frame'); const context = iframe ? await iframe.contentFrame() : page; if (!context) { throw new Error('Could not access form context'); } // Fill event details using unified form handling await context.fill('input[name="post_title"], #tribe-events-title', eventData.title); // Handle description field (TinyMCE or textarea) const descField = await context.$('#content, textarea[name="post_content"], .wp-editor-area'); if (descField) { await descField.fill(eventData.description); } // Set event dates const startDateField = await context.$('input[name="EventStartDate"], #EventStartDate, [data-field="start-date"]'); if (startDateField) { await startDateField.fill(eventData.startDate.toISOString().split('T')[0]); } const endDateField = await context.$('input[name="EventEndDate"], #EventEndDate, [data-field="end-date"]'); if (endDateField) { await endDateField.fill(eventData.endDate.toISOString().split('T')[0]); } // Add venue information const venueField = await context.$('input[name="venue[Venue]"], #venue-name, [data-field="venue"]'); if (venueField) { await venueField.fill(eventData.venue); } // Add organizer information const organizerField = await context.$('input[name="organizer[Organizer]"], #organizer-name, [data-field="organizer"]'); if (organizerField) { await organizerField.fill(eventData.organizer); } // Submit the form const submitButton = await context.$('button[type="submit"], input[type="submit"], .tribe-submit, [data-action="submit"]'); if (submitButton) { await submitButton.click(); } // Wait for submission processing await page.waitForTimeout(3000); return eventData; } async function getEventList(page) { await page.goto(`${BASE_URL}/trainer/events/`); await page.waitForSelector('.hvac-events-list, .tribe-events-list, table, .event-item', { timeout: 10000 }); const events = await page.evaluate(() => { const eventElements = document.querySelectorAll('.event-item, tr[data-event-id], .hvac-event-row'); return Array.from(eventElements).map(el => { return { title: el.querySelector('.event-title, .title, h3, h4')?.textContent?.trim(), id: el.getAttribute('data-event-id') || el.getAttribute('data-id'), editLink: el.querySelector('a[href*="edit"], .edit-link')?.href }; }); }); return events; } async function deleteTestEvent(page, eventTitle) { const events = await getEventList(page); const testEvent = events.find(e => e.title?.includes(eventTitle)); if (testEvent && testEvent.editLink) { await page.goto(testEvent.editLink); // Look for delete button in edit form const deleteButton = await page.$('button[data-action="delete"], .delete-event, a[href*="delete"]'); if (deleteButton) { await deleteButton.click(); await page.waitForTimeout(2000); } } } async function takeTestScreenshot(page, name) { const screenshotDir = path.join(__dirname, '../../screenshots/event-manager'); await require('fs').promises.mkdir(screenshotDir, { recursive: true }); await page.screenshot({ path: path.join(screenshotDir, `${name}-${Date.now()}.png`), fullPage: true }); } test.describe('Event Manager Consolidation Tests', () => { test.setTimeout(TEST_TIMEOUT); test.beforeEach(async ({ page }) => { await page.setViewportSize({ width: 1280, height: 720 }); // Login before each test to ensure authenticated access await authHelpers.loginAs(page, 'trainer'); }); test.describe('Unified Event Manager API Tests', () => { test('should handle event creation through single interface', async ({ page }) => { const eventData = await createTestEvent(page); // Verify event was created await page.waitForTimeout(5000); const currentUrl = page.url(); const isSuccess = currentUrl.includes('success') || currentUrl.includes('events') || currentUrl.includes('dashboard'); expect(isSuccess).toBeTruthy(); await takeTestScreenshot(page, 'event-created-unified'); // Cleanup await deleteTestEvent(page, eventData.title); }); test('should validate event manager singleton pattern', async ({ page }) => { await page.goto(`${BASE_URL}/trainer/events/create/`); // Check for duplicate initialization issues const duplicateErrors = await page.evaluate(() => { return window.console.logs?.filter(log => log.includes('duplicate') || log.includes('already initialized') ) || []; }); expect(duplicateErrors.length).toBe(0); }); test('should use memory-efficient data loading', async ({ page, browserName }) => { test.skip(browserName !== 'chromium', 'Memory testing requires Chromium'); const initialMemory = await page.evaluate(() => { return performance.memory ? performance.memory.usedJSHeapSize : 0; }); // Load event list (should use generator-based loading) await page.goto(`${BASE_URL}/trainer/events/`); await page.waitForLoadState('networkidle'); const afterListMemory = await page.evaluate(() => { return performance.memory ? performance.memory.usedJSHeapSize : 0; }); // Load event creation form await page.goto(`${BASE_URL}/trainer/events/create/`); await page.waitForLoadState('networkidle'); const afterFormMemory = await page.evaluate(() => { return performance.memory ? performance.memory.usedJSHeapSize : 0; }); const memoryIncrease = (afterFormMemory - initialMemory) / 1024 / 1024; // MB console.log(`Memory increase during event operations: ${memoryIncrease.toFixed(2)}MB`); // Should not exceed 50MB increase for event operations expect(memoryIncrease).toBeLessThan(50); }); test('should handle concurrent event operations', async ({ browser }) => { const contexts = await Promise.all([ browser.newContext(), browser.newContext() ]); const pages = await Promise.all(contexts.map(ctx => ctx.newPage())); // Simulate concurrent event creation - each needs its own login const eventPromises = pages.map(async (page, index) => { await authHelpers.loginAs(page, 'trainer'); const eventData = { ...TEST_EVENT_DATA, title: `Concurrent Event ${index} ${Date.now()}` }; return createTestEvent(page, eventData); }); const results = await Promise.all(eventPromises); // Both events should be created successfully expect(results.length).toBe(2); results.forEach(result => { expect(result.title).toBeTruthy(); }); // Cleanup await Promise.all(contexts.map(ctx => ctx.close())); }); }); test.describe('Template Routing and Loading Tests', () => { test('should route to correct templates based on URL', async ({ page }) => { const routes = [ { url: '/trainer/events/', template: 'events-list' }, { url: '/trainer/events/create/', template: 'event-create' }, { url: '/trainer/dashboard/', template: 'dashboard' } ]; for (const route of routes) { await page.goto(`${BASE_URL}${route.url}`); await page.waitForLoadState('domcontentloaded'); // Check for template-specific elements const hasTemplateElements = await page.evaluate(() => { return document.querySelector('.hvac-page-wrapper, .hvac-template, main') !== null; }); expect(hasTemplateElements).toBeTruthy(); console.log(`Template routing working for ${route.url}`); } }); test('should load templates with proper WordPress integration', async ({ page }) => { await page.goto(`${BASE_URL}/trainer/events/create/`); // Check for WordPress template elements const wordpressElements = await page.evaluate(() => { return { hasHeader: !!document.querySelector('header, .site-header'), hasFooter: !!document.querySelector('footer, .site-footer'), hasNavigation: !!document.querySelector('nav, .navigation, .hvac-nav'), hasWordPressBody: document.body.className.includes('wp-') }; }); expect(wordpressElements.hasHeader).toBeTruthy(); expect(wordpressElements.hasFooter).toBeTruthy(); expect(wordpressElements.hasNavigation).toBeTruthy(); await takeTestScreenshot(page, 'template-wordpress-integration'); }); test('should handle template fallbacks gracefully', async ({ page }) => { // Try to access a non-existent event edit page await page.goto(`${BASE_URL}/trainer/events/edit/99999/`); // Should either redirect or show error page, not crash await page.waitForTimeout(3000); const pageContent = await page.evaluate(() => { return { hasErrorMessage: !!document.querySelector('.error, .not-found, .hvac-error'), bodyText: document.body.textContent }; }); // Should handle gracefully without server error expect(pageContent.bodyText).not.toContain('Fatal error'); expect(pageContent.bodyText).not.toContain('500 Internal Server Error'); }); }); test.describe('Form Submission Workflow Tests', () => { test('should process form submissions with proper validation', async ({ page }) => { await page.goto(`${BASE_URL}/trainer/events/create/`); // Try to submit empty form (should trigger validation) const iframe = await page.$('iframe#tec-create-frame'); const context = iframe ? await iframe.contentFrame() : page; const submitButton = await context.$('button[type="submit"], input[type="submit"]'); if (submitButton) { await submitButton.click(); await page.waitForTimeout(2000); // Check for validation messages const hasValidation = await context.evaluate(() => { return !!document.querySelector('.error, .validation-error, .required-field-error'); }); // Should have validation for required fields console.log('Form validation triggered:', hasValidation); } }); test('should maintain form state during submission', async ({ page }) => { await loginAsUser(page, 'trainer'); await page.goto(`${BASE_URL}/trainer/events/create/`); const iframe = await page.$('iframe#tec-create-frame'); const context = iframe ? await iframe.contentFrame() : page; // Fill partial form data const testTitle = `State Test Event ${Date.now()}`; await context.fill('input[name="post_title"], #tribe-events-title', testTitle); // Trigger form interaction (but don't submit) const descField = await context.$('#content, textarea[name="post_content"]'); if (descField) { await descField.fill('Test description'); await descField.blur(); // Trigger save/state management } await page.waitForTimeout(1000); // Check if title is still there const titleValue = await context.inputValue('input[name="post_title"], #tribe-events-title'); expect(titleValue).toBe(testTitle); }); test('should handle submission errors gracefully', async ({ page }) => { await loginAsUser(page, 'trainer'); await page.goto(`${BASE_URL}/trainer/events/create/`); // Create event with invalid data to trigger error const iframe = await page.$('iframe#tec-create-frame'); const context = iframe ? await iframe.contentFrame() : page; // Fill with problematic data await context.fill('input[name="post_title"], #tribe-events-title', 'X'); // Too short const submitButton = await context.$('button[type="submit"], input[type="submit"]'); if (submitButton) { await submitButton.click(); await page.waitForTimeout(3000); // Should show error message, not crash const hasError = await page.evaluate(() => { return !document.body.textContent.includes('Fatal error') && !document.body.textContent.includes('500 Internal Server Error'); }); expect(hasError).toBeTruthy(); } }); }); test.describe('TEC Integration Validation Tests', () => { test('should integrate with The Events Calendar plugin', async ({ page }) => { await loginAsUser(page, 'trainer'); await page.goto(`${BASE_URL}/trainer/events/create/`); // Check for TEC-specific elements const tecIntegration = await page.evaluate(() => { return { hasFrame: !!document.querySelector('iframe#tec-create-frame'), hasTecClasses: document.body.className.includes('tribe') || !!document.querySelector('[class*="tribe"]'), hasTecScripts: !!document.querySelector('script[src*="tribe"]') }; }); // Should have some TEC integration const hasIntegration = tecIntegration.hasFrame || tecIntegration.hasTecClasses || tecIntegration.hasTecScripts; expect(hasIntegration).toBeTruthy(); console.log('TEC integration detected:', tecIntegration); }); test('should map form fields correctly for TEC', async ({ page }) => { await loginAsUser(page, 'trainer'); const eventData = await createTestEvent(page); // Verify event appears in events list const events = await getEventList(page); const createdEvent = events.find(e => e.title?.includes(eventData.title.split(' ')[0])); expect(createdEvent).toBeTruthy(); console.log('Event created and found in list:', createdEvent?.title); // Cleanup if (createdEvent) { await deleteTestEvent(page, eventData.title); } }); test('should handle TEC plugin dependencies', async ({ page }) => { await loginAsUser(page, 'trainer'); await page.goto(`${BASE_URL}/trainer/events/`); // Check if TEC is properly loaded const tecStatus = await page.evaluate(() => { return { hasTecGlobal: typeof window.tribe !== 'undefined', hasEventElements: !!document.querySelector('.tribe-events, .hvac-events, [data-event]'), hasNoTecErrors: !document.body.textContent.includes('TEC plugin not found') }; }); expect(tecStatus.hasNoTecErrors).toBeTruthy(); console.log('TEC dependency status:', tecStatus); }); }); test.describe('Role-Based Access Control Tests', () => { test('should enforce trainer role permissions', async ({ page }) => { await loginAsUser(page, 'trainer'); // Trainer should access their own events await page.goto(`${BASE_URL}/trainer/events/`); await expect(page.locator('.hvac-events-list, .tribe-events-list, table')).toBeVisible(); // Should not access master trainer pages await page.goto(`${BASE_URL}/master-trainer/master-dashboard/`); const hasAccess = await page.evaluate(() => { return !document.body.textContent.includes('Access denied') && !document.body.textContent.includes('Insufficient permissions'); }); // Regular trainer should not access master trainer pages // (This might redirect or show access denied) console.log('Trainer access to master dashboard:', hasAccess); }); test('should enforce master trainer permissions', async ({ page }) => { try { await loginAsUser(page, 'masterTrainer'); // Master trainer should access both trainer and master pages await page.goto(`${BASE_URL}/master-trainer/master-dashboard/`); await expect(page.locator('.hvac-master-dashboard, .hvac-dashboard')).toBeVisible(); await page.goto(`${BASE_URL}/trainer/events/`); await expect(page.locator('.hvac-events-list, .tribe-events-list, table')).toBeVisible(); } catch (error) { console.log('Master trainer test skipped - user may not exist'); } }); test('should validate nonce security for form submissions', async ({ page }) => { await loginAsUser(page, 'trainer'); await page.goto(`${BASE_URL}/trainer/events/create/`); // Check for nonce fields in forms const hasNonce = await page.evaluate(() => { const nonceFields = document.querySelectorAll('input[name*="nonce"], input[name*="_token"]'); return nonceFields.length > 0; }); expect(hasNonce).toBeTruthy(); console.log('Nonce security fields found'); }); }); test.describe('Event Lifecycle Management Tests', () => { test('should complete full event CRUD cycle', async ({ page }) => { await loginAsUser(page, 'trainer'); // CREATE const originalData = await createTestEvent(page); await takeTestScreenshot(page, 'event-created'); // READ const events = await getEventList(page); const createdEvent = events.find(e => e.title?.includes(originalData.title.split(' ')[0])); expect(createdEvent).toBeTruthy(); // UPDATE (if edit link available) if (createdEvent && createdEvent.editLink) { await page.goto(createdEvent.editLink); await page.waitForTimeout(3000); const iframe = await page.$('iframe'); const context = iframe ? await iframe.contentFrame() : page; const titleField = await context.$('input[name="post_title"], #tribe-events-title'); if (titleField) { const updatedTitle = `Updated ${originalData.title}`; await titleField.fill(updatedTitle); const submitButton = await context.$('button[type="submit"], input[type="submit"]'); if (submitButton) { await submitButton.click(); await page.waitForTimeout(3000); } } await takeTestScreenshot(page, 'event-updated'); } // DELETE (cleanup) await deleteTestEvent(page, originalData.title); }); test('should handle event list pagination and filtering', async ({ page }) => { await loginAsUser(page, 'trainer'); await page.goto(`${BASE_URL}/trainer/events/`); // Check for pagination controls const paginationExists = await page.evaluate(() => { return !!document.querySelector('.pagination, .page-numbers, .nav-links'); }); // Check for filter controls const filterExists = await page.evaluate(() => { return !!document.querySelector('.event-filter, select[name*="filter"], input[name*="search"]'); }); console.log('Event list features - Pagination:', paginationExists, 'Filters:', filterExists); }); }); }); // Export event manager test configuration module.exports = { testDir: __dirname, timeout: TEST_TIMEOUT, retries: 1, workers: 1, // Sequential for event operations use: { baseURL: BASE_URL, screenshot: 'only-on-failure', video: 'retain-on-failure', trace: 'on-first-retry' } };