eyeling 1.24.12 → 1.24.14
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 +19 -14
- package/package.json +1 -1
- package/test/playground.test.js +116 -8
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,8 @@ 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
|
+
|
|
3731
3736
|
### I.4 Error handling and explainability
|
|
3732
3737
|
|
|
3733
3738
|
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 +3746,21 @@ The playground also exposes two configuration toggles that are especially useful
|
|
|
3741
3746
|
|
|
3742
3747
|
Together these choices make the playground better suited to live explanation, teaching, and debugging than a minimal browser wrapper would be.
|
|
3743
3748
|
|
|
3744
|
-
### I.5
|
|
3749
|
+
### I.5 Local state and compact share links
|
|
3750
|
+
|
|
3751
|
+
The playground deliberately separates ordinary editing from link sharing.
|
|
3745
3752
|
|
|
3746
|
-
|
|
3753
|
+
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
3754
|
|
|
3748
|
-
|
|
3755
|
+
When a user does want a portable link, the **Copy share link** button creates one on demand:
|
|
3749
3756
|
|
|
3750
|
-
-
|
|
3751
|
-
-
|
|
3752
|
-
-
|
|
3753
|
-
- `proofcomments` — initializes the proof-comments checkbox,
|
|
3754
|
-
- `httpsderef` — initializes the HTTPS dereferencing checkbox.
|
|
3757
|
+
- unedited examples that were loaded from a URL can be shared as short `?url=...` links,
|
|
3758
|
+
- edited programs are shared with a compact compressed `?state=...` payload,
|
|
3759
|
+
- default option values are omitted from that payload to keep links small.
|
|
3755
3760
|
|
|
3756
|
-
This
|
|
3761
|
+
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
3762
|
|
|
3758
|
-
|
|
3763
|
+
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
3764
|
|
|
3760
3765
|
### I.6 What the playground is good for
|
|
3761
3766
|
|
|
@@ -3775,7 +3780,7 @@ For short reasoning tasks, the playground can be a faster debugging surface than
|
|
|
3775
3780
|
|
|
3776
3781
|
#### I.6.4 Sharing examples
|
|
3777
3782
|
|
|
3778
|
-
A
|
|
3783
|
+
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
3784
|
|
|
3780
3785
|
### I.7 Limits of the playground
|
|
3781
3786
|
|
|
@@ -3787,7 +3792,7 @@ In short: the playground is best thought of as a compact interactive front end f
|
|
|
3787
3792
|
|
|
3788
3793
|
### I.8 Why it matters
|
|
3789
3794
|
|
|
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
|
|
3795
|
+
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
3796
|
|
|
3792
3797
|
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
3798
|
|
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();
|
|
@@ -534,6 +574,10 @@ async function main() {
|
|
|
534
574
|
'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/builtin/sudoku.js',
|
|
535
575
|
{ ct: 'application/javascript', body: localSudokuBuiltin },
|
|
536
576
|
],
|
|
577
|
+
[
|
|
578
|
+
'https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/input/sudoku.trig',
|
|
579
|
+
{ code: 404, ct: 'text/plain', body: 'not found' },
|
|
580
|
+
],
|
|
537
581
|
]);
|
|
538
582
|
|
|
539
583
|
async function getText(url) {
|
|
@@ -551,13 +595,17 @@ async function main() {
|
|
|
551
595
|
});
|
|
552
596
|
}
|
|
553
597
|
|
|
598
|
+
beginTest('clean /playground URL serves the playground');
|
|
554
599
|
const cleanRes = await getText(cleanPlaygroundUrl);
|
|
555
600
|
assert.equal(cleanRes.statusCode, 200, 'clean /playground URL should serve the playground');
|
|
556
601
|
assert.match(cleanRes.body, /Eyeling N3 Playground/, 'clean /playground URL should load the playground');
|
|
602
|
+
endTest();
|
|
557
603
|
|
|
604
|
+
beginTest('legacy /demo URL serves the redirect page');
|
|
558
605
|
const legacyRes = await getText(legacyDemoUrl);
|
|
559
606
|
assert.equal(legacyRes.statusCode, 200, 'legacy /demo URL should serve redirect page');
|
|
560
607
|
assert.match(legacyRes.body, /playground/, 'legacy /demo URL should point to the playground');
|
|
608
|
+
endTest();
|
|
561
609
|
|
|
562
610
|
await cdp.send(
|
|
563
611
|
'Fetch.enable',
|
|
@@ -582,7 +630,7 @@ async function main() {
|
|
|
582
630
|
'Fetch.fulfillRequest',
|
|
583
631
|
{
|
|
584
632
|
requestId: p.requestId,
|
|
585
|
-
responseCode: 200,
|
|
633
|
+
responseCode: hit.code || 200,
|
|
586
634
|
responseHeaders: [
|
|
587
635
|
{ name: 'Content-Type', value: `${hit.ct}; charset=utf-8` },
|
|
588
636
|
{ name: 'Cache-Control', value: 'no-store' },
|
|
@@ -647,6 +695,7 @@ async function main() {
|
|
|
647
695
|
})
|
|
648
696
|
: [];
|
|
649
697
|
const renderedPanel = document.getElementById('output-rendered');
|
|
698
|
+
const outputTabs = document.querySelector('.output-tabs');
|
|
650
699
|
const renderedTab = document.getElementById('output-rendered-tab');
|
|
651
700
|
const sourceTab = document.getElementById('output-source-tab');
|
|
652
701
|
const sourceWrapper = document.getElementById('output-source');
|
|
@@ -659,8 +708,12 @@ async function main() {
|
|
|
659
708
|
renderedHtml: renderedPanel ? String(renderedPanel.innerHTML || '') : '',
|
|
660
709
|
renderedHidden: renderedPanel ? !!renderedPanel.hidden : true,
|
|
661
710
|
sourceHidden: sourceWrapper ? sourceWrapper.classList.contains('markdown-source-hidden') : true,
|
|
711
|
+
outputTabsHidden: outputTabs ? !!outputTabs.hidden : true,
|
|
662
712
|
renderedTabSelected: renderedTab ? renderedTab.getAttribute('aria-selected') === 'true' : false,
|
|
663
713
|
sourceTabSelected: sourceTab ? sourceTab.getAttribute('aria-selected') === 'true' : false,
|
|
714
|
+
shareStatus: document.getElementById('share-status') ? String(document.getElementById('share-status').textContent || '') : '',
|
|
715
|
+
backgroundStatus: document.getElementById('background-status') ? String(document.getElementById('background-status').textContent || '') : '',
|
|
716
|
+
href: String(window.location.href || ''),
|
|
664
717
|
highlighted,
|
|
665
718
|
};
|
|
666
719
|
})()`)) || { status: '', output: '', highlighted: [] }
|
|
@@ -709,6 +762,10 @@ async function main() {
|
|
|
709
762
|
})()`);
|
|
710
763
|
}
|
|
711
764
|
|
|
765
|
+
async function makeShareUrlInPage() {
|
|
766
|
+
return await evalInPage(`window.__eyelingPlaygroundMakeShareUrl()`);
|
|
767
|
+
}
|
|
768
|
+
|
|
712
769
|
async function loadUrlIntoEditor(url) {
|
|
713
770
|
const payload = JSON.stringify(String(url));
|
|
714
771
|
await evalInPage(`(() => {
|
|
@@ -752,6 +809,7 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
752
809
|
`;
|
|
753
810
|
|
|
754
811
|
// 1) Baseline smoke test: the default program runs to completion.
|
|
812
|
+
beginTest('playground runs the default Socrates program');
|
|
755
813
|
await clickRun();
|
|
756
814
|
const baseline = await waitForState(
|
|
757
815
|
'default program completion',
|
|
@@ -763,9 +821,13 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
763
821
|
);
|
|
764
822
|
assert.ok(typeof baseline.output === 'string' && baseline.output.length > 0, 'Expected non-empty output');
|
|
765
823
|
for (const [re, msg] of DEFAULT_PROGRAM_EXPECTS) assert.match(baseline.output, re, msg);
|
|
766
|
-
|
|
824
|
+
assert.equal(baseline.outputTabsHidden, true, 'Expected plain Turtle output to hide Markdown tabs');
|
|
825
|
+
assert.equal(baseline.renderedHidden, true, 'Expected plain Turtle output to skip rendered Markdown panel');
|
|
826
|
+
assert.equal(baseline.sourceHidden, false, 'Expected plain Turtle output to show source directly');
|
|
827
|
+
endTest();
|
|
767
828
|
|
|
768
829
|
// 2) N3 syntax errors should be shown in Output and highlight the offending line.
|
|
830
|
+
beginTest('playground shows syntax errors in Output and highlights the offending line');
|
|
769
831
|
await setProgram(syntaxErrorProgram);
|
|
770
832
|
await clickRun();
|
|
771
833
|
const syntaxErr = await waitForState(
|
|
@@ -777,9 +839,10 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
777
839
|
assert.match(syntaxErr.output, /\n\^\s*$/m, 'Expected caret line in syntax error output');
|
|
778
840
|
assert.equal(syntaxErr.highlighted[0].line, 3, 'Expected line 3 to be highlighted');
|
|
779
841
|
assert.equal(syntaxErr.highlighted[0].text, '^', 'Expected highlighted line text to match the broken line');
|
|
780
|
-
|
|
842
|
+
endTest();
|
|
781
843
|
|
|
782
844
|
// 3) Inference fuse output should be visible in the Output pane.
|
|
845
|
+
beginTest('playground clearly shows inference fuse output');
|
|
783
846
|
await setProgram(fuseProgram);
|
|
784
847
|
await clickRun();
|
|
785
848
|
const fuse = await waitForState(
|
|
@@ -793,9 +856,10 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
793
856
|
assert.match(fuse.output, /Inference fuse triggered\./i, 'Expected fuse message in Output');
|
|
794
857
|
assert.match(fuse.output, /Fired rule:/i, 'Expected fired rule explanation in Output');
|
|
795
858
|
assert.match(fuse.output, /Matched instance:/i, 'Expected matched instance in Output');
|
|
796
|
-
|
|
859
|
+
endTest();
|
|
797
860
|
|
|
798
861
|
// 4) log:outputString should render as clean text, not raw triples.
|
|
862
|
+
beginTest('playground renders log:outputString Markdown with Rendered/Markdown source tabs');
|
|
799
863
|
await setProgram(outputStringProgram);
|
|
800
864
|
await clickRun();
|
|
801
865
|
const rendered = await waitForState(
|
|
@@ -812,6 +876,7 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
812
876
|
/:report\s+log:outputString\s+"|# Derived triples/i,
|
|
813
877
|
'Expected clean rendered output without raw triples',
|
|
814
878
|
);
|
|
879
|
+
assert.equal(rendered.outputTabsHidden, false, 'Expected Markdown output tabs to be visible for log:outputString');
|
|
815
880
|
assert.equal(rendered.renderedHidden, false, 'Expected rendered Markdown tab to be visible by default');
|
|
816
881
|
assert.equal(rendered.sourceHidden, true, 'Expected Markdown source tab to be hidden by default');
|
|
817
882
|
assert.equal(rendered.renderedTabSelected, true, 'Expected Rendered tab to be selected by default');
|
|
@@ -822,6 +887,7 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
822
887
|
|
|
823
888
|
await clickOutputSourceTab();
|
|
824
889
|
const sourceView = await getPlaygroundState();
|
|
890
|
+
assert.equal(sourceView.outputTabsHidden, false, 'Expected Markdown output tabs to stay visible in source view');
|
|
825
891
|
assert.equal(sourceView.sourceTabSelected, true, 'Expected Markdown source tab to be selectable');
|
|
826
892
|
assert.equal(sourceView.renderedHidden, true, 'Expected rendered Markdown panel to hide after selecting source');
|
|
827
893
|
assert.equal(sourceView.sourceHidden, false, 'Expected source editor to show after selecting source');
|
|
@@ -830,15 +896,49 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
830
896
|
await clickOutputRenderedTab();
|
|
831
897
|
const renderedAgain = await getPlaygroundState();
|
|
832
898
|
assert.equal(renderedAgain.renderedTabSelected, true, 'Expected Rendered tab to be selectable again');
|
|
833
|
-
|
|
899
|
+
endTest();
|
|
900
|
+
|
|
901
|
+
// 5) Normal editing should not keep rewriting the browser URL with raw N3 content.
|
|
902
|
+
beginTest('playground keeps the live URL short and creates compact share links on demand');
|
|
903
|
+
assert.doesNotMatch(renderedAgain.href, /[?&](?:edit|program)=/, 'Expected live URL to avoid raw editor content');
|
|
904
|
+
const compactShareUrl = await makeShareUrlInPage();
|
|
905
|
+
assert.match(compactShareUrl, /[?&]state=/, 'Expected an on-demand compact state parameter');
|
|
906
|
+
assert.doesNotMatch(compactShareUrl, /[?&](?:edit|program)=/, 'Expected share link to avoid raw edit/program params');
|
|
907
|
+
assert.ok(compactShareUrl.length < playgroundUrl.length + encodeURIComponent(outputStringProgram).length, 'Expected compact share URL to be shorter than raw editor URL');
|
|
908
|
+
endTest();
|
|
909
|
+
|
|
910
|
+
// 6) URL-loaded examples should auto-load matching examples/input/<stem>.trig and run in RDF/TriG mode.
|
|
911
|
+
beginTest('playground auto-loads companion TriG sidecars and uses RDF/TriG mode');
|
|
912
|
+
await loadUrlIntoEditor(`${started.baseUrl}/examples/smoke-arithmetic.n3`);
|
|
913
|
+
const smokeLoaded = await waitForState(
|
|
914
|
+
'smoke-arithmetic URL loaded with companion TriG input',
|
|
915
|
+
(st) => /companion RDF\/TriG input/i.test(String(st.status || '')) && /input\/smoke-arithmetic\.trig/i.test(String(st.backgroundStatus || '')),
|
|
916
|
+
20000,
|
|
917
|
+
);
|
|
918
|
+
assert.match(smokeLoaded.backgroundStatus, /smoke-arithmetic\.trig/i, 'Expected companion TriG sidecar in background status');
|
|
919
|
+
await clickRun();
|
|
920
|
+
const smoke = await waitForState(
|
|
921
|
+
'URL-loaded smoke-arithmetic example completion with sidecar input',
|
|
922
|
+
(st) =>
|
|
923
|
+
String(st.status || '')
|
|
924
|
+
.trim()
|
|
925
|
+
.startsWith('Done') && /product = 42/i.test(String(st.output || '')),
|
|
926
|
+
30000,
|
|
927
|
+
);
|
|
928
|
+
assert.match(smoke.output, /product = 42/i, 'Expected result derived from companion TriG evidence');
|
|
929
|
+
endTest();
|
|
834
930
|
|
|
835
|
-
//
|
|
931
|
+
// 7) URL-loaded repository examples should auto-load matching examples/builtin/<stem>.js.
|
|
932
|
+
beginTest('playground auto-loads a companion example builtin for URL-loaded Sudoku');
|
|
836
933
|
await loadUrlIntoEditor('https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/sudoku.n3');
|
|
837
934
|
await waitForState(
|
|
838
935
|
'sudoku URL loaded with companion builtin',
|
|
839
936
|
(st) => /loaded n3 into the editor and loaded its example builtin/i.test(String(st.status || '')),
|
|
840
937
|
20000,
|
|
841
938
|
);
|
|
939
|
+
const urlLoadedShareUrl = await makeShareUrlInPage();
|
|
940
|
+
assert.match(urlLoadedShareUrl, /[?&]url=/, 'Expected URL-loaded examples to share as a short url= link');
|
|
941
|
+
assert.doesNotMatch(urlLoadedShareUrl, /[?&]state=/, 'Expected unedited URL-loaded examples to avoid state payloads');
|
|
842
942
|
await clickRun();
|
|
843
943
|
const sudoku = await waitForState(
|
|
844
944
|
'URL-loaded Sudoku example completion',
|
|
@@ -850,14 +950,18 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
850
950
|
);
|
|
851
951
|
assert.match(sudoku.output, /Completed grid/i, 'Expected Sudoku rendered output');
|
|
852
952
|
assert.match(sudoku.output, /unique valid Sudoku solution/i, 'Expected Sudoku builtin-backed result');
|
|
853
|
-
|
|
953
|
+
endTest();
|
|
854
954
|
|
|
855
955
|
// Ensure no uncaught runtime exceptions.
|
|
956
|
+
beginTest('playground has no uncaught runtime exceptions');
|
|
856
957
|
assert.equal(exceptions.length, 0, `Uncaught exceptions in playground.html: ${JSON.stringify(exceptions[0] || {})}`);
|
|
958
|
+
endTest();
|
|
857
959
|
|
|
858
960
|
// Console errors are noisy and often indicate a broken UI.
|
|
859
961
|
// (We suppress known noise like /favicon.ico on the server.)
|
|
962
|
+
beginTest('playground has no console errors');
|
|
860
963
|
assert.equal(consoleErrors.length, 0, `Console errors in playground.html: ${JSON.stringify(consoleErrors[0] || {})}`);
|
|
964
|
+
endTest();
|
|
861
965
|
|
|
862
966
|
// Cleanup.
|
|
863
967
|
try {
|
|
@@ -866,9 +970,13 @@ ${JSON.stringify(last, null, 2)}`);
|
|
|
866
970
|
} finally {
|
|
867
971
|
await cleanup();
|
|
868
972
|
}
|
|
973
|
+
|
|
974
|
+
printSummary();
|
|
869
975
|
}
|
|
870
976
|
|
|
871
977
|
main().catch((e) => {
|
|
978
|
+
if (!recordCurrentFailure()) nonTestFailure = true;
|
|
979
|
+
printSummary();
|
|
872
980
|
fail(e && e.stack ? e.stack : String(e));
|
|
873
981
|
process.exit(1);
|
|
874
982
|
});
|