Files
cv-site/tests/mjs/75-html-invoker-commands.test.mjs
T
juanatsap 2d3d3de8cd feat: lazy load ninja-keys + HTML Invoker Commands API
- Lazy load ninja-keys only on CMD+K press (0 requests on initial load)
- Use esm.sh bundled module (3 requests vs ~81 previously)
- Add esm.sh to CSP whitelist
- Implement HTML Invoker Commands API for modals:
  - commandfor="modal-id" + command="show-modal" for opening
  - commandfor="modal-id" + command="close" for closing
  - Removes need for onclick handlers on modal buttons
- Refactor index.html into layout partials (head, body-scripts)
- Add comprehensive tests for both features
2025-12-02 08:29:54 +00:00

241 lines
9.7 KiB
JavaScript

#!/usr/bin/env bun
/**
* HTML INVOKER COMMANDS API TEST
* ==============================
* Tests the new HTML commandfor/command attributes for modals:
* - Buttons have commandfor and command attributes
* - command="show-modal" opens dialogs
* - command="close" closes dialogs
* - No onclick handlers for modal operations
*
* Browser support: Chrome/Edge 135+, Firefox Nightly, Safari TP
* @see https://developer.chrome.com/blog/command-and-commandfor
*/
import { chromium } from 'playwright';
const URL = "http://localhost:1999";
async function testInvokerCommands() {
console.log('🎯 HTML INVOKER COMMANDS API TEST\n');
console.log('='.repeat(70));
const browser = await chromium.launch({ headless: process.env.HEADLESS === 'true' });
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
const testResults = [];
await page.goto(URL);
await page.waitForTimeout(2000);
// ========================================================================
// TEST 1: Buttons have commandfor and command attributes
// ========================================================================
console.log('\n1️⃣ Testing buttons have commandfor/command attributes...');
const buttonsWithCommand = await page.evaluate(() => {
const buttons = document.querySelectorAll('[commandfor]');
return Array.from(buttons).map(btn => ({
id: btn.id || btn.className.split(' ')[0],
commandfor: btn.getAttribute('commandfor'),
command: btn.getAttribute('command'),
hasOnclick: btn.hasAttribute('onclick')
}));
});
console.log(` Found ${buttonsWithCommand.length} buttons with commandfor attribute:`);
buttonsWithCommand.forEach(btn => {
console.log(` - ${btn.id}: commandfor="${btn.commandfor}" command="${btn.command}" onclick=${btn.hasOnclick}`);
});
const hasCommandButtons = buttonsWithCommand.length >= 6; // At least 6 modal buttons
console.log(` ${hasCommandButtons ? '✅ PASS' : '❌ FAIL'} - At least 6 buttons use commandfor`);
testResults.push({ test: 'Buttons have commandfor attributes', passed: hasCommandButtons });
// ========================================================================
// TEST 2: No onclick for showModal/close
// ========================================================================
console.log('\n2️⃣ Testing no onclick handlers for modal operations...');
const noOnclickForModals = buttonsWithCommand.every(btn => !btn.hasOnclick);
console.log(` All command buttons without onclick: ${noOnclickForModals}`);
console.log(` ${noOnclickForModals ? '✅ PASS' : '❌ FAIL'} - No onclick handlers on command buttons`);
testResults.push({ test: 'No onclick on command buttons', passed: noOnclickForModals });
// ========================================================================
// TEST 3: Info button opens info-modal
// ========================================================================
console.log('\n3️⃣ Testing info button opens modal via command attribute...');
const infoButton = await page.$('#info-button');
if (infoButton) {
const infoAttrs = await page.$eval('#info-button', el => ({
commandfor: el.getAttribute('commandfor'),
command: el.getAttribute('command')
}));
console.log(` Info button: commandfor="${infoAttrs.commandfor}" command="${infoAttrs.command}"`);
// Click the button
await infoButton.click();
await page.waitForTimeout(500);
const infoModalOpen = await page.evaluate(() => {
const modal = document.getElementById('info-modal');
return modal && modal.hasAttribute('open');
});
console.log(` Info modal opened: ${infoModalOpen}`);
console.log(` ${infoModalOpen ? '✅ PASS' : '❌ FAIL'} - command="show-modal" works`);
testResults.push({ test: 'command="show-modal" opens dialog', passed: infoModalOpen });
// Test close button
if (infoModalOpen) {
const closeButton = await page.$('#info-modal [command="close"]');
if (closeButton) {
await closeButton.click();
await page.waitForTimeout(300);
const modalClosed = await page.evaluate(() => {
const modal = document.getElementById('info-modal');
return modal && !modal.hasAttribute('open');
});
console.log(` Info modal closed: ${modalClosed}`);
console.log(` ${modalClosed ? '✅ PASS' : '❌ FAIL'} - command="close" works`);
testResults.push({ test: 'command="close" closes dialog', passed: modalClosed });
} else {
console.log(' ⚠️ Close button with command="close" not found');
await page.keyboard.press('Escape');
testResults.push({ test: 'command="close" closes dialog', passed: false });
}
}
} else {
console.log(' ⚠️ Info button not found');
testResults.push({ test: 'command="show-modal" opens dialog', passed: false });
testResults.push({ test: 'command="close" closes dialog', passed: false });
}
// ========================================================================
// TEST 4: Contact button opens contact-modal
// ========================================================================
console.log('\n4️⃣ Testing contact button opens modal...');
const contactButton = await page.$('#contact-button');
if (contactButton) {
await contactButton.click();
await page.waitForTimeout(500);
const contactModalOpen = await page.evaluate(() => {
const modal = document.getElementById('contact-modal');
return modal && modal.hasAttribute('open');
});
console.log(` Contact modal opened: ${contactModalOpen}`);
console.log(` ${contactModalOpen ? '✅ PASS' : '❌ FAIL'} - Contact modal opens`);
testResults.push({ test: 'Contact modal opens via command', passed: contactModalOpen });
// Close with ESC
await page.keyboard.press('Escape');
await page.waitForTimeout(300);
} else {
console.log(' ⚠️ Contact button not found');
testResults.push({ test: 'Contact modal opens via command', passed: false });
}
// ========================================================================
// TEST 5: Shortcuts button opens shortcuts-modal
// ========================================================================
console.log('\n5️⃣ Testing shortcuts button opens modal...');
const shortcutsButton = await page.$('#shortcuts-button');
if (shortcutsButton) {
await shortcutsButton.click();
await page.waitForTimeout(500);
const shortcutsModalOpen = await page.evaluate(() => {
const modal = document.getElementById('shortcuts-modal');
return modal && modal.hasAttribute('open');
});
console.log(` Shortcuts modal opened: ${shortcutsModalOpen}`);
console.log(` ${shortcutsModalOpen ? '✅ PASS' : '❌ FAIL'} - Shortcuts modal opens`);
testResults.push({ test: 'Shortcuts modal opens via command', passed: shortcutsModalOpen });
// Close with ESC
await page.keyboard.press('Escape');
await page.waitForTimeout(300);
} else {
console.log(' ⚠️ Shortcuts button not found');
testResults.push({ test: 'Shortcuts modal opens via command', passed: false });
}
// ========================================================================
// TEST 6: PDF button in action bar opens pdf-modal
// ========================================================================
console.log('\n6️⃣ Testing PDF action button opens modal...');
const pdfActionButton = await page.$('#action-bar-pdf-btn');
if (pdfActionButton) {
const pdfAttrs = await page.$eval('#action-bar-pdf-btn', el => ({
commandfor: el.getAttribute('commandfor'),
command: el.getAttribute('command')
}));
console.log(` PDF button: commandfor="${pdfAttrs.commandfor}" command="${pdfAttrs.command}"`);
await pdfActionButton.click();
await page.waitForTimeout(500);
const pdfModalOpen = await page.evaluate(() => {
const modal = document.getElementById('pdf-modal');
return modal && modal.hasAttribute('open');
});
console.log(` PDF modal opened: ${pdfModalOpen}`);
console.log(` ${pdfModalOpen ? '✅ PASS' : '❌ FAIL'} - PDF modal opens via command`);
testResults.push({ test: 'PDF modal opens via command', passed: pdfModalOpen });
// Close with ESC
await page.keyboard.press('Escape');
await page.waitForTimeout(300);
} else {
console.log(' ⚠️ PDF action button not found');
testResults.push({ test: 'PDF modal opens via command', passed: false });
}
// ========================================================================
// 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`);
console.log("=".repeat(70) + "\n");
if (failedTests === 0) {
console.log("🎉 ALL HTML INVOKER COMMANDS TESTS PASSED!");
} else {
console.log("⚠️ SOME TESTS FAILED - See details above");
console.log(" Note: command/commandfor requires Chrome 135+, Edge 135+, Firefox Nightly, Safari TP");
}
// Auto-close after tests if HEADLESS env is set
if (process.env.HEADLESS === 'true') {
await browser.close();
process.exit(failedTests === 0 ? 0 : 1);
} else {
console.log("\nBrowser will stay open for manual inspection.");
console.log("Press Ctrl+C when done.\n");
await new Promise(() => {}); // Keep browser open
}
}
await testInvokerCommands();