f3fc6a2632
- Add gh-dashboard (debba/gh-dashboard) as collaboration project in EN/ES CV data - Add head-language-switch.html template partial with test - Change CV header text-align-last from justify to left - Update .gitignore to exclude prompt symlinks and cv-site binary
224 lines
9.0 KiB
JavaScript
224 lines
9.0 KiB
JavaScript
#!/usr/bin/env bun
|
|
/**
|
|
* HEAD-SUPPORT EXTENSION TEST
|
|
* ============================
|
|
* Tests that the head-support extension updates <head> tags
|
|
* and <html lang> on language switching via HTMX
|
|
* - Verifies <html lang> updates on language switch
|
|
* - Verifies <title> updates on language switch
|
|
* - Verifies <meta description> updates
|
|
* - Verifies no duplicate tags after multiple switches
|
|
*/
|
|
|
|
import { chromium } from 'playwright';
|
|
|
|
const URL = "http://localhost:1999";
|
|
|
|
async function testHeadSupport() {
|
|
console.log('🏷️ HEAD-SUPPORT EXTENSION TEST\n');
|
|
console.log('='.repeat(70));
|
|
|
|
const browser = await chromium.launch({ headless: true });
|
|
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("\n1️⃣ Loading page (English default)...");
|
|
await page.goto(URL);
|
|
await page.waitForTimeout(2000);
|
|
|
|
// ========================================================================
|
|
// TEST 1: Head-support extension is loaded
|
|
// ========================================================================
|
|
console.log("\n2️⃣ Testing head-support extension loaded...");
|
|
const extLoaded = await page.evaluate(() => {
|
|
const body = document.body;
|
|
const hxExt = body.getAttribute('hx-ext');
|
|
return {
|
|
hasHxExt: hxExt !== null && hxExt.includes('head-support'),
|
|
hxExtValue: hxExt
|
|
};
|
|
});
|
|
|
|
console.log(` hx-ext attribute: ${extLoaded.hxExtValue}`);
|
|
console.log(` ${extLoaded.hasHxExt ? '✅ PASS' : '❌ FAIL'} - head-support extension activated`);
|
|
testResults.push({ test: 'Head-support Extension Loaded', passed: extLoaded.hasHxExt });
|
|
|
|
// ========================================================================
|
|
// TEST 2: Initial state - English
|
|
// ========================================================================
|
|
console.log("\n3️⃣ Testing initial state (English)...");
|
|
const initialState = await page.evaluate(() => {
|
|
return {
|
|
htmlLang: document.documentElement.getAttribute('lang'),
|
|
title: document.title,
|
|
metaDesc: document.querySelector('meta[name="description"]')?.getAttribute('content') || '',
|
|
canonical: document.querySelector('link[rel="canonical"]')?.getAttribute('href') || ''
|
|
};
|
|
});
|
|
|
|
console.log(` <html lang>: ${initialState.htmlLang}`);
|
|
console.log(` <title>: ${initialState.title}`);
|
|
console.log(` canonical: ${initialState.canonical}`);
|
|
const initialOk = initialState.htmlLang === 'en';
|
|
console.log(` ${initialOk ? '✅ PASS' : '❌ FAIL'} - Initial state is English`);
|
|
testResults.push({ test: 'Initial English State', passed: initialOk });
|
|
|
|
// ========================================================================
|
|
// TEST 3: Switch to Spanish via HTMX button
|
|
// ========================================================================
|
|
console.log("\n4️⃣ Switching to Spanish via HTMX...");
|
|
|
|
// Click the Spanish button
|
|
const esButton = await page.$('button[aria-label="Español"]');
|
|
if (esButton) {
|
|
await esButton.click();
|
|
await page.waitForTimeout(3000); // Wait for HTMX swap + head-support processing
|
|
|
|
const spanishState = await page.evaluate(() => {
|
|
return {
|
|
htmlLang: document.documentElement.getAttribute('lang'),
|
|
title: document.title,
|
|
metaDesc: document.querySelector('meta[name="description"]')?.getAttribute('content') || '',
|
|
canonical: document.querySelector('link[rel="canonical"]')?.getAttribute('href') || '',
|
|
ogLocale: document.querySelector('meta[property="og:locale"]')?.getAttribute('content') || ''
|
|
};
|
|
});
|
|
|
|
console.log(` <html lang>: ${spanishState.htmlLang}`);
|
|
console.log(` <title>: ${spanishState.title}`);
|
|
console.log(` canonical: ${spanishState.canonical}`);
|
|
console.log(` og:locale: ${spanishState.ogLocale}`);
|
|
|
|
const langChanged = spanishState.htmlLang === 'es';
|
|
const canonicalChanged = spanishState.canonical.includes('lang=es');
|
|
const descChanged = spanishState.metaDesc.length > 0;
|
|
|
|
console.log(` ${langChanged ? '✅ PASS' : '❌ FAIL'} - <html lang> changed to "es"`);
|
|
console.log(` ${canonicalChanged ? '✅ PASS' : '❌ FAIL'} - canonical URL updated to Spanish`);
|
|
console.log(` ${descChanged ? '✅ PASS' : '❌ FAIL'} - meta description present`);
|
|
|
|
testResults.push({ test: 'HTML Lang Updated to ES', passed: langChanged });
|
|
testResults.push({ test: 'Canonical URL Updated to ES', passed: canonicalChanged });
|
|
testResults.push({ test: 'Meta Description Present', passed: descChanged });
|
|
} else {
|
|
console.log(' ❌ FAIL - Spanish button not found');
|
|
testResults.push({ test: 'HTML Lang Updated to ES', passed: false });
|
|
testResults.push({ test: 'Canonical URL Updated to ES', passed: false });
|
|
testResults.push({ test: 'Meta Description Present', passed: false });
|
|
}
|
|
|
|
// ========================================================================
|
|
// TEST 4: Switch back to English
|
|
// ========================================================================
|
|
console.log("\n5️⃣ Switching back to English...");
|
|
|
|
const enButton = await page.$('button[aria-label="English"]');
|
|
if (enButton) {
|
|
await enButton.click();
|
|
await page.waitForTimeout(3000);
|
|
|
|
const englishState = await page.evaluate(() => {
|
|
return {
|
|
htmlLang: document.documentElement.getAttribute('lang'),
|
|
title: document.title,
|
|
canonical: document.querySelector('link[rel="canonical"]')?.getAttribute('href') || '',
|
|
ogLocale: document.querySelector('meta[property="og:locale"]')?.getAttribute('content') || ''
|
|
};
|
|
});
|
|
|
|
console.log(` <html lang>: ${englishState.htmlLang}`);
|
|
console.log(` <title>: ${englishState.title}`);
|
|
console.log(` canonical: ${englishState.canonical}`);
|
|
console.log(` og:locale: ${englishState.ogLocale}`);
|
|
|
|
const langBack = englishState.htmlLang === 'en';
|
|
const canonicalBack = englishState.canonical.includes('lang=en');
|
|
|
|
console.log(` ${langBack ? '✅ PASS' : '❌ FAIL'} - <html lang> back to "en"`);
|
|
console.log(` ${canonicalBack ? '✅ PASS' : '❌ FAIL'} - canonical URL back to English`);
|
|
|
|
testResults.push({ test: 'HTML Lang Restored to EN', passed: langBack });
|
|
testResults.push({ test: 'Canonical URL Restored to EN', passed: canonicalBack });
|
|
} else {
|
|
console.log(' ❌ FAIL - English button not found');
|
|
testResults.push({ test: 'HTML Lang Restored to EN', passed: false });
|
|
testResults.push({ test: 'Canonical URL Restored to EN', passed: false });
|
|
}
|
|
|
|
// ========================================================================
|
|
// TEST 5: No duplicate tags after multiple switches
|
|
// ========================================================================
|
|
console.log("\n6️⃣ Testing no duplicate tags after multiple switches...");
|
|
|
|
const duplicateCheck = await page.evaluate(() => {
|
|
const titles = document.querySelectorAll('title').length;
|
|
const descriptions = document.querySelectorAll('meta[name="description"]').length;
|
|
const canonicals = document.querySelectorAll('link[rel="canonical"]').length;
|
|
const ogLocales = document.querySelectorAll('meta[property="og:locale"]').length;
|
|
|
|
return {
|
|
titles,
|
|
descriptions,
|
|
canonicals,
|
|
ogLocales
|
|
};
|
|
});
|
|
|
|
console.log(` <title> tags: ${duplicateCheck.titles}`);
|
|
console.log(` <meta description> tags: ${duplicateCheck.descriptions}`);
|
|
console.log(` <link canonical> tags: ${duplicateCheck.canonicals}`);
|
|
console.log(` og:locale tags: ${duplicateCheck.ogLocales}`);
|
|
|
|
const noDuplicates = duplicateCheck.titles === 1 &&
|
|
duplicateCheck.descriptions === 1 &&
|
|
duplicateCheck.canonicals === 1 &&
|
|
duplicateCheck.ogLocales === 1;
|
|
|
|
console.log(` ${noDuplicates ? '✅ PASS' : '❌ FAIL'} - No duplicate tags`);
|
|
testResults.push({ test: 'No Duplicate Tags', passed: noDuplicates });
|
|
|
|
// ========================================================================
|
|
// 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");
|
|
|
|
await browser.close();
|
|
|
|
if (failedTests === 0) {
|
|
console.log("🎉 HEAD-SUPPORT EXTENSION VALIDATED!\n");
|
|
process.exit(0);
|
|
} else {
|
|
console.log("⚠️ SOME TESTS FAILED - See details above\n");
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
await testHeadSupport();
|