Files
rose-ash/tests/hs-parse-audit.js
giles 5c42f4842b Tests: cek-try-seq / htmx / hs-diag / perform-chain + node HS runners
New spec tests: test-cek-try-seq (CEK try/seq), test-htmx (htmx
directive coverage, 292L), test-hs-diag, test-perform-chain (IO
suspension chains).

tests/hs-*.js: Node.js-side hyperscript runners for browser-mode
testing (hs-behavioral-node, hs-behavioral-runner, hs-parse-audit,
hs-run-timed).

Vendors shared/static/scripts/htmx.min.js.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 09:09:27 +00:00

118 lines
4.2 KiB
JavaScript

#!/usr/bin/env node
/**
* Audit: extract all HS sources from behavioral tests and check parse/compile.
* Uses child_process.execSync with timeout to handle hangs.
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const PROJECT = path.resolve(__dirname, '..');
// Extract HS sources from test file with their suite/test names
const testSx = fs.readFileSync(path.join(PROJECT, 'spec/tests/test-hyperscript-behavioral.sx'), 'utf8');
// Extract suites and their test counts from comments
const suitePattern = /;; ── (\S+) \((\d+) tests\)/g;
const suites = [];
let m;
while ((m = suitePattern.exec(testSx)) !== null) {
suites.push({ name: m[1], count: parseInt(m[2]) });
}
console.log('Test suites (831 total):');
let grandTotal = 0;
for (const s of suites) {
grandTotal += s.count;
console.log(` ${s.name}: ${s.count}`);
}
console.log(` TOTAL: ${grandTotal}`);
// Categorize tests by type: DOM-action tests vs eval-only tests vs stub tests
const lines = testSx.split('\n');
let currentSuite = '';
let inDeftest = false;
let testName = '';
let testBody = '';
let depth = 0;
const testCategories = {};
for (const line of lines) {
const suiteMatch = line.match(/\(defsuite\s+"([^"]+)"/);
if (suiteMatch) { currentSuite = suiteMatch[1]; continue; }
const testMatch = line.match(/\(deftest\s+"([^"]+)"/);
if (testMatch) { inDeftest = true; testName = testMatch[1]; testBody = line; depth = 1; continue; }
if (inDeftest) {
testBody += '\n' + line;
depth += (line.match(/\(/g) || []).length - (line.match(/\)/g) || []).length;
if (depth <= 0) {
// Categorize this test
if (!testCategories[currentSuite]) testCategories[currentSuite] = { domAction: 0, evalOnly: 0, stub: 0, notImpl: 0, names: [] };
if (testBody.includes('NOT IMPLEMENTED') || testBody.includes('not-implemented')) {
testCategories[currentSuite].stub++;
} else if (testBody.includes('dom-dispatch') || testBody.includes('dom-set-inner-html') && testBody.includes('hs-activate!')) {
testCategories[currentSuite].domAction++;
testCategories[currentSuite].names.push(testName);
} else if (testBody.includes('eval-hs') || testBody.includes('hs-eval')) {
testCategories[currentSuite].evalOnly++;
} else {
testCategories[currentSuite].domAction++;
testCategories[currentSuite].names.push(testName);
}
inDeftest = false;
testBody = '';
}
}
}
// Count unique HS sources
const attrPattern = /dom-set-attr\s+\S+\s+"_"\s+"([^"]+)"/g;
const hsSources = new Set();
while ((m = attrPattern.exec(testSx)) !== null) {
hsSources.add(m[1].replace(/\\"/g, '"'));
}
console.log(`\n${hsSources.size} unique HS sources across ${grandTotal} tests`);
// Analyze HS source complexity
const features = {
'on click': 0, 'on load': 0, 'on keyup': 0, 'on mouseenter': 0,
'on mutation': 0, 'on intersect': 0, 'on every': 0,
'add .': 0, 'remove .': 0, 'toggle .': 0, 'set ': 0, 'put ': 0,
'if ': 0, 'repeat': 0, 'wait ': 0, 'send ': 0, 'take ': 0,
'transition': 0, 'hide': 0, 'show': 0, 'log': 0,
'call ': 0, 'fetch ': 0, 'tell ': 0, 'halt': 0,
'morph': 0, 'go ': 0, 'append': 0, 'settle': 0,
'def ': 0, 'increment': 0, 'decrement': 0,
'bind ': 0, 'closest': 0, 'in ': 0, 'as ': 0,
'init': 0, 'every ': 0, 'measure': 0,
};
for (const src of hsSources) {
for (const feat of Object.keys(features)) {
if (src.includes(feat)) features[feat]++;
}
}
console.log('\nFeature usage in test sources:');
for (const [feat, count] of Object.entries(features).sort((a,b) => b[1] - a[1])) {
if (count > 0) console.log(` ${feat.trim()}: ${count}`);
}
// Summarize categories
console.log('\nPer-suite breakdown:');
let totalDom = 0, totalEval = 0, totalStub = 0;
for (const [suite, cat] of Object.entries(testCategories).sort((a,b) => (b[1].domAction+b[1].evalOnly) - (a[1].domAction+a[1].evalOnly))) {
const total = cat.domAction + cat.evalOnly + cat.stub;
totalDom += cat.domAction;
totalEval += cat.evalOnly;
totalStub += cat.stub;
console.log(` ${suite}: ${total} tests (${cat.domAction} DOM, ${cat.evalOnly} eval, ${cat.stub} stub)`);
}
console.log(`\nTotals: ${totalDom} DOM-action, ${totalEval} eval-only, ${totalStub} stubs`);