Files
cv-site/tests/mjs/12-skeleton-language-transitions.test.mjs
juanatsap 1f6f8e417e docs: Update skeleton loader implementation from hyperscript to JavaScript
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
2025-11-18 19:32:28 +00:00

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();