#!/usr/bin/env node /** * Nexus Test Harness * Validates the scene loads without errors using only Node.js built-ins. * Run: node test.js */ import { execSync } from 'child_process'; import { readFileSync, statSync } from 'fs'; import { resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); let passed = 0; let failed = 0; function pass(name) { console.log(` ✓ ${name}`); passed++; } function fail(name, reason) { console.log(` ✗ ${name}`); if (reason) console.log(` → ${reason}`); failed++; } function section(name) { console.log(`\n${name}`); } // ── Syntax checks ────────────────────────────────────────────────────────── section('JS Syntax'); for (const file of ['app.js', 'ws-client.js']) { try { execSync(`node --check ${resolve(__dirname, file)}`, { stdio: 'pipe' }); pass(`${file} parses without syntax errors`); } catch (e) { fail(`${file} syntax check`, e.stderr?.toString().trim() || e.message); } } // ── File size budget ──────────────────────────────────────────────────────── section('File Size Budget (< 500 KB)'); for (const file of ['app.js', 'ws-client.js']) { try { const bytes = statSync(resolve(__dirname, file)).size; const kb = (bytes / 1024).toFixed(1); if (bytes < 500 * 1024) { pass(`${file} is ${kb} KB`); } else { fail(`${file} exceeds 500 KB budget`, `${kb} KB`); } } catch (e) { fail(`${file} size check`, e.message); } } // ── JSON validation ───────────────────────────────────────────────────────── section('JSON Files'); for (const file of ['manifest.json', 'portals.json', 'vision.json']) { try { const raw = readFileSync(resolve(__dirname, file), 'utf8'); JSON.parse(raw); pass(`${file} is valid JSON`); } catch (e) { fail(`${file}`, e.message); } } // ── HTML structure ────────────────────────────────────────────────────────── section('HTML Structure (index.html)'); const html = (() => { try { return readFileSync(resolve(__dirname, 'index.html'), 'utf8'); } catch (e) { fail('index.html readable', e.message); return ''; } })(); if (html) { const checks = [ ['DOCTYPE declaration', //i], [' attribute', /]+lang=/i], ['charset meta tag', /]+charset/i], ['viewport meta tag', /]+viewport/i], [' tag', /<title>[^<]+<\/title>/i], ['importmap script', /<script[^>]+type="importmap"/i], ['three.js in importmap', /"three"\s*:/], ['app.js module script', /<script[^>]+type="module"[^>]+src="app\.js"/i], ['debug-toggle element', /id="debug-toggle"/], ['</html> closing tag', /<\/html>/i], ]; for (const [name, re] of checks) { if (re.test(html)) { pass(name); } else { fail(name, `pattern not found: ${re}`); } } } // ── app.js static analysis ────────────────────────────────────────────────── section('app.js Scene Components'); const appJs = (() => { try { return readFileSync(resolve(__dirname, 'app.js'), 'utf8'); } catch (e) { fail('app.js readable', e.message); return ''; } })(); if (appJs) { const checks = [ ['NEXUS.colors palette defined', /const NEXUS\s*=\s*\{/], ['THREE.Scene created', /new THREE\.Scene\(\)/], ['THREE.PerspectiveCamera created', /new THREE\.PerspectiveCamera\(/], ['THREE.WebGLRenderer created', /new THREE\.WebGLRenderer\(/], ['renderer appended to DOM', /document\.body\.appendChild\(renderer\.domElement\)/], ['animate function defined', /function animate\s*\(\)/], ['requestAnimationFrame called', /requestAnimationFrame\(animate\)/], ['renderer.render called', /renderer\.render\(scene,\s*camera\)/], ['resize handler registered', /addEventListener\(['"]resize['"]/], ['clock defined', /new THREE\.Clock\(\)/], ['star field created', /new THREE\.Points\(/], ['constellation lines built', /buildConstellationLines/], ['ws-client imported', /import.*ws-client/], ['wsClient.connect called', /wsClient\.connect\(\)/], ]; for (const [name, re] of checks) { if (re.test(appJs)) { pass(name); } else { fail(name, `pattern not found: ${re}`); } } } // ── Summary ───────────────────────────────────────────────────────────────── console.log(`\n${'─'.repeat(50)}`); console.log(`Results: ${passed} passed, ${failed} failed`); if (failed > 0) { console.log('\nSome tests failed. Fix the issues above before committing.\n'); process.exit(1); } else { console.log('\nAll tests passed.\n'); }