Files

209 lines
8.2 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env bun
/**
* CMD+K API SCROLL POSITION TEST
* ==============================
* Tests the /api/cmd-k endpoint integration with ninja-keys command palette.
*
* Tests:
* 1. API returns valid JSON with experiences, projects, courses
* 2. ninja-keys receives data from API (not hardcoded)
* 3. Scroll navigation works for dynamic entries:
* - Experience items (e.g., Olympic Broadcasting)
* - Project items (e.g., Somos Una Ola)
* - Course items (e.g., Codecademy Certifications)
* 4. Static navigation actions still work (e.g., Skills section)
*
* Related: doc/16-CMD-K-API.md
*/
import { chromium } from 'playwright';
const URL = "http://localhost:1999";
async function testCmdKScroll() {
console.log('🎯 CMD+K SCROLL POSITION 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 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...");
await page.goto(URL);
await page.waitForTimeout(2000);
// Helper function to check if element is in viewport
async function isElementInViewport(selector) {
return await page.evaluate((sel) => {
const el = document.querySelector(sel);
if (!el) return { found: false };
const rect = el.getBoundingClientRect();
return {
found: true,
top: rect.top,
inViewport: rect.top >= 0 && rect.top < window.innerHeight
};
}, selector);
}
// Helper function to scroll to top
async function scrollToTop() {
await page.evaluate(() => window.scrollTo(0, 0));
await page.waitForTimeout(300);
}
// Helper function to invoke ninja-keys action directly by ID
async function invokeNinjaKeyAction(actionId) {
const result = await page.evaluate((id) => {
const nk = document.getElementById('cmd-k-bar');
if (!nk || !nk.data) return { success: false, reason: 'ninja-keys not found' };
const action = nk.data.find(a => a.id === id);
if (!action) return { success: false, reason: `action ${id} not found` };
if (action.handler) {
action.handler();
return { success: true, actionTitle: action.title };
}
return { success: false, reason: 'no handler' };
}, actionId);
console.log(` [DEBUG] Action "${actionId}": ${result.success ? 'invoked ' + result.actionTitle : result.reason}`);
await page.waitForTimeout(1000);
return result.success;
}
// ========================================================================
// TEST 1: Scroll to Experience item (Olympic Broadcasting)
// ========================================================================
console.log("\n2️⃣ Testing scroll to Experience (Olympic Broadcasting)...");
await scrollToTop();
const expBefore = await isElementInViewport('#exp-olympic-broadcasting');
console.log(` Element found: ${expBefore.found}`);
console.log(` Before: Element top position = ${expBefore.top?.toFixed(0) || 'N/A'}`);
// API now returns exp-olympic-broadcasting as the action ID
await invokeNinjaKeyAction('exp-olympic-broadcasting');
const expAfter = await isElementInViewport('#exp-olympic-broadcasting');
console.log(` After: Element top position = ${expAfter.top?.toFixed(0) || 'N/A'}`);
const expPassed = expBefore.found && expAfter.found && expAfter.inViewport;
console.log(` ${expPassed ? '✅ PASS' : '❌ FAIL'} - Scrolled to Olympic Broadcasting`);
testResults.push({ test: 'Experience - Olympic Broadcasting', passed: expPassed });
// ========================================================================
// TEST 2: Scroll to Project item (Somos Una Ola)
// ========================================================================
console.log("\n3️⃣ Testing scroll to Project (Somos Una Ola)...");
await scrollToTop();
const projBefore = await isElementInViewport('#proj-somos-una-ola');
console.log(` Element found: ${projBefore.found}`);
console.log(` Before: Element top position = ${projBefore.top?.toFixed(0) || 'N/A'}`);
// API now returns proj-somos-una-ola as the action ID
await invokeNinjaKeyAction('proj-somos-una-ola');
const projAfter = await isElementInViewport('#proj-somos-una-ola');
console.log(` After: Element top position = ${projAfter.top?.toFixed(0) || 'N/A'}`);
const projPassed = projBefore.found && projAfter.found && projAfter.inViewport;
console.log(` ${projPassed ? '✅ PASS' : '❌ FAIL'} - Scrolled to Somos Una Ola`);
testResults.push({ test: 'Project - Somos Una Ola', passed: projPassed });
// ========================================================================
// TEST 3: Scroll to Course item (Codecademy)
// ========================================================================
console.log("\n4️⃣ Testing scroll to Course (Codecademy)...");
await scrollToTop();
const courseBefore = await isElementInViewport('#course-codecademy-certifications');
console.log(` Element found: ${courseBefore.found}`);
console.log(` Before: Element top position = ${courseBefore.top?.toFixed(0) || 'N/A'}`);
// API now returns course-codecademy-certifications as the action ID
await invokeNinjaKeyAction('course-codecademy-certifications');
const courseAfter = await isElementInViewport('#course-codecademy-certifications');
console.log(` After: Element top position = ${courseAfter.top?.toFixed(0) || 'N/A'}`);
const coursePassed = courseBefore.found && courseAfter.found && courseAfter.inViewport;
console.log(` ${coursePassed ? '✅ PASS' : '❌ FAIL'} - Scrolled to Codecademy Certifications`);
testResults.push({ test: 'Course - Codecademy', passed: coursePassed });
// ========================================================================
// TEST 4: Scroll to section (Skills)
// ========================================================================
console.log("\n5️⃣ Testing scroll to Section (Skills)...");
await scrollToTop();
const skillsBefore = await isElementInViewport('#skills');
console.log(` Element found: ${skillsBefore.found}`);
console.log(` Before: Element top position = ${skillsBefore.top?.toFixed(0) || 'N/A'}`);
await invokeNinjaKeyAction('nav-skills');
const skillsAfter = await isElementInViewport('#skills');
console.log(` After: Element top position = ${skillsAfter.top?.toFixed(0) || 'N/A'}`);
const skillsPassed = skillsBefore.found && skillsAfter.found && skillsAfter.inViewport;
console.log(` ${skillsPassed ? '✅ PASS' : '❌ FAIL'} - Scrolled to Skills section`);
testResults.push({ test: 'Section - Skills', passed: skillsPassed });
// ========================================================================
// 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 FOUND:\n`);
errors.forEach((err, i) => {
console.log(`${i + 1}. ${err}`);
});
}
console.log("=".repeat(70) + "\n");
if (failedTests === 0 && errors.length === 0) {
console.log("🎉 ALL CMD+K SCROLL TESTS PASSED!");
} else {
console.log("⚠️ SOME TESTS FAILED - See details above");
}
// Auto-close after tests if HEADLESS env is set, otherwise keep open
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 testCmdKScroll();