const { test, expect } = require('@playwright/test'); const { HVACTestBase } = require('./page-objects/HVACTestBase'); /** * Modal Form Management Test Suite * * Tests the modal system for creating new entities including: * - Organizer creation modal * - Category creation modal * - Venue creation modal * - Form validation and error handling * - AJAX submission and response handling * - Modal lifecycle (open, close, reset) * - Backdrop interaction and keyboard controls * - Data persistence and form integration */ test.describe('Modal Form Management System', () => { let hvacTest; test.beforeEach(async ({ page }) => { hvacTest = new HVACTestBase(page); await hvacTest.loginAsTrainer(); await hvacTest.navigateToCreateEvent(); // Wait for selectors and modals to be ready await expect(page.locator('.organizer-selector')).toBeVisible(); await expect(page.locator('.category-selector')).toBeVisible(); await expect(page.locator('.venue-selector')).toBeVisible(); }); test.describe('Organizer Modal', () => { test('should open organizer creation modal correctly', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); // Modal should be visible await expect(page.locator('#organizer-modal')).toBeVisible(); await expect(page.locator('#organizer-modal .modal-backdrop')).toBeVisible(); // Check modal structure await expect(page.locator('#organizer-modal .modal-title')).toContainText('Create New Organizer'); await expect(page.locator('#organizer-modal .modal-close')).toBeVisible(); // Form fields should be visible and empty await expect(page.locator('#new-organizer-name')).toBeVisible(); await expect(page.locator('#new-organizer-email')).toBeVisible(); await expect(page.locator('#new-organizer-phone')).toBeVisible(); await expect(page.locator('#new-organizer-organization')).toBeVisible(); // Verify initial state const nameValue = await page.locator('#new-organizer-name').inputValue(); expect(nameValue).toBe(''); }); test('should close modal with close button', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); await expect(page.locator('#organizer-modal')).toBeVisible(); await page.click('#organizer-modal .modal-close'); await expect(page.locator('#organizer-modal')).not.toBeVisible(); }); test('should close modal with backdrop click', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); await expect(page.locator('#organizer-modal')).toBeVisible(); // Click on backdrop (outside modal content) await page.click('#organizer-modal .modal-backdrop', { position: { x: 10, y: 10 } }); await expect(page.locator('#organizer-modal')).not.toBeVisible(); }); test('should close modal with Escape key', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); await expect(page.locator('#organizer-modal')).toBeVisible(); await page.keyboard.press('Escape'); await expect(page.locator('#organizer-modal')).not.toBeVisible(); }); test('should validate required fields', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); // Try to submit without required fields await page.click('#save-organizer'); // Should show validation errors await expect(page.locator('#new-organizer-name-error')).toBeVisible(); await expect(page.locator('#new-organizer-email-error')).toBeVisible(); // Modal should remain open await expect(page.locator('#organizer-modal')).toBeVisible(); }); test('should validate email format', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); // Fill with invalid email await page.fill('#new-organizer-name', 'John Doe'); await page.fill('#new-organizer-email', 'invalid-email'); await page.click('#save-organizer'); await expect(page.locator('#new-organizer-email-error')).toContainText('Invalid email format'); }); test('should create organizer successfully', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); // Fill valid form data await page.fill('#new-organizer-name', 'John Doe'); await page.fill('#new-organizer-email', 'john@example.com'); await page.fill('#new-organizer-phone', '555-1234'); await page.fill('#new-organizer-organization', 'HVAC Corp'); // Mock successful AJAX response await page.route('**/wp-admin/admin-ajax.php', async route => { if (route.request().postData()?.includes('create_organizer')) { await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ success: true, data: { id: 'temp_123', name: 'John Doe', email: 'john@example.com' } }) }); } else { await route.continue(); } }); await page.click('#save-organizer'); // Modal should close await expect(page.locator('#organizer-modal')).not.toBeVisible(); // Organizer should be selected in the selector await expect(page.locator('.organizer-selector .selected-item')).toContainText('John Doe'); }); test('should handle AJAX errors gracefully', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); await page.fill('#new-organizer-name', 'John Doe'); await page.fill('#new-organizer-email', 'john@example.com'); // Mock AJAX failure await page.route('**/wp-admin/admin-ajax.php', route => route.abort()); await page.click('#save-organizer'); // Should show error message await expect(page.locator('.modal-error')).toContainText('Failed to create organizer'); // Modal should remain open for retry await expect(page.locator('#organizer-modal')).toBeVisible(); }); test('should reset form when closed and reopened', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); // Fill some data await page.fill('#new-organizer-name', 'Test Name'); await page.fill('#new-organizer-email', 'test@example.com'); // Close modal await page.click('#organizer-modal .modal-close'); // Reopen modal await page.click('.organizer-selector .create-new-btn'); // Form should be reset const nameValue = await page.locator('#new-organizer-name').inputValue(); const emailValue = await page.locator('#new-organizer-email').inputValue(); expect(nameValue).toBe(''); expect(emailValue).toBe(''); }); }); test.describe('Category Modal', () => { test('should open category creation modal correctly', async ({ page }) => { await page.click('.category-selector .create-new-btn'); await expect(page.locator('#category-modal')).toBeVisible(); await expect(page.locator('#category-modal .modal-title')).toContainText('Create New Category'); // Category-specific fields await expect(page.locator('#new-category-name')).toBeVisible(); await expect(page.locator('#new-category-description')).toBeVisible(); await expect(page.locator('#new-category-parent')).toBeVisible(); }); test('should validate category name requirement', async ({ page }) => { await page.click('.category-selector .create-new-btn'); await page.click('#save-category'); await expect(page.locator('#new-category-name-error')).toContainText('Category name is required'); }); test('should create category successfully', async ({ page }) => { await page.click('.category-selector .create-new-btn'); await page.fill('#new-category-name', 'Advanced Training'); await page.fill('#new-category-description', 'Advanced HVAC training courses'); // Mock successful response await page.route('**/wp-admin/admin-ajax.php', async route => { if (route.request().postData()?.includes('create_category')) { await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ success: true, data: { id: 'temp_456', name: 'Advanced Training' } }) }); } else { await route.continue(); } }); await page.click('#save-category'); await expect(page.locator('#category-modal')).not.toBeVisible(); await expect(page.locator('.category-selector .selected-item')).toContainText('Advanced Training'); }); test('should handle parent category selection', async ({ page }) => { await page.click('.category-selector .create-new-btn'); // Parent dropdown should show available categories await page.click('#new-category-parent'); await expect(page.locator('#new-category-parent option')).toHaveCount({ min: 1 }); // Select a parent category await page.selectOption('#new-category-parent', { index: 1 }); const selectedParent = await page.locator('#new-category-parent').inputValue(); expect(selectedParent).not.toBe(''); }); }); test.describe('Venue Modal', () => { test('should open venue creation modal correctly', async ({ page }) => { await page.click('.venue-selector .create-new-btn'); await expect(page.locator('#venue-modal')).toBeVisible(); await expect(page.locator('#venue-modal .modal-title')).toContainText('Create New Venue'); // Venue-specific fields await expect(page.locator('#new-venue-name')).toBeVisible(); await expect(page.locator('#new-venue-address')).toBeVisible(); await expect(page.locator('#new-venue-city')).toBeVisible(); await expect(page.locator('#new-venue-state')).toBeVisible(); await expect(page.locator('#new-venue-zip')).toBeVisible(); await expect(page.locator('#new-venue-capacity')).toBeVisible(); await expect(page.locator('#new-venue-facilities')).toBeVisible(); }); test('should validate venue required fields', async ({ page }) => { await page.click('.venue-selector .create-new-btn'); await page.click('#save-venue'); // Check for multiple required field errors await expect(page.locator('#new-venue-name-error')).toBeVisible(); await expect(page.locator('#new-venue-address-error')).toBeVisible(); await expect(page.locator('#new-venue-city-error')).toBeVisible(); }); test('should validate capacity as number', async ({ page }) => { await page.click('.venue-selector .create-new-btn'); await page.fill('#new-venue-name', 'Test Venue'); await page.fill('#new-venue-address', '123 Main St'); await page.fill('#new-venue-city', 'Test City'); await page.fill('#new-venue-capacity', 'not-a-number'); await page.click('#save-venue'); await expect(page.locator('#new-venue-capacity-error')).toContainText('Capacity must be a number'); }); test('should create venue successfully', async ({ page }) => { await page.click('.venue-selector .create-new-btn'); // Fill comprehensive venue data await page.fill('#new-venue-name', 'Conference Center'); await page.fill('#new-venue-address', '456 Business Ave'); await page.fill('#new-venue-city', 'Business City'); await page.fill('#new-venue-state', 'CA'); await page.fill('#new-venue-zip', '90210'); await page.fill('#new-venue-capacity', '200'); await page.fill('#new-venue-facilities', 'Projector, WiFi, Parking'); // Mock successful response await page.route('**/wp-admin/admin-ajax.php', async route => { if (route.request().postData()?.includes('create_venue')) { await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ success: true, data: { id: 'temp_789', name: 'Conference Center', address: '456 Business Ave, Business City, CA 90210' } }) }); } else { await route.continue(); } }); await page.click('#save-venue'); await expect(page.locator('#venue-modal')).not.toBeVisible(); await expect(page.locator('.venue-selector .selected-venue')).toContainText('Conference Center'); }); }); test.describe('Modal System Behavior', () => { test('should handle multiple modal opens correctly', async ({ page }) => { // Open organizer modal await page.click('.organizer-selector .create-new-btn'); await expect(page.locator('#organizer-modal')).toBeVisible(); // Close and open category modal await page.keyboard.press('Escape'); await page.click('.category-selector .create-new-btn'); await expect(page.locator('#category-modal')).toBeVisible(); await expect(page.locator('#organizer-modal')).not.toBeVisible(); }); test('should prevent body scroll when modal is open', async ({ page }) => { const initialOverflow = await page.evaluate(() => document.body.style.overflow); await page.click('.organizer-selector .create-new-btn'); const modalOverflow = await page.evaluate(() => document.body.style.overflow); expect(modalOverflow).toBe('hidden'); await page.keyboard.press('Escape'); const finalOverflow = await page.evaluate(() => document.body.style.overflow); expect(finalOverflow).toBe(initialOverflow); }); test('should focus management properly', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); // Focus should be on first form field const focusedElement = await page.evaluate(() => document.activeElement.id); expect(focusedElement).toBe('new-organizer-name'); // Tab should cycle through modal fields await page.keyboard.press('Tab'); const nextFocused = await page.evaluate(() => document.activeElement.id); expect(nextFocused).toBe('new-organizer-email'); }); test('should trap focus within modal', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); // Get all focusable elements in modal const modalElements = await page.locator('#organizer-modal [tabindex]:not([tabindex="-1"]), #organizer-modal input, #organizer-modal button, #organizer-modal select, #organizer-modal textarea').count(); // Tab through all elements and ensure focus stays in modal for (let i = 0; i < modalElements + 2; i++) { await page.keyboard.press('Tab'); const activeElement = await page.evaluate(() => { const active = document.activeElement; return active.closest('#organizer-modal') !== null; }); expect(activeElement).toBe(true); } }); test('should handle rapid modal interactions', async ({ page }) => { // Rapidly open and close modals for (let i = 0; i < 5; i++) { await page.click('.organizer-selector .create-new-btn'); await expect(page.locator('#organizer-modal')).toBeVisible(); await page.keyboard.press('Escape'); await expect(page.locator('#organizer-modal')).not.toBeVisible(); } // Should still work normally await page.click('.organizer-selector .create-new-btn'); await expect(page.locator('#organizer-modal')).toBeVisible(); }); }); test.describe('Form Validation', () => { test('should show real-time validation feedback', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); // Start typing invalid email await page.fill('#new-organizer-email', 'invalid'); // Blur to trigger validation await page.click('#new-organizer-name'); await expect(page.locator('#new-organizer-email-error')).toContainText('Invalid email format'); // Fix email await page.fill('#new-organizer-email', 'valid@example.com'); await page.click('#new-organizer-name'); await expect(page.locator('#new-organizer-email-error')).not.toBeVisible(); }); test('should prevent submission with invalid data', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); // Fill partially invalid data await page.fill('#new-organizer-name', 'Jo'); // Too short await page.fill('#new-organizer-email', 'invalid-email'); const submitButton = page.locator('#save-organizer'); await submitButton.click(); // Should not submit await expect(page.locator('#organizer-modal')).toBeVisible(); // Button should show loading state briefly then return to normal await expect(submitButton).not.toHaveClass(/loading/); }); test('should sanitize input data', async ({ page }) => { await page.click('.organizer-selector .create-new-btn'); // Try to inject HTML/JS await page.fill('#new-organizer-name', 'John Doe'); await page.fill('#new-organizer-organization', ''); const nameValue = await page.locator('#new-organizer-name').inputValue(); const orgValue = await page.locator('#new-organizer-organization').inputValue(); // Should contain cleaned values expect(nameValue).not.toContain('