diff --git a/wordpress-dev/tests/e2e/certificate-preview-simple.test.ts b/wordpress-dev/tests/e2e/certificate-preview-simple.test.ts new file mode 100644 index 00000000..81d83207 --- /dev/null +++ b/wordpress-dev/tests/e2e/certificate-preview-simple.test.ts @@ -0,0 +1,255 @@ +import { test, expect } from '@playwright/test'; +import { STAGING_URL, PATHS } from './config/staging-config'; + +/** + * Simple Certificate Preview Test + * + * Tests the certificate preview functionality by checking: + * - Generate Certificates page loads correctly with AJAX + * - Certificate Reports page shows existing certificates + * - Certificate security URL patterns are correct + */ +test.describe('Certificate Preview - Simple Tests', () => { + + test.beforeEach(async ({ page }) => { + // Login as test_trainer + await page.goto(PATHS.login); + await page.fill('#user_login', 'test_trainer'); + await page.fill('#user_pass', 'Test123!'); + await page.click('#wp-submit'); + + // Wait for dashboard redirect + await page.waitForURL('**/hvac-dashboard/**'); + await page.waitForLoadState('networkidle'); + }); + + test('should access Generate Certificates page and verify AJAX functionality', async ({ page }) => { + console.log('=== Testing Generate Certificates Page Access ==='); + + // Navigate to Generate Certificates + await page.goto(PATHS.generateCertificates); + await page.waitForLoadState('networkidle'); + + // Verify page loads + await expect(page.locator('h1')).toContainText('Generate Certificates'); + console.log('✅ Generate Certificates page loads correctly'); + + // Check if event selector exists (AJAX component) + const eventSelect = page.locator('#event_id'); + await expect(eventSelect).toBeVisible(); + console.log('✅ Event selector is visible'); + + // Check if AJAX container exists (should be hidden initially) + const attendeesContainer = page.locator('#step-select-attendees'); + await expect(attendeesContainer).toBeHidden(); + console.log('✅ AJAX attendees container exists and is hidden initially (correct behavior)'); + + // Verify AJAX JavaScript is loaded + const ajaxScript = await page.evaluate(() => { + return typeof window.hvacCertificateData !== 'undefined'; + }); + expect(ajaxScript).toBeTruthy(); + console.log('✅ Certificate AJAX JavaScript is loaded'); + + // Test event selection if events are available + const options = await eventSelect.locator('option:not([value=""])').count(); + console.log(`Found ${options} events with attendees`); + + if (options > 0) { + // Select first event to test AJAX loading + const firstOption = eventSelect.locator('option:not([value=""])').first(); + const eventValue = await firstOption.getAttribute('value'); + + console.log(`Testing AJAX with event ID: ${eventValue}`); + await eventSelect.selectOption(eventValue); + + // Wait a moment for AJAX to potentially load + await page.waitForTimeout(2000); + + // Check if attendees section becomes visible or gets content + const attendeesVisible = await attendeesContainer.isVisible(); + console.log(`Attendees section visible after selection: ${attendeesVisible}`); + } + }); + + test('should access Certificate Reports page and verify certificate security URLs', async ({ page }) => { + console.log('=== Testing Certificate Reports Page ==='); + + // Navigate to Certificate Reports + await page.goto(PATHS.certificatesReport); + await page.waitForLoadState('networkidle'); + + // Verify page loads + await expect(page.locator('h1')).toContainText('Certificate Reports'); + console.log('✅ Certificate Reports page loads correctly'); + + // Check for certificate table or stats + const certificateTable = page.locator('.hvac-certificate-table'); + const certificateStats = page.locator('.hvac-certificate-stats'); + + const hasTable = await certificateTable.isVisible(); + const hasStats = await certificateStats.isVisible(); + + console.log(`Certificate table visible: ${hasTable}`); + console.log(`Certificate stats visible: ${hasStats}`); + + if (hasTable) { + // Check for certificate download links with security pattern + const downloadLinks = page.locator('a[href*="hvac-certificate/"]'); + const linkCount = await downloadLinks.count(); + + console.log(`Found ${linkCount} certificate download links`); + + if (linkCount > 0) { + const firstLink = downloadLinks.first(); + const href = await firstLink.getAttribute('href'); + console.log(`Certificate download URL: ${href}`); + + // Verify URL format matches security pattern + expect(href).toMatch(/\/hvac-certificate\/[a-zA-Z0-9]{32}$/); + console.log('✅ Certificate security URL format is correct'); + } + } + + if (hasStats) { + // Check certificate statistics + const statCards = page.locator('.hvac-stat-card'); + const statCount = await statCards.count(); + console.log(`Found ${statCount} certificate statistics cards`); + + if (statCount > 0) { + for (let i = 0; i < statCount; i++) { + const card = statCards.nth(i); + const title = await card.locator('h3').textContent(); + const value = await card.locator('.hvac-stat-value').textContent(); + console.log(`Stat: ${title} = ${value}`); + } + } + } + }); + + test('should verify certificate security system is active', async ({ page }) => { + console.log('=== Testing Certificate Security System ==='); + + // Test direct access to certificate URL with invalid token + const invalidToken = 'a'.repeat(32); + const invalidUrl = `${STAGING_URL}/hvac-certificate/${invalidToken}`; + + console.log(`Testing invalid certificate URL: ${invalidUrl}`); + + const response = await page.goto(invalidUrl); + + if (response) { + const status = response.status(); + console.log(`Response status: ${status}`); + + // Should get an error response for invalid token + if (status === 200) { + // Check for error message in content + const content = await page.textContent('body'); + const hasError = /invalid|expired|not found/i.test(content); + expect(hasError).toBeTruthy(); + console.log('✅ Invalid certificate URL shows error message'); + } else { + // Non-200 status is also acceptable (403, 404, etc.) + expect([403, 404]).toContain(status); + console.log('✅ Invalid certificate URL returns error status'); + } + } + }); + + test('should verify AJAX endpoints are accessible', async ({ page }) => { + console.log('=== Testing Certificate AJAX Endpoints ==='); + + // Navigate to a page that loads certificate JavaScript + await page.goto(PATHS.generateCertificates); + await page.waitForLoadState('networkidle'); + + // Test if AJAX endpoints are properly configured + const ajaxData = await page.evaluate(() => { + if (typeof window.hvacCertificateData !== 'undefined') { + return { + ajaxUrl: window.hvacCertificateData.ajaxUrl, + hasGenerateNonce: !!window.hvacCertificateData.generateNonce, + hasEmailNonce: !!window.hvacCertificateData.emailNonce, + hasRevokeNonce: !!window.hvacCertificateData.revokeNonce + }; + } + return null; + }); + + expect(ajaxData).not.toBeNull(); + expect(ajaxData.ajaxUrl).toContain('admin-ajax.php'); + expect(ajaxData.hasGenerateNonce).toBeTruthy(); + + console.log('✅ AJAX configuration is properly loaded'); + console.log(`AJAX URL: ${ajaxData.ajaxUrl}`); + console.log(`Generate nonce present: ${ajaxData.hasGenerateNonce}`); + console.log(`Email nonce present: ${ajaxData.hasEmailNonce}`); + console.log(`Revoke nonce present: ${ajaxData.hasRevokeNonce}`); + }); + + test('should verify certificate preview modal HTML structure exists', async ({ page }) => { + console.log('=== Testing Certificate Preview Modal Structure ==='); + + // Navigate to Generate Certificates page + await page.goto(PATHS.generateCertificates); + await page.waitForLoadState('networkidle'); + + // Check if the preview JavaScript is loaded + const previewFunctionExists = await page.evaluate(() => { + return typeof window.GenerateCertificates !== 'undefined' && + typeof window.GenerateCertificates?.showCertificatePreview === 'function'; + }); + + if (previewFunctionExists) { + console.log('✅ Certificate preview JavaScript functions are loaded'); + } else { + console.log('ℹ️ Certificate preview functions not found in global scope'); + } + + // Test creating a preview modal programmatically + await page.evaluate(() => { + // Simulate the preview button functionality + if (typeof jQuery !== 'undefined') { + const $ = jQuery; + + // Create a test preview button + $('body').append(` + + `); + + // Trigger preview modal creation + $('.test-preview-btn').click(); + } + }); + + // Wait for modal to be created + await page.waitForTimeout(1000); + + // Check if modal was created + const modal = page.locator('#hvac-certificate-preview-modal'); + const modalExists = await modal.count() > 0; + + if (modalExists) { + console.log('✅ Certificate preview modal can be created'); + + // Verify modal structure + const modalHeader = page.locator('#hvac-certificate-preview-modal .hvac-modal-header'); + const modalBody = page.locator('#hvac-certificate-preview-modal .hvac-modal-body'); + const iframe = page.locator('#hvac-certificate-preview-iframe'); + + expect(await modalHeader.count()).toBeGreaterThan(0); + expect(await modalBody.count()).toBeGreaterThan(0); + expect(await iframe.count()).toBeGreaterThan(0); + + console.log('✅ Modal has correct structure with header, body, and iframe'); + } else { + console.log('ℹ️ Preview modal creation needs further investigation'); + } + }); +}); \ No newline at end of file diff --git a/wordpress-dev/tests/e2e/certificate-preview-test.test.ts b/wordpress-dev/tests/e2e/certificate-preview-test.test.ts new file mode 100644 index 00000000..94d34cb4 --- /dev/null +++ b/wordpress-dev/tests/e2e/certificate-preview-test.test.ts @@ -0,0 +1,222 @@ +import { test, expect } from '@playwright/test'; +import { STAGING_URL, PATHS } from './config/staging-config'; + +/** + * Certificate Preview Test + * + * Tests the new certificate preview functionality: + * - AJAX certificate generation + * - Preview URL generation with secure tokens + * - Modal display of certificate content + * - Real PDF preview (not blank iframe) + */ +test.describe('Certificate Preview Functionality', () => { + + test.beforeEach(async ({ page }) => { + // Login as test_trainer + await page.goto(PATHS.login); + await page.fill('#user_login', 'test_trainer'); + await page.fill('#user_pass', 'Test123!'); + await page.click('#wp-submit'); + + // Wait for dashboard redirect + await page.waitForURL('**/hvac-dashboard/**'); + await page.waitForLoadState('networkidle'); + }); + + test('should generate certificates with preview functionality', async ({ page }) => { + console.log('=== Testing Certificate Preview Functionality ==='); + + // Navigate to Generate Certificates + await page.goto(PATHS.generateCertificates); + await page.waitForLoadState('networkidle'); + + // Verify page loads + await expect(page.locator('h1')).toContainText('Generate Certificates'); + + // Select an event that has attendees + const eventSelect = page.locator('#event_id'); + await expect(eventSelect).toBeVisible(); + + // Get available events + const options = await eventSelect.locator('option:not([value=""])').all(); + console.log(`Found ${options.length} events with attendees`); + + if (options.length === 0) { + console.log('No events with attendees found - skipping test'); + return; + } + + // Select the first event + const firstOption = options[0]; + const eventValue = await firstOption.getAttribute('value'); + const eventText = await firstOption.textContent(); + console.log(`Selecting event: ${eventText} (ID: ${eventValue})`); + + await eventSelect.selectOption(eventValue); + + // Wait for attendees to load via AJAX + await page.waitForSelector('#step-select-attendees', { state: 'visible' }); + await page.waitForSelector('.hvac-attendees-table', { state: 'visible' }); + + // Verify attendees loaded + const attendeeRows = page.locator('.hvac-attendees-table tbody tr'); + const attendeeCount = await attendeeRows.count(); + console.log(`Found ${attendeeCount} attendees for this event`); + + expect(attendeeCount).toBeGreaterThan(0); + + // Select the first attendee without an existing certificate + const firstCheckbox = page.locator('.attendee-checkbox').first(); + await expect(firstCheckbox).toBeVisible(); + await firstCheckbox.check(); + + // Generate certificates + console.log('Generating certificates...'); + const generateButton = page.locator('#generate-certificates-form button[type="submit"]'); + await expect(generateButton).toBeVisible(); + await generateButton.click(); + + // Wait for AJAX response + await page.waitForSelector('.hvac-success-message, .hvac-errors', { timeout: 10000 }); + + // Check for success message + const successMessage = page.locator('.hvac-success-message'); + if (await successMessage.isVisible()) { + const messageText = await successMessage.textContent(); + console.log('Certificate generation success:', messageText); + + // Look for preview buttons + const previewButtons = page.locator('.hvac-preview-certificate'); + const previewCount = await previewButtons.count(); + console.log(`Found ${previewCount} preview buttons`); + + if (previewCount > 0) { + console.log('Testing certificate preview modal...'); + + // Click the first preview button + const firstPreviewButton = previewButtons.first(); + const attendeeName = await firstPreviewButton.getAttribute('data-attendee'); + const previewUrl = await firstPreviewButton.getAttribute('data-url'); + + console.log(`Preview button for: ${attendeeName}`); + console.log(`Preview URL: ${previewUrl}`); + + expect(previewUrl).toContain('hvac-certificate/'); + expect(previewUrl).toMatch(/hvac-certificate\/[a-zA-Z0-9]{32}$/); + + // Click preview button + await firstPreviewButton.click(); + + // Wait for modal to appear + await page.waitForSelector('#hvac-certificate-preview-modal', { state: 'visible' }); + + // Verify modal structure + const modal = page.locator('#hvac-certificate-preview-modal'); + await expect(modal).toBeVisible(); + + const modalTitle = page.locator('#hvac-certificate-preview-modal h3'); + await expect(modalTitle).toContainText(attendeeName); + + // Verify iframe is present and has the correct src + const iframe = page.locator('#hvac-certificate-preview-iframe'); + await expect(iframe).toBeVisible(); + + const iframeSrc = await iframe.getAttribute('src'); + console.log(`Iframe src: ${iframeSrc}`); + expect(iframeSrc).toBe(previewUrl); + + // Test iframe content loads (not blank) + await page.waitForTimeout(3000); // Give iframe time to load + + // Try to access iframe content to verify it's not blank + // Note: Cross-origin restrictions may prevent full content verification + try { + await iframe.waitForLoadState('networkidle', { timeout: 5000 }); + console.log('Certificate preview iframe loaded successfully'); + } catch (error) { + console.log('Iframe load check skipped due to cross-origin restrictions'); + } + + // Test modal close functionality + const closeButton = page.locator('.hvac-modal-close'); + await expect(closeButton).toBeVisible(); + await closeButton.click(); + + // Verify modal closes + await page.waitForSelector('#hvac-certificate-preview-modal', { state: 'hidden' }); + console.log('Modal closed successfully'); + + console.log('✅ Certificate preview functionality working correctly'); + } else { + console.log('⚠️ No preview buttons found - check if certificates were generated'); + } + } else { + const errorMessage = page.locator('.hvac-errors'); + if (await errorMessage.isVisible()) { + const errorText = await errorMessage.textContent(); + console.log('Certificate generation error:', errorText); + throw new Error(`Certificate generation failed: ${errorText}`); + } + } + }); + + test('should verify certificate security URL format', async ({ page }) => { + console.log('=== Testing Certificate Security URL Format ==='); + + // Navigate to Certificate Reports to check existing certificates + await page.goto(PATHS.certificatesReport); + await page.waitForLoadState('networkidle'); + + // Check if there are any certificates + const certificateTable = page.locator('.hvac-certificate-table'); + + if (await certificateTable.isVisible()) { + const downloadLinks = page.locator('a[href*="hvac-certificate/"]'); + const linkCount = await downloadLinks.count(); + + if (linkCount > 0) { + const firstLink = downloadLinks.first(); + const href = await firstLink.getAttribute('href'); + console.log(`Certificate download URL: ${href}`); + + // Verify URL format matches security pattern + expect(href).toMatch(/\/hvac-certificate\/[a-zA-Z0-9]{32}$/); + console.log('✅ Certificate security URL format is correct'); + } else { + console.log('No certificate download links found'); + } + } else { + console.log('No certificate table found - may need to generate certificates first'); + } + }); + + test('should test direct certificate URL access', async ({ page }) => { + console.log('=== Testing Direct Certificate URL Access ==='); + + // Test with an invalid token + const invalidToken = 'a'.repeat(32); + const invalidUrl = `${STAGING_URL}/hvac-certificate/${invalidToken}`; + + console.log(`Testing invalid certificate URL: ${invalidUrl}`); + + const response = await page.goto(invalidUrl); + + // Should get an error page for invalid token + if (response) { + const status = response.status(); + console.log(`Response status: ${status}`); + + // Could be 404, 403, or a custom error page + expect([200, 403, 404]).toContain(status); + + if (status === 200) { + // Check for error message in content + const content = await page.textContent('body'); + expect(content).toMatch(/invalid|expired|not found/i); + } + } + + console.log('✅ Invalid certificate URL properly handled'); + }); +}); \ No newline at end of file diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-certificates.css b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-certificates.css index 1c430df6..b450719a 100644 --- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-certificates.css +++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-certificates.css @@ -297,10 +297,91 @@ /* Search results indicator */ .hvac-search-results { background-color: #f0f7ff; - border-left: 4px solid #2271b1; - padding: 10px 15px; - margin-bottom: 20px; - border-radius: 0 4px 4px 0; +} + +/* Certificate Preview Modal */ +#hvac-certificate-preview-modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); + z-index: 10000; +} + +#hvac-certificate-preview-modal .hvac-modal-content { + position: relative; + background-color: #fff; + margin: 3% auto; + width: 90%; + max-width: 1000px; + height: 85%; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + overflow: hidden; + display: flex; + flex-direction: column; +} + +#hvac-certificate-preview-modal .hvac-modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px 20px; + background-color: #f8f9fa; + border-bottom: 1px solid #dee2e6; +} + +#hvac-certificate-preview-modal .hvac-modal-header h3 { + margin: 0; + font-size: 18px; + color: #333; +} + +#hvac-certificate-preview-modal .hvac-modal-close { + font-size: 24px; + font-weight: bold; + color: #999; + cursor: pointer; + line-height: 1; + padding: 5px; +} + +#hvac-certificate-preview-modal .hvac-modal-close:hover { + color: #333; +} + +#hvac-certificate-preview-modal .hvac-modal-body { + flex: 1; + padding: 0; + overflow: hidden; +} + +#hvac-certificate-preview-iframe { + width: 100%; + height: 100%; + border: none; +} + +/* Certificate Preview Buttons */ +.hvac-certificate-previews { + margin-top: 15px; + padding: 15px; + background-color: #f8f9fa; + border-radius: 5px; +} + +.hvac-certificate-previews h4 { + margin-top: 0; + margin-bottom: 10px; + color: #333; +} + +.hvac-preview-certificate { + margin-right: 10px; + margin-bottom: 5px; } .hvac-search-results p { diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-certificate-actions.js b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-certificate-actions.js index 222c0734..caef97ef 100644 --- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-certificate-actions.js +++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-certificate-actions.js @@ -199,9 +199,298 @@ } }; + // Generate Certificates AJAX functionality + const GenerateCertificates = { + /** + * Initialize generate certificates functionality + */ + init: function() { + // Event selection + $('#event_id').on('change', this.loadAttendees); + + // Certificate generation + $('#generate-certificates-form').on('submit', this.generateCertificates); + + // Helper buttons + $('#select-all-attendees').on('click', this.selectAllAttendees); + $('#select-checked-in').on('click', this.selectCheckedInAttendees); + $('#deselect-all-attendees').on('click', this.deselectAllAttendees); + + // Certificate preview (use event delegation for dynamic buttons) + $(document).on('click', '.hvac-preview-certificate', this.showCertificatePreview); + $(document).on('click', '.hvac-modal-close', this.closeCertificatePreview); + $(document).on('click', '#hvac-certificate-preview-modal', function(e) { + if (e.target === this) { + GenerateCertificates.closeCertificatePreview(e); + } + }); + }, + + /** + * Load attendees via AJAX when event is selected + */ + loadAttendees: function(e) { + const eventId = $(this).val(); + + if (!eventId) { + $('#step-select-attendees').hide(); + return; + } + + // Show loading + $('#attendees-loading').show(); + $('#attendees-content').hide(); + + $.ajax({ + url: hvacCertificateData.ajaxUrl, + type: 'POST', + data: { + action: 'hvac_get_event_attendees', + event_id: eventId, + nonce: hvacCertificateData.generateNonce + }, + success: function(response) { + if (response.success) { + GenerateCertificates.renderAttendees(response.data.attendees, response.data.event_title); + $('#step-select-attendees').show(); + } else { + alert('Error: ' + response.data.message); + } + }, + error: function() { + alert('Failed to load attendees. Please try again.'); + }, + complete: function() { + $('#attendees-loading').hide(); + $('#attendees-content').show(); + } + }); + }, + + /** + * Render attendees table + */ + renderAttendees: function(attendees, eventTitle) { + if (attendees.length === 0) { + $('#attendees-table-container').html('
This event has no attendees.
'); + return; + } + + let tableHtml = ` +| + + | +Attendee | +Status | +Certificate | +|
|---|---|---|---|---|
| + ${!attendee.has_certificate ? + `` : + '' + } + | +${attendee.holder_name} | +${attendee.holder_email} | +${statusText} | +${certificateStatus} | +
${response.data.message}
+Failed to generate certificates. Please try again.
+You don't have any events. Create an event first.
-