/** * HVAC Bundled Assets Standalone Test Suite * * Tests for HVAC_Bundled_Assets class functionality without requiring live server * This version runs pure JavaScript testing and validation * * @package HVAC_Community_Events * @since 2.0.0 */ const { test, expect } = require('@playwright/test'); const fs = require('fs'); const path = require('path'); /** * Bundled Assets Standalone Test Framework */ class BundledAssetsStandaloneTestFramework { constructor() { this.projectRoot = path.resolve(__dirname, '..'); this.bundleDir = path.join(this.projectRoot, 'assets', 'js', 'dist'); this.testResults = { manifestTests: [], bundleValidationTests: [], securityTests: [], performanceTests: [], fallbackTests: [], integrationTests: [] }; } /** * Test Bundle File Existence and Validation */ async testBundleFileValidation() { console.log('šŸ“ Testing Bundle File Validation...'); await test.step('Bundle files exist and have valid structure', async () => { const expectedBundles = [ 'hvac-core.bundle.js', 'hvac-dashboard.bundle.js', 'hvac-certificates.bundle.js', 'hvac-master.bundle.js', 'hvac-trainer.bundle.js', 'hvac-events.bundle.js', 'hvac-safari-compat.bundle.js', 'hvac-admin.bundle.js' ]; const expectedChunks = [ 'trainer-profile.chunk.js', 'event-editing.chunk.js', 'organizers-venues.chunk.js', 'trainer-communication.chunk.js', 'trainer-registration.chunk.js' ]; const bundleValidation = { bundles: {}, chunks: {}, totalSize: 0, validFiles: 0, invalidFiles: 0 }; // Check bundle files for (const bundleFile of expectedBundles) { const bundlePath = path.join(this.bundleDir, bundleFile); const exists = fs.existsSync(bundlePath); if (exists) { const stats = fs.statSync(bundlePath); const sizeKB = Math.round(stats.size / 1024); const sizeMB = (stats.size / (1024 * 1024)).toFixed(2); bundleValidation.bundles[bundleFile] = { exists: true, size: stats.size, sizeKB, sizeMB: parseFloat(sizeMB), validSize: stats.size > 0 && stats.size <= (1024 * 1024), // Under 1MB limit lastModified: stats.mtime.toISOString() }; bundleValidation.totalSize += stats.size; bundleValidation.validFiles++; } else { bundleValidation.bundles[bundleFile] = { exists: false, error: 'File not found' }; bundleValidation.invalidFiles++; } } // Check chunk files for (const chunkFile of expectedChunks) { const chunkPath = path.join(this.bundleDir, chunkFile); const exists = fs.existsSync(chunkPath); if (exists) { const stats = fs.statSync(chunkPath); bundleValidation.chunks[chunkFile] = { exists: true, size: stats.size, sizeKB: Math.round(stats.size / 1024) }; bundleValidation.totalSize += stats.size; bundleValidation.validFiles++; } else { bundleValidation.chunks[chunkFile] = { exists: false, error: 'File not found' }; bundleValidation.invalidFiles++; } } // Validate results expect(bundleValidation.validFiles).toBeGreaterThan(0); expect(bundleValidation.totalSize).toBeGreaterThan(0); // Check core bundle exists and is reasonable size expect(bundleValidation.bundles['hvac-core.bundle.js'].exists).toBe(true); expect(bundleValidation.bundles['hvac-core.bundle.js'].size).toBeGreaterThan(50000); // At least 50KB this.testResults.bundleValidationTests.push({ test: 'Bundle file validation', status: 'passed', details: bundleValidation }); console.log(`āœ… Found ${bundleValidation.validFiles} valid bundle files`); console.log(`šŸ“Š Total bundle size: ${(bundleValidation.totalSize / (1024 * 1024)).toFixed(2)}MB`); }); } /** * Test Security Validation Patterns */ async testSecurityValidation() { console.log('šŸ”’ Testing Security Validation...'); await test.step('Filename security validation', async () => { const filenameTests = { validFilenames: [ 'hvac-core.bundle.js', 'hvac-trainer.bundle.js', 'trainer-profile.chunk.js', 'event-editing.chunk.js' ], invalidFilenames: [ 'hvac-.js', '../../../etc/passwd.js', 'hvac-bundle.js; echo "hacked" > /tmp/hack' ], validationRegex: /^[a-zA-Z0-9._-]+$/ }; const securityValidation = { validFiles: [], invalidFiles: [], validationPassed: 0, validationFailed: 0 }; // Test valid filenames filenameTests.validFilenames.forEach(filename => { const isValid = filenameTests.validationRegex.test(filename); if (isValid) { securityValidation.validFiles.push(filename); securityValidation.validationPassed++; } else { securityValidation.invalidFiles.push({ filename, reason: 'Failed regex validation' }); securityValidation.validationFailed++; } }); // Test invalid filenames filenameTests.invalidFilenames.forEach(filename => { const isValid = filenameTests.validationRegex.test(filename); if (!isValid) { securityValidation.invalidFiles.push({ filename, reason: 'Correctly rejected malicious filename', status: 'properly_blocked' }); securityValidation.validationPassed++; // This is expected behavior } else { securityValidation.validationFailed++; } }); // All valid filenames should pass expect(securityValidation.validFiles.length).toBe(filenameTests.validFilenames.length); // All invalid filenames should be rejected const properlyBlocked = securityValidation.invalidFiles.filter( item => item.status === 'properly_blocked' ).length; expect(properlyBlocked).toBe(filenameTests.invalidFilenames.length); this.testResults.securityTests.push({ test: 'Filename security validation', status: 'passed', details: securityValidation }); console.log(`āœ… Security validation: ${securityValidation.validationPassed} passed, ${securityValidation.validationFailed} failed`); }); await test.step('File size limit validation', async () => { const sizeLimitTest = { maxSize: 1024 * 1024, // 1MB testFiles: [], oversizedFiles: [], validSizedFiles: [] }; // Check actual bundle files against size limits const bundleFiles = fs.readdirSync(this.bundleDir).filter(file => file.endsWith('.js')); bundleFiles.forEach(filename => { const filePath = path.join(this.bundleDir, filename); const stats = fs.statSync(filePath); const fileInfo = { filename, size: stats.size, sizeMB: (stats.size / (1024 * 1024)).toFixed(2), exceedsLimit: stats.size > sizeLimitTest.maxSize }; sizeLimitTest.testFiles.push(fileInfo); if (fileInfo.exceedsLimit) { sizeLimitTest.oversizedFiles.push(fileInfo); } else { sizeLimitTest.validSizedFiles.push(fileInfo); } }); // Most files should be under the size limit expect(sizeLimitTest.validSizedFiles.length).toBeGreaterThan(0); // Log any oversized files for awareness if (sizeLimitTest.oversizedFiles.length > 0) { console.log(`āš ļø Found ${sizeLimitTest.oversizedFiles.length} oversized files that would trigger fallback:`); sizeLimitTest.oversizedFiles.forEach(file => { console.log(` ${file.filename}: ${file.sizeMB}MB`); }); } this.testResults.securityTests.push({ test: 'File size limit validation', status: 'passed', details: sizeLimitTest }); }); } /** * Test Manifest Structure Validation */ async testManifestStructure() { console.log('šŸ“‹ Testing Manifest Structure...'); await test.step('Manifest structure validation', async () => { // Create a sample manifest based on actual files const actualFiles = fs.readdirSync(this.bundleDir).filter(file => file.endsWith('.js') || file.endsWith('.css')); const manifestTest = { sampleManifest: {}, structure: { totalEntries: 0, jsFiles: 0, cssFiles: 0, chunkFiles: 0, bundleFiles: 0 }, validation: { isValidJSON: true, isValidArray: false, // Manifest should be object, not array hasRequiredEntries: false } }; // Build sample manifest from actual files actualFiles.forEach(filename => { const keyName = filename.replace('.bundle', '').replace('.chunk', ''); manifestTest.sampleManifest[keyName] = filename; manifestTest.structure.totalEntries++; if (filename.endsWith('.js')) { manifestTest.structure.jsFiles++; } if (filename.endsWith('.css')) { manifestTest.structure.cssFiles++; } if (filename.includes('.chunk.')) { manifestTest.structure.chunkFiles++; } else if (filename.includes('.bundle.')) { manifestTest.structure.bundleFiles++; } }); // Validate manifest structure manifestTest.validation.isValidArray = Array.isArray(manifestTest.sampleManifest); manifestTest.validation.hasRequiredEntries = manifestTest.structure.totalEntries > 0; // Test JSON serialization try { const jsonString = JSON.stringify(manifestTest.sampleManifest); const parsed = JSON.parse(jsonString); manifestTest.validation.isValidJSON = typeof parsed === 'object' && parsed !== null; } catch (error) { manifestTest.validation.isValidJSON = false; } // Validate results expect(manifestTest.validation.isValidJSON).toBe(true); expect(manifestTest.validation.isValidArray).toBe(false); // Should be object expect(manifestTest.validation.hasRequiredEntries).toBe(true); expect(manifestTest.structure.bundleFiles).toBeGreaterThan(0); this.testResults.manifestTests.push({ test: 'Manifest structure validation', status: 'passed', details: manifestTest }); console.log(`āœ… Manifest validation: ${manifestTest.structure.totalEntries} entries, ${manifestTest.structure.bundleFiles} bundles, ${manifestTest.structure.chunkFiles} chunks`); }); } /** * Test Performance Characteristics */ async testPerformanceCharacteristics() { console.log('ā±ļø Testing Performance Characteristics...'); await test.step('Bundle loading performance simulation', async () => { const performanceTest = { bundles: {}, loadTimeSimulation: {}, performanceThresholds: { maxLoadTime: 5000, // 5 seconds optimalLoadTime: 2000, // 2 seconds criticalSize: 500000 // 500KB } }; const bundleFiles = fs.readdirSync(this.bundleDir).filter(file => file.endsWith('.bundle.js')); bundleFiles.forEach(filename => { const filePath = path.join(this.bundleDir, filename); const stats = fs.statSync(filePath); // Simulate load time based on file size (rough approximation) const simulatedLoadTime = Math.max(200, (stats.size / 1024) * 5); // ~5ms per KB const bundlePerf = { filename, size: stats.size, sizeKB: Math.round(stats.size / 1024), simulatedLoadTime: Math.round(simulatedLoadTime), exceedsOptimalTime: simulatedLoadTime > performanceTest.performanceThresholds.optimalLoadTime, exceedsMaxTime: simulatedLoadTime > performanceTest.performanceThresholds.maxLoadTime, isCriticalSize: stats.size > performanceTest.performanceThresholds.criticalSize }; performanceTest.bundles[filename] = bundlePerf; // Categorize into performance groups if (bundlePerf.exceedsMaxTime) { performanceTest.loadTimeSimulation.slow = performanceTest.loadTimeSimulation.slow || []; performanceTest.loadTimeSimulation.slow.push(bundlePerf); } else if (bundlePerf.exceedsOptimalTime) { performanceTest.loadTimeSimulation.moderate = performanceTest.loadTimeSimulation.moderate || []; performanceTest.loadTimeSimulation.moderate.push(bundlePerf); } else { performanceTest.loadTimeSimulation.fast = performanceTest.loadTimeSimulation.fast || []; performanceTest.loadTimeSimulation.fast.push(bundlePerf); } }); // Performance assertions const totalBundles = Object.keys(performanceTest.bundles).length; const slowBundles = performanceTest.loadTimeSimulation.slow ? performanceTest.loadTimeSimulation.slow.length : 0; expect(totalBundles).toBeGreaterThan(0); expect(slowBundles).toBeLessThan(totalBundles); // Most bundles should not be slow this.testResults.performanceTests.push({ test: 'Bundle loading performance', status: 'passed', details: performanceTest }); console.log(`āœ… Performance test: ${totalBundles} bundles analyzed, ${slowBundles} potentially slow`); }); } /** * Test Integration Patterns */ async testIntegrationPatterns() { console.log('šŸ”— Testing Integration Patterns...'); await test.step('WordPress integration pattern validation', async () => { const integrationTest = { hooks: [ { name: 'wp_enqueue_scripts', callback: 'enqueue_bundled_assets', context: 'frontend' }, { name: 'admin_enqueue_scripts', callback: 'enqueue_admin_bundled_assets', context: 'admin' }, { name: 'wp_head', callback: 'add_bundle_preload_hints', context: 'frontend' } ], bundleMapping: { 'hvac-core': ['trainer-dashboard', 'certificate-page', 'master-trainer-page'], 'hvac-dashboard': ['trainer-dashboard'], 'hvac-certificates': ['certificate-page'], 'hvac-master': ['master-trainer-page'], 'hvac-trainer': ['trainer-page'], 'hvac-events': ['event-page'], 'hvac-admin': ['wp-admin'] }, localizationData: { objectName: 'hvacBundleData', securityConfig: { report_errors: true, error_endpoint: '/wp-json/hvac/v1/bundle-errors', performance_monitoring: true, max_load_time: 5000, retry_attempts: 2 } } }; // Validate hook structure expect(integrationTest.hooks).toHaveLength(3); expect(integrationTest.hooks.find(h => h.name === 'wp_enqueue_scripts')).toBeDefined(); // Validate bundle mapping const coreContexts = integrationTest.bundleMapping['hvac-core']; expect(coreContexts).toContain('trainer-dashboard'); expect(coreContexts).toContain('certificate-page'); // Validate security configuration expect(integrationTest.localizationData.securityConfig.report_errors).toBe(true); expect(integrationTest.localizationData.securityConfig.max_load_time).toBe(5000); this.testResults.integrationTests.push({ test: 'WordPress integration patterns', status: 'passed', details: integrationTest }); console.log('āœ… Integration patterns validated'); }); } /** * Run all standalone tests */ async runAllTests() { console.log('šŸš€ Starting Bundled Assets Standalone Test Suite...'); await this.testBundleFileValidation(); await this.testSecurityValidation(); await this.testManifestStructure(); await this.testPerformanceCharacteristics(); await this.testIntegrationPatterns(); return this.generateTestReport(); } /** * Generate comprehensive test report */ generateTestReport() { const totalTests = Object.values(this.testResults).flat().length; const passedTests = Object.values(this.testResults).flat().filter(test => test.status === 'passed').length; const report = { summary: { totalTests, passedTests, failedTests: totalTests - passedTests, passRate: totalTests > 0 ? ((passedTests / totalTests) * 100).toFixed(1) + '%' : '0%' }, categories: { manifestTests: this.testResults.manifestTests.length, bundleValidationTests: this.testResults.bundleValidationTests.length, securityTests: this.testResults.securityTests.length, performanceTests: this.testResults.performanceTests.length, fallbackTests: this.testResults.fallbackTests.length, integrationTests: this.testResults.integrationTests.length }, details: this.testResults, timestamp: new Date().toISOString(), projectRoot: this.projectRoot, bundleDirectory: this.bundleDir }; console.log('\nšŸ“Š BUNDLED ASSETS STANDALONE TEST REPORT'); console.log('=========================================='); console.log(`Total Tests: ${report.summary.totalTests}`); console.log(`Passed: ${report.summary.passedTests}`); console.log(`Failed: ${report.summary.failedTests}`); console.log(`Pass Rate: ${report.summary.passRate}`); console.log('\nTest Categories:'); Object.entries(report.categories).forEach(([category, count]) => { console.log(` ${category}: ${count} tests`); }); console.log(`\nBundle Directory: ${this.bundleDir}`); return report; } } // Test Suite Execution test.describe('HVAC Bundled Assets Standalone Tests', () => { test('Execute all bundled assets standalone functionality tests', async () => { const testFramework = new BundledAssetsStandaloneTestFramework(); // Run comprehensive test suite const report = await testFramework.runAllTests(); // Assert overall test success expect(report.summary.passedTests).toBeGreaterThan(0); expect(report.summary.failedTests).toBe(0); expect(parseFloat(report.summary.passRate)).toBe(100.0); console.log('āœ… All Bundled Assets Standalone Tests Completed Successfully'); }); });