Multi-class add/remove, async IO in test runner — 280/831 (34%)
- Parser: add .foo .bar collects multiple class refs into multi-add-class AST - Compiler: multi-add-class/multi-remove-class emit (do (dom-add-class...) ...) - Test runner: drives IO suspension chains (wait/fetch/settle) via _driveAsync so async HS tests (wait 100ms, settle, fetch) can complete - Assertion failed: 51→49 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -653,6 +653,14 @@
|
||||
(quote dom-add-class)
|
||||
(hs-to-sx (nth ast 2))
|
||||
(nth ast 1)))
|
||||
((= head (quote multi-add-class))
|
||||
(let ((target (hs-to-sx (nth ast 1)))
|
||||
(classes (rest (rest ast))))
|
||||
(cons (quote do) (map (fn (cls) (list (quote dom-add-class) target cls)) classes))))
|
||||
((= head (quote multi-remove-class))
|
||||
(let ((target (hs-to-sx (nth ast 1)))
|
||||
(classes (rest (rest ast))))
|
||||
(cons (quote do) (map (fn (cls) (list (quote dom-remove-class) target cls)) classes))))
|
||||
((= head (quote remove-class))
|
||||
(list
|
||||
(quote dom-remove-class)
|
||||
|
||||
@@ -666,10 +666,20 @@
|
||||
(if
|
||||
(= (tp-type) "class")
|
||||
(let
|
||||
((cls (get (adv!) "value")))
|
||||
((cls (get (adv!) "value"))
|
||||
(extra-classes (list)))
|
||||
;; Collect additional class refs
|
||||
(define collect-classes!
|
||||
(fn ()
|
||||
(when (= (tp-type) "class")
|
||||
(set! extra-classes (append extra-classes (list (get (adv!) "value"))))
|
||||
(collect-classes!))))
|
||||
(collect-classes!)
|
||||
(let
|
||||
((tgt (parse-tgt-kw "to" (list (quote me)))))
|
||||
(list (quote add-class) cls tgt)))
|
||||
(if (empty? extra-classes)
|
||||
(list (quote add-class) cls tgt)
|
||||
(cons (quote multi-add-class) (cons tgt (cons cls extra-classes))))))
|
||||
nil)))
|
||||
(define
|
||||
parse-remove-cmd
|
||||
@@ -678,10 +688,19 @@
|
||||
(if
|
||||
(= (tp-type) "class")
|
||||
(let
|
||||
((cls (get (adv!) "value")))
|
||||
((cls (get (adv!) "value"))
|
||||
(extra-classes (list)))
|
||||
(define collect-classes!
|
||||
(fn ()
|
||||
(when (= (tp-type) "class")
|
||||
(set! extra-classes (append extra-classes (list (get (adv!) "value"))))
|
||||
(collect-classes!))))
|
||||
(collect-classes!)
|
||||
(let
|
||||
((tgt (parse-tgt-kw "from" (list (quote me)))))
|
||||
(list (quote remove-class) cls tgt)))
|
||||
(if (empty? extra-classes)
|
||||
(list (quote remove-class) cls tgt)
|
||||
(cons (quote multi-remove-class) (cons tgt (cons cls extra-classes))))))
|
||||
nil)))
|
||||
(define
|
||||
parse-toggle-cmd
|
||||
|
||||
@@ -653,6 +653,14 @@
|
||||
(quote dom-add-class)
|
||||
(hs-to-sx (nth ast 2))
|
||||
(nth ast 1)))
|
||||
((= head (quote multi-add-class))
|
||||
(let ((target (hs-to-sx (nth ast 1)))
|
||||
(classes (rest (rest ast))))
|
||||
(cons (quote do) (map (fn (cls) (list (quote dom-add-class) target cls)) classes))))
|
||||
((= head (quote multi-remove-class))
|
||||
(let ((target (hs-to-sx (nth ast 1)))
|
||||
(classes (rest (rest ast))))
|
||||
(cons (quote do) (map (fn (cls) (list (quote dom-remove-class) target cls)) classes))))
|
||||
((= head (quote remove-class))
|
||||
(list
|
||||
(quote dom-remove-class)
|
||||
|
||||
@@ -666,10 +666,20 @@
|
||||
(if
|
||||
(= (tp-type) "class")
|
||||
(let
|
||||
((cls (get (adv!) "value")))
|
||||
((cls (get (adv!) "value"))
|
||||
(extra-classes (list)))
|
||||
;; Collect additional class refs
|
||||
(define collect-classes!
|
||||
(fn ()
|
||||
(when (= (tp-type) "class")
|
||||
(set! extra-classes (append extra-classes (list (get (adv!) "value"))))
|
||||
(collect-classes!))))
|
||||
(collect-classes!)
|
||||
(let
|
||||
((tgt (parse-tgt-kw "to" (list (quote me)))))
|
||||
(list (quote add-class) cls tgt)))
|
||||
(if (empty? extra-classes)
|
||||
(list (quote add-class) cls tgt)
|
||||
(cons (quote multi-add-class) (cons tgt (cons cls extra-classes))))))
|
||||
nil)))
|
||||
(define
|
||||
parse-remove-cmd
|
||||
@@ -678,10 +688,19 @@
|
||||
(if
|
||||
(= (tp-type) "class")
|
||||
(let
|
||||
((cls (get (adv!) "value")))
|
||||
((cls (get (adv!) "value"))
|
||||
(extra-classes (list)))
|
||||
(define collect-classes!
|
||||
(fn ()
|
||||
(when (= (tp-type) "class")
|
||||
(set! extra-classes (append extra-classes (list (get (adv!) "value"))))
|
||||
(collect-classes!))))
|
||||
(collect-classes!)
|
||||
(let
|
||||
((tgt (parse-tgt-kw "from" (list (quote me)))))
|
||||
(list (quote remove-class) cls tgt)))
|
||||
(if (empty? extra-classes)
|
||||
(list (quote remove-class) cls tgt)
|
||||
(cons (quote multi-remove-class) (cons tgt (cons cls extra-classes))))))
|
||||
nil)))
|
||||
(define
|
||||
parse-toggle-cmd
|
||||
|
||||
@@ -198,16 +198,14 @@ test.describe('Hyperscript behavioral tests', () => {
|
||||
let result;
|
||||
try {
|
||||
result = await Promise.race([
|
||||
page.evaluate(idx => {
|
||||
page.evaluate(async (idx) => {
|
||||
const K = window.SxKernel;
|
||||
// Thorough cleanup: replace body to kill all event listeners
|
||||
const newBody = document.createElement('body');
|
||||
document.documentElement.replaceChild(newBody, document.body);
|
||||
|
||||
const thunk = K.eval(`(get (nth _test-registry ${idx}) "thunk")`);
|
||||
if (!thunk) return { p: false, e: 'no thunk' };
|
||||
|
||||
// Capture errors — only from THIS test's execution
|
||||
let lastErr = null;
|
||||
const orig = console.error;
|
||||
console.error = function() {
|
||||
@@ -215,12 +213,52 @@ test.describe('Hyperscript behavioral tests', () => {
|
||||
if (m.startsWith('[sx]')) lastErr = m;
|
||||
orig.apply(console, arguments);
|
||||
};
|
||||
|
||||
// Drive async suspension chains (wait, fetch, etc.)
|
||||
let pending = 0;
|
||||
const oldDrive = window._driveAsync;
|
||||
window._driveAsync = function driveAsync(result) {
|
||||
if (!result || !result.suspended) return;
|
||||
pending++;
|
||||
const req = result.request;
|
||||
const items = req && (req.items || req);
|
||||
const op = items && items[0];
|
||||
const opName = typeof op === 'string' ? op : (op && op.name) || String(op);
|
||||
const arg = items && items[1];
|
||||
function doResume(val, delay) {
|
||||
setTimeout(() => {
|
||||
try { const r = result.resume(val); pending--; driveAsync(r); }
|
||||
catch(e) { pending--; }
|
||||
}, delay);
|
||||
}
|
||||
if (opName === 'io-sleep' || opName === 'wait') doResume(null, Math.min(typeof arg === 'number' ? arg : 0, 10));
|
||||
else if (opName === 'io-fetch') doResume({ok: true, text: ''}, 1);
|
||||
else if (opName === 'io-settle') doResume(null, 5);
|
||||
else if (opName === 'io-wait-event') doResume(null, 5);
|
||||
else pending--;
|
||||
};
|
||||
|
||||
try {
|
||||
K.callFn(thunk, []);
|
||||
const r = K.callFn(thunk, []);
|
||||
// If thunk itself suspended, drive it
|
||||
if (r && r.suspended) window._driveAsync(r);
|
||||
// Wait for all pending async chains to settle
|
||||
if (pending > 0) {
|
||||
await new Promise(resolve => {
|
||||
let waited = 0;
|
||||
const check = () => {
|
||||
if (pending <= 0 || waited > 2000) resolve();
|
||||
else { waited += 10; setTimeout(check, 10); }
|
||||
};
|
||||
setTimeout(check, 10);
|
||||
});
|
||||
}
|
||||
console.error = orig;
|
||||
window._driveAsync = oldDrive;
|
||||
return lastErr ? { p: false, e: lastErr.replace(/[\\"]/g, ' ').slice(0, 150) } : { p: true, e: null };
|
||||
} catch(e) {
|
||||
console.error = orig;
|
||||
window._driveAsync = oldDrive;
|
||||
return { p: false, e: (e.message || '').replace(/[\\"]/g, ' ').slice(0, 150) };
|
||||
}
|
||||
}, i),
|
||||
@@ -285,9 +323,9 @@ test.describe('Hyperscript behavioral tests', () => {
|
||||
console.log(` [${info.count}x] ${e}`);
|
||||
}
|
||||
// Show samples of "bar" error specifically
|
||||
const barSamples = results.filter(r => !r.p && (r.e||'').match(/exception: \w+ *$/)).slice(0, 10);
|
||||
const barSamples = results.filter(r => !r.p && (r.e||'').includes('Assertion failed')).slice(0, 20);
|
||||
if (barSamples.length > 0) {
|
||||
console.log(` "bar" error samples (${barSamples.length}):`);
|
||||
console.log(` Assertion failures (${barSamples.length}):`);
|
||||
for (const s of barSamples) console.log(` ${s.s}/${s.n}`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user