@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.
@@ -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.");
@@ -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
+
@@ -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("active-codecs-tbody"), "repl.html must contain active codecs table");
41
- assert(html.includes("codec-preset-select"), "repl.html must contain codec preset templates");
42
- assert(html.includes("btn-input-popup"), "repl.html must contain input button");
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 defining inline custom codec via :codec define
206
- await evaluateAsync(`window.kRepl.executeCommand(":type bool = <{} true, {} false>")`);
207
- const defineCodecOut = await evaluateAsync(`(async () => {
208
- await window.kRepl.executeCommand(":codec define yn bool ({ parse: (t) => Value.variant(t.trim() === 'yes' ? 'true' : 'false', Value.product({})), print: (v) => v.tag === 'true' ? 'YES' : 'NO' })");
209
- const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
210
- return lines[lines.length - 1];
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(defineCodecOut.includes("defined codec yn"), `Expected defined codec yn, got: ${defineCodecOut}`);
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 modal interaction via openCodecsModal
215
- const modalIsOpen = await evaluateAsync(`(() => {
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
- return document.getElementById("codecs-modal")?.classList.contains("open");
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(modalIsOpen, true, "Codecs modal should open");
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 opens input popup modal
227
- await evaluateAsync(`window.kRepl.executeCommand(":input int")`);
228
- const inputModalOpen = await evaluateAsync(`document.getElementById("input-popup-modal")?.classList.contains("open")`);
229
- assert.strictEqual(inputModalOpen, true, "Executing :input int should open input popup modal");
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
- // Enter value '55' in popup and submit
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
- const textEl = document.getElementById("input-popup-text");
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 submitting input popup, got: ${submitResult}`);
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
- // Verify modal is closed after submission
242
- const modalClosedAfterSubmit = await evaluateAsync(`!document.getElementById("input-popup-modal")?.classList.contains("open")`);
243
- assert.strictEqual(modalClosedAfterSubmit, true, "Input modal should be closed after submit");
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
- // Test :input directive without args opens input popup
246
- await evaluateAsync(`window.kRepl.executeCommand(":input")`);
247
- const inputWithoutArgsOpensModal = await evaluateAsync(`document.getElementById("input-popup-modal")?.classList.contains("open")`);
248
- assert.strictEqual(inputWithoutArgsOpensModal, true, "Executing :input without args should open input popup");
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
- // Test cancelInputPopup
251
- await evaluateAsync(`window.kRepl.cancelInputPopup()`);
252
- const modalClosedAfterCancel = await evaluateAsync(`!document.getElementById("input-popup-modal")?.classList.contains("open")`);
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 JSON codec with float64 in browser: :input {string a, float64 n} then {"a":"Woj","n":123}
319
+ // Test one-line codec input: :input json.mjs {"a":"Test","n":456}
256
320
  await evaluateAsync(`(async () => {
257
- await window.kRepl.executeCommand(":load core.k");
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 inputPromptOut = await evaluateAsync(`(async () => {
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(inputPromptOut.includes("enter value text"), `Expected input prompt, got: ${inputPromptOut}`);
327
+ assert(patternResult.includes('json.mjs: {"a":"Test","n":456}'), `Expected json.mjs: {"a":"Test","n":456}, got: ${patternResult}`);
267
328
 
268
- // Enter JSON value with float64
269
- const jsonResult = await evaluateAsync(`(async () => {
270
- await window.kRepl.executeCommand('{"a":"Woj","n":123}');
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(jsonResult.includes('json: {"a":"Woj","n":123}'), `Expected json: {"a":"Woj","n":123}, got: ${jsonResult}`);
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 has cycle or vardef
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 {