@fraczak/k 6.8.0 → 6.10.1
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/CHANGELOG.md +40 -0
- package/CLAUDE.md +1 -1
- package/DOCS/BIG_PICTURE.md +3 -5
- package/DOCS/CODECS.md +80 -181
- package/DOCS/CODEC_ARCHITECTURE.md +194 -0
- package/DOCS/REPL.md +69 -51
- package/Examples/arithmetics.k +1 -0
- package/Examples/poly.k +46 -0
- package/backends/llvm/runtime/krt.c +4 -0
- package/backends/llvm/src/kvm2llvm.mjs +2 -2
- package/backends/llvm/tests/test-llvm.mjs +19 -0
- package/backends/wasm/src/kvm2wasm.mjs +4 -0
- package/backends/wasm/tests/test-wasm.mjs +4 -0
- package/browser/repl-browser.mjs +131 -779
- package/browser/shims/process.mjs +7 -0
- package/browser/tree-view.mjs +5 -2
- package/browser/vfs-data.mjs +14 -14
- package/codecs/README.md +7 -0
- package/codecs/ieee.mjs +50 -75
- package/codecs/int.mjs +83 -62
- package/codecs/json-codec.mjs +5 -77
- package/codecs/json.mjs +12 -56
- package/codecs/k-parse.mjs +3 -19
- package/codecs/runtime/codec-sdk.mjs +94 -0
- package/codecs/runtime/pattern-k.mjs +45 -0
- package/codecs/string-codec.mjs +48 -74
- package/codecs/unit.mjs +13 -61
- package/codecs/utf16.mjs +13 -56
- package/codecs/utf8.mjs +11 -55
- package/package.json +1 -1
- package/patterns/from-k.mjs +2 -22
- package/repl-codecs.mjs +69 -196
- package/repl.mjs +177 -281
- package/scripts/build-repl-html.mjs +114 -385
- package/scripts/run-tests.mjs +1 -0
- package/tests/test-codecs.mjs +186 -0
- package/tests/test-poly.mjs +44 -0
- package/tests/test-repl-html.mjs +163 -68
- package/tests/test-repl.mjs +210 -156
- package/tests/test-structural-values.mjs +17 -0
- package/tests/test-tree-view.mjs +3 -0
- package/codecs/example-pipeline.mjs +0 -163
- package/codecs/runtime/ieee-pattern.mjs +0 -30
- package/codecs/runtime/test-codec.mjs +0 -89
- package/codecs/runtime/typeFromValue.mjs +0 -34
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
import * as SDK from "../codecs/runtime/codec-sdk.mjs";
|
|
9
|
+
import * as IntCodec from "../codecs/int.mjs";
|
|
10
|
+
import * as JsonCodec from "../codecs/json.mjs";
|
|
11
|
+
import * as Utf8Codec from "../codecs/utf8.mjs";
|
|
12
|
+
import * as Utf16Codec from "../codecs/utf16.mjs";
|
|
13
|
+
import * as IeeeCodec from "../codecs/ieee.mjs";
|
|
14
|
+
import * as UnitCodec from "../codecs/unit.mjs";
|
|
15
|
+
|
|
16
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
17
|
+
|
|
18
|
+
function runCLI(codecRelPath, args, input = null) {
|
|
19
|
+
const inputBuffer = typeof input === "string" ? Buffer.from(input, "utf8") : input;
|
|
20
|
+
const res = spawnSync(process.execPath, [path.join(root, codecRelPath), ...args], {
|
|
21
|
+
input: inputBuffer,
|
|
22
|
+
encoding: null
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
status: res.status,
|
|
26
|
+
stdout: res.stdout,
|
|
27
|
+
stderr: res.stderr,
|
|
28
|
+
stdoutText: res.stdout ? res.stdout.toString("utf8") : "",
|
|
29
|
+
stderrText: res.stderr ? res.stderr.toString("utf8") : ""
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log("=== Testing Codec Architecture & CLI Contract ===");
|
|
34
|
+
|
|
35
|
+
// 1. SDK Exports
|
|
36
|
+
console.log("1. Verifying SDK exports...");
|
|
37
|
+
assert.equal(typeof SDK.Value, "function");
|
|
38
|
+
assert.equal(typeof SDK.isProduct, "function");
|
|
39
|
+
assert.equal(typeof SDK.isVariant, "function");
|
|
40
|
+
assert.equal(typeof SDK.withPattern, "function");
|
|
41
|
+
assert.equal(typeof SDK.encodeToWire, "function");
|
|
42
|
+
assert.equal(typeof SDK.decodeWire, "function");
|
|
43
|
+
assert.equal(typeof SDK.runCodecCLI, "function");
|
|
44
|
+
assert.equal(typeof SDK.isMainEntrypoint, "function");
|
|
45
|
+
assert.equal(typeof SDK.patternFromFilter, "function");
|
|
46
|
+
|
|
47
|
+
// 2. In-memory Enveloped Value Contract
|
|
48
|
+
console.log("2. Verifying in-memory parse -> enveloped value (P, v)...");
|
|
49
|
+
for (const [name, codec, sampleInput] of [
|
|
50
|
+
["int.mjs", IntCodec, "42"],
|
|
51
|
+
["json.mjs", JsonCodec, '{"x": 1}'],
|
|
52
|
+
["utf8.mjs", Utf8Codec, "hello"],
|
|
53
|
+
["utf16.mjs", Utf16Codec, "hello"],
|
|
54
|
+
["ieee.mjs", IeeeCodec, "3.14"],
|
|
55
|
+
["unit.mjs", UnitCodec, undefined]
|
|
56
|
+
]) {
|
|
57
|
+
assert.equal(typeof codec.parse, "function", `${name} must export parse()`);
|
|
58
|
+
assert.equal(typeof codec.print, "function", `${name} must export print()`);
|
|
59
|
+
const parsed = sampleInput !== undefined ? codec.parse(sampleInput) : codec.parse();
|
|
60
|
+
assert(parsed && typeof parsed === "object", `${name} parse() must return an object`);
|
|
61
|
+
assert(parsed.pattern, `${name} parse() must produce an enveloped value with .pattern`);
|
|
62
|
+
const printed = codec.print(parsed);
|
|
63
|
+
assert(printed !== undefined, `${name} print() must return a formatted result`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 3. CLI --help Flag
|
|
67
|
+
console.log("3. Verifying CLI --help flag...");
|
|
68
|
+
for (const relPath of [
|
|
69
|
+
"codecs/int.mjs",
|
|
70
|
+
"codecs/json.mjs",
|
|
71
|
+
"codecs/utf8.mjs",
|
|
72
|
+
"codecs/utf16.mjs",
|
|
73
|
+
"codecs/ieee.mjs",
|
|
74
|
+
"codecs/unit.mjs"
|
|
75
|
+
]) {
|
|
76
|
+
const res = runCLI(relPath, ["--help"]);
|
|
77
|
+
assert.equal(res.status, 0, `${relPath} --help should exit 0`);
|
|
78
|
+
assert(res.stdoutText.includes("Usage:"), `${relPath} --help should output usage`);
|
|
79
|
+
assert(res.stdoutText.includes("--parse"), `${relPath} --help should mention --parse`);
|
|
80
|
+
assert(res.stdoutText.includes("--print"), `${relPath} --help should mention --print`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 4. CLI --parse and --print Pipelines
|
|
84
|
+
console.log("4. Verifying CLI --parse and --print pipelines...");
|
|
85
|
+
|
|
86
|
+
// int.mjs: single integer
|
|
87
|
+
{
|
|
88
|
+
const parseRes = runCLI("codecs/int.mjs", ["--parse"], "-12345\n");
|
|
89
|
+
assert.equal(parseRes.status, 0);
|
|
90
|
+
const printRes = runCLI("codecs/int.mjs", ["--print"], parseRes.stdout);
|
|
91
|
+
assert.equal(printRes.status, 0);
|
|
92
|
+
assert.equal(printRes.stdout.toString("utf8").trim(), "-12345");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// int.mjs: integer list
|
|
96
|
+
{
|
|
97
|
+
const parseRes = runCLI("codecs/int.mjs", ["--parse"], "[0, 1, -2, 3]\n");
|
|
98
|
+
assert.equal(parseRes.status, 0);
|
|
99
|
+
const printRes = runCLI("codecs/int.mjs", ["--print"], parseRes.stdout);
|
|
100
|
+
assert.equal(printRes.status, 0);
|
|
101
|
+
assert.equal(printRes.stdout.toString("utf8").trim(), "[0,1,-2,3]");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// json.mjs: complex JSON
|
|
105
|
+
{
|
|
106
|
+
const jsonInput = JSON.stringify({ name: "k", active: true, count: 42, tags: ["fast", "typed"] });
|
|
107
|
+
const parseRes = runCLI("codecs/json.mjs", ["--parse"], jsonInput);
|
|
108
|
+
assert.equal(parseRes.status, 0);
|
|
109
|
+
const printRes = runCLI("codecs/json.mjs", ["--print"], parseRes.stdout);
|
|
110
|
+
assert.equal(printRes.status, 0);
|
|
111
|
+
assert.deepEqual(JSON.parse(printRes.stdout.toString("utf8")), JSON.parse(jsonInput));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// utf8.mjs: unicode text
|
|
115
|
+
{
|
|
116
|
+
const utf8Input = "Hello 🌍, ąęćłó! \nSecond line.";
|
|
117
|
+
const parseRes = runCLI("codecs/utf8.mjs", ["--parse"], utf8Input);
|
|
118
|
+
assert.equal(parseRes.status, 0);
|
|
119
|
+
const printRes = runCLI("codecs/utf8.mjs", ["--print"], parseRes.stdout);
|
|
120
|
+
assert.equal(printRes.status, 0);
|
|
121
|
+
assert.equal(printRes.stdout.toString("utf8"), utf8Input);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ieee.mjs: floating point
|
|
125
|
+
{
|
|
126
|
+
const parseRes = runCLI("codecs/ieee.mjs", ["--parse"], "2.71828\n");
|
|
127
|
+
assert.equal(parseRes.status, 0);
|
|
128
|
+
const printRes = runCLI("codecs/ieee.mjs", ["--print"], parseRes.stdout);
|
|
129
|
+
assert.equal(printRes.status, 0);
|
|
130
|
+
assert.equal(printRes.stdout.toString("utf8").trim(), "2.71828");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// unit.mjs: {}
|
|
134
|
+
{
|
|
135
|
+
const parseRes = runCLI("codecs/unit.mjs", ["--parse"], "");
|
|
136
|
+
assert.equal(parseRes.status, 0);
|
|
137
|
+
const printRes = runCLI("codecs/unit.mjs", ["--print"], parseRes.stdout);
|
|
138
|
+
assert.equal(printRes.status, 0);
|
|
139
|
+
assert.equal(printRes.stdout.toString("utf8").trim(), "{}");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 5. Error Handling
|
|
143
|
+
console.log("5. Verifying CLI error handling...");
|
|
144
|
+
{
|
|
145
|
+
// Unknown flag
|
|
146
|
+
const res = runCLI("codecs/int.mjs", ["--unknown"]);
|
|
147
|
+
assert.equal(res.status, 1);
|
|
148
|
+
assert(res.stderrText.includes("Usage:"));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
{
|
|
152
|
+
// Invalid parse syntax
|
|
153
|
+
const res = runCLI("codecs/int.mjs", ["--parse"], "not_a_number");
|
|
154
|
+
assert.equal(res.status, 1);
|
|
155
|
+
assert(res.stderrText.includes("Invalid integer syntax"));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
{
|
|
159
|
+
// Invalid wire input to --print
|
|
160
|
+
const res = runCLI("codecs/int.mjs", ["--print"], Buffer.from([0x00, 0x01]));
|
|
161
|
+
assert.equal(res.status, 1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 6. Pattern Derivation via k Type System
|
|
165
|
+
console.log("6. Verifying pattern derivation via k type system (patternFromFilter)...");
|
|
166
|
+
{
|
|
167
|
+
const intPat = SDK.patternFromFilter('?< <bits 0, bits 1, {} _>=bits "+", bits "-">');
|
|
168
|
+
assert.deepEqual(IntCodec.INT_PATTERN, intPat);
|
|
169
|
+
|
|
170
|
+
const unitPat = SDK.patternFromFilter("?{}");
|
|
171
|
+
assert.deepEqual(UnitCodec.UNIT_PATTERN, unitPat);
|
|
172
|
+
|
|
173
|
+
const listPat = SDK.patternFromFilter(
|
|
174
|
+
'$ bits = < bits 0, bits 1, {} _ >; $ int = < bits "+", bits "-" >; ?< {} nil, { $int car, list cdr } cons > = list'
|
|
175
|
+
);
|
|
176
|
+
assert.deepEqual(IntCodec.INT_LIST_PATTERN, listPat);
|
|
177
|
+
|
|
178
|
+
// Verify caching returns identical structure
|
|
179
|
+
const cached = SDK.patternFromFilter("?{}");
|
|
180
|
+
assert.deepEqual(cached, unitPat);
|
|
181
|
+
|
|
182
|
+
// Verify error when script is not a filter or type expression
|
|
183
|
+
assert.throws(() => SDK.patternFromFilter(".field"), /Main expression must be a filter or a type name/);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log("OK - All codec tests passed.");
|
package/tests/test-poly.mjs
CHANGED
|
@@ -126,4 +126,48 @@ async function evalK(snippet) {
|
|
|
126
126
|
console.log("Test 9 (zip): Passed");
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// Test 10: Split, Merge, and Merge Sort
|
|
130
|
+
{
|
|
131
|
+
const resNil = await evalK(`nil sort`);
|
|
132
|
+
assert.match(resNil, /\|nil/);
|
|
133
|
+
|
|
134
|
+
const resSingle = await evalK(`
|
|
135
|
+
l = {1 int car, nil cdr} cons;
|
|
136
|
+
l sort
|
|
137
|
+
`);
|
|
138
|
+
assert.match(resSingle, /1/);
|
|
139
|
+
|
|
140
|
+
const resSplit = await evalK(`
|
|
141
|
+
l4 = {1 int car, {2 int car, {3 int car, {4 int car, nil cdr} cons cdr} cons cdr} cons cdr} cons;
|
|
142
|
+
l4 split
|
|
143
|
+
`);
|
|
144
|
+
assert.match(resSplit, /first.*second/);
|
|
145
|
+
|
|
146
|
+
const resSorted = await evalK(`
|
|
147
|
+
gt_int? = { { .x /"+" x, .y /"+" y } GT if, () then } .then;
|
|
148
|
+
_merge = <
|
|
149
|
+
{.xs /nil if, .ys then} .then,
|
|
150
|
+
{.ys /nil if, .xs then} .then,
|
|
151
|
+
{
|
|
152
|
+
{.xs car x, .ys car y} gt_int? if,
|
|
153
|
+
{.ys car car, {.xs xs, .ys cdr ys} _merge cdr} cons then
|
|
154
|
+
} .then,
|
|
155
|
+
{.xs car car, {.xs cdr xs, .ys ys} _merge cdr} cons
|
|
156
|
+
>;
|
|
157
|
+
_sort = list? <
|
|
158
|
+
/nil nil,
|
|
159
|
+
/cons <
|
|
160
|
+
{.cdr /nil if, .car singleton then} .then,
|
|
161
|
+
|cons split {.first _sort xs, .second _sort ys} _merge
|
|
162
|
+
>
|
|
163
|
+
> list?;
|
|
164
|
+
l = {5 int car, {2 int car, {4 int car, {1 int car, {3 int car, nil cdr} cons cdr} cons cdr} cons cdr} cons cdr} cons;
|
|
165
|
+
l _sort
|
|
166
|
+
`);
|
|
167
|
+
assert.match(resSorted, /\|1.*\|0\|1.*\|1\|1.*\|0\|0\|1.*\|1\|0\|1/);
|
|
168
|
+
console.log("Test 10 (split, merge, sort): Passed");
|
|
169
|
+
}
|
|
170
|
+
|
|
129
171
|
console.log("==> All Polymorphic List Tests Passed Successfully!");
|
|
172
|
+
|
|
173
|
+
|
package/tests/test-repl-html.mjs
CHANGED
|
@@ -37,10 +37,9 @@ assert(html.includes("wasm-in-process"), "repl.html must mention wasm-in-process
|
|
|
37
37
|
assert(html.includes("arithmetics.k"), "repl.html must embed standard examples");
|
|
38
38
|
assert(html.includes("btn-codecs"), "repl.html must contain Codecs button");
|
|
39
39
|
assert(html.includes("codecs-modal"), "repl.html must contain Codecs modal");
|
|
40
|
-
assert(html.includes("
|
|
41
|
-
assert(html.includes("
|
|
42
|
-
assert(html.includes("
|
|
43
|
-
assert(html.includes("input-popup-modal"), "repl.html must contain input popup modal");
|
|
40
|
+
assert(html.includes("enabled-codecs-grid"), "repl.html must contain enabled codecs grid");
|
|
41
|
+
assert(!html.includes("btn-input-popup"), "repl.html must NOT contain input button");
|
|
42
|
+
assert(!html.includes("input-popup-modal"), "repl.html must NOT contain input popup modal");
|
|
44
43
|
console.log(" repl.html is valid, self-contained, and has size:", (html.length / 1024).toFixed(1), "KB");
|
|
45
44
|
|
|
46
45
|
// 3. If Chromium is available, run end-to-end browser test
|
|
@@ -183,90 +182,127 @@ if (chromiumBin) {
|
|
|
183
182
|
})()`);
|
|
184
183
|
assert.strictEqual(codecsOut, "(none)");
|
|
185
184
|
|
|
186
|
-
// Test :codec load int
|
|
185
|
+
// Test :codec load int.mjs
|
|
187
186
|
const loadIntOut = await evaluateAsync(`(async () => {
|
|
188
|
-
await window.kRepl.executeCommand(":codec load int");
|
|
187
|
+
await window.kRepl.executeCommand(":codec load int.mjs");
|
|
189
188
|
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
190
189
|
return lines[lines.length - 1];
|
|
191
190
|
})()`);
|
|
192
|
-
assert(loadIntOut.includes("loaded codec int"), `Expected loaded codec int, got: ${loadIntOut}`);
|
|
191
|
+
assert(loadIntOut.includes("loaded codec int.mjs"), `Expected loaded codec int.mjs, got: ${loadIntOut}`);
|
|
193
192
|
|
|
194
193
|
// Check codecs badge updated to 1
|
|
195
194
|
const badgeCount = await evaluateAsync(`document.getElementById("codecs-count-badge")?.textContent`);
|
|
196
|
-
assert.strictEqual(badgeCount, "1", "Codecs badge count should be 1 after loading int codec");
|
|
195
|
+
assert.strictEqual(badgeCount, "1", "Codecs badge count should be 1 after loading int.mjs codec");
|
|
197
196
|
|
|
198
|
-
// Test evaluating 10 int with int codec active -> output should format using int serializer
|
|
197
|
+
// Test evaluating 10 int with int.mjs codec active -> output should format using int.mjs serializer
|
|
199
198
|
const formattedVal = await evaluateAsync(`(async () => {
|
|
200
199
|
await window.kRepl.executeCommand("10 int");
|
|
201
200
|
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
202
201
|
return lines[lines.length - 1];
|
|
203
202
|
})()`);
|
|
204
|
-
assert(formattedVal.includes("int: 10"), `Expected 'int: 10' in output, got: ${formattedVal}`);
|
|
203
|
+
assert(formattedVal.includes("int.mjs: 10"), `Expected 'int.mjs: 10' in output, got: ${formattedVal}`);
|
|
205
204
|
|
|
206
|
-
// Test
|
|
207
|
-
await evaluateAsync(`
|
|
208
|
-
const defineCodecOut = await evaluateAsync(`(async () => {
|
|
205
|
+
// Test modal interaction via openCodecsModal (only enabled codecs shown)
|
|
206
|
+
const modalCheck = await evaluateAsync(`(() => {
|
|
209
207
|
window.kRepl.openCodecsModal();
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
document.getElementById("
|
|
213
|
-
|
|
208
|
+
const modal = document.getElementById("codecs-modal");
|
|
209
|
+
const cardInt = document.getElementById("card-codec-int_mjs");
|
|
210
|
+
const grid = document.getElementById("enabled-codecs-grid");
|
|
211
|
+
const cards = grid ? Array.from(grid.querySelectorAll(".codec-card")) : [];
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
open: modal?.classList.contains("open"),
|
|
215
|
+
hasIntCard: Boolean(cardInt),
|
|
216
|
+
cardCount: cards.length
|
|
217
|
+
};
|
|
214
218
|
})()`);
|
|
215
|
-
assert(
|
|
219
|
+
assert.strictEqual(modalCheck.open, true, "Codecs modal should open");
|
|
220
|
+
assert.strictEqual(modalCheck.hasIntCard, true, "Card for enabled int.mjs codec should exist");
|
|
221
|
+
assert.strictEqual(modalCheck.cardCount, 1, "Only 1 enabled codec card should exist");
|
|
216
222
|
|
|
217
|
-
// Test
|
|
218
|
-
const
|
|
223
|
+
// Test loading unit.mjs via REPL command
|
|
224
|
+
const loadUnitOut = await evaluateAsync(`(async () => {
|
|
225
|
+
await window.kRepl.executeCommand(":codec load unit.mjs");
|
|
219
226
|
window.kRepl.openCodecsModal();
|
|
220
|
-
|
|
227
|
+
const cardUnit = document.getElementById("card-codec-unit_mjs");
|
|
228
|
+
const badge = document.getElementById("codecs-count-badge");
|
|
229
|
+
return {
|
|
230
|
+
hasUnitCard: Boolean(cardUnit),
|
|
231
|
+
badgeCount: badge?.textContent
|
|
232
|
+
};
|
|
221
233
|
})()`);
|
|
222
|
-
assert.strictEqual(
|
|
234
|
+
assert.strictEqual(loadUnitOut.hasUnitCard, true, "card-codec-unit_mjs should exist after loading unit.mjs");
|
|
235
|
+
assert.strictEqual(loadUnitOut.badgeCount, "2", "Badge count should be 2 after loading unit.mjs");
|
|
236
|
+
|
|
237
|
+
// Test unloading unit.mjs via card Unload button
|
|
238
|
+
const unloadUnitCheck = await evaluateAsync(`(async () => {
|
|
239
|
+
const unloadBtn = document.querySelector("#card-codec-unit_mjs .btn-danger");
|
|
240
|
+
unloadBtn.click();
|
|
241
|
+
await new Promise(r => setTimeout(r, 60));
|
|
242
|
+
const cardUnit = document.getElementById("card-codec-unit_mjs");
|
|
243
|
+
const badge = document.getElementById("codecs-count-badge");
|
|
244
|
+
return {
|
|
245
|
+
hasUnitCard: Boolean(cardUnit),
|
|
246
|
+
badgeCount: badge?.textContent
|
|
247
|
+
};
|
|
248
|
+
})()`);
|
|
249
|
+
assert.strictEqual(unloadUnitCheck.hasUnitCard, false, "card-codec-unit_mjs should be removed after Unload");
|
|
250
|
+
assert.strictEqual(unloadUnitCheck.badgeCount, "1", "Badge count should be 1 after unloading unit.mjs");
|
|
223
251
|
|
|
224
252
|
// Close codecs modal
|
|
225
253
|
await evaluateAsync(`(() => {
|
|
226
254
|
document.getElementById("codecs-modal")?.classList.remove("open");
|
|
227
255
|
})()`);
|
|
228
256
|
|
|
229
|
-
// Test :input
|
|
230
|
-
await evaluateAsync(`window.kRepl.executeCommand(":input
|
|
231
|
-
const
|
|
232
|
-
|
|
257
|
+
// Test :input directive without args outputs usage in terminal
|
|
258
|
+
await evaluateAsync(`window.kRepl.executeCommand(":input")`);
|
|
259
|
+
const inputUsageCheck = await evaluateAsync(`(() => {
|
|
260
|
+
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
261
|
+
return lines.slice(-2).join(" ");
|
|
262
|
+
})()`);
|
|
263
|
+
assert(inputUsageCheck.includes("Usage: :input <codec.mjs> [text]"), `Expected usage text, got: ${inputUsageCheck}`);
|
|
264
|
+
|
|
265
|
+
// Test :input int.mjs sets prompt to int.mjs>
|
|
266
|
+
await evaluateAsync(`window.kRepl.executeCommand(":input int.mjs")`);
|
|
267
|
+
const promptAfterInput = await evaluateAsync(`document.getElementById("prompt-label")?.textContent`);
|
|
268
|
+
assert.strictEqual(promptAfterInput, "int.mjs> ", `Expected prompt label 'int.mjs> ', got: '${promptAfterInput}'`);
|
|
233
269
|
|
|
234
|
-
// Enter value '55'
|
|
270
|
+
// Enter value '55' at the prompt
|
|
235
271
|
const submitResult = await evaluateAsync(`(async () => {
|
|
236
|
-
|
|
237
|
-
textEl.value = "55";
|
|
238
|
-
await window.kRepl.submitInputPopup();
|
|
272
|
+
await window.kRepl.executeCommand("55");
|
|
239
273
|
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
240
274
|
return lines[lines.length - 1];
|
|
241
275
|
})()`);
|
|
242
|
-
assert(submitResult.includes("int: 55"), `Expected int: 55 after
|
|
243
|
-
|
|
244
|
-
// Verify
|
|
245
|
-
const
|
|
246
|
-
assert.strictEqual(
|
|
247
|
-
|
|
248
|
-
// Test :input
|
|
249
|
-
await evaluateAsync(`
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
276
|
+
assert(submitResult.includes("int.mjs: 55"), `Expected int.mjs: 55 after entering value, got: ${submitResult}`);
|
|
277
|
+
|
|
278
|
+
// Verify prompt is restored to '> '
|
|
279
|
+
const promptAfterValue = await evaluateAsync(`document.getElementById("prompt-label")?.textContent`);
|
|
280
|
+
assert.strictEqual(promptAfterValue, "> ", `Expected prompt label '> ', got: '${promptAfterValue}'`);
|
|
281
|
+
|
|
282
|
+
// Test Tab autocompletion for :input: typing ":input " and pressing Tab does NOT repeat :input
|
|
283
|
+
const tabCompletionCheck = await evaluateAsync(`(() => {
|
|
284
|
+
const inputEl = document.getElementById("repl-input");
|
|
285
|
+
inputEl.value = ":input ";
|
|
286
|
+
inputEl.selectionStart = inputEl.selectionEnd = 7;
|
|
287
|
+
const event = new KeyboardEvent("keydown", { key: "Tab", bubbles: true, cancelable: true });
|
|
288
|
+
inputEl.dispatchEvent(event);
|
|
289
|
+
return {
|
|
290
|
+
value: inputEl.value,
|
|
291
|
+
hasDuplicates: inputEl.value.includes(":input :input")
|
|
292
|
+
};
|
|
293
|
+
})()`);
|
|
294
|
+
assert.strictEqual(tabCompletionCheck.hasDuplicates, false, `:input Tab completion duplicated command: '${tabCompletionCheck.value}'`);
|
|
295
|
+
assert(tabCompletionCheck.value.startsWith(":input int.mjs"), `Expected completion to start with ':input int.mjs', got: '${tabCompletionCheck.value}'`);
|
|
257
296
|
|
|
258
|
-
// Test JSON codec with float64 in browser: :input
|
|
297
|
+
// Test JSON codec with float64 in browser: :input json.mjs then {"a":"Woj","n":123}
|
|
259
298
|
await evaluateAsync(`(async () => {
|
|
260
299
|
await window.kRepl.executeCommand(":load core.k");
|
|
261
300
|
await window.kRepl.executeCommand(":load ieee.k");
|
|
262
|
-
await window.kRepl.executeCommand(":codec load json");
|
|
301
|
+
await window.kRepl.executeCommand(":codec load json.mjs");
|
|
302
|
+
await window.kRepl.executeCommand(":input json.mjs");
|
|
263
303
|
})()`);
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
267
|
-
return lines[lines.length - 1];
|
|
268
|
-
})()`);
|
|
269
|
-
assert(inputPromptOut.includes("enter value text"), `Expected input prompt, got: ${inputPromptOut}`);
|
|
304
|
+
const jsonPromptCheck = await evaluateAsync(`document.getElementById("prompt-label")?.textContent`);
|
|
305
|
+
assert.strictEqual(jsonPromptCheck, "json.mjs> ", `Expected prompt 'json.mjs> ', got: '${jsonPromptCheck}'`);
|
|
270
306
|
|
|
271
307
|
// Enter JSON value with float64
|
|
272
308
|
const jsonResult = await evaluateAsync(`(async () => {
|
|
@@ -274,33 +310,54 @@ if (chromiumBin) {
|
|
|
274
310
|
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
275
311
|
return lines[lines.length - 1];
|
|
276
312
|
})()`);
|
|
277
|
-
assert(jsonResult.includes('json: {"a":"Woj","n":123}'), `Expected json: {"a":"Woj","n":123}, got: ${jsonResult}`);
|
|
313
|
+
assert(jsonResult.includes('json.mjs: {"a":"Woj","n":123}'), `Expected json.mjs: {"a":"Woj","n":123}, got: ${jsonResult}`);
|
|
278
314
|
|
|
279
315
|
// Verify Reset button is removed from navbar
|
|
280
316
|
const hasResetBtn = await evaluateAsync(`Boolean(document.getElementById("btn-reset"))`);
|
|
281
317
|
assert.strictEqual(hasResetBtn, false, "Reset button should be removed from navbar");
|
|
282
318
|
|
|
283
|
-
// Test
|
|
319
|
+
// Test one-line codec input: :input json.mjs {"a":"Test","n":456}
|
|
284
320
|
await evaluateAsync(`(async () => {
|
|
285
|
-
await window.kRepl.executeCommand(
|
|
286
|
-
await window.kRepl.executeCommand('{"a":"Test","n":456}');
|
|
321
|
+
await window.kRepl.executeCommand(':input json.mjs {"a":"Test","n":456}');
|
|
287
322
|
})()`);
|
|
288
323
|
const patternResult = await evaluateAsync(`(() => {
|
|
289
324
|
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
290
325
|
return lines[lines.length - 1];
|
|
291
326
|
})()`);
|
|
292
|
-
assert(patternResult.includes('json: {"a":"Test","n":456}'), `Expected json: {"a":"Test","n":456}, got: ${patternResult}`);
|
|
327
|
+
assert(patternResult.includes('json.mjs: {"a":"Test","n":456}'), `Expected json.mjs: {"a":"Test","n":456}, got: ${patternResult}`);
|
|
328
|
+
|
|
329
|
+
// Test integer list input: :input int.mjs [0,1,2]
|
|
330
|
+
await evaluateAsync(`(async () => {
|
|
331
|
+
await window.kRepl.executeCommand(":input int.mjs [0,1,2]");
|
|
332
|
+
})()`);
|
|
333
|
+
const listResult = await evaluateAsync(`(() => {
|
|
334
|
+
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
335
|
+
return lines[lines.length - 1];
|
|
336
|
+
})()`);
|
|
337
|
+
assert(listResult.includes('int.mjs: [0,1,2]'), `Expected int.mjs: [0,1,2], got: ${listResult}`);
|
|
293
338
|
|
|
294
|
-
// Test
|
|
339
|
+
// Test utf8.mjs input: :input utf8.mjs Hello world
|
|
295
340
|
await evaluateAsync(`(async () => {
|
|
296
|
-
await window.kRepl.executeCommand(":
|
|
297
|
-
await window.kRepl.executeCommand(
|
|
341
|
+
await window.kRepl.executeCommand(":codec load utf8.mjs");
|
|
342
|
+
await window.kRepl.executeCommand(":input utf8.mjs Hello world");
|
|
298
343
|
})()`);
|
|
299
|
-
const
|
|
344
|
+
const utf8Result = await evaluateAsync(`(() => {
|
|
300
345
|
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
301
346
|
return lines[lines.length - 1];
|
|
302
347
|
})()`);
|
|
303
|
-
assert(
|
|
348
|
+
assert(utf8Result.includes('utf8.mjs: Hello world'), `Expected utf8.mjs: Hello world, got: ${utf8Result}`);
|
|
349
|
+
|
|
350
|
+
// Test ieee.mjs input: :input ieee.mjs 3.14159
|
|
351
|
+
await evaluateAsync(`(async () => {
|
|
352
|
+
await window.kRepl.executeCommand(":codec load ieee.mjs");
|
|
353
|
+
await window.kRepl.executeCommand(":input ieee.mjs 3.14159");
|
|
354
|
+
})()`);
|
|
355
|
+
const ieeeResult = await evaluateAsync(`(() => {
|
|
356
|
+
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
357
|
+
return lines[lines.length - 1];
|
|
358
|
+
})()`);
|
|
359
|
+
assert(ieeeResult.includes('ieee.mjs: 3.14159'), `Expected ieee.mjs: 3.14159, got: ${ieeeResult}`);
|
|
360
|
+
|
|
304
361
|
|
|
305
362
|
// Verify text selection holds in output element
|
|
306
363
|
const selectionCheck = await evaluateAsync(`(() => {
|
|
@@ -336,6 +393,7 @@ if (chromiumBin) {
|
|
|
336
393
|
// Check Value section
|
|
337
394
|
const valSection = container.querySelector(".k-tree-value-section");
|
|
338
395
|
if (!valSection) return { error: "No value section" };
|
|
396
|
+
if (valSection.open) return { error: "Value section should be collapsed by default (open === false)" };
|
|
339
397
|
|
|
340
398
|
// Check product details nodes hierarchy
|
|
341
399
|
// Level 1: root product (depth 1)
|
|
@@ -355,9 +413,10 @@ if (chromiumBin) {
|
|
|
355
413
|
if (!level4) return { error: "Level 4 product node missing" };
|
|
356
414
|
if (level4.open) return { error: "Level 4 product should be collapsed (open === false)" };
|
|
357
415
|
|
|
358
|
-
// Check pattern section exists and
|
|
416
|
+
// Check pattern section exists and is collapsed by default
|
|
359
417
|
const patSection = container.querySelector(".k-tree-pat-section");
|
|
360
418
|
if (!patSection) return { error: "No pattern section" };
|
|
419
|
+
if (patSection.open) return { error: "Pattern section should be collapsed by default (open === false)" };
|
|
361
420
|
|
|
362
421
|
// Test Mode Toggle: click Raw
|
|
363
422
|
rawBtn.click();
|
|
@@ -480,11 +539,17 @@ if (chromiumBin) {
|
|
|
480
539
|
btnHelp.click();
|
|
481
540
|
const helpModalOpen = document.getElementById("help-modal")?.classList.contains("open");
|
|
482
541
|
const modalText = document.getElementById("help-modal")?.textContent || "";
|
|
483
|
-
const hasRel = modalText.includes(":rel ");
|
|
542
|
+
const hasRel = modalText.includes(":rel <name>");
|
|
484
543
|
const hasVal = modalText.includes(":val");
|
|
485
544
|
const hasTiming = modalText.includes(":timing");
|
|
545
|
+
const hasTime = modalText.includes(":time");
|
|
546
|
+
const hasT = modalText.includes(":t <name>");
|
|
486
547
|
const hasCodecDefine = modalText.includes(":codec define");
|
|
487
548
|
const hasType = modalText.includes(":type <name>");
|
|
549
|
+
const hasTypes = modalText.includes(":types");
|
|
550
|
+
const hasCodes = modalText.includes(":codes");
|
|
551
|
+
const hasD = modalText.includes(":d <name>");
|
|
552
|
+
const hasEngine = modalText.includes(":engine");
|
|
488
553
|
return {
|
|
489
554
|
found: true,
|
|
490
555
|
isVisible,
|
|
@@ -493,18 +558,48 @@ if (chromiumBin) {
|
|
|
493
558
|
hasRel,
|
|
494
559
|
hasVal,
|
|
495
560
|
hasTiming,
|
|
561
|
+
hasTime,
|
|
562
|
+
hasT,
|
|
496
563
|
hasCodecDefine,
|
|
497
|
-
hasType
|
|
564
|
+
hasType,
|
|
565
|
+
hasTypes,
|
|
566
|
+
hasCodes,
|
|
567
|
+
hasD,
|
|
568
|
+
hasEngine
|
|
498
569
|
};
|
|
499
570
|
})()`);
|
|
500
571
|
assert.strictEqual(narrowNavbarCheck.found, true, "#btn-help should exist");
|
|
501
572
|
assert.strictEqual(narrowNavbarCheck.isVisible, true, "#btn-help should be visible within narrow viewport");
|
|
502
573
|
assert.strictEqual(narrowNavbarCheck.helpModalOpen, true, "Clicking #btn-help in narrow viewport should open help modal");
|
|
503
|
-
assert.strictEqual(narrowNavbarCheck.hasRel,
|
|
574
|
+
assert.strictEqual(narrowNavbarCheck.hasRel, true, "Help modal should contain :rel <name>");
|
|
575
|
+
assert.strictEqual(narrowNavbarCheck.hasType, true, "Help modal should contain :type <name>");
|
|
576
|
+
assert.strictEqual(narrowNavbarCheck.hasTypes, true, "Help modal should contain :types");
|
|
577
|
+
assert.strictEqual(narrowNavbarCheck.hasEngine, true, "Help modal should contain :engine");
|
|
578
|
+
assert.strictEqual(narrowNavbarCheck.hasCodes, false, "Help modal should not contain :codes");
|
|
579
|
+
assert.strictEqual(narrowNavbarCheck.hasTime, false, "Help modal should not contain :time");
|
|
580
|
+
assert.strictEqual(narrowNavbarCheck.hasT, false, "Help modal should not contain :t <name>");
|
|
581
|
+
assert.strictEqual(narrowNavbarCheck.hasD, false, "Help modal should not contain :d <name>");
|
|
504
582
|
assert.strictEqual(narrowNavbarCheck.hasVal, false, "Help modal should not contain :val");
|
|
505
583
|
assert.strictEqual(narrowNavbarCheck.hasTiming, false, "Help modal should not contain :timing");
|
|
506
584
|
assert.strictEqual(narrowNavbarCheck.hasCodecDefine, false, "Help modal should not contain :codec define");
|
|
507
|
-
|
|
585
|
+
|
|
586
|
+
// 6. Test dual engine switching in browser (wasm <-> js)
|
|
587
|
+
const engineCheck = await evaluateAsync(`(async () => {
|
|
588
|
+
const initialEngine = window.kRepl.getEngine();
|
|
589
|
+
await window.kRepl.executeCommand(":engine js");
|
|
590
|
+
const jsEngine = window.kRepl.getEngine();
|
|
591
|
+
const badgeJsText = document.getElementById("engine-badge")?.textContent;
|
|
592
|
+
await window.kRepl.executeCommand("() | ok");
|
|
593
|
+
await window.kRepl.executeCommand(":engine wasm");
|
|
594
|
+
const wasmEngine = window.kRepl.getEngine();
|
|
595
|
+
const badgeWasmText = document.getElementById("engine-badge")?.textContent;
|
|
596
|
+
return { initialEngine, jsEngine, wasmEngine, badgeJsText, badgeWasmText };
|
|
597
|
+
})()`);
|
|
598
|
+
assert.strictEqual(engineCheck.initialEngine, "wasm");
|
|
599
|
+
assert.strictEqual(engineCheck.jsEngine, "js");
|
|
600
|
+
assert.strictEqual(engineCheck.badgeJsText, "js-in-process");
|
|
601
|
+
assert.strictEqual(engineCheck.wasmEngine, "wasm");
|
|
602
|
+
assert.strictEqual(engineCheck.badgeWasmText, "wasm-in-process");
|
|
508
603
|
|
|
509
604
|
ws.close();
|
|
510
605
|
console.log(" Browser execution verified successfully!");
|