<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Fjorth — QUnit Tests</title>
<!-- Prefer local QUnit assets to avoid network prompts; if unavailable, swap to CDN manually -->
<link rel="stylesheet" href="qunit.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<!-- Interpreter (callback errors) -->
<script src="fjorth.js"></script>
<!-- QUnit (local preferred) -->
<script id="qunit-js" src="qunit.js"></script>
<!-- Tests (ported to QUnit) -->
<script src="test.js"> </script>
<!-- Prefer local QUnit script; provide CDN fallback comment if needed -->
<!-- If local qunit.js isn't available in your environment, replace the line above with:
<script id="qunit-js" src="https://app.unpkg.com/qunit@2.24.1/files/qunit/qunit.js"></script>
-->
<!-- Additional tests: Structured Error Handling -->
<script>
(function(){
function runCodeExpectErr(code, vmOpts={}) {
return new Promise((resolve) => {
const seen=[];
const vm = new Fjorth(Object.assign({ onError: e => seen.push(e), stepBudget: 2000, sliceMs: 0 }, vmOpts));
try {
vm.reset();
vm.load(code);
vm.run((err) => {
resolve({ vm, err, seen });
});
} catch (e) {
resolve({ vm, err: e, seen: [...seen, e] });
}
});
}
function startErrTests(){
QUnit.module('Structured Error Handling (callback API)');
QUnit.test('undefined word: onError + idle get same Error with rich fields', async assert => {
const { vm, err, seen } = await runCodeExpectErr('NOPE');
assert.ok(err instanceof Error, 'idle callback receives Error');
assert.strictEqual(seen.length, 1, 'onError called once');
assert.strictEqual(seen[0], err, 'onError error === idle error');
assert.strictEqual(err.code, 'undefined_word');
assert.strictEqual(err.phase, 'interpret');
assert.strictEqual(err.token, 'NOPE');
assert.strictEqual(vm.lastError, err, 'vm.lastError points to same error');
assert.strictEqual(vm.running, false, 'vm halted');
});
QUnit.test('stack underflow: structured exec error and halt, no further tokens executed', async assert => {
const { vm, err } = await runCodeExpectErr('DUP 1 2 +');
assert.strictEqual(err.code, 'stack_underflow');
assert.strictEqual(err.phase, 'exec');
assert.ok(typeof err.stackDepth === 'number', 'err.stackDepth is number');
assert.ok(typeof err.rstackDepth === 'number', 'err.rstackDepth is number');
assert.ok(typeof err.ip === 'number' || typeof err.ip === 'undefined', 'err.ip present or undefined');
assert.strictEqual(vm.D.length, 0, 'no subsequent execution after error');
});
QUnit.test('rstack underflow: structured exec error', async assert => {
const { err } = await runCodeExpectErr('R>');
assert.strictEqual(err.code, 'rstack_underflow');
assert.strictEqual(err.phase, 'exec');
});
QUnit.test('compile-time misuse: ELSE outside compile', async assert => {
const { err } = await runCodeExpectErr('ELSE');
assert.strictEqual(err.code, 'else_outside_compile');
assert.strictEqual(err.phase, 'compile');
});
QUnit.test('compile-time: ":" with no name', async assert => {
const { err } = await runCodeExpectErr(':');
assert.strictEqual(err.code, 'colon_no_name');
assert.strictEqual(err.phase, 'compile');
});
QUnit.test('errors do not throw synchronously from run()', assert => {
assert.expect(1);
const vm = new Fjorth({});
vm.reset();
vm.load('NOPE');
let threw = false;
try { vm.run(() => {}); } catch (e) { threw = true; }
assert.strictEqual(threw, false, 'no synchronous throw');
});
}
if (window.QUnit) {
startErrTests();
} else {
window.addEventListener('load', () => { if (window.QUnit) startErrTests(); });
}
})();
</script>
</body>
</html>