xbintsc 0.3.25 → 0.3.46

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.
Files changed (187) hide show
  1. package/README.md +39 -3
  2. package/dist/src/cli/main.js +124 -30
  3. package/dist/src/cli/main.js.map +1 -1
  4. package/dist/src/codegen/generator/calls/access.js +8 -1
  5. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  6. package/dist/src/codegen/generator/module.js +10 -0
  7. package/dist/src/codegen/generator/module.js.map +1 -1
  8. package/dist/src/codegen/generator/state.js +24 -0
  9. package/dist/src/codegen/generator/state.js.map +1 -1
  10. package/dist/src/codegen/generator/tables.d.ts +6 -0
  11. package/dist/src/codegen/generator/tables.js +38 -0
  12. package/dist/src/codegen/generator/tables.js.map +1 -1
  13. package/dist/src/driver/bundler/commonjs.d.ts +52 -0
  14. package/dist/src/driver/bundler/commonjs.js +493 -0
  15. package/dist/src/driver/bundler/commonjs.js.map +1 -0
  16. package/dist/src/driver/bundler/graph.js +14 -0
  17. package/dist/src/driver/bundler/graph.js.map +1 -1
  18. package/dist/src/driver/bundler/merge.js +14 -0
  19. package/dist/src/driver/bundler/merge.js.map +1 -1
  20. package/dist/src/driver/bundler/types.d.ts +8 -0
  21. package/dist/src/driver/compiler.d.ts +8 -1
  22. package/dist/src/driver/compiler.js +73 -5
  23. package/dist/src/driver/compiler.js.map +1 -1
  24. package/dist/src/driver/config.d.ts +53 -0
  25. package/dist/src/driver/config.js +141 -0
  26. package/dist/src/driver/config.js.map +1 -0
  27. package/dist/src/driver/icon.d.ts +62 -0
  28. package/dist/src/driver/icon.js +151 -0
  29. package/dist/src/driver/icon.js.map +1 -0
  30. package/dist/src/driver/index.d.ts +4 -0
  31. package/dist/src/driver/index.js +4 -0
  32. package/dist/src/driver/index.js.map +1 -1
  33. package/dist/src/driver/mac-bundle.d.ts +41 -0
  34. package/dist/src/driver/mac-bundle.js +123 -0
  35. package/dist/src/driver/mac-bundle.js.map +1 -0
  36. package/dist/src/driver/win-icon.d.ts +27 -0
  37. package/dist/src/driver/win-icon.js +80 -0
  38. package/dist/src/driver/win-icon.js.map +1 -0
  39. package/dist/src/parser/statements.js +17 -4
  40. package/dist/src/parser/statements.js.map +1 -1
  41. package/dist/tests/cli/main.test.js +46 -1
  42. package/dist/tests/cli/main.test.js.map +1 -1
  43. package/dist/tests/driver/config.test.d.ts +1 -0
  44. package/dist/tests/driver/config.test.js +124 -0
  45. package/dist/tests/driver/config.test.js.map +1 -0
  46. package/dist/tests/driver/icon.test.d.ts +1 -0
  47. package/dist/tests/driver/icon.test.js +421 -0
  48. package/dist/tests/driver/icon.test.js.map +1 -0
  49. package/dist/tests/driver/modules.test.js +109 -0
  50. package/dist/tests/driver/modules.test.js.map +1 -1
  51. package/dist/tests/e2e/fetch.test.d.ts +12 -0
  52. package/dist/tests/e2e/fetch.test.js +272 -0
  53. package/dist/tests/e2e/fetch.test.js.map +1 -0
  54. package/dist/tests/e2e/gc.test.d.ts +1 -0
  55. package/dist/tests/e2e/gc.test.js +168 -0
  56. package/dist/tests/e2e/gc.test.js.map +1 -0
  57. package/dist/tests/e2e/gui-dom.test.d.ts +5 -0
  58. package/dist/tests/e2e/gui-dom.test.js +102 -0
  59. package/dist/tests/e2e/gui-dom.test.js.map +1 -0
  60. package/dist/tests/e2e/gui-events.test.d.ts +5 -0
  61. package/dist/tests/e2e/gui-events.test.js +60 -0
  62. package/dist/tests/e2e/gui-events.test.js.map +1 -0
  63. package/dist/tests/e2e/gui-helpers.d.ts +31 -0
  64. package/dist/tests/e2e/gui-helpers.js +74 -0
  65. package/dist/tests/e2e/gui-helpers.js.map +1 -0
  66. package/dist/tests/e2e/gui-layout.test.d.ts +4 -0
  67. package/dist/tests/e2e/gui-layout.test.js +88 -0
  68. package/dist/tests/e2e/gui-layout.test.js.map +1 -0
  69. package/dist/tests/e2e/gui-lifecycle.test.d.ts +5 -0
  70. package/dist/tests/e2e/gui-lifecycle.test.js +58 -0
  71. package/dist/tests/e2e/gui-lifecycle.test.js.map +1 -0
  72. package/dist/tests/e2e/gui-paint.test.d.ts +5 -0
  73. package/dist/tests/e2e/gui-paint.test.js +111 -0
  74. package/dist/tests/e2e/gui-paint.test.js.map +1 -0
  75. package/dist/tests/e2e/gui-script.test.d.ts +5 -0
  76. package/dist/tests/e2e/gui-script.test.js +78 -0
  77. package/dist/tests/e2e/gui-script.test.js.map +1 -0
  78. package/dist/tests/e2e/gui-style.test.d.ts +5 -0
  79. package/dist/tests/e2e/gui-style.test.js +175 -0
  80. package/dist/tests/e2e/gui-style.test.js.map +1 -0
  81. package/dist/tests/e2e/harness.d.ts +2 -0
  82. package/dist/tests/e2e/harness.js +1 -0
  83. package/dist/tests/e2e/harness.js.map +1 -1
  84. package/dist/tests/e2e/icon.test.d.ts +7 -0
  85. package/dist/tests/e2e/icon.test.js +80 -0
  86. package/dist/tests/e2e/icon.test.js.map +1 -0
  87. package/dist/tests/e2e/npm-modules.test.d.ts +4 -1
  88. package/dist/tests/e2e/npm-modules.test.js +47 -1
  89. package/dist/tests/e2e/npm-modules.test.js.map +1 -1
  90. package/dist/tests/e2e/runtime-coverage-typed-array.test.d.ts +9 -0
  91. package/dist/tests/e2e/runtime-coverage-typed-array.test.js +140 -0
  92. package/dist/tests/e2e/runtime-coverage-typed-array.test.js.map +1 -0
  93. package/dist/tests/parser/statements.test.js +11 -0
  94. package/dist/tests/parser/statements.test.js.map +1 -1
  95. package/package.json +3 -2
  96. package/runtime/ext_gui/css.cpp +4 -517
  97. package/runtime/ext_gui/css_internal.h +27 -0
  98. package/runtime/ext_gui/css_match.cpp +195 -0
  99. package/runtime/ext_gui/css_selector.cpp +207 -0
  100. package/runtime/ext_gui/css_sheet.cpp +129 -0
  101. package/runtime/ext_gui/dom.cpp +7 -403
  102. package/runtime/ext_gui/dom_api.cpp +63 -1146
  103. package/runtime/ext_gui/dom_api_document.cpp +96 -0
  104. package/runtime/ext_gui/dom_api_events.cpp +123 -0
  105. package/runtime/ext_gui/dom_api_internal.h +135 -0
  106. package/runtime/ext_gui/dom_api_node.cpp +329 -0
  107. package/runtime/ext_gui/dom_api_node_read.cpp +294 -0
  108. package/runtime/ext_gui/dom_api_proto.cpp +135 -0
  109. package/runtime/ext_gui/dom_api_style.cpp +192 -0
  110. package/runtime/ext_gui/dom_internal.h +39 -0
  111. package/runtime/ext_gui/dom_parse.cpp +348 -0
  112. package/runtime/ext_gui/dom_serialize.cpp +54 -0
  113. package/runtime/ext_gui/gui.cpp +56 -286
  114. package/runtime/ext_gui/gui_events.cpp +294 -0
  115. package/runtime/ext_gui/gui_internal.h +29 -0
  116. package/runtime/ext_gui/layout.cpp +6 -597
  117. package/runtime/ext_gui/layout_flex.cpp +200 -0
  118. package/runtime/ext_gui/layout_flow.cpp +191 -0
  119. package/runtime/ext_gui/layout_flow_internal.h +149 -0
  120. package/runtime/ext_gui/layout_inline.cpp +181 -0
  121. package/runtime/ext_gui/layout_internal.h +20 -0
  122. package/runtime/ext_gui/renderer.cpp +15 -679
  123. package/runtime/ext_gui/renderer_image.cpp +233 -0
  124. package/runtime/ext_gui/renderer_internal.h +112 -0
  125. package/runtime/ext_gui/renderer_shaders.h +108 -0
  126. package/runtime/ext_gui/renderer_text.cpp +342 -0
  127. package/runtime/ext_gui/style.cpp +12 -660
  128. package/runtime/ext_gui/style_internal.h +33 -0
  129. package/runtime/ext_gui/style_print.cpp +113 -0
  130. package/runtime/ext_gui/style_ua.cpp +98 -0
  131. package/runtime/ext_gui/style_values.cpp +468 -0
  132. package/runtime/ext_gui/window.cpp +1 -0
  133. package/runtime/ext_node/buffer/parts/prototype.inc +1 -0
  134. package/runtime/ext_node/dgram/dgram.c +1 -0
  135. package/runtime/ext_node/events/events.c +1 -0
  136. package/runtime/ext_node/fs/fs_ops.c +10 -25
  137. package/runtime/ext_node/fs/glob.c +13 -30
  138. package/runtime/ext_node/fs/promises.c +1 -0
  139. package/runtime/ext_node/http/parts/prototypes.inc +5 -0
  140. package/runtime/ext_node/net/parts/prototypes.inc +2 -0
  141. package/runtime/ext_node/process/process.c +9 -7
  142. package/runtime/ext_node/stream/stream.c +1 -0
  143. package/runtime/ext_node/util/util.c +6 -6
  144. package/runtime/rt.h +16 -0
  145. package/runtime/rt_internal.h +67 -2
  146. package/runtime/xt_alloc.c +442 -5
  147. package/runtime/xt_builtins.c +1 -0
  148. package/runtime/xt_containers/access.inc +32 -1
  149. package/runtime/xt_fetch/buffer.inc +64 -0
  150. package/runtime/xt_fetch/headers.inc +312 -0
  151. package/runtime/xt_fetch/request.inc +386 -0
  152. package/runtime/xt_fetch/response.inc +107 -0
  153. package/runtime/xt_fetch/socket.inc +72 -0
  154. package/runtime/xt_fetch/url.inc +108 -0
  155. package/runtime/xt_fetch.c +39 -0
  156. package/runtime/xt_generator.c +95 -1
  157. package/runtime/xt_loop.c +35 -1
  158. package/runtime/xt_promise.c +46 -0
  159. package/runtime/xt_stdlib2/error.inc +1 -0
  160. package/runtime/xt_stdlib2/regexp-match.inc +8 -7
  161. package/runtime/xt_stdlib2/set.inc +1 -1
  162. package/runtime/xt_symbol.c +2 -0
  163. package/runtime/xt_typed_array/construction.inc +142 -0
  164. package/runtime/xt_typed_array/elements.inc +92 -0
  165. package/runtime/xt_typed_array/methods.inc +329 -0
  166. package/runtime/xt_typed_array.c +72 -0
  167. package/runtime/xt_values/number-format.inc +26 -0
  168. package/scripts/check-file-length.ts +85 -0
  169. package/src/cli/main.ts +137 -29
  170. package/src/codegen/generator/calls/access.ts +9 -1
  171. package/src/codegen/generator/module.ts +10 -0
  172. package/src/codegen/generator/state.ts +24 -0
  173. package/src/codegen/generator/tables.ts +40 -0
  174. package/src/driver/bundler/commonjs.ts +553 -0
  175. package/src/driver/bundler/graph.ts +14 -0
  176. package/src/driver/bundler/merge.ts +15 -0
  177. package/src/driver/bundler/types.ts +8 -0
  178. package/src/driver/compiler.ts +83 -5
  179. package/src/driver/config.ts +173 -0
  180. package/src/driver/icon.ts +184 -0
  181. package/src/driver/index.ts +30 -0
  182. package/src/driver/mac-bundle.ts +156 -0
  183. package/src/driver/win-icon.ts +96 -0
  184. package/src/parser/statements.ts +17 -4
  185. package/dist/tests/e2e/gui.test.d.ts +0 -12
  186. package/dist/tests/e2e/gui.test.js +0 -702
  187. package/dist/tests/e2e/gui.test.js.map +0 -1
@@ -0,0 +1,102 @@
1
+ /**
2
+ * End-to-end tests for the `gui` extension — DOM element handles, mutation,
3
+ * attribute/class/style APIs and element event bubbling.
4
+ */
5
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
6
+ import { createGuiHarness, guiAvailable } from "./gui-helpers.js";
7
+ const harness = createGuiHarness();
8
+ describe.skipIf(!guiAvailable)("gui extension — dom", () => {
9
+ beforeAll(() => harness.setup());
10
+ afterAll(() => harness.cleanup());
11
+ it("exposes DOM element handles, mutation and element events", () => {
12
+ const { value, stdout } = harness.compileAndRun("dom", `
13
+ import { createWindow, run } from "gui";
14
+
15
+ const HTML = \`
16
+ <html><head><style>
17
+ body { margin: 0; }
18
+ #box { width: 100px; height: 50px; }
19
+ #inner { width: 20px; height: 20px; color: #888888; }
20
+ </style></head><body>
21
+ <div id="box"><div id="inner">x</div></div>
22
+ </body></html>\`;
23
+
24
+ const win = createWindow({ title: "dom", width: 400, height: 300 });
25
+ const log = [];
26
+ win.on("click", (e) => log.push("win:" + e.target));
27
+ win.on("ready", () => {
28
+ const doc = win.document;
29
+ const box = doc.querySelector("#box");
30
+ const inner = doc.querySelector("#inner");
31
+ console.log("tag=" + inner.tagName + ":" + inner.nodeType);
32
+ console.log("text=" + inner.textContent);
33
+ console.log("same=" + (doc.querySelector("#box") === box));
34
+ console.log("parent=" + inner.parentNode.id + ":" + inner.parentElement.tagName);
35
+ console.log("count=" + doc.querySelectorAll("div").length);
36
+ console.log("body=" + (doc.body != null) + ":" + (doc.documentElement.tagName === "HTML"));
37
+ console.log("byId=" + doc.getElementById("inner").id);
38
+ inner.setAttribute("data-x", "1");
39
+ console.log("attr=" + inner.getAttribute("data-x") + ":" + inner.hasAttribute("data-x"));
40
+ inner.removeAttribute("data-x");
41
+ console.log("attr2=" + inner.hasAttribute("data-x"));
42
+ inner.classList.add("hot", "warm");
43
+ console.log("class=" + inner.className + ":" + inner.classList.contains("warm"));
44
+ console.log("toggle=" + inner.classList.toggle("hot") + ":" + inner.className);
45
+ inner.style.setProperty("color", "#00ff00");
46
+ console.log("style=" + inner.style.getPropertyValue("color") + ":" + win.computedStyle("#inner", "color"));
47
+ console.log("offset=" + box.offsetWidth + "x" + box.offsetHeight);
48
+ console.log("contains=" + box.contains(inner) + ":" + inner.contains(box));
49
+ const created = doc.createElement("div");
50
+ created.id = "new";
51
+ created.textContent = "hi";
52
+ box.appendChild(created);
53
+ console.log("created=" + created.isConnected + ":" + doc.querySelector("#new").textContent + ":" + win.computedStyle("#new", "font-size"));
54
+ const rect = doc.querySelector("#new").getBoundingClientRect();
55
+ console.log("rect=" + rect.width + "x" + rect.height);
56
+ box.removeChild(created);
57
+ console.log("removed=" + created.isConnected + ":" + (doc.querySelector("#new") === undefined));
58
+ // Element listeners bubble from target up to ancestors.
59
+ box.addEventListener("click", (e) => log.push("box:" + e.target.id));
60
+ inner.addEventListener("click", (e) => log.push("inner:" + e.target.id));
61
+ inner.click();
62
+ for (const line of log) console.log("event=" + line);
63
+ // Window-level handlers keep their string target descriptor.
64
+ win.sendEvent("click", { x: 5, y: 5, button: 0 });
65
+ console.log("winCompat=" + log[log.length - 1]);
66
+ // stopPropagation halts bubbling but the window handler still runs.
67
+ const log2 = [];
68
+ const box2 = doc.querySelector("#box");
69
+ box2.addEventListener("click", () => log2.push("box"));
70
+ inner.addEventListener("click", (e) => { log2.push("inner"); e.stopPropagation(); });
71
+ inner.click();
72
+ console.log("stopped=" + log2.join(","));
73
+ win.close();
74
+ });
75
+ win.loadHTML(HTML);
76
+ run();
77
+ `);
78
+ expect(value("tag")).toBe("DIV:1");
79
+ expect(value("text")).toBe("x");
80
+ expect(value("same")).toBe("true"); // handle identity is stable
81
+ expect(value("parent")).toBe("box:DIV");
82
+ expect(value("count")).toBe("2");
83
+ expect(value("body")).toBe("true:true");
84
+ expect(value("byId")).toBe("inner");
85
+ expect(value("attr")).toBe("1:true");
86
+ expect(value("attr2")).toBe("false");
87
+ expect(value("class")).toBe("hot warm:true");
88
+ expect(value("toggle")).toBe("false:warm"); // toggle removed the existing class
89
+ expect(value("style")).toBe("#00ff00:rgb(0, 255, 0)");
90
+ expect(value("offset")).toBe("100x50");
91
+ expect(value("contains")).toBe("true:false");
92
+ expect(value("created")).toBe("true:hi:16px");
93
+ expect(value("removed")).toBe("false:true");
94
+ // Element listeners fire target-first, then bubble to the ancestor. The
95
+ // legacy window handler runs last and still receives a descriptor string.
96
+ expect(stdout).toContain("event=inner:inner");
97
+ expect(stdout).toContain("event=box:inner");
98
+ expect(value("winCompat")).toBe("win:div#inner.warm");
99
+ expect(value("stopped")).toBe("inner");
100
+ });
101
+ });
102
+ //# sourceMappingURL=gui-dom.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gui-dom.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-dom.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAElE,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;AAEnC,QAAQ,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACzD,SAAS,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAElC,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,KAAK,EACL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiEC,CACF,CAAC;QAEF,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,4BAA4B;QAChE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,oCAAoC;QAChF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QACtD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC5C,wEAAwE;QACxE,0EAA0E;QAC1E,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QAC9C,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QAC5C,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACtD,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * End-to-end tests for the `gui` extension — hit testing and input event
3
+ * delivery (pointer, wheel, keyboard).
4
+ */
5
+ export {};
@@ -0,0 +1,60 @@
1
+ /**
2
+ * End-to-end tests for the `gui` extension — hit testing and input event
3
+ * delivery (pointer, wheel, keyboard).
4
+ */
5
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
6
+ import { createGuiHarness, guiAvailable } from "./gui-helpers.js";
7
+ const harness = createGuiHarness();
8
+ describe.skipIf(!guiAvailable)("gui extension — events", () => {
9
+ beforeAll(() => harness.setup());
10
+ afterAll(() => harness.cleanup());
11
+ it("hit-tests elements and delivers input events to handlers", () => {
12
+ const { value, stdout } = harness.compileAndRun("input", `
13
+ import { createWindow, run } from "gui";
14
+
15
+ const HTML = \`
16
+ <html><head><style>
17
+ body { margin: 0; }
18
+ #box { width: 100px; height: 50px; }
19
+ #inner { width: 20px; height: 20px; }
20
+ </style></head><body>
21
+ <div id="box"><div id="inner">x</div></div>
22
+ </body></html>
23
+ \`;
24
+
25
+ const win = createWindow({ title: "input", width: 400, height: 300 });
26
+ const log = [];
27
+ win.on("mousemove", (e) => log.push("move:" + e.target));
28
+ win.on("mousedown", (e) => log.push("down:" + e.button + ":" + e.target));
29
+ win.on("click", (e) => log.push("click:" + e.x + "," + e.y + ":" + e.target));
30
+ win.on("wheel", (e) => log.push("wheel:" + e.deltaX + "," + e.deltaY));
31
+ win.on("keydown", (e) => log.push("key:" + e.key + ":" + e.shift));
32
+
33
+ win.on("ready", () => {
34
+ console.log("hit-inner=" + win.hitTest(5, 5));
35
+ console.log("hit-box=" + win.hitTest(50, 40));
36
+ console.log("hit-none=[" + win.hitTest(390, 290) + "]");
37
+ win.sendEvent("mousemove", { x: 5, y: 5 });
38
+ win.sendEvent("mousedown", { x: 5, y: 5, button: 0 });
39
+ win.sendEvent("click", { x: 5, y: 5, button: 0 });
40
+ win.sendEvent("wheel", { x: 5, y: 5, deltaX: 1, deltaY: 2 });
41
+ win.sendEvent("keydown", { key: "a", code: "KeyA" });
42
+ for (const line of log) console.log("event=" + line);
43
+ win.close();
44
+ });
45
+ win.loadHTML(HTML);
46
+ run();
47
+ `);
48
+ // Hit testing returns the deepest element under the point.
49
+ expect(value("hit-inner")).toBe("div#inner");
50
+ expect(value("hit-box")).toBe("div#box");
51
+ expect(value("hit-none")).toBe("[]");
52
+ // Handlers receive the payload (coordinates, button, target, deltas, key).
53
+ expect(stdout).toContain("event=move:div#inner");
54
+ expect(stdout).toContain("event=down:0:div#inner");
55
+ expect(stdout).toContain("event=click:5,5:div#inner");
56
+ expect(stdout).toContain("event=wheel:1,2");
57
+ expect(stdout).toContain("event=key:a:false");
58
+ });
59
+ });
60
+ //# sourceMappingURL=gui-events.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gui-events.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-events.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAElE,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;AAEnC,QAAQ,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,wBAAwB,EAAE,GAAG,EAAE;IAC5D,SAAS,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAElC,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,OAAO,EACP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCC,CACF,CAAC;QAEF,2DAA2D;QAC3D,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrC,2EAA2E;QAC3E,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;QACtD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Shared helpers for the `gui` extension e2e suites.
3
+ *
4
+ * Each suite compiles TypeScript programs that import `gui`, links them against
5
+ * the prebuilt `runtime/lib/<os>-<arch>/gui.a` and runs them. Programs close
6
+ * themselves through the `XT_GUI_AUTOCLOSE_MS` hook, so the tests never block on
7
+ * a real window. The suites are skipped when the GUI archive has not been built
8
+ * (run `npm run gui` first) and on Windows, where the archive is not produced
9
+ * yet.
10
+ *
11
+ * `createGuiHarness()` owns one temp directory per suite plus the shared 4x4
12
+ * test PNG; the suite calls `setup()` in `beforeAll` and `cleanup()` in
13
+ * `afterAll`.
14
+ */
15
+ /** True when the GUI archive exists and we are not on Windows. */
16
+ export declare const guiAvailable: boolean;
17
+ export interface GuiRunResult {
18
+ stdout: string;
19
+ status: number | null;
20
+ /** Read a `label=value` line out of `stdout`, asserting the label exists. */
21
+ value(label: string): string;
22
+ }
23
+ export interface GuiHarness {
24
+ /** Absolute path of the 4x4 test PNG written during `setup()`. */
25
+ readonly logoPath: string;
26
+ setup(): void;
27
+ cleanup(): void;
28
+ /** Compile a program and run it with the auto-close hook, returning stdout. */
29
+ compileAndRun(name: string, source: string, extraFiles?: Record<string, string>): GuiRunResult;
30
+ }
31
+ export declare function createGuiHarness(): GuiHarness;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Shared helpers for the `gui` extension e2e suites.
3
+ *
4
+ * Each suite compiles TypeScript programs that import `gui`, links them against
5
+ * the prebuilt `runtime/lib/<os>-<arch>/gui.a` and runs them. Programs close
6
+ * themselves through the `XT_GUI_AUTOCLOSE_MS` hook, so the tests never block on
7
+ * a real window. The suites are skipped when the GUI archive has not been built
8
+ * (run `npm run gui` first) and on Windows, where the archive is not produced
9
+ * yet.
10
+ *
11
+ * `createGuiHarness()` owns one temp directory per suite plus the shared 4x4
12
+ * test PNG; the suite calls `setup()` in `beforeAll` and `cleanup()` in
13
+ * `afterAll`.
14
+ */
15
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
16
+ import { tmpdir } from "node:os";
17
+ import { join } from "node:path";
18
+ import { spawnSync } from "node:child_process";
19
+ import { expect } from "vitest";
20
+ import { build } from "../../src/driver/compiler.js";
21
+ import { createDefaultRegistry } from "../../src/extensions/registry.js";
22
+ import { guiExtension } from "../../src/extensions/gui/index.js";
23
+ import { findRuntimeDir, platformSlug } from "../../src/driver/paths.js";
24
+ const archivePath = join(findRuntimeDir(), "lib", platformSlug(), "gui.a");
25
+ /** True when the GUI archive exists and we are not on Windows. */
26
+ export const guiAvailable = existsSync(archivePath) && process.platform !== "win32";
27
+ /** A 4x4 PNG: left half red, right half blue (base64-encoded inline). */
28
+ const LOGO_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAFElEQVR42mP4z8DwH4Sh1H8G0gUALFAf4eNWqTEAAAAASUVORK5CYII=";
29
+ export function createGuiHarness() {
30
+ let workdir = "";
31
+ let logoPath = "";
32
+ const compileAndRun = (name, source, extraFiles = {}) => {
33
+ for (const [fileName, contents] of Object.entries(extraFiles)) {
34
+ writeFileSync(join(workdir, fileName), contents);
35
+ }
36
+ const entry = join(workdir, `${name}.ts`);
37
+ writeFileSync(entry, source);
38
+ const extensions = createDefaultRegistry().register(guiExtension);
39
+ const result = build(entry, {
40
+ emit: "exe",
41
+ outDir: join(workdir, `out-${name}`),
42
+ cacheDir: join(workdir, `.cache-${name}`),
43
+ extensions,
44
+ });
45
+ expect(result.diagnostics.filter((diagnostic) => diagnostic.category === "error")).toEqual([]);
46
+ const executed = spawnSync(result.outputPath, [], {
47
+ encoding: "utf8",
48
+ env: { ...process.env, XT_GUI_AUTOCLOSE_MS: "400" },
49
+ });
50
+ expect(executed.status, executed.stderr).toBe(0);
51
+ const stdout = executed.stdout;
52
+ const value = (label) => {
53
+ const line = stdout.split("\n").find((entry) => entry.startsWith(`${label}=`));
54
+ expect(line, `missing ${label} in:\n${stdout}`).toBeDefined();
55
+ return line.slice(label.length + 1).trim();
56
+ };
57
+ return { stdout, status: executed.status, value };
58
+ };
59
+ return {
60
+ get logoPath() {
61
+ return logoPath;
62
+ },
63
+ setup() {
64
+ workdir = mkdtempSync(join(tmpdir(), "xbintsc-gui-"));
65
+ logoPath = join(workdir, "logo.png");
66
+ writeFileSync(logoPath, Buffer.from(LOGO_PNG_BASE64, "base64"));
67
+ },
68
+ cleanup() {
69
+ rmSync(workdir, { recursive: true, force: true });
70
+ },
71
+ compileAndRun,
72
+ };
73
+ }
74
+ //# sourceMappingURL=gui-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gui-helpers.js","sourceRoot":"","sources":["../../../tests/e2e/gui-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzE,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,OAAO,CAAC,CAAC;AAE3E,kEAAkE;AAClE,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAsBpF,yEAAyE;AACzE,MAAM,eAAe,GACnB,0GAA0G,CAAC;AAE7G,MAAM,UAAU,gBAAgB;IAC9B,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,QAAQ,GAAG,EAAE,CAAC;IAElB,MAAM,aAAa,GAAG,CACpB,IAAY,EACZ,MAAc,EACd,aAAqC,EAAE,EACzB,EAAE;QAChB,KAAK,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9D,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;QACnD,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC;QAC1C,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC7B,MAAM,UAAU,GAAG,qBAAqB,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QAClE,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,EAAE;YAC1B,IAAI,EAAE,KAAK;YACX,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAAC;YACpC,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,UAAU,IAAI,EAAE,CAAC;YACzC,UAAU;SACX,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC/F,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,EAAE;YAChD,QAAQ,EAAE,MAAM;YAChB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE,KAAK,EAAE;SACpD,CAAC,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;QAC/B,MAAM,KAAK,GAAG,CAAC,KAAa,EAAU,EAAE;YACtC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC;YAC/E,MAAM,CAAC,IAAI,EAAE,WAAW,KAAK,SAAS,MAAM,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;YAC9D,OAAO,IAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9C,CAAC,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;IACpD,CAAC,CAAC;IAEF,OAAO;QACL,IAAI,QAAQ;YACV,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,KAAK;YACH,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;YACtD,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;YACrC,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC,CAAC;QAClE,CAAC;QACD,OAAO;YACL,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,aAAa;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * End-to-end tests for the `gui` extension — box layout and text measurement.
3
+ */
4
+ export {};
@@ -0,0 +1,88 @@
1
+ /**
2
+ * End-to-end tests for the `gui` extension — box layout and text measurement.
3
+ */
4
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
5
+ import { createGuiHarness, guiAvailable } from "./gui-helpers.js";
6
+ const harness = createGuiHarness();
7
+ describe.skipIf(!guiAvailable)("gui extension — layout", () => {
8
+ beforeAll(() => harness.setup());
9
+ afterAll(() => harness.cleanup());
10
+ it("lays out block, inline and flex boxes", () => {
11
+ const { value } = harness.compileAndRun("layout", `
12
+ import { createWindow, run } from "gui";
13
+
14
+ const HTML = \`
15
+ <html><head><style>
16
+ body { margin: 0; }
17
+ #box { width: 200px; height: 100px; padding: 10px; border: 2px solid #000; margin: 5px; }
18
+ #row { display: flex; width: 300px; gap: 10px; }
19
+ #row .a { width: 50px; height: 20px; }
20
+ #row .b { flex: 1; height: 20px; }
21
+ #wrap { width: 60px; font-size: 10px; }
22
+ </style></head><body>
23
+ <div id="box">hello</div>
24
+ <div id="row"><div class="a"></div><div class="b"></div></div>
25
+ <div id="wrap">aaaa bbbb cccc dddd</div>
26
+ </body></html>
27
+ \`;
28
+
29
+ const win = createWindow({ title: "layout", width: 800, height: 600 });
30
+ win.on("ready", () => {
31
+ const rect = (selector) => {
32
+ const r = win.getBoundingClientRect(selector);
33
+ return r.x + "," + r.y + "," + r.width + "," + r.height;
34
+ };
35
+ console.log("box=" + rect("#box"));
36
+ console.log("a=" + rect("#row .a"));
37
+ console.log("b=" + rect("#row .b"));
38
+ console.log("wrap=" + rect("#wrap"));
39
+ console.log("lh=" + win.fontMetrics(10).lineHeight);
40
+ });
41
+ win.loadHTML(HTML);
42
+ run();
43
+ `);
44
+ // Border box = content + padding (20) + border (4); margin offsets by 5.
45
+ expect(value("box")).toBe("5,5,224,124");
46
+ // Flex row: `.a` keeps 50px, `.b` grows into the remaining 240px after a 10px gap.
47
+ expect(value("a")).toBe("0,134,50,20");
48
+ expect(value("b")).toBe("60,134,240,20");
49
+ // 60px-wide box wraps "aaaa bbbb" / "cccc dddd" onto two lines. The line
50
+ // height comes from the real font metrics, so assert against them rather
51
+ // than hard-coding a pixel value (fonts differ per platform).
52
+ const wrap = value("wrap").split(",").map(Number);
53
+ expect(wrap.slice(0, 3)).toEqual([0, 154, 60]);
54
+ expect(wrap[3]).toBeCloseTo(2 * Number(value("lh")), 1);
55
+ });
56
+ it("shapes and measures text with the font stack", () => {
57
+ const { value } = harness.compileAndRun("text", `
58
+ import { createWindow, run } from "gui";
59
+
60
+ const win = createWindow({ title: "text", width: 400, height: 300 });
61
+ win.on("ready", () => {
62
+ const fm16 = win.fontMetrics(16);
63
+ console.log("ready=" + fm16.ready);
64
+ console.log("lh16=" + fm16.lineHeight);
65
+ console.log("ascent16=" + fm16.ascent);
66
+ console.log("m16=" + win.measureText("Hello", 16));
67
+ console.log("m32=" + win.measureText("Hello", 32));
68
+ console.log("empty=" + win.measureText("", 16));
69
+ console.log("wide=" + (win.measureText("WWWW", 16) > win.measureText("iiii", 16)));
70
+ win.close();
71
+ });
72
+ win.loadHTML("<html><body></body></html>");
73
+ run();
74
+ `);
75
+ // Metrics are positive and scale ~linearly with the font size (this holds
76
+ // for both the real shaper and the no-font fallback).
77
+ const m16 = Number(value("m16"));
78
+ const m32 = Number(value("m32"));
79
+ expect(m16).toBeGreaterThan(0);
80
+ expect(m32).toBeGreaterThan(m16 * 1.8);
81
+ expect(m32).toBeLessThan(m16 * 2.2);
82
+ expect(Number(value("lh16"))).toBeGreaterThan(0);
83
+ expect(Number(value("ascent16"))).toBeGreaterThan(0);
84
+ expect(value("empty")).toBe("0");
85
+ expect(value("wide")).toBe("true");
86
+ });
87
+ });
88
+ //# sourceMappingURL=gui-layout.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gui-layout.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-layout.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAElE,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;AAEnC,QAAQ,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,wBAAwB,EAAE,GAAG,EAAE;IAC5D,SAAS,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAElC,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CACrC,QAAQ,EACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCC,CACF,CAAC;QAEF,yEAAyE;QACzE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACzC,mFAAmF;QACnF,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACzC,yEAAyE;QACzE,yEAAyE;QACzE,8DAA8D;QAC9D,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CACrC,MAAM,EACN;;;;;;;;;;;;;;;;;OAiBC,CACF,CAAC;QAEF,0EAA0E;QAC1E,sDAAsD;QACtD,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACjC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,GAAG,CAAC,CAAC,eAAe,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;QACpC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * End-to-end tests for the `gui` extension — window lifecycle and the frame
3
+ * loop (`requestAnimationFrame` / `cancelAnimationFrame`).
4
+ */
5
+ export {};
@@ -0,0 +1,58 @@
1
+ /**
2
+ * End-to-end tests for the `gui` extension — window lifecycle and the frame
3
+ * loop (`requestAnimationFrame` / `cancelAnimationFrame`).
4
+ */
5
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
6
+ import { createGuiHarness, guiAvailable } from "./gui-helpers.js";
7
+ const harness = createGuiHarness();
8
+ describe.skipIf(!guiAvailable)("gui extension — lifecycle", () => {
9
+ beforeAll(() => harness.setup());
10
+ afterAll(() => harness.cleanup());
11
+ it("opens a GPU-backed window and delivers lifecycle events", () => {
12
+ const { stdout } = harness.compileAndRun("window", `
13
+ import { createWindow, run } from "gui";
14
+ const win = createWindow({ title: "test window", width: 320, height: 240 });
15
+ win.on("ready", () => console.log("gui ready"));
16
+ win.on("close", () => console.log("gui closed"));
17
+ win.setBackground("#101010");
18
+ win.loadHTML("<h1>hi</h1>");
19
+ run();
20
+ console.log("gui exited");
21
+ `);
22
+ const lines = stdout.trim().split("\n");
23
+ expect(lines).toContain("gui ready");
24
+ expect(lines).toContain("gui closed");
25
+ expect(lines).toContain("gui exited");
26
+ });
27
+ it("runs requestAnimationFrame callbacks each frame and honours cancelAnimationFrame", () => {
28
+ const { stdout, value } = harness.compileAndRun("raf", `
29
+ import { createWindow, run } from "gui";
30
+
31
+ const win = createWindow({ title: "raf", width: 320, height: 240 });
32
+ let frames = 0;
33
+ const tick = (t: number) => {
34
+ frames = frames + 1;
35
+ win.document.getElementById("label").textContent = String(frames);
36
+ if (frames < 3) win.requestAnimationFrame(tick);
37
+ else {
38
+ console.log("frames=" + frames + ":" + (t > 0));
39
+ console.log("label=" + win.document.getElementById("label").textContent);
40
+ win.close();
41
+ }
42
+ };
43
+ win.on("ready", () => {
44
+ const cancelled = win.requestAnimationFrame(() => console.log("cancelled-ran"));
45
+ win.cancelAnimationFrame(cancelled);
46
+ const id = win.requestAnimationFrame(tick);
47
+ console.log("id=" + (id > 0));
48
+ });
49
+ win.loadHTML("<div id='label'>0</div>");
50
+ run();
51
+ `);
52
+ expect(value("id")).toBe("true");
53
+ expect(value("frames")).toBe("3:true"); // callback receives a timestamp
54
+ expect(value("label")).toBe("3"); // the DOM write was visible on the next read
55
+ expect(stdout).not.toContain("cancelled-ran");
56
+ });
57
+ });
58
+ //# sourceMappingURL=gui-lifecycle.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gui-lifecycle.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-lifecycle.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAElE,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;AAEnC,QAAQ,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,2BAA2B,EAAE,GAAG,EAAE;IAC/D,SAAS,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAElC,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CACtC,QAAQ,EACR;;;;;;;;;OASC,CACF,CAAC;QACF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kFAAkF,EAAE,GAAG,EAAE;QAC1F,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,KAAK,EACL;;;;;;;;;;;;;;;;;;;;;;;OAuBC,CACF,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,gCAAgC;QACxE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,6CAA6C;QAC/E,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * End-to-end tests for the `gui` extension — the paint/display list: backgrounds,
3
+ * borders, shaped text runs and images.
4
+ */
5
+ export {};
@@ -0,0 +1,111 @@
1
+ /**
2
+ * End-to-end tests for the `gui` extension — the paint/display list: backgrounds,
3
+ * borders, shaped text runs and images.
4
+ */
5
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
6
+ import { createGuiHarness, guiAvailable } from "./gui-helpers.js";
7
+ const harness = createGuiHarness();
8
+ describe.skipIf(!guiAvailable)("gui extension — paint", () => {
9
+ beforeAll(() => harness.setup());
10
+ afterAll(() => harness.cleanup());
11
+ it("sizes and paints images (intrinsic and explicit)", () => {
12
+ const { stdout } = harness.compileAndRun("image", `
13
+ import { createWindow, run } from "gui";
14
+
15
+ const HTML = \`
16
+ <html><head><style>
17
+ body { margin: 0; }
18
+ #logo { width: 40px; height: 40px; }
19
+ </style></head><body>
20
+ <img id="logo" src="${harness.logoPath}">
21
+ <img id="natural" src="${harness.logoPath}">
22
+ </body></html>
23
+ \`;
24
+
25
+ const win = createWindow({ title: "image", width: 400, height: 300 });
26
+ win.on("ready", () => {
27
+ const logo = win.getBoundingClientRect("#logo");
28
+ console.log("logo=" + logo.width + "x" + logo.height);
29
+ const natural = win.getBoundingClientRect("#natural");
30
+ console.log("natural=" + natural.width + "x" + natural.height);
31
+ console.log(win.paintList().trim());
32
+ win.close();
33
+ });
34
+ win.loadHTML(HTML);
35
+ run();
36
+ `);
37
+ // Explicit CSS size wins; without one the intrinsic 4x4 size is used.
38
+ expect(stdout).toContain("logo=40x40");
39
+ expect(stdout).toContain("natural=4x4");
40
+ expect(stdout).toContain(`image x=0.0 y=0.0 w=40.0 h=40.0 src=${harness.logoPath}`);
41
+ expect(stdout).toContain(`image x=0.0 y=40.0 w=4.0 h=4.0 src=${harness.logoPath}`);
42
+ });
43
+ it("builds a display list of backgrounds and borders", () => {
44
+ const { value, stdout } = harness.compileAndRun("paint", `
45
+ import { createWindow, run } from "gui";
46
+
47
+ const HTML = \`
48
+ <html><head><style>
49
+ body { margin: 0; background-color: #123456; }
50
+ #box { width: 100px; height: 40px; background: #ff0000; border: 4px solid #00ff00; border-radius: 8px; }
51
+ </style></head><body>
52
+ <div id="box">x</div>
53
+ </body></html>
54
+ \`;
55
+
56
+ const win = createWindow({ title: "paint", width: 800, height: 600 });
57
+ win.on("ready", () => {
58
+ const r = win.getBoundingClientRect("#box");
59
+ console.log("box-rect=" + r.x + "," + r.y + "," + r.width + "," + r.height);
60
+ console.log("paint-count=" + win.paintCount());
61
+ console.log("paint-list-start");
62
+ console.log(win.paintList());
63
+ console.log("paint-list-end");
64
+ });
65
+ win.loadHTML(HTML);
66
+ run();
67
+ `);
68
+ const list = stdout.slice(stdout.indexOf("paint-list-start"), stdout.indexOf("paint-list-end"));
69
+ const occurrences = (needle) => list.split(needle).length - 1;
70
+ // Border box = 100x40 content + 4px border on every side.
71
+ expect(value("box-rect")).toBe("0,0,108,48");
72
+ // body background + #box background + 4 border edges.
73
+ expect(value("paint-count")).toBe("6");
74
+ expect(occurrences("#123456ff")).toBe(1); // body background
75
+ expect(occurrences("#ff0000ff")).toBe(1); // #box background
76
+ expect(occurrences("#00ff00ff")).toBe(4); // four border edges
77
+ // The background rectangle carries the 8px corner radius.
78
+ expect(list).toContain("r=8.0 color=#ff0000ff");
79
+ });
80
+ it("emits shaped text runs into the display list", () => {
81
+ const { stdout } = harness.compileAndRun("text-runs", `
82
+ import { createWindow, run } from "gui";
83
+
84
+ const HTML = \`
85
+ <html><head><style>
86
+ body { margin: 0; }
87
+ #t { font-size: 20px; color: #0000ff; }
88
+ </style></head><body>
89
+ <div id="t">Hi</div>
90
+ </body></html>
91
+ \`;
92
+
93
+ const win = createWindow({ title: "text-runs", width: 400, height: 300 });
94
+ win.on("ready", () => {
95
+ const r = win.getBoundingClientRect("#t");
96
+ console.log("rect=" + r.x + "," + r.y + "," + r.width + "," + r.height);
97
+ console.log("paint-list-start");
98
+ console.log(win.paintList());
99
+ console.log("paint-list-end");
100
+ });
101
+ win.loadHTML(HTML);
102
+ run();
103
+ `);
104
+ const list = stdout.slice(stdout.indexOf("paint-list-start"), stdout.indexOf("paint-list-end"));
105
+ // The text fragment carries the resolved font size and colour.
106
+ expect(list).toContain('text x=0.0');
107
+ expect(list).toContain('size=20.0');
108
+ expect(list).toContain('color=#0000ffff "Hi"');
109
+ });
110
+ });
111
+ //# sourceMappingURL=gui-paint.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gui-paint.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-paint.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * End-to-end tests for the `gui` extension — compiling and running `<script>`
3
+ * bodies (inline and external, with imports) from loaded HTML.
4
+ */
5
+ export {};