16194328b6
## Documentation Updates (doc/11-PDF-EXPORT.md) Added "References Section Integration" section with: - Template code examples for shortcut URL usage - Data configuration structure - Key features and use cases - Security attributes documentation - Test command reference ## Test Updates (tests/mjs/28-references-pdf-download.test.mjs) Completely rewrote test to match new direct link behavior: - ❌ OLD: Tested modal opening, onclick handlers - ✅ NEW: Tests direct shortcut URLs New test coverage (5 tests): 1. English direct shortcut URL validation 2. Spanish direct shortcut URL validation 3. HTTP 301 redirect verification 4. Year validation (404 for invalid years) 5. Works without JavaScript (PDF export context) Verifies: - Correct href: /cv-jamr-{year}-{lang}.pdf - No onclick handlers (pure HTML link) - Security attributes (target="_blank", rel="noopener noreferrer") - Year-aware dynamic URLs - Language-aware (es/en) - Backend 301 redirects working - 404 for past/future years ## Benefits - Comprehensive test coverage for new behavior - Complete documentation with examples - Easy to maintain and understand - Validates entire shortcut URL feature end-to-end
339 lines
14 KiB
JavaScript
Executable File
339 lines
14 KiB
JavaScript
Executable File
#!/usr/bin/env bun
|
|
/**
|
|
* REFERENCES SECTION - DIRECT PDF SHORTCUT LINK TEST
|
|
* ====================================================
|
|
* Tests the direct PDF download shortcut links in the References section
|
|
*
|
|
* Coverage:
|
|
* - References section renders with "Download this curriculum" links
|
|
* - Links point directly to shortcut URLs (no modal)
|
|
* - English CV: /cv-jamr-{year}-en.pdf
|
|
* - Spanish CV: /cv-jamr-{year}-es.pdf
|
|
* - Year is dynamic (current year)
|
|
* - Links have target="_blank" and proper security attributes
|
|
* - No onclick handlers (direct navigation)
|
|
* - Works in non-browser environments (PDF exports, emails, etc.)
|
|
*
|
|
* Integration:
|
|
* - Data: cv-en.json and cv-es.json with action: "downloadPDF"
|
|
* - Template: references.html with shortcut URL logic
|
|
* - Backend: DefaultCVShortcut handler for year validation
|
|
*/
|
|
|
|
import { chromium } from 'playwright';
|
|
|
|
const URL = "http://localhost:1999";
|
|
const CURRENT_YEAR = new Date().getFullYear();
|
|
|
|
async function testReferencesPDFShortcutLinks() {
|
|
console.log('📚 REFERENCES SECTION - DIRECT PDF SHORTCUT LINK TEST\n');
|
|
console.log('='.repeat(70));
|
|
|
|
const browser = await chromium.launch({ headless: false });
|
|
const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } });
|
|
const page = await context.newPage();
|
|
|
|
const errors = [];
|
|
const testResults = [];
|
|
|
|
page.on('console', msg => {
|
|
if (msg.type() === 'error') {
|
|
errors.push(msg.text());
|
|
console.log(`❌ CONSOLE ERROR: ${msg.text()}`);
|
|
}
|
|
});
|
|
|
|
// ========================================================================
|
|
// TEST 1: English Version - Direct Shortcut URL
|
|
// ========================================================================
|
|
console.log("\n1️⃣ Testing English Direct Shortcut URL...");
|
|
|
|
await page.goto(`${URL}/?lang=en`);
|
|
await page.waitForTimeout(1500);
|
|
|
|
// Scroll to references section
|
|
await page.evaluate(() => {
|
|
const referencesSection = document.querySelector('#references');
|
|
if (referencesSection) {
|
|
referencesSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
}
|
|
});
|
|
await page.waitForTimeout(1000);
|
|
|
|
const enReferences = await page.evaluate((currentYear) => {
|
|
const section = document.querySelector('#references');
|
|
if (!section) return { found: false };
|
|
|
|
// Find the reference-item div that contains "Download this curriculum"
|
|
const referenceItems = section.querySelectorAll('.reference-item');
|
|
let downloadLink = null;
|
|
|
|
for (const item of referenceItems) {
|
|
if (item.textContent.includes('Download this curriculum')) {
|
|
downloadLink = item.querySelector('a');
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
found: true,
|
|
sectionExists: !!section,
|
|
downloadLinkExists: !!downloadLink,
|
|
downloadLinkText: downloadLink ? downloadLink.textContent.trim() : null,
|
|
href: downloadLink ? downloadLink.getAttribute('href') : null,
|
|
target: downloadLink ? downloadLink.getAttribute('target') : null,
|
|
rel: downloadLink ? downloadLink.getAttribute('rel') : null,
|
|
hasOnClick: downloadLink ? downloadLink.hasAttribute('onclick') : false,
|
|
expectedHref: `/cv-jamr-${currentYear}-en.pdf`,
|
|
hrefMatches: downloadLink ? downloadLink.getAttribute('href') === `/cv-jamr-${currentYear}-en.pdf` : false
|
|
};
|
|
}, CURRENT_YEAR);
|
|
|
|
console.log(` References section: ${enReferences.sectionExists ? '✅' : '❌'}`);
|
|
console.log(` Download link exists: ${enReferences.downloadLinkExists ? '✅' : '❌'}`);
|
|
console.log(` Link text: "${enReferences.downloadLinkText}"`);
|
|
console.log(` Href: "${enReferences.href}"`);
|
|
console.log(` Expected: "${enReferences.expectedHref}"`);
|
|
console.log(` Href matches: ${enReferences.hrefMatches ? '✅' : '❌'}`);
|
|
console.log(` Target="_blank": ${enReferences.target === '_blank' ? '✅' : '❌'}`);
|
|
console.log(` Rel security attrs: ${enReferences.rel === 'noopener noreferrer' ? '✅' : '❌'}`);
|
|
console.log(` No onclick handler: ${!enReferences.hasOnClick ? '✅' : '❌'}`);
|
|
|
|
const test1Passed = enReferences.sectionExists &&
|
|
enReferences.downloadLinkExists &&
|
|
enReferences.hrefMatches &&
|
|
enReferences.target === '_blank' &&
|
|
enReferences.rel === 'noopener noreferrer' &&
|
|
!enReferences.hasOnClick;
|
|
|
|
console.log(` ${test1Passed ? '✅ PASS' : '❌ FAIL'} - English direct shortcut URL`);
|
|
testResults.push({ test: 'EN Direct Shortcut URL', passed: test1Passed });
|
|
|
|
// ========================================================================
|
|
// TEST 2: Spanish Version - Direct Shortcut URL
|
|
// ========================================================================
|
|
console.log("\n2️⃣ Testing Spanish Direct Shortcut URL...");
|
|
|
|
await page.goto(`${URL}/?lang=es`);
|
|
await page.waitForTimeout(1500);
|
|
|
|
// Scroll to references section
|
|
await page.evaluate(() => {
|
|
const referencesSection = document.querySelector('#references');
|
|
if (referencesSection) {
|
|
referencesSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
}
|
|
});
|
|
await page.waitForTimeout(1000);
|
|
|
|
const esReferences = await page.evaluate((currentYear) => {
|
|
const section = document.querySelector('#references');
|
|
if (!section) return { found: false };
|
|
|
|
// Find the reference-item div that contains "Descargar este currículum"
|
|
const referenceItems = section.querySelectorAll('.reference-item');
|
|
let downloadLink = null;
|
|
|
|
for (const item of referenceItems) {
|
|
if (item.textContent.includes('Descargar este currículum')) {
|
|
downloadLink = item.querySelector('a');
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
found: true,
|
|
sectionExists: !!section,
|
|
downloadLinkExists: !!downloadLink,
|
|
downloadLinkText: downloadLink ? downloadLink.textContent.trim() : null,
|
|
href: downloadLink ? downloadLink.getAttribute('href') : null,
|
|
target: downloadLink ? downloadLink.getAttribute('target') : null,
|
|
rel: downloadLink ? downloadLink.getAttribute('rel') : null,
|
|
hasOnClick: downloadLink ? downloadLink.hasAttribute('onclick') : false,
|
|
expectedHref: `/cv-jamr-${currentYear}-es.pdf`,
|
|
hrefMatches: downloadLink ? downloadLink.getAttribute('href') === `/cv-jamr-${currentYear}-es.pdf` : false
|
|
};
|
|
}, CURRENT_YEAR);
|
|
|
|
console.log(` References section: ${esReferences.sectionExists ? '✅' : '❌'}`);
|
|
console.log(` Download link exists: ${esReferences.downloadLinkExists ? '✅' : '❌'}`);
|
|
console.log(` Link text: "${esReferences.downloadLinkText}"`);
|
|
console.log(` Href: "${esReferences.href}"`);
|
|
console.log(` Expected: "${esReferences.expectedHref}"`);
|
|
console.log(` Href matches: ${esReferences.hrefMatches ? '✅' : '❌'}`);
|
|
console.log(` Target="_blank": ${esReferences.target === '_blank' ? '✅' : '❌'}`);
|
|
console.log(` Rel security attrs: ${esReferences.rel === 'noopener noreferrer' ? '✅' : '❌'}`);
|
|
console.log(` No onclick handler: ${!esReferences.hasOnClick ? '✅' : '❌'}`);
|
|
|
|
const test2Passed = esReferences.sectionExists &&
|
|
esReferences.downloadLinkExists &&
|
|
esReferences.hrefMatches &&
|
|
esReferences.target === '_blank' &&
|
|
esReferences.rel === 'noopener noreferrer' &&
|
|
!esReferences.hasOnClick;
|
|
|
|
console.log(` ${test2Passed ? '✅ PASS' : '❌ FAIL'} - Spanish direct shortcut URL`);
|
|
testResults.push({ test: 'ES Direct Shortcut URL', passed: test2Passed });
|
|
|
|
// ========================================================================
|
|
// TEST 3: Shortcut URL Responds with 301 Redirect
|
|
// ========================================================================
|
|
console.log("\n3️⃣ Testing Shortcut URL Redirects...");
|
|
|
|
// Test English shortcut
|
|
const enResponse = await page.request.get(`${URL}/cv-jamr-${CURRENT_YEAR}-en.pdf`, {
|
|
maxRedirects: 0,
|
|
failOnStatusCode: false
|
|
});
|
|
|
|
const enStatus = enResponse.status();
|
|
const enLocation = enResponse.headers()['location'];
|
|
|
|
console.log(` English shortcut HTTP status: ${enStatus} ${enStatus === 301 ? '✅' : '❌'}`);
|
|
console.log(` Redirect location: ${enLocation}`);
|
|
console.log(` Points to export/pdf: ${enLocation?.includes('/export/pdf') ? '✅' : '❌'}`);
|
|
console.log(` Has lang=en: ${enLocation?.includes('lang=en') ? '✅' : '❌'}`);
|
|
console.log(` Has length=short: ${enLocation?.includes('length=short') ? '✅' : '❌'}`);
|
|
console.log(` Has version=with_skills: ${enLocation?.includes('version=with_skills') ? '✅' : '❌'}`);
|
|
|
|
// Test Spanish shortcut
|
|
const esResponse = await page.request.get(`${URL}/cv-jamr-${CURRENT_YEAR}-es.pdf`, {
|
|
maxRedirects: 0,
|
|
failOnStatusCode: false
|
|
});
|
|
|
|
const esStatus = esResponse.status();
|
|
const esLocation = esResponse.headers()['location'];
|
|
|
|
console.log(` Spanish shortcut HTTP status: ${esStatus} ${esStatus === 301 ? '✅' : '❌'}`);
|
|
console.log(` Redirect location: ${esLocation}`);
|
|
console.log(` Has lang=es: ${esLocation?.includes('lang=es') ? '✅' : '❌'}`);
|
|
|
|
const test3Passed = enStatus === 301 &&
|
|
esStatus === 301 &&
|
|
enLocation?.includes('/export/pdf') &&
|
|
enLocation?.includes('lang=en') &&
|
|
esLocation?.includes('lang=es');
|
|
|
|
console.log(` ${test3Passed ? '✅ PASS' : '❌ FAIL'} - Shortcut URL redirects`);
|
|
testResults.push({ test: 'Shortcut URL Redirects', passed: test3Passed });
|
|
|
|
// ========================================================================
|
|
// TEST 4: Invalid Year Returns 404
|
|
// ========================================================================
|
|
console.log("\n4️⃣ Testing Invalid Year Validation...");
|
|
|
|
const invalidYear = CURRENT_YEAR - 1; // Last year
|
|
const futureYear = CURRENT_YEAR + 1; // Next year
|
|
|
|
const pastResponse = await page.request.get(`${URL}/cv-jamr-${invalidYear}-en.pdf`, {
|
|
failOnStatusCode: false
|
|
});
|
|
|
|
const futureResponse = await page.request.get(`${URL}/cv-jamr-${futureYear}-en.pdf`, {
|
|
failOnStatusCode: false
|
|
});
|
|
|
|
const pastStatus = pastResponse.status();
|
|
const futureStatus = futureResponse.status();
|
|
|
|
console.log(` Past year (${invalidYear}) status: ${pastStatus} ${pastStatus === 404 ? '✅' : '❌'}`);
|
|
console.log(` Future year (${futureYear}) status: ${futureStatus} ${futureStatus === 404 ? '✅' : '❌'}`);
|
|
|
|
const test4Passed = pastStatus === 404 && futureStatus === 404;
|
|
|
|
console.log(` ${test4Passed ? '✅ PASS' : '❌ FAIL'} - Year validation (404 for invalid years)`);
|
|
testResults.push({ test: 'Year Validation', passed: test4Passed });
|
|
|
|
// ========================================================================
|
|
// TEST 5: Link Works in PDF Export Context (No JavaScript)
|
|
// ========================================================================
|
|
console.log("\n5️⃣ Testing Link Works Without JavaScript...");
|
|
|
|
// Navigate with JavaScript disabled
|
|
const noJSContext = await browser.newContext({
|
|
javaScriptEnabled: false,
|
|
viewport: { width: 1920, height: 1080 }
|
|
});
|
|
const noJSPage = await noJSContext.newPage();
|
|
|
|
await noJSPage.goto(`${URL}/?lang=en`);
|
|
await noJSPage.waitForTimeout(1500);
|
|
|
|
const linkWorksWithoutJS = await noJSPage.evaluate((currentYear) => {
|
|
const section = document.querySelector('#references');
|
|
if (!section) return { found: false };
|
|
|
|
const referenceItems = section.querySelectorAll('.reference-item');
|
|
let downloadLink = null;
|
|
|
|
for (const item of referenceItems) {
|
|
if (item.textContent.includes('Download this curriculum')) {
|
|
downloadLink = item.querySelector('a');
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
found: true,
|
|
linkExists: !!downloadLink,
|
|
href: downloadLink ? downloadLink.getAttribute('href') : null,
|
|
isDirectLink: downloadLink ? downloadLink.getAttribute('href') === `/cv-jamr-${currentYear}-en.pdf` : false,
|
|
noOnClick: downloadLink ? !downloadLink.hasAttribute('onclick') : false
|
|
};
|
|
}, CURRENT_YEAR);
|
|
|
|
console.log(` Link exists without JS: ${linkWorksWithoutJS.linkExists ? '✅' : '❌'}`);
|
|
console.log(` Href is direct PDF link: ${linkWorksWithoutJS.isDirectLink ? '✅' : '❌'}`);
|
|
console.log(` No onclick dependency: ${linkWorksWithoutJS.noOnClick ? '✅' : '❌'}`);
|
|
|
|
const test5Passed = linkWorksWithoutJS.linkExists &&
|
|
linkWorksWithoutJS.isDirectLink &&
|
|
linkWorksWithoutJS.noOnClick;
|
|
|
|
console.log(` ${test5Passed ? '✅ PASS' : '❌ FAIL'} - Works without JavaScript`);
|
|
testResults.push({ test: 'No JavaScript Required', passed: test5Passed });
|
|
|
|
await noJSContext.close();
|
|
|
|
// ========================================================================
|
|
// FINAL RESULTS
|
|
// ========================================================================
|
|
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, index) => {
|
|
const status = result.passed ? '✅ PASS' : '❌ FAIL';
|
|
console.log(`${index + 1}. ${status} - ${result.test}`);
|
|
});
|
|
|
|
console.log('\n' + '='.repeat(70));
|
|
console.log(`Total Tests: ${totalTests}`);
|
|
console.log(`✅ Passed: ${passedTests}`);
|
|
console.log(`❌ Failed: ${failedTests}`);
|
|
|
|
if (errors.length > 0) {
|
|
console.log(`\n⚠️ Console Errors Detected: ${errors.length}`);
|
|
errors.forEach(err => console.log(` - ${err}`));
|
|
}
|
|
|
|
const allPassed = passedTests === totalTests && errors.length === 0;
|
|
console.log('\n' + '='.repeat(70));
|
|
console.log(allPassed ? '🎉 ALL TESTS PASSED!' : '💥 SOME TESTS FAILED');
|
|
console.log('='.repeat(70));
|
|
|
|
await browser.close();
|
|
|
|
// Exit with appropriate code
|
|
process.exit(allPassed ? 0 : 1);
|
|
}
|
|
|
|
testReferencesPDFShortcutLinks().catch(err => {
|
|
console.error('❌ FATAL ERROR:', err);
|
|
process.exit(1);
|
|
});
|