1f6f8e417e
MIGRATION SUMMARY: - Moved skeleton loader logic from hyperscript to JavaScript (main.js) - Changed from htmx:oobAfterSwap to htmx:afterSettle event - Changed OOB swap from innerHTML to outerHTML for proper element replacement - Added languageSwitching flag for state tracking - Added 100ms delay after afterSettle for final render completion DOCUMENTATION UPDATES: - 2-MODERN-WEB-TECHNIQUES.md: Updated skeleton loader section with
248 lines
11 KiB
JavaScript
Executable File
248 lines
11 KiB
JavaScript
Executable File
#!/usr/bin/env bun
|
|
/**
|
|
* SKELETON LOADERS TEST
|
|
* ======================
|
|
* Tests skeleton loader animations during language transitions
|
|
* - Verifies skeleton loaders appear during language switching
|
|
* - Checks component-wrapper structure exists
|
|
* - Validates .loading class is added/removed correctly
|
|
* - Tests skeleton appears on multiple consecutive switches
|
|
* - Ensures no visual glitches or stuck loading states
|
|
*/
|
|
|
|
import { chromium } from 'playwright';
|
|
|
|
const URL = "http://localhost:1999";
|
|
|
|
async function testSkeletonLoaders() {
|
|
console.log('💀 SKELETON LOADERS TEST\n');
|
|
console.log('='.repeat(70));
|
|
|
|
const browser = await chromium.launch({ headless: false });
|
|
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
|
|
|
|
const errors = [];
|
|
const testResults = [];
|
|
|
|
page.on('console', msg => {
|
|
if (msg.type() === 'error') {
|
|
errors.push(msg.text());
|
|
console.log(`❌ ERROR: ${msg.text()}`);
|
|
}
|
|
});
|
|
|
|
console.log("\n1️⃣ Loading page (English default)...");
|
|
await page.goto(URL);
|
|
await page.waitForTimeout(2000);
|
|
|
|
// ========================================================================
|
|
// TEST 1: Component wrapper structure exists
|
|
// ========================================================================
|
|
console.log("\n2️⃣ Testing Component Wrapper Structure...");
|
|
const structure = await page.evaluate(() => {
|
|
const wrappers = document.querySelectorAll('.component-wrapper');
|
|
const hasActual = Array.from(wrappers).every(w => w.querySelector('.actual-content'));
|
|
const hasSkeleton = Array.from(wrappers).every(w => w.querySelector('.skeleton-content'));
|
|
|
|
return {
|
|
wrapperCount: wrappers.length,
|
|
allHaveActual: hasActual,
|
|
allHaveSkeleton: hasSkeleton
|
|
};
|
|
});
|
|
|
|
console.log(` Component wrappers found: ${structure.wrapperCount}`);
|
|
console.log(` All have .actual-content: ${structure.allHaveActual ? '✅' : '❌'}`);
|
|
console.log(` All have .skeleton-content: ${structure.allHaveSkeleton ? '✅' : '❌'}`);
|
|
|
|
const structurePassed = structure.wrapperCount > 0 && structure.allHaveActual && structure.allHaveSkeleton;
|
|
console.log(` ${structurePassed ? '✅ PASS' : '❌ FAIL'} - Dual-state structure exists`);
|
|
testResults.push({ test: 'Component Wrapper Structure', passed: structurePassed });
|
|
|
|
// ========================================================================
|
|
// TEST 2: Skeleton CSS exists and is loaded
|
|
// ========================================================================
|
|
console.log("\n3️⃣ Testing Skeleton CSS...");
|
|
const cssCheck = await page.evaluate(() => {
|
|
const skeleton = document.querySelector('.skeleton-content .skeleton');
|
|
if (!skeleton) return { exists: false };
|
|
|
|
const styles = window.getComputedStyle(skeleton);
|
|
return {
|
|
exists: true,
|
|
hasAnimation: styles.animation !== 'none' && styles.animation !== '',
|
|
background: styles.background,
|
|
borderRadius: styles.borderRadius
|
|
};
|
|
});
|
|
|
|
console.log(` Skeleton elements exist: ${cssCheck.exists ? '✅' : '❌'}`);
|
|
console.log(` Has shimmer animation: ${cssCheck.hasAnimation ? '✅' : '❌'}`);
|
|
console.log(` ${cssCheck.exists && cssCheck.hasAnimation ? '✅ PASS' : '❌ FAIL'} - Skeleton CSS loaded`);
|
|
testResults.push({ test: 'Skeleton CSS', passed: cssCheck.exists && cssCheck.hasAnimation });
|
|
|
|
// ========================================================================
|
|
// TEST 3: Monitor parent container .loading class during language switch
|
|
// ========================================================================
|
|
console.log("\n4️⃣ Testing First Language Switch (EN → ES)...");
|
|
|
|
// Set up console log monitoring to track our JavaScript skeleton loader messages
|
|
const consoleMessages = [];
|
|
page.on('console', msg => {
|
|
const text = msg.text();
|
|
if (text.includes('Skeleton loader:')) {
|
|
consoleMessages.push(text);
|
|
}
|
|
});
|
|
|
|
// Click Spanish button
|
|
await page.click('.selector-btn[aria-label="Español"]');
|
|
await page.waitForTimeout(800);
|
|
|
|
// Check the console messages
|
|
const addedMessages1 = consoleMessages.filter(m => m.includes('Added .loading')).length;
|
|
const removedMessages1 = consoleMessages.filter(m => m.includes('Removed .loading')).length;
|
|
|
|
console.log(` Skeleton loader added .loading: ${addedMessages1 > 0 ? '✅' : '❌'} (${addedMessages1} events)`);
|
|
console.log(` Skeleton loader removed .loading: ${removedMessages1 > 0 ? '✅' : '❌'} (${removedMessages1} events)`);
|
|
|
|
const switch1Passed = addedMessages1 > 0 && removedMessages1 > 0;
|
|
console.log(` ${switch1Passed ? '✅ PASS' : '❌ FAIL'} - Skeleton displayed during transition`);
|
|
testResults.push({ test: 'First Language Switch', passed: switch1Passed });
|
|
|
|
// ========================================================================
|
|
// TEST 4: Second language switch (ES → EN)
|
|
// ========================================================================
|
|
console.log("\n5️⃣ Testing Second Language Switch (ES → EN)...");
|
|
|
|
// Clear console messages
|
|
const beforeSwitch2 = consoleMessages.length;
|
|
|
|
// Click English button
|
|
await page.click('.selector-btn[aria-label="English"]');
|
|
await page.waitForTimeout(800);
|
|
|
|
// Check new console messages since last switch
|
|
const newMessages2 = consoleMessages.slice(beforeSwitch2);
|
|
const addedMessages2 = newMessages2.filter(m => m.includes('Added .loading')).length;
|
|
const removedMessages2 = newMessages2.filter(m => m.includes('Removed .loading')).length;
|
|
|
|
console.log(` Skeleton loader added .loading: ${addedMessages2 > 0 ? '✅' : '❌'} (${addedMessages2} events)`);
|
|
console.log(` Skeleton loader removed .loading: ${removedMessages2 > 0 ? '✅' : '❌'} (${removedMessages2} events)`);
|
|
|
|
const switch2Passed = addedMessages2 > 0 && removedMessages2 > 0;
|
|
console.log(` ${switch2Passed ? '✅ PASS' : '❌ FAIL'} - Skeleton still works on second switch`);
|
|
testResults.push({ test: 'Second Language Switch', passed: switch2Passed });
|
|
|
|
// ========================================================================
|
|
// TEST 5: Third language switch (EN → ES) - consistency check
|
|
// ========================================================================
|
|
console.log("\n6️⃣ Testing Third Language Switch (EN → ES)...");
|
|
|
|
// Clear console messages
|
|
const beforeSwitch3 = consoleMessages.length;
|
|
|
|
// Click Spanish button
|
|
await page.click('.selector-btn[aria-label="Español"]');
|
|
await page.waitForTimeout(800);
|
|
|
|
// Check new console messages since last switch
|
|
const newMessages3 = consoleMessages.slice(beforeSwitch3);
|
|
const addedMessages3 = newMessages3.filter(m => m.includes('Added .loading')).length;
|
|
const removedMessages3 = newMessages3.filter(m => m.includes('Removed .loading')).length;
|
|
|
|
console.log(` Skeleton loader added .loading: ${addedMessages3 > 0 ? '✅' : '❌'} (${addedMessages3} events)`);
|
|
console.log(` Skeleton loader removed .loading: ${removedMessages3 > 0 ? '✅' : '❌'} (${removedMessages3} events)`);
|
|
|
|
const switch3Passed = addedMessages3 > 0 && removedMessages3 > 0;
|
|
console.log(` ${switch3Passed ? '✅ PASS' : '❌ FAIL'} - Consistent behavior on third switch`);
|
|
testResults.push({ test: 'Third Language Switch', passed: switch3Passed });
|
|
|
|
// ========================================================================
|
|
// TEST 6: No stuck loading states
|
|
// ========================================================================
|
|
console.log("\n7️⃣ Testing for Stuck Loading States...");
|
|
|
|
const finalState = await page.evaluate(() => {
|
|
const page1 = document.querySelector('#cv-inner-content-page-1');
|
|
const page2 = document.querySelector('#cv-inner-content-page-2');
|
|
const wrappers = document.querySelectorAll('.component-wrapper');
|
|
|
|
return {
|
|
page1HasLoading: page1?.classList.contains('loading') || false,
|
|
page2HasLoading: page2?.classList.contains('loading') || false,
|
|
anyWrapperHasLoading: Array.from(wrappers).some(w => w.classList.contains('loading'))
|
|
};
|
|
});
|
|
|
|
console.log(` Page 1 stuck with .loading: ${finalState.page1HasLoading ? '❌ BUG' : '✅ Clean'}`);
|
|
console.log(` Page 2 stuck with .loading: ${finalState.page2HasLoading ? '❌ BUG' : '✅ Clean'}`);
|
|
console.log(` Any wrapper stuck with .loading: ${finalState.anyWrapperHasLoading ? '❌ BUG' : '✅ Clean'}`);
|
|
|
|
const noStuckStates = !finalState.page1HasLoading && !finalState.page2HasLoading && !finalState.anyWrapperHasLoading;
|
|
console.log(` ${noStuckStates ? '✅ PASS' : '❌ FAIL'} - No stuck loading states`);
|
|
testResults.push({ test: 'No Stuck Loading States', passed: noStuckStates });
|
|
|
|
// ========================================================================
|
|
// TEST 7: JavaScript event handlers work
|
|
// ========================================================================
|
|
console.log("\n8️⃣ Testing JavaScript Event Handlers...");
|
|
|
|
const jsCheck = await page.evaluate(() => {
|
|
// Check if main.js loaded and languageSwitching variable exists
|
|
const hasLanguageSwitchingFlag = typeof window.languageSwitching !== 'undefined';
|
|
|
|
// Verify the flag is in clean state (false)
|
|
const flagIsClean = window.languageSwitching === false;
|
|
|
|
return {
|
|
hasLanguageSwitchingFlag,
|
|
flagIsClean
|
|
};
|
|
});
|
|
|
|
console.log(` JavaScript languageSwitching flag exists: ${jsCheck.hasLanguageSwitchingFlag ? '✅' : '❌'}`);
|
|
console.log(` Flag is in clean state (false): ${jsCheck.flagIsClean ? '✅' : '❌'}`);
|
|
|
|
const jsPassed = jsCheck.hasLanguageSwitchingFlag && jsCheck.flagIsClean;
|
|
console.log(` ${jsPassed ? '✅ PASS' : '❌ FAIL'} - JavaScript event handlers configured`);
|
|
testResults.push({ test: 'JavaScript Event Handlers', passed: jsPassed });
|
|
|
|
// ========================================================================
|
|
// FINAL SUMMARY
|
|
// ========================================================================
|
|
console.log("\n" + "=".repeat(70));
|
|
console.log("📊 TEST SUMMARY\n");
|
|
|
|
const totalTests = testResults.length;
|
|
const passedTests = testResults.filter(r => r.passed).length;
|
|
const failedTests = totalTests - passedTests;
|
|
|
|
testResults.forEach(result => {
|
|
console.log(` ${result.passed ? '✅' : '❌'} ${result.test}`);
|
|
});
|
|
|
|
console.log(`\n Total: ${passedTests}/${totalTests} tests passed`);
|
|
|
|
if (errors.length === 0) {
|
|
console.log("\n✅ NO CONSOLE ERRORS");
|
|
} else {
|
|
console.log(`\n⚠️ ${errors.length} CONSOLE ERRORS`);
|
|
}
|
|
|
|
console.log("=".repeat(70) + "\n");
|
|
|
|
if (failedTests === 0) {
|
|
console.log("🎉 SKELETON LOADERS VALIDATED!");
|
|
} else {
|
|
console.log("⚠️ SOME TESTS FAILED - See details above");
|
|
}
|
|
|
|
console.log("\nBrowser will stay open for manual inspection.");
|
|
console.log("Press Ctrl+C when done.\n");
|
|
|
|
await new Promise(() => {}); // Keep browser open
|
|
}
|
|
|
|
await testSkeletonLoaders();
|