@fraczak/k 6.5.0 → 6.6.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.
@@ -287,6 +287,147 @@ if (chromiumBin) {
287
287
  })()`);
288
288
  assert.strictEqual(selectionCheck, true, "Selection in terminal output should hold after click");
289
289
 
290
+ // === Test Tree View Functionality ===
291
+ console.log(" Verifying Tree View: initial depth <= 3, folding/unfolding, mode toggle, and pattern cycles...");
292
+
293
+ // 1. Evaluate a 5-level deep nested product: { { { { {} e } d } c } b }
294
+ await evaluateAsync(`window.kRepl.executeCommand("{ { { { {} e } d } c } b }")`);
295
+
296
+ const treeCheck = await evaluateAsync(`(() => {
297
+ const entries = document.querySelectorAll(".terminal-entry");
298
+ const lastEntry = entries[entries.length - 1];
299
+ const container = lastEntry.querySelector(".k-entry-container");
300
+ if (!container) return { error: "No .k-entry-container found" };
301
+
302
+ const treeView = container.querySelector(".k-tree-view");
303
+ const rawView = container.querySelector(".k-raw-view");
304
+ const treeBtn = container.querySelector(".k-btn-mode.active");
305
+ const rawBtn = container.querySelectorAll(".k-btn-mode")[1];
306
+
307
+ // Check Value section
308
+ const valSection = container.querySelector(".k-tree-value-section");
309
+ if (!valSection) return { error: "No value section" };
310
+
311
+ // Check product details nodes hierarchy
312
+ // Level 1: root product (depth 1)
313
+ const level1 = valSection.querySelector(".k-tree-section-body > .k-tree-node.k-tree-product");
314
+ if (!level1 || !level1.open) return { error: "Level 1 product should be open" };
315
+
316
+ // Level 2: .b product (depth 2)
317
+ const level2 = level1.querySelector(".k-tree-children .k-tree-node.k-tree-product");
318
+ if (!level2 || !level2.open) return { error: "Level 2 product should be open" };
319
+
320
+ // Level 3: .c product (depth 3)
321
+ const level3 = level2.querySelector(".k-tree-children .k-tree-node.k-tree-product");
322
+ if (!level3 || !level3.open) return { error: "Level 3 product should be open" };
323
+
324
+ // Level 4: .d product (depth 4) -> MUST BE COLLAPSED (open === false)
325
+ const level4 = level3.querySelector(".k-tree-children .k-tree-node.k-tree-product");
326
+ if (!level4) return { error: "Level 4 product node missing" };
327
+ if (level4.open) return { error: "Level 4 product should be collapsed (open === false)" };
328
+
329
+ // Check pattern section exists and has cycle or vardef
330
+ const patSection = container.querySelector(".k-tree-pat-section");
331
+ if (!patSection) return { error: "No pattern section" };
332
+
333
+ // Test Mode Toggle: click Raw
334
+ rawBtn.click();
335
+ const rawVisible = rawView.style.display !== "none" && treeView.style.display === "none";
336
+
337
+ // Click Tree back
338
+ container.querySelector(".k-btn-mode").click();
339
+ const treeVisible = treeView.style.display !== "none" && rawView.style.display === "none";
340
+
341
+ // Test Unfolding Level 4
342
+ level4.open = true;
343
+ level4.dispatchEvent(new Event("toggle"));
344
+
345
+ // After toggle, level 4's children should be populated with .e
346
+ const level4Text = level4.textContent;
347
+ const unfoldedHasE = level4Text.includes(".e") || level4Text.includes("42");
348
+
349
+ return {
350
+ success: true,
351
+ rawVisible,
352
+ treeVisible,
353
+ unfoldedHasE
354
+ };
355
+ })()`);
356
+
357
+ assert.strictEqual(treeCheck.success, true, `Tree view validation failed: ${treeCheck.error}`);
358
+ assert.strictEqual(treeCheck.rawVisible, true, "Clicking Raw should show raw view and hide tree view");
359
+ assert.strictEqual(treeCheck.treeVisible, true, "Clicking Tree should restore tree view");
360
+ assert.strictEqual(treeCheck.unfoldedHasE, true, "Unfolding level 4 should lazily populate its children");
361
+
362
+ // 2. Test recursive pattern tree with cycle detection
363
+ await evaluateAsync(`window.kRepl.executeCommand("10")`);
364
+ const patCycleCheck = await evaluateAsync(`(() => {
365
+ const entries = document.querySelectorAll(".terminal-entry");
366
+ const lastEntry = entries[entries.length - 1];
367
+ const patSection = lastEntry.querySelector(".k-tree-pat-section");
368
+ if (!patSection) return { error: "No pattern section for 10" };
369
+
370
+ const cycleBadges = patSection.querySelectorAll(".k-badge-cycle");
371
+ const varDefs = patSection.querySelectorAll(".k-pat-vardef");
372
+
373
+ return {
374
+ hasCycleBadge: cycleBadges.length > 0,
375
+ hasVarDef: varDefs.length > 0
376
+ };
377
+ })()`);
378
+
379
+ assert.strictEqual(patCycleCheck.hasCycleBadge, true, "Pattern tree for recursive type should have cycle badge");
380
+ assert.strictEqual(patCycleCheck.hasVarDef, true, "Pattern tree for recursive type should have variable definition (=X0)");
381
+
382
+ // 3. Test variant chain depth limits and Expand All / Collapse All on 10
383
+ const variantDepthAndActionsCheck = await evaluateAsync(`(() => {
384
+ const entries = document.querySelectorAll(".terminal-entry");
385
+ const lastEntry = entries[entries.length - 1];
386
+ const valSection = lastEntry.querySelector(".k-tree-value-section");
387
+
388
+ // Hierarchy for 10 (variant chain: + -> 1 -> 0 -> 1 -> ...)
389
+ const v1 = valSection?.querySelector(".k-tree-section-body > .k-tree-node.k-tree-variant");
390
+ const v2 = v1?.querySelector(".k-tree-children .k-tree-node.k-tree-variant");
391
+ const v3 = v2?.querySelector(".k-tree-children .k-tree-node.k-tree-variant");
392
+ const v4 = v3?.querySelector(".k-tree-children .k-tree-node.k-tree-variant");
393
+
394
+ const v1Open = !!v1?.open;
395
+ const v2Open = !!v2?.open;
396
+ const v3Open = !!v3?.open;
397
+ const v4Collapsed = v4 ? !v4.open : false;
398
+
399
+ // Click Expand All
400
+ const actionBtns = lastEntry.querySelectorAll(".k-btn-action");
401
+ const expandBtn = actionBtns[0];
402
+ const collapseBtn = actionBtns[1];
403
+
404
+ expandBtn.click();
405
+ const allOpenAfterExpand = v4.open;
406
+
407
+ // Click Collapse All
408
+ collapseBtn.click();
409
+ const newV4 = valSection?.querySelector(
410
+ ".k-tree-section-body > .k-tree-node.k-tree-variant .k-tree-children .k-tree-node.k-tree-variant .k-tree-children .k-tree-node.k-tree-variant .k-tree-children .k-tree-node.k-tree-variant"
411
+ );
412
+ const v4CollapsedAgain = newV4 ? !newV4.open : false;
413
+
414
+ return {
415
+ v1Open,
416
+ v2Open,
417
+ v3Open,
418
+ v4Collapsed,
419
+ allOpenAfterExpand,
420
+ v4CollapsedAgain
421
+ };
422
+ })()`);
423
+
424
+ assert.strictEqual(variantDepthAndActionsCheck.v1Open, true, "Variant level 1 must be open");
425
+ assert.strictEqual(variantDepthAndActionsCheck.v2Open, true, "Variant level 2 must be open");
426
+ assert.strictEqual(variantDepthAndActionsCheck.v3Open, true, "Variant level 3 must be open");
427
+ assert.strictEqual(variantDepthAndActionsCheck.v4Collapsed, true, "Variant level 4 must be collapsed by default");
428
+ assert.strictEqual(variantDepthAndActionsCheck.allOpenAfterExpand, true, "Expand button should expand collapsed nodes");
429
+ assert.strictEqual(variantDepthAndActionsCheck.v4CollapsedAgain, true, "Collapse button should reset deeper nodes to collapsed");
430
+
290
431
  ws.close();
291
432
  console.log(" Browser execution verified successfully!");
292
433
  } finally {
@@ -0,0 +1,185 @@
1
+ import assert from "node:assert";
2
+ import { Value } from "../Value.mjs";
3
+ import {
4
+ renderValueNode,
5
+ renderPatternTree,
6
+ createOutputEntryElement
7
+ } from "../browser/tree-view.mjs";
8
+
9
+ console.log("=== Testing Tree View (browser/tree-view.mjs) ===");
10
+
11
+ // Lightweight DOM mock for testing tree construction in Node.js
12
+ class MockElement {
13
+ constructor(tagName) {
14
+ this.tagName = tagName.toUpperCase();
15
+ this.className = "";
16
+ this._textContent = "";
17
+ this._innerHTML = "";
18
+ this.open = false;
19
+ this.children = [];
20
+ this.listeners = {};
21
+ this.style = {};
22
+ }
23
+
24
+ get textContent() {
25
+ if (this._textContent) return this._textContent;
26
+ return this.children.map((c) => (typeof c === "string" ? c : c.textContent)).join("");
27
+ }
28
+
29
+ set textContent(val) {
30
+ this._textContent = String(val);
31
+ this.children = [];
32
+ }
33
+
34
+ get innerHTML() {
35
+ return this._innerHTML || this.textContent;
36
+ }
37
+
38
+ set innerHTML(val) {
39
+ this._innerHTML = String(val);
40
+ this._textContent = String(val).replace(/<[^>]*>/g, "");
41
+ }
42
+
43
+ appendChild(child) {
44
+ this.children.push(child);
45
+ return child;
46
+ }
47
+
48
+ addEventListener(type, fn) {
49
+ if (!this.listeners[type]) this.listeners[type] = [];
50
+ this.listeners[type].push(fn);
51
+ }
52
+
53
+ dispatchEvent(event) {
54
+ const list = this.listeners[event.type] || [];
55
+ for (const fn of list) fn(event);
56
+ }
57
+
58
+ querySelectorAll(selector) {
59
+ const results = [];
60
+ const check = (el) => {
61
+ for (const child of el.children) {
62
+ if (child instanceof MockElement) {
63
+ let matches = false;
64
+ if (selector === "details" && child.tagName === "DETAILS") matches = true;
65
+ if (selector.startsWith(".") && child.className.split(" ").includes(selector.slice(1))) matches = true;
66
+ if (matches) results.push(child);
67
+ check(child);
68
+ }
69
+ }
70
+ };
71
+ check(this);
72
+ return results;
73
+ }
74
+
75
+ querySelector(selector) {
76
+ return this.querySelectorAll(selector)[0] || null;
77
+ }
78
+ }
79
+
80
+ globalThis.document = {
81
+ createElement(tag) {
82
+ return new MockElement(tag);
83
+ }
84
+ };
85
+ globalThis.Event = class Event {
86
+ constructor(type) {
87
+ this.type = type;
88
+ }
89
+ };
90
+
91
+ // 1. Test renderValueNode for empty product
92
+ const emptyVal = Value.product({});
93
+ const emptyEl = renderValueNode(emptyVal);
94
+ assert.strictEqual(emptyEl.textContent, "{}", "Empty product must render as '{}'");
95
+
96
+ // 2. Test renderValueNode for nested products (depth tracking)
97
+ // { a: { b: { c: { d: {} } } } }
98
+ const deepProduct = Value.product({
99
+ a: Value.product({
100
+ b: Value.product({
101
+ c: Value.product({
102
+ d: Value.product({})
103
+ })
104
+ })
105
+ })
106
+ });
107
+
108
+ const treeRoot = renderValueNode(deepProduct, 1, 3);
109
+ assert.strictEqual(treeRoot.tagName, "DETAILS", "Root product should be a <details> element");
110
+ assert.strictEqual(treeRoot.open, true, "Root product (depth 1) must be open");
111
+
112
+ // Find child .a
113
+ const nodeA = treeRoot.querySelectorAll(".k-tree-node")[0];
114
+ assert(nodeA, "Node .a must exist");
115
+ assert.strictEqual(nodeA.open, true, "Node .a (depth 2) must be open");
116
+
117
+ // Find child .b
118
+ const nodeB = nodeA.querySelectorAll(".k-tree-node")[0];
119
+ assert(nodeB, "Node .b must exist");
120
+ assert.strictEqual(nodeB.open, true, "Node .b (depth 3) must be open");
121
+
122
+ // Find child .c
123
+ const nodeC = nodeB.querySelectorAll(".k-tree-node")[0];
124
+ assert(nodeC, "Node .c must exist");
125
+ assert.strictEqual(nodeC.open, false, "Node .c (depth 4) must be collapsed (open === false)");
126
+
127
+ // Test lazy unfolding of node .c
128
+ assert.strictEqual(nodeC.querySelector(".k-tree-children").children.length, 0, "Children of collapsed node must not be populated initially");
129
+ nodeC.open = true;
130
+ nodeC.dispatchEvent(new Event("toggle"));
131
+ assert(nodeC.querySelector(".k-tree-children").children.length > 0, "Unfolding node .c must lazily populate its children");
132
+ assert(nodeC.textContent.includes(".d:"), "Unfolded children must include .d:");
133
+
134
+ // 3. Test renderValueNode for variant chain (integers: + -> 1 -> 0 -> 1 -> _ -> {})
135
+ const variantVal = Value.variant(
136
+ "+",
137
+ Value.variant(
138
+ "1",
139
+ Value.variant(
140
+ "0",
141
+ Value.variant(
142
+ "1",
143
+ Value.variant("_", Value.product({}))
144
+ )
145
+ )
146
+ )
147
+ );
148
+
149
+ const varTree = renderValueNode(variantVal, 1, 3);
150
+ assert.strictEqual(varTree.tagName, "DETAILS");
151
+ assert.strictEqual(varTree.open, true, "Variant depth 1 (+) must be open");
152
+
153
+ const var2 = varTree.querySelectorAll(".k-tree-variant")[0];
154
+ assert.strictEqual(var2.open, true, "Variant depth 2 (1) must be open");
155
+
156
+ const var3 = var2.querySelectorAll(".k-tree-variant")[0];
157
+ assert.strictEqual(var3.open, true, "Variant depth 3 (0) must be open");
158
+
159
+ const var4 = var3.querySelectorAll(".k-tree-variant")[0];
160
+ assert.strictEqual(var4.open, false, "Variant depth 4 (1) must be collapsed");
161
+
162
+ // 4. Test renderPatternTree with cycles (recursive integer type: ?<<X0 0, X0 1, {} _>=X0 +, X0 ->)
163
+ const recursivePattern = [
164
+ ["closed-union", [["+", 1], ["-", 1]]],
165
+ ["closed-union", [["0", 1], ["1", 1], ["_", 2]]],
166
+ ["closed-product", []]
167
+ ];
168
+
169
+ const patTree = renderPatternTree(recursivePattern, 3);
170
+ assert.strictEqual(patTree.tagName, "DETAILS");
171
+ assert.strictEqual(patTree.open, true, "Root pattern node must be open");
172
+ assert(patTree.textContent.includes("recursive"), "Recursive cycle must be detected and marked 'recursive'");
173
+
174
+ // 5. Test createOutputEntryElement
175
+ const entryEl = createOutputEntryElement({
176
+ value: variantVal,
177
+ rawText: "{}|_|1|0|1|+ ?<<X0 0, X0 1, {} _>=X0 +, X0 ->\nint: 11",
178
+ codecOutputs: ["int: 11"]
179
+ });
180
+
181
+ assert(entryEl.querySelector(".k-tree-view"), "Tree view container must exist");
182
+ assert(entryEl.querySelector(".k-raw-view"), "Raw view container must exist");
183
+ assert(entryEl.textContent.includes("int: 11"), "Codec output must be visible in container");
184
+
185
+ console.log("OK - All tree view unit tests passed.");