@fraczak/k 6.7.0 → 6.9.0
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 +10 -0
- package/DOCS/CODECS.md +80 -181
- package/DOCS/CODEC_ARCHITECTURE.md +194 -0
- package/DOCS/REPL.md +49 -60
- package/Examples/arithmetics.k +1 -0
- package/Examples/poly.k +46 -0
- package/TODO.md +112 -0
- package/browser/repl-browser.mjs +112 -733
- package/browser/shims/process.mjs +7 -0
- package/browser/tree-view.mjs +5 -2
- package/browser/vfs-data.mjs +14 -12
- 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 +78 -184
- package/repl.mjs +168 -226
- package/scripts/build-repl-html.mjs +273 -424
- 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 +190 -53
- package/tests/test-repl.mjs +150 -114
- 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
package/scripts/run-tests.mjs
CHANGED
|
@@ -19,6 +19,7 @@ const tests = [
|
|
|
19
19
|
["tests/test-hash-fuzz.mjs", node, ["tests/test-hash-fuzz.mjs"]],
|
|
20
20
|
["tests/test-hash-normalization.mjs", node, ["tests/test-hash-normalization.mjs"]],
|
|
21
21
|
["tests/test-structural-values.mjs", node, ["tests/test-structural-values.mjs"]],
|
|
22
|
+
["tests/test-codecs.mjs", node, ["tests/test-codecs.mjs"]],
|
|
22
23
|
["tests/test-deep-wire-value.mjs", node, ["tests/test-deep-wire-value.mjs"]],
|
|
23
24
|
["tests/test-k-object.mjs", node, ["tests/test-k-object.mjs"]],
|
|
24
25
|
["tests/test-kir.mjs", node, ["tests/test-kir.mjs"]],
|
|
@@ -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
|
|
@@ -145,6 +144,7 @@ if (chromiumBin) {
|
|
|
145
144
|
"ieee.mjs",
|
|
146
145
|
"int.mjs",
|
|
147
146
|
"json.mjs",
|
|
147
|
+
"poly.k",
|
|
148
148
|
"unit.mjs",
|
|
149
149
|
"utf8.mjs"
|
|
150
150
|
].sort();
|
|
@@ -182,96 +182,182 @@ if (chromiumBin) {
|
|
|
182
182
|
})()`);
|
|
183
183
|
assert.strictEqual(codecsOut, "(none)");
|
|
184
184
|
|
|
185
|
-
// Test :codec load int
|
|
185
|
+
// Test :codec load int.mjs
|
|
186
186
|
const loadIntOut = await evaluateAsync(`(async () => {
|
|
187
|
-
await window.kRepl.executeCommand(":codec load int");
|
|
187
|
+
await window.kRepl.executeCommand(":codec load int.mjs");
|
|
188
188
|
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
189
189
|
return lines[lines.length - 1];
|
|
190
190
|
})()`);
|
|
191
|
-
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}`);
|
|
192
192
|
|
|
193
193
|
// Check codecs badge updated to 1
|
|
194
194
|
const badgeCount = await evaluateAsync(`document.getElementById("codecs-count-badge")?.textContent`);
|
|
195
|
-
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");
|
|
196
196
|
|
|
197
|
-
// 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
|
|
198
198
|
const formattedVal = await evaluateAsync(`(async () => {
|
|
199
199
|
await window.kRepl.executeCommand("10 int");
|
|
200
200
|
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
201
201
|
return lines[lines.length - 1];
|
|
202
202
|
})()`);
|
|
203
|
-
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}`);
|
|
204
204
|
|
|
205
|
-
// Test
|
|
206
|
-
await evaluateAsync(`
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
205
|
+
// Test modal interaction via openCodecsModal (only enabled codecs shown)
|
|
206
|
+
const modalCheck = await evaluateAsync(`(() => {
|
|
207
|
+
window.kRepl.openCodecsModal();
|
|
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
|
+
};
|
|
211
218
|
})()`);
|
|
212
|
-
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");
|
|
213
222
|
|
|
214
|
-
// Test
|
|
215
|
-
const
|
|
223
|
+
// Test loading unit.mjs via REPL command
|
|
224
|
+
const loadUnitOut = await evaluateAsync(`(async () => {
|
|
225
|
+
await window.kRepl.executeCommand(":codec load unit.mjs");
|
|
216
226
|
window.kRepl.openCodecsModal();
|
|
217
|
-
|
|
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
|
+
};
|
|
233
|
+
})()`);
|
|
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
|
+
};
|
|
218
248
|
})()`);
|
|
219
|
-
assert.strictEqual(
|
|
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");
|
|
220
251
|
|
|
221
252
|
// Close codecs modal
|
|
222
253
|
await evaluateAsync(`(() => {
|
|
223
254
|
document.getElementById("codecs-modal")?.classList.remove("open");
|
|
224
255
|
})()`);
|
|
225
256
|
|
|
226
|
-
// Test :input
|
|
227
|
-
await evaluateAsync(`window.kRepl.executeCommand(":input
|
|
228
|
-
const
|
|
229
|
-
|
|
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}`);
|
|
230
264
|
|
|
231
|
-
//
|
|
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}'`);
|
|
269
|
+
|
|
270
|
+
// Enter value '55' at the prompt
|
|
232
271
|
const submitResult = await evaluateAsync(`(async () => {
|
|
233
|
-
|
|
234
|
-
textEl.value = "55";
|
|
235
|
-
await window.kRepl.submitInputPopup();
|
|
272
|
+
await window.kRepl.executeCommand("55");
|
|
236
273
|
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
237
274
|
return lines[lines.length - 1];
|
|
238
275
|
})()`);
|
|
239
|
-
assert(submitResult.includes("int: 55"), `Expected int: 55 after
|
|
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}'`);
|
|
240
296
|
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
297
|
+
// Test JSON codec with float64 in browser: :input json.mjs then {"a":"Woj","n":123}
|
|
298
|
+
await evaluateAsync(`(async () => {
|
|
299
|
+
await window.kRepl.executeCommand(":load core.k");
|
|
300
|
+
await window.kRepl.executeCommand(":load ieee.k");
|
|
301
|
+
await window.kRepl.executeCommand(":codec load json.mjs");
|
|
302
|
+
await window.kRepl.executeCommand(":input json.mjs");
|
|
303
|
+
})()`);
|
|
304
|
+
const jsonPromptCheck = await evaluateAsync(`document.getElementById("prompt-label")?.textContent`);
|
|
305
|
+
assert.strictEqual(jsonPromptCheck, "json.mjs> ", `Expected prompt 'json.mjs> ', got: '${jsonPromptCheck}'`);
|
|
244
306
|
|
|
245
|
-
//
|
|
246
|
-
await evaluateAsync(`
|
|
247
|
-
|
|
248
|
-
|
|
307
|
+
// Enter JSON value with float64
|
|
308
|
+
const jsonResult = await evaluateAsync(`(async () => {
|
|
309
|
+
await window.kRepl.executeCommand('{"a":"Woj","n":123}');
|
|
310
|
+
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
311
|
+
return lines[lines.length - 1];
|
|
312
|
+
})()`);
|
|
313
|
+
assert(jsonResult.includes('json.mjs: {"a":"Woj","n":123}'), `Expected json.mjs: {"a":"Woj","n":123}, got: ${jsonResult}`);
|
|
249
314
|
|
|
250
|
-
//
|
|
251
|
-
await evaluateAsync(`
|
|
252
|
-
|
|
253
|
-
assert.strictEqual(modalClosedAfterCancel, true, "Input modal should close on cancel");
|
|
315
|
+
// Verify Reset button is removed from navbar
|
|
316
|
+
const hasResetBtn = await evaluateAsync(`Boolean(document.getElementById("btn-reset"))`);
|
|
317
|
+
assert.strictEqual(hasResetBtn, false, "Reset button should be removed from navbar");
|
|
254
318
|
|
|
255
|
-
// Test
|
|
319
|
+
// Test one-line codec input: :input json.mjs {"a":"Test","n":456}
|
|
256
320
|
await evaluateAsync(`(async () => {
|
|
257
|
-
await window.kRepl.executeCommand(
|
|
258
|
-
await window.kRepl.executeCommand(":load ieee.k");
|
|
259
|
-
await window.kRepl.executeCommand(":codec load json");
|
|
321
|
+
await window.kRepl.executeCommand(':input json.mjs {"a":"Test","n":456}');
|
|
260
322
|
})()`);
|
|
261
|
-
const
|
|
262
|
-
await window.kRepl.executeCommand(":input {string a, float64 n}");
|
|
323
|
+
const patternResult = await evaluateAsync(`(() => {
|
|
263
324
|
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
264
325
|
return lines[lines.length - 1];
|
|
265
326
|
})()`);
|
|
266
|
-
assert(
|
|
327
|
+
assert(patternResult.includes('json.mjs: {"a":"Test","n":456}'), `Expected json.mjs: {"a":"Test","n":456}, got: ${patternResult}`);
|
|
267
328
|
|
|
268
|
-
//
|
|
269
|
-
|
|
270
|
-
await window.kRepl.executeCommand(
|
|
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(`(() => {
|
|
271
334
|
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
272
335
|
return lines[lines.length - 1];
|
|
273
336
|
})()`);
|
|
274
|
-
assert(
|
|
337
|
+
assert(listResult.includes('int.mjs: [0,1,2]'), `Expected int.mjs: [0,1,2], got: ${listResult}`);
|
|
338
|
+
|
|
339
|
+
// Test utf8.mjs input: :input utf8.mjs Hello world
|
|
340
|
+
await evaluateAsync(`(async () => {
|
|
341
|
+
await window.kRepl.executeCommand(":codec load utf8.mjs");
|
|
342
|
+
await window.kRepl.executeCommand(":input utf8.mjs Hello world");
|
|
343
|
+
})()`);
|
|
344
|
+
const utf8Result = await evaluateAsync(`(() => {
|
|
345
|
+
const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
|
|
346
|
+
return lines[lines.length - 1];
|
|
347
|
+
})()`);
|
|
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
|
+
|
|
275
361
|
|
|
276
362
|
// Verify text selection holds in output element
|
|
277
363
|
const selectionCheck = await evaluateAsync(`(() => {
|
|
@@ -307,6 +393,7 @@ if (chromiumBin) {
|
|
|
307
393
|
// Check Value section
|
|
308
394
|
const valSection = container.querySelector(".k-tree-value-section");
|
|
309
395
|
if (!valSection) return { error: "No value section" };
|
|
396
|
+
if (valSection.open) return { error: "Value section should be collapsed by default (open === false)" };
|
|
310
397
|
|
|
311
398
|
// Check product details nodes hierarchy
|
|
312
399
|
// Level 1: root product (depth 1)
|
|
@@ -326,9 +413,10 @@ if (chromiumBin) {
|
|
|
326
413
|
if (!level4) return { error: "Level 4 product node missing" };
|
|
327
414
|
if (level4.open) return { error: "Level 4 product should be collapsed (open === false)" };
|
|
328
415
|
|
|
329
|
-
// Check pattern section exists and
|
|
416
|
+
// Check pattern section exists and is collapsed by default
|
|
330
417
|
const patSection = container.querySelector(".k-tree-pat-section");
|
|
331
418
|
if (!patSection) return { error: "No pattern section" };
|
|
419
|
+
if (patSection.open) return { error: "Pattern section should be collapsed by default (open === false)" };
|
|
332
420
|
|
|
333
421
|
// Test Mode Toggle: click Raw
|
|
334
422
|
rawBtn.click();
|
|
@@ -428,6 +516,55 @@ if (chromiumBin) {
|
|
|
428
516
|
assert.strictEqual(variantDepthAndActionsCheck.allOpenAfterExpand, true, "Expand button should expand collapsed nodes");
|
|
429
517
|
assert.strictEqual(variantDepthAndActionsCheck.v4CollapsedAgain, true, "Collapse button should reset deeper nodes to collapsed");
|
|
430
518
|
|
|
519
|
+
// 4. Verify shortened welcome banner
|
|
520
|
+
const bannerCheck = await evaluateAsync(`(() => {
|
|
521
|
+
const banner = document.querySelector(".welcome-banner");
|
|
522
|
+
const title = banner?.querySelector(".welcome-title")?.textContent.trim();
|
|
523
|
+
const desc = banner?.querySelector(".welcome-desc");
|
|
524
|
+
const tips = banner?.querySelector(".welcome-tips");
|
|
525
|
+
return { hasBanner: !!banner, title, hasDesc: !!desc, hasTips: !!tips };
|
|
526
|
+
})()`);
|
|
527
|
+
assert.strictEqual(bannerCheck.hasBanner, true, "Welcome banner should be present");
|
|
528
|
+
assert.strictEqual(bannerCheck.title, "k interactive repl", "Welcome banner title should be 'k interactive repl'");
|
|
529
|
+
assert.strictEqual(bannerCheck.hasDesc, false, "Welcome banner description should be removed");
|
|
530
|
+
assert.strictEqual(bannerCheck.hasTips, false, "Welcome banner tips should be removed");
|
|
531
|
+
|
|
532
|
+
// 5. Test narrow viewport rendering (e.g. 600px width)
|
|
533
|
+
await send("Emulation.setDeviceMetricsOverride", { width: 600, height: 800, deviceScaleFactor: 1, mobile: false });
|
|
534
|
+
const narrowNavbarCheck = await evaluateAsync(`(() => {
|
|
535
|
+
const btnHelp = document.getElementById("btn-help");
|
|
536
|
+
if (!btnHelp) return { found: false };
|
|
537
|
+
const rect = btnHelp.getBoundingClientRect();
|
|
538
|
+
const isVisible = rect.width > 0 && rect.height > 0 && rect.left >= 0 && rect.right <= window.innerWidth;
|
|
539
|
+
btnHelp.click();
|
|
540
|
+
const helpModalOpen = document.getElementById("help-modal")?.classList.contains("open");
|
|
541
|
+
const modalText = document.getElementById("help-modal")?.textContent || "";
|
|
542
|
+
const hasRel = modalText.includes(":rel ");
|
|
543
|
+
const hasVal = modalText.includes(":val");
|
|
544
|
+
const hasTiming = modalText.includes(":timing");
|
|
545
|
+
const hasCodecDefine = modalText.includes(":codec define");
|
|
546
|
+
const hasType = modalText.includes(":type <name>");
|
|
547
|
+
return {
|
|
548
|
+
found: true,
|
|
549
|
+
isVisible,
|
|
550
|
+
rect: { left: rect.left, right: rect.right, top: rect.top, bottom: rect.bottom },
|
|
551
|
+
helpModalOpen,
|
|
552
|
+
hasRel,
|
|
553
|
+
hasVal,
|
|
554
|
+
hasTiming,
|
|
555
|
+
hasCodecDefine,
|
|
556
|
+
hasType
|
|
557
|
+
};
|
|
558
|
+
})()`);
|
|
559
|
+
assert.strictEqual(narrowNavbarCheck.found, true, "#btn-help should exist");
|
|
560
|
+
assert.strictEqual(narrowNavbarCheck.isVisible, true, "#btn-help should be visible within narrow viewport");
|
|
561
|
+
assert.strictEqual(narrowNavbarCheck.helpModalOpen, true, "Clicking #btn-help in narrow viewport should open help modal");
|
|
562
|
+
assert.strictEqual(narrowNavbarCheck.hasRel, false, "Help modal should not contain :rel");
|
|
563
|
+
assert.strictEqual(narrowNavbarCheck.hasVal, false, "Help modal should not contain :val");
|
|
564
|
+
assert.strictEqual(narrowNavbarCheck.hasTiming, false, "Help modal should not contain :timing");
|
|
565
|
+
assert.strictEqual(narrowNavbarCheck.hasCodecDefine, false, "Help modal should not contain :codec define");
|
|
566
|
+
assert.strictEqual(narrowNavbarCheck.hasType, true, "Help modal should contain :type <name>");
|
|
567
|
+
|
|
431
568
|
ws.close();
|
|
432
569
|
console.log(" Browser execution verified successfully!");
|
|
433
570
|
} finally {
|