eyeling 1.24.13 → 1.24.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HANDBOOK.md +21 -14
- package/package.json +1 -1
- package/test/playground.test.js +120 -9
package/HANDBOOK.md
CHANGED
|
@@ -3682,7 +3682,7 @@ That is exactly the sort of explanation that N3, and Eyeling in particular, can
|
|
|
3682
3682
|
|
|
3683
3683
|
## Appendix I — The Eyeling Playground
|
|
3684
3684
|
|
|
3685
|
-
The **Eyeling Playground** is the browser-based front end for experimenting with Eyeling without a local install or command-line workflow. It is meant for teaching, quick debugging, live demos, and shareable reasoning examples. Rather than treating reasoning as an offline batch process, the playground makes it interactive: users can edit N3 directly in the browser, load remote N3 from a URL, run reasoning, inspect streamed output, and
|
|
3685
|
+
The **Eyeling Playground** is the browser-based front end for experimenting with Eyeling without a local install or command-line workflow. It is meant for teaching, quick debugging, live demos, and shareable reasoning examples. Rather than treating reasoning as an offline batch process, the playground makes it interactive: users can edit N3 directly in the browser, load remote N3 from a URL, run reasoning, inspect streamed or rendered output, autosave local state, and create a compact share link when needed.
|
|
3686
3686
|
|
|
3687
3687
|
This appendix explains what the playground is for, how it is structured, and why it matters in practice.
|
|
3688
3688
|
|
|
@@ -3696,7 +3696,8 @@ The playground exists to lower that initial friction. It lets a user:
|
|
|
3696
3696
|
- edit or paste a small N3 program,
|
|
3697
3697
|
- run reasoning immediately,
|
|
3698
3698
|
- inspect output and errors in place,
|
|
3699
|
-
-
|
|
3699
|
+
- autosave local work between reloads,
|
|
3700
|
+
- and copy a compact share link when the setup should be shared.
|
|
3700
3701
|
|
|
3701
3702
|
That makes the playground useful not only for newcomers, but also for experienced users who want a fast feedback loop for small examples.
|
|
3702
3703
|
|
|
@@ -3714,6 +3715,8 @@ A key recent addition is **background knowledge mode**. When enabled, the N3 loa
|
|
|
3714
3715
|
|
|
3715
3716
|
That separation is helpful both pedagogically and practically. It mirrors real reasoning work, where a user often reasons _over_ a fixed body of data rather than constantly rewriting it.
|
|
3716
3717
|
|
|
3718
|
+
For repository examples, the playground also follows the same sidecar-input convention as the example test runner. When a loaded URL looks like `.../examples/name.n3`, the playground probes for `.../examples/input/name.trig`. If that companion TriG file exists, it is loaded automatically as background evidence and the run uses RDF/TriG compatibility mode, matching the command-line `-r` behavior used by `npm run test:examples`.
|
|
3719
|
+
|
|
3717
3720
|
### I.3 Execution behavior
|
|
3718
3721
|
|
|
3719
3722
|
The playground is designed to feel responsive even when reasoning is not trivial. To do that, it uses a browser execution model that can run inference in a worker rather than blocking the main UI thread. Output is then surfaced back into the page.
|
|
@@ -3728,6 +3731,10 @@ This matters because the playground is not just a text box plus a submit button.
|
|
|
3728
3731
|
|
|
3729
3732
|
The output behavior also adapts to the kind of N3 program being run. In some cases the natural result is a streamed list of derived triples. In others, such as programs using output-oriented constructs like `log:outputString`, a rendered text result is more appropriate. The playground supports both styles.
|
|
3730
3733
|
|
|
3734
|
+
For Markdown-oriented `log:outputString` examples, the output pane has two views: a rendered Markdown view and a Markdown source view. The rendered view is selected by default when the output is text intended for presentation, while the source view keeps the exact generated Markdown available for copying, inspection, or comparison.
|
|
3735
|
+
|
|
3736
|
+
Repository example reports often contain relative source links that are written for the checked-in files under `examples/output/*.md`. When such an example is loaded into the playground from a raw URL, the rendered Markdown view resolves those relative links against the corresponding static output page rather than against `/playground`, so links like `../name.n3` and `../input/name.trig` continue to point to the intended example files.
|
|
3737
|
+
|
|
3731
3738
|
### I.4 Error handling and explainability
|
|
3732
3739
|
|
|
3733
3740
|
For an interactive reasoning environment, error behavior matters almost as much as successful output. The playground therefore gives particular attention to syntax and runtime feedback.
|
|
@@ -3741,21 +3748,21 @@ The playground also exposes two configuration toggles that are especially useful
|
|
|
3741
3748
|
|
|
3742
3749
|
Together these choices make the playground better suited to live explanation, teaching, and debugging than a minimal browser wrapper would be.
|
|
3743
3750
|
|
|
3744
|
-
### I.5
|
|
3751
|
+
### I.5 Local state and compact share links
|
|
3752
|
+
|
|
3753
|
+
The playground deliberately separates ordinary editing from link sharing.
|
|
3745
3754
|
|
|
3746
|
-
|
|
3755
|
+
During normal use, the live browser URL is kept short. Editor content and UI state are autosaved in `localStorage`, so reloading the page can restore local work without continuously rewriting the address bar with a large encoded N3 program.
|
|
3747
3756
|
|
|
3748
|
-
|
|
3757
|
+
When a user does want a portable link, the **Copy share link** button creates one on demand:
|
|
3749
3758
|
|
|
3750
|
-
-
|
|
3751
|
-
-
|
|
3752
|
-
-
|
|
3753
|
-
- `proofcomments` — initializes the proof-comments checkbox,
|
|
3754
|
-
- `httpsderef` — initializes the HTTPS dereferencing checkbox.
|
|
3759
|
+
- unedited examples that were loaded from a URL can be shared as short `?url=...` links,
|
|
3760
|
+
- edited programs are shared with a compact compressed `?state=...` payload,
|
|
3761
|
+
- default option values are omitted from that payload to keep links small.
|
|
3755
3762
|
|
|
3756
|
-
This
|
|
3763
|
+
This keeps everyday use pleasant while preserving the important tutorial and issue-reporting workflow: a link can still capture the imported resource, the local editable overlay, background-knowledge mode, proof-comments mode, and HTTPS-dereferencing mode.
|
|
3757
3764
|
|
|
3758
|
-
|
|
3765
|
+
For compatibility, older `?edit=`, `?program=`, `?url=`, compact `?state=`, and hash-based links are still accepted when opened. The old `/demo` entry point is also kept as a redirect to the canonical `/playground` page.
|
|
3759
3766
|
|
|
3760
3767
|
### I.6 What the playground is good for
|
|
3761
3768
|
|
|
@@ -3775,7 +3782,7 @@ For short reasoning tasks, the playground can be a faster debugging surface than
|
|
|
3775
3782
|
|
|
3776
3783
|
#### I.6.4 Sharing examples
|
|
3777
3784
|
|
|
3778
|
-
A
|
|
3785
|
+
A copied share link can capture enough context for another person to reproduce an example quickly, without forcing the live browser URL to carry the full editor content during normal use. This is valuable in issue reports, discussions, teaching material, and public-facing demonstrations.
|
|
3779
3786
|
|
|
3780
3787
|
### I.7 Limits of the playground
|
|
3781
3788
|
|
|
@@ -3787,7 +3794,7 @@ In short: the playground is best thought of as a compact interactive front end f
|
|
|
3787
3794
|
|
|
3788
3795
|
### I.8 Why it matters
|
|
3789
3796
|
|
|
3790
|
-
The Eyeling Playground shows that N3 reasoning can be made substantially more approachable without flattening the underlying logic into a toy interface. A relatively small set of features — an editor, a URL loader, background knowledge mode, responsive execution, proof toggles, and
|
|
3797
|
+
The Eyeling Playground shows that N3 reasoning can be made substantially more approachable without flattening the underlying logic into a toy interface. A relatively small set of features — an editor, a URL loader, background knowledge mode, responsive execution, proof toggles, rendered Markdown output, local autosave, and compact share links — is enough to support serious educational and exploratory work.
|
|
3791
3798
|
|
|
3792
3799
|
That is the main value of the playground. It gives Eyeling a public-facing, browser-native environment where reasoning is not hidden behind setup overhead, and where examples can move easily between author, teacher, student, and reviewer.
|
|
3793
3800
|
|
package/package.json
CHANGED
package/test/playground.test.js
CHANGED
|
@@ -22,8 +22,18 @@ const TTY = process.stdout.isTTY;
|
|
|
22
22
|
const C = TTY
|
|
23
23
|
? { g: '\x1b[32m', r: '\x1b[31m', y: '\x1b[33m', dim: '\x1b[2m', n: '\x1b[0m' }
|
|
24
24
|
: { g: '', r: '', y: '', dim: '', n: '' };
|
|
25
|
+
const msTag = (ms) => `${C.dim}(${ms} ms)${C.n}`;
|
|
26
|
+
|
|
27
|
+
const TOTAL_TESTS = 11;
|
|
28
|
+
const idxWidth = String(TOTAL_TESTS).length;
|
|
29
|
+
let passed = 0;
|
|
30
|
+
let failed = 0;
|
|
31
|
+
let currentTest = null;
|
|
32
|
+
let nonTestFailure = false;
|
|
33
|
+
const suiteStart = Date.now();
|
|
34
|
+
|
|
25
35
|
function ok(msg) {
|
|
26
|
-
console.log(`${C.g}OK
|
|
36
|
+
console.log(`${C.g}OK${C.n} ${msg}`);
|
|
27
37
|
}
|
|
28
38
|
function info(msg) {
|
|
29
39
|
console.log(`${C.y}==${C.n} ${msg}`);
|
|
@@ -31,6 +41,36 @@ function info(msg) {
|
|
|
31
41
|
function fail(msg) {
|
|
32
42
|
console.error(`${C.r}FAIL${C.n} ${msg}`);
|
|
33
43
|
}
|
|
44
|
+
function beginTest(msg) {
|
|
45
|
+
currentTest = { msg, start: Date.now() };
|
|
46
|
+
}
|
|
47
|
+
function endTest() {
|
|
48
|
+
const tc = currentTest;
|
|
49
|
+
if (!tc) return;
|
|
50
|
+
const idx = String(passed + failed + 1).padStart(idxWidth, '0');
|
|
51
|
+
ok(`${idx} ${tc.msg} ${msTag(Date.now() - tc.start)}`);
|
|
52
|
+
passed += 1;
|
|
53
|
+
currentTest = null;
|
|
54
|
+
}
|
|
55
|
+
function recordCurrentFailure() {
|
|
56
|
+
const tc = currentTest;
|
|
57
|
+
if (!tc) return false;
|
|
58
|
+
const idx = String(passed + failed + 1).padStart(idxWidth, '0');
|
|
59
|
+
fail(`${idx} ${tc.msg} ${msTag(Date.now() - tc.start)}`);
|
|
60
|
+
failed += 1;
|
|
61
|
+
currentTest = null;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
function printSummary() {
|
|
65
|
+
console.log('');
|
|
66
|
+
const suiteMs = Date.now() - suiteStart;
|
|
67
|
+
info(`Total elapsed: ${suiteMs} ms (${(suiteMs / 1000).toFixed(2)} s)`);
|
|
68
|
+
if (failed === 0 && !nonTestFailure) {
|
|
69
|
+
ok(`All playground tests passed (${passed}/${TOTAL_TESTS})`);
|
|
70
|
+
} else {
|
|
71
|
+
fail(`Some playground tests failed (${passed}/${TOTAL_TESTS})`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
34
74
|
|
|
35
75
|
function guessContentType(p) {
|
|
36
76
|
const ext = path.extname(p).toLowerCase();
|
|
@@ -495,6 +535,8 @@ async function main() {
|
|
|
495
535
|
// Intercept CodeMirror + remote GitHub raw URLs (keep test deterministic).
|
|
496
536
|
const localPkg = fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8');
|
|
497
537
|
const localEyeling = fs.readFileSync(path.join(ROOT, 'eyeling.js'), 'utf8');
|
|
538
|
+
const localSmokeArithmetic = fs.readFileSync(path.join(ROOT, 'examples', 'smoke-arithmetic.n3'), 'utf8');
|
|
539
|
+
const localSmokeArithmeticTrig = fs.readFileSync(path.join(ROOT, 'examples', 'input', 'smoke-arithmetic.trig'), 'utf8');
|
|
498
540
|
const localSudoku = fs.readFileSync(path.join(ROOT, 'examples', 'sudoku.n3'), 'utf8');
|
|
499
541
|
const localSudokuBuiltin = fs.readFileSync(path.join(ROOT, 'examples', 'builtin', 'sudoku.js'), 'utf8');
|
|
500
542
|
|
|
@@ -526,6 +568,14 @@ async function main() {
|
|
|
526
568
|
'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/eyeling.js',
|
|
527
569
|
{ ct: 'application/javascript', body: localEyeling },
|
|
528
570
|
],
|
|
571
|
+
[
|
|
572
|
+
'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/smoke-arithmetic.n3',
|
|
573
|
+
{ ct: 'text/plain', body: localSmokeArithmetic },
|
|
574
|
+
],
|
|
575
|
+
[
|
|
576
|
+
'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/input/smoke-arithmetic.trig',
|
|
577
|
+
{ ct: 'text/plain', body: localSmokeArithmeticTrig },
|
|
578
|
+
],
|
|
529
579
|
[
|
|
530
580
|
'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/sudoku.n3',
|
|
531
581
|
{ ct: 'text/plain', body: localSudoku },
|
|
@@ -534,6 +584,10 @@ async function main() {
|
|
|
534
584
|
'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/builtin/sudoku.js',
|
|
535
585
|
{ ct: 'application/javascript', body: localSudokuBuiltin },
|
|
536
586
|
],
|
|
587
|
+
[
|
|
588
|
+
'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/input/sudoku.trig',
|
|
589
|
+
{ code: 404, ct: 'text/plain', body: 'not found' },
|
|
590
|
+
],
|
|
537
591
|
]);
|
|
538
592
|
|
|
539
593
|
async function getText(url) {
|
|
@@ -551,13 +605,17 @@ async function main() {
|
|
|
551
605
|
});
|
|
552
606
|
}
|
|
553
607
|
|
|
608
|
+
beginTest('clean /playground URL serves the playground');
|
|
554
609
|
const cleanRes = await getText(cleanPlaygroundUrl);
|
|
555
610
|
assert.equal(cleanRes.statusCode, 200, 'clean /playground URL should serve the playground');
|
|
556
611
|
assert.match(cleanRes.body, /Eyeling N3 Playground/, 'clean /playground URL should load the playground');
|
|
612
|
+
endTest();
|
|
557
613
|
|
|
614
|
+
beginTest('legacy /demo URL serves the redirect page');
|
|
558
615
|
const legacyRes = await getText(legacyDemoUrl);
|
|
559
616
|
assert.equal(legacyRes.statusCode, 200, 'legacy /demo URL should serve redirect page');
|
|
560
617
|
assert.match(legacyRes.body, /playground/, 'legacy /demo URL should point to the playground');
|
|
618
|
+
endTest();
|
|
561
619
|
|
|
562
620
|
await cdp.send(
|
|
563
621
|
'Fetch.enable',
|
|
@@ -582,7 +640,7 @@ async function main() {
|
|
|
582
640
|
'Fetch.fulfillRequest',
|
|
583
641
|
{
|
|
584
642
|
requestId: p.requestId,
|
|
585
|
-
responseCode: 200,
|
|
643
|
+
responseCode: hit.code || 200,
|
|
586
644
|
responseHeaders: [
|
|
587
645
|
{ name: 'Content-Type', value: `${hit.ct}; charset=utf-8` },
|
|
588
646
|
{ name: 'Cache-Control', value: 'no-store' },
|
|
@@ -647,6 +705,7 @@ async function main() {
|
|
|
647
705
|
})
|
|
648
706
|
: [];
|
|
649
707
|
const renderedPanel = document.getElementById('output-rendered');
|
|
708
|
+
const outputTabs = document.querySelector('.output-tabs');
|
|
650
709
|
const renderedTab = document.getElementById('output-rendered-tab');
|
|
651
710
|
const sourceTab = document.getElementById('output-source-tab');
|
|
652
711
|
const sourceWrapper = document.getElementById('output-source');
|
|
@@ -659,9 +718,11 @@ async function main() {
|
|
|
659
718
|
renderedHtml: renderedPanel ? String(renderedPanel.innerHTML || '') : '',
|
|
660
719
|
renderedHidden: renderedPanel ? !!renderedPanel.hidden : true,
|
|
661
720
|
sourceHidden: sourceWrapper ? sourceWrapper.classList.contains('markdown-source-hidden') : true,
|
|
721
|
+
outputTabsHidden: outputTabs ? !!outputTabs.hidden : true,
|
|
662
722
|
renderedTabSelected: renderedTab ? renderedTab.getAttribute('aria-selected') === 'true' : false,
|
|
663
723
|
sourceTabSelected: sourceTab ? sourceTab.getAttribute('aria-selected') === 'true' : false,
|
|
664
724
|
shareStatus: document.getElementById('share-status') ? String(document.getElementById('share-status').textContent || '') : '',
|
|
725
|
+
backgroundStatus: document.getElementById('background-status') ? String(document.getElementById('background-status').textContent || '') : '',
|
|
665
726
|
href: String(window.location.href || ''),
|
|
666
727
|
highlighted,
|
|
667
728
|
};
|
|
@@ -758,6 +819,7 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
758
819
|
`;
|
|
759
820
|
|
|
760
821
|
// 1) Baseline smoke test: the default program runs to completion.
|
|
822
|
+
beginTest('playground runs the default Socrates program');
|
|
761
823
|
await clickRun();
|
|
762
824
|
const baseline = await waitForState(
|
|
763
825
|
'default program completion',
|
|
@@ -769,9 +831,13 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
769
831
|
);
|
|
770
832
|
assert.ok(typeof baseline.output === 'string' && baseline.output.length > 0, 'Expected non-empty output');
|
|
771
833
|
for (const [re, msg] of DEFAULT_PROGRAM_EXPECTS) assert.match(baseline.output, re, msg);
|
|
772
|
-
|
|
834
|
+
assert.equal(baseline.outputTabsHidden, true, 'Expected plain Turtle output to hide Markdown tabs');
|
|
835
|
+
assert.equal(baseline.renderedHidden, true, 'Expected plain Turtle output to skip rendered Markdown panel');
|
|
836
|
+
assert.equal(baseline.sourceHidden, false, 'Expected plain Turtle output to show source directly');
|
|
837
|
+
endTest();
|
|
773
838
|
|
|
774
839
|
// 2) N3 syntax errors should be shown in Output and highlight the offending line.
|
|
840
|
+
beginTest('playground shows syntax errors in Output and highlights the offending line');
|
|
775
841
|
await setProgram(syntaxErrorProgram);
|
|
776
842
|
await clickRun();
|
|
777
843
|
const syntaxErr = await waitForState(
|
|
@@ -783,9 +849,10 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
783
849
|
assert.match(syntaxErr.output, /\n\^\s*$/m, 'Expected caret line in syntax error output');
|
|
784
850
|
assert.equal(syntaxErr.highlighted[0].line, 3, 'Expected line 3 to be highlighted');
|
|
785
851
|
assert.equal(syntaxErr.highlighted[0].text, '^', 'Expected highlighted line text to match the broken line');
|
|
786
|
-
|
|
852
|
+
endTest();
|
|
787
853
|
|
|
788
854
|
// 3) Inference fuse output should be visible in the Output pane.
|
|
855
|
+
beginTest('playground clearly shows inference fuse output');
|
|
789
856
|
await setProgram(fuseProgram);
|
|
790
857
|
await clickRun();
|
|
791
858
|
const fuse = await waitForState(
|
|
@@ -799,9 +866,10 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
799
866
|
assert.match(fuse.output, /Inference fuse triggered\./i, 'Expected fuse message in Output');
|
|
800
867
|
assert.match(fuse.output, /Fired rule:/i, 'Expected fired rule explanation in Output');
|
|
801
868
|
assert.match(fuse.output, /Matched instance:/i, 'Expected matched instance in Output');
|
|
802
|
-
|
|
869
|
+
endTest();
|
|
803
870
|
|
|
804
871
|
// 4) log:outputString should render as clean text, not raw triples.
|
|
872
|
+
beginTest('playground renders log:outputString Markdown with Rendered/Markdown source tabs');
|
|
805
873
|
await setProgram(outputStringProgram);
|
|
806
874
|
await clickRun();
|
|
807
875
|
const rendered = await waitForState(
|
|
@@ -818,6 +886,7 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
818
886
|
/:report\s+log:outputString\s+"|# Derived triples/i,
|
|
819
887
|
'Expected clean rendered output without raw triples',
|
|
820
888
|
);
|
|
889
|
+
assert.equal(rendered.outputTabsHidden, false, 'Expected Markdown output tabs to be visible for log:outputString');
|
|
821
890
|
assert.equal(rendered.renderedHidden, false, 'Expected rendered Markdown tab to be visible by default');
|
|
822
891
|
assert.equal(rendered.sourceHidden, true, 'Expected Markdown source tab to be hidden by default');
|
|
823
892
|
assert.equal(rendered.renderedTabSelected, true, 'Expected Rendered tab to be selected by default');
|
|
@@ -828,6 +897,7 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
828
897
|
|
|
829
898
|
await clickOutputSourceTab();
|
|
830
899
|
const sourceView = await getPlaygroundState();
|
|
900
|
+
assert.equal(sourceView.outputTabsHidden, false, 'Expected Markdown output tabs to stay visible in source view');
|
|
831
901
|
assert.equal(sourceView.sourceTabSelected, true, 'Expected Markdown source tab to be selectable');
|
|
832
902
|
assert.equal(sourceView.renderedHidden, true, 'Expected rendered Markdown panel to hide after selecting source');
|
|
833
903
|
assert.equal(sourceView.sourceHidden, false, 'Expected source editor to show after selecting source');
|
|
@@ -836,17 +906,50 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
836
906
|
await clickOutputRenderedTab();
|
|
837
907
|
const renderedAgain = await getPlaygroundState();
|
|
838
908
|
assert.equal(renderedAgain.renderedTabSelected, true, 'Expected Rendered tab to be selectable again');
|
|
839
|
-
|
|
909
|
+
endTest();
|
|
840
910
|
|
|
841
911
|
// 5) Normal editing should not keep rewriting the browser URL with raw N3 content.
|
|
912
|
+
beginTest('playground keeps the live URL short and creates compact share links on demand');
|
|
842
913
|
assert.doesNotMatch(renderedAgain.href, /[?&](?:edit|program)=/, 'Expected live URL to avoid raw editor content');
|
|
843
914
|
const compactShareUrl = await makeShareUrlInPage();
|
|
844
915
|
assert.match(compactShareUrl, /[?&]state=/, 'Expected an on-demand compact state parameter');
|
|
845
916
|
assert.doesNotMatch(compactShareUrl, /[?&](?:edit|program)=/, 'Expected share link to avoid raw edit/program params');
|
|
846
917
|
assert.ok(compactShareUrl.length < playgroundUrl.length + encodeURIComponent(outputStringProgram).length, 'Expected compact share URL to be shorter than raw editor URL');
|
|
847
|
-
|
|
918
|
+
endTest();
|
|
919
|
+
|
|
920
|
+
// 6) URL-loaded examples should auto-load matching examples/input/<stem>.trig and run in RDF/TriG mode.
|
|
921
|
+
beginTest('playground auto-loads companion TriG sidecars and uses RDF/TriG mode');
|
|
922
|
+
await loadUrlIntoEditor('https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/smoke-arithmetic.n3');
|
|
923
|
+
const smokeLoaded = await waitForState(
|
|
924
|
+
'smoke-arithmetic URL loaded with companion TriG input',
|
|
925
|
+
(st) => /companion RDF\/TriG input/i.test(String(st.status || '')) && /input\/smoke-arithmetic\.trig/i.test(String(st.backgroundStatus || '')),
|
|
926
|
+
20000,
|
|
927
|
+
);
|
|
928
|
+
assert.match(smokeLoaded.backgroundStatus, /smoke-arithmetic\.trig/i, 'Expected companion TriG sidecar in background status');
|
|
929
|
+
await clickRun();
|
|
930
|
+
const smoke = await waitForState(
|
|
931
|
+
'URL-loaded smoke-arithmetic example completion with sidecar input',
|
|
932
|
+
(st) =>
|
|
933
|
+
String(st.status || '')
|
|
934
|
+
.trim()
|
|
935
|
+
.startsWith('Done') && /product = 42/i.test(String(st.output || '')),
|
|
936
|
+
30000,
|
|
937
|
+
);
|
|
938
|
+
assert.match(smoke.output, /product = 42/i, 'Expected result derived from companion TriG evidence');
|
|
939
|
+
assert.match(
|
|
940
|
+
smoke.renderedHtml,
|
|
941
|
+
new RegExp('href="' + started.baseUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '/examples/smoke-arithmetic\\.n3"'),
|
|
942
|
+
'Expected relative Markdown source links to resolve against the static output page, not /playground',
|
|
943
|
+
);
|
|
944
|
+
assert.match(
|
|
945
|
+
smoke.renderedHtml,
|
|
946
|
+
new RegExp('href="' + started.baseUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '/examples/input/smoke-arithmetic\\.trig"'),
|
|
947
|
+
'Expected relative Markdown TriG links to resolve against the static output page, not /playground',
|
|
948
|
+
);
|
|
949
|
+
endTest();
|
|
848
950
|
|
|
849
|
-
//
|
|
951
|
+
// 7) URL-loaded repository examples should auto-load matching examples/builtin/<stem>.js.
|
|
952
|
+
beginTest('playground auto-loads a companion example builtin for URL-loaded Sudoku');
|
|
850
953
|
await loadUrlIntoEditor('https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/sudoku.n3');
|
|
851
954
|
await waitForState(
|
|
852
955
|
'sudoku URL loaded with companion builtin',
|
|
@@ -867,14 +970,18 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
867
970
|
);
|
|
868
971
|
assert.match(sudoku.output, /Completed grid/i, 'Expected Sudoku rendered output');
|
|
869
972
|
assert.match(sudoku.output, /unique valid Sudoku solution/i, 'Expected Sudoku builtin-backed result');
|
|
870
|
-
|
|
973
|
+
endTest();
|
|
871
974
|
|
|
872
975
|
// Ensure no uncaught runtime exceptions.
|
|
976
|
+
beginTest('playground has no uncaught runtime exceptions');
|
|
873
977
|
assert.equal(exceptions.length, 0, `Uncaught exceptions in playground.html: ${JSON.stringify(exceptions[0] || {})}`);
|
|
978
|
+
endTest();
|
|
874
979
|
|
|
875
980
|
// Console errors are noisy and often indicate a broken UI.
|
|
876
981
|
// (We suppress known noise like /favicon.ico on the server.)
|
|
982
|
+
beginTest('playground has no console errors');
|
|
877
983
|
assert.equal(consoleErrors.length, 0, `Console errors in playground.html: ${JSON.stringify(consoleErrors[0] || {})}`);
|
|
984
|
+
endTest();
|
|
878
985
|
|
|
879
986
|
// Cleanup.
|
|
880
987
|
try {
|
|
@@ -883,9 +990,13 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
883
990
|
} finally {
|
|
884
991
|
await cleanup();
|
|
885
992
|
}
|
|
993
|
+
|
|
994
|
+
printSummary();
|
|
886
995
|
}
|
|
887
996
|
|
|
888
997
|
main().catch((e) => {
|
|
998
|
+
if (!recordCurrentFailure()) nonTestFailure = true;
|
|
999
|
+
printSummary();
|
|
889
1000
|
fail(e && e.stack ? e.stack : String(e));
|
|
890
1001
|
process.exit(1);
|
|
891
1002
|
});
|