Files
the-nexus/test.js

151 lines
5.4 KiB
JavaScript
Raw Normal View History

#!/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', /<!DOCTYPE html>/i],
['<html lang> attribute', /<html[^>]+lang=/i],
['charset meta tag', /<meta[^>]+charset/i],
['viewport meta tag', /<meta[^>]+viewport/i],
['<title> 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');
}