xbintsc 0.3.25 → 0.3.35

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 (154) hide show
  1. package/README.md +33 -0
  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/state.js +20 -0
  7. package/dist/src/codegen/generator/state.js.map +1 -1
  8. package/dist/src/codegen/generator/tables.d.ts +6 -0
  9. package/dist/src/codegen/generator/tables.js +38 -0
  10. package/dist/src/codegen/generator/tables.js.map +1 -1
  11. package/dist/src/driver/bundler/commonjs.d.ts +52 -0
  12. package/dist/src/driver/bundler/commonjs.js +493 -0
  13. package/dist/src/driver/bundler/commonjs.js.map +1 -0
  14. package/dist/src/driver/bundler/graph.js +14 -0
  15. package/dist/src/driver/bundler/graph.js.map +1 -1
  16. package/dist/src/driver/bundler/merge.js +14 -0
  17. package/dist/src/driver/bundler/merge.js.map +1 -1
  18. package/dist/src/driver/bundler/types.d.ts +8 -0
  19. package/dist/src/driver/compiler.d.ts +8 -1
  20. package/dist/src/driver/compiler.js +73 -5
  21. package/dist/src/driver/compiler.js.map +1 -1
  22. package/dist/src/driver/config.d.ts +53 -0
  23. package/dist/src/driver/config.js +141 -0
  24. package/dist/src/driver/config.js.map +1 -0
  25. package/dist/src/driver/icon.d.ts +62 -0
  26. package/dist/src/driver/icon.js +151 -0
  27. package/dist/src/driver/icon.js.map +1 -0
  28. package/dist/src/driver/index.d.ts +4 -0
  29. package/dist/src/driver/index.js +4 -0
  30. package/dist/src/driver/index.js.map +1 -1
  31. package/dist/src/driver/mac-bundle.d.ts +41 -0
  32. package/dist/src/driver/mac-bundle.js +123 -0
  33. package/dist/src/driver/mac-bundle.js.map +1 -0
  34. package/dist/src/driver/win-icon.d.ts +27 -0
  35. package/dist/src/driver/win-icon.js +80 -0
  36. package/dist/src/driver/win-icon.js.map +1 -0
  37. package/dist/src/parser/statements.js +17 -4
  38. package/dist/src/parser/statements.js.map +1 -1
  39. package/dist/tests/cli/main.test.js +46 -1
  40. package/dist/tests/cli/main.test.js.map +1 -1
  41. package/dist/tests/driver/config.test.d.ts +1 -0
  42. package/dist/tests/driver/config.test.js +124 -0
  43. package/dist/tests/driver/config.test.js.map +1 -0
  44. package/dist/tests/driver/icon.test.d.ts +1 -0
  45. package/dist/tests/driver/icon.test.js +421 -0
  46. package/dist/tests/driver/icon.test.js.map +1 -0
  47. package/dist/tests/driver/modules.test.js +109 -0
  48. package/dist/tests/driver/modules.test.js.map +1 -1
  49. package/dist/tests/e2e/fetch.test.d.ts +12 -0
  50. package/dist/tests/e2e/fetch.test.js +272 -0
  51. package/dist/tests/e2e/fetch.test.js.map +1 -0
  52. package/dist/tests/e2e/gui-dom.test.d.ts +5 -0
  53. package/dist/tests/e2e/gui-dom.test.js +102 -0
  54. package/dist/tests/e2e/gui-dom.test.js.map +1 -0
  55. package/dist/tests/e2e/gui-events.test.d.ts +5 -0
  56. package/dist/tests/e2e/gui-events.test.js +60 -0
  57. package/dist/tests/e2e/gui-events.test.js.map +1 -0
  58. package/dist/tests/e2e/gui-helpers.d.ts +31 -0
  59. package/dist/tests/e2e/gui-helpers.js +74 -0
  60. package/dist/tests/e2e/gui-helpers.js.map +1 -0
  61. package/dist/tests/e2e/gui-layout.test.d.ts +4 -0
  62. package/dist/tests/e2e/gui-layout.test.js +88 -0
  63. package/dist/tests/e2e/gui-layout.test.js.map +1 -0
  64. package/dist/tests/e2e/gui-lifecycle.test.d.ts +5 -0
  65. package/dist/tests/e2e/gui-lifecycle.test.js +58 -0
  66. package/dist/tests/e2e/gui-lifecycle.test.js.map +1 -0
  67. package/dist/tests/e2e/gui-paint.test.d.ts +5 -0
  68. package/dist/tests/e2e/gui-paint.test.js +111 -0
  69. package/dist/tests/e2e/gui-paint.test.js.map +1 -0
  70. package/dist/tests/e2e/gui-script.test.d.ts +5 -0
  71. package/dist/tests/e2e/gui-script.test.js +78 -0
  72. package/dist/tests/e2e/gui-script.test.js.map +1 -0
  73. package/dist/tests/e2e/gui-style.test.d.ts +5 -0
  74. package/dist/tests/e2e/gui-style.test.js +175 -0
  75. package/dist/tests/e2e/gui-style.test.js.map +1 -0
  76. package/dist/tests/e2e/icon.test.d.ts +7 -0
  77. package/dist/tests/e2e/icon.test.js +80 -0
  78. package/dist/tests/e2e/icon.test.js.map +1 -0
  79. package/dist/tests/e2e/npm-modules.test.d.ts +4 -1
  80. package/dist/tests/e2e/npm-modules.test.js +47 -1
  81. package/dist/tests/e2e/npm-modules.test.js.map +1 -1
  82. package/dist/tests/e2e/runtime-coverage-typed-array.test.d.ts +9 -0
  83. package/dist/tests/e2e/runtime-coverage-typed-array.test.js +140 -0
  84. package/dist/tests/e2e/runtime-coverage-typed-array.test.js.map +1 -0
  85. package/dist/tests/parser/statements.test.js +11 -0
  86. package/dist/tests/parser/statements.test.js.map +1 -1
  87. package/package.json +1 -1
  88. package/runtime/ext_gui/css.cpp +4 -517
  89. package/runtime/ext_gui/css_internal.h +27 -0
  90. package/runtime/ext_gui/css_match.cpp +195 -0
  91. package/runtime/ext_gui/css_selector.cpp +207 -0
  92. package/runtime/ext_gui/css_sheet.cpp +129 -0
  93. package/runtime/ext_gui/dom.cpp +7 -403
  94. package/runtime/ext_gui/dom_api.cpp +63 -1146
  95. package/runtime/ext_gui/dom_api_document.cpp +96 -0
  96. package/runtime/ext_gui/dom_api_events.cpp +123 -0
  97. package/runtime/ext_gui/dom_api_internal.h +135 -0
  98. package/runtime/ext_gui/dom_api_node.cpp +329 -0
  99. package/runtime/ext_gui/dom_api_node_read.cpp +294 -0
  100. package/runtime/ext_gui/dom_api_proto.cpp +130 -0
  101. package/runtime/ext_gui/dom_api_style.cpp +192 -0
  102. package/runtime/ext_gui/dom_internal.h +39 -0
  103. package/runtime/ext_gui/dom_parse.cpp +348 -0
  104. package/runtime/ext_gui/dom_serialize.cpp +54 -0
  105. package/runtime/ext_gui/gui.cpp +56 -286
  106. package/runtime/ext_gui/gui_events.cpp +294 -0
  107. package/runtime/ext_gui/gui_internal.h +29 -0
  108. package/runtime/ext_gui/layout.cpp +6 -597
  109. package/runtime/ext_gui/layout_flex.cpp +200 -0
  110. package/runtime/ext_gui/layout_flow.cpp +191 -0
  111. package/runtime/ext_gui/layout_flow_internal.h +149 -0
  112. package/runtime/ext_gui/layout_inline.cpp +181 -0
  113. package/runtime/ext_gui/layout_internal.h +20 -0
  114. package/runtime/ext_gui/renderer.cpp +15 -679
  115. package/runtime/ext_gui/renderer_image.cpp +233 -0
  116. package/runtime/ext_gui/renderer_internal.h +112 -0
  117. package/runtime/ext_gui/renderer_shaders.h +108 -0
  118. package/runtime/ext_gui/renderer_text.cpp +342 -0
  119. package/runtime/ext_gui/style.cpp +12 -660
  120. package/runtime/ext_gui/style_internal.h +33 -0
  121. package/runtime/ext_gui/style_print.cpp +113 -0
  122. package/runtime/ext_gui/style_ua.cpp +98 -0
  123. package/runtime/ext_gui/style_values.cpp +468 -0
  124. package/runtime/rt.h +6 -0
  125. package/runtime/rt_internal.h +4 -0
  126. package/runtime/xt_builtins.c +1 -0
  127. package/runtime/xt_containers/access.inc +32 -1
  128. package/runtime/xt_fetch/buffer.inc +64 -0
  129. package/runtime/xt_fetch/headers.inc +312 -0
  130. package/runtime/xt_fetch/request.inc +386 -0
  131. package/runtime/xt_fetch/response.inc +107 -0
  132. package/runtime/xt_fetch/socket.inc +72 -0
  133. package/runtime/xt_fetch/url.inc +108 -0
  134. package/runtime/xt_fetch.c +39 -0
  135. package/runtime/xt_stdlib2/set.inc +1 -1
  136. package/runtime/xt_typed_array.c +614 -0
  137. package/src/cli/main.ts +137 -29
  138. package/src/codegen/generator/calls/access.ts +9 -1
  139. package/src/codegen/generator/state.ts +20 -0
  140. package/src/codegen/generator/tables.ts +40 -0
  141. package/src/driver/bundler/commonjs.ts +553 -0
  142. package/src/driver/bundler/graph.ts +14 -0
  143. package/src/driver/bundler/merge.ts +15 -0
  144. package/src/driver/bundler/types.ts +8 -0
  145. package/src/driver/compiler.ts +83 -5
  146. package/src/driver/config.ts +173 -0
  147. package/src/driver/icon.ts +184 -0
  148. package/src/driver/index.ts +30 -0
  149. package/src/driver/mac-bundle.ts +156 -0
  150. package/src/driver/win-icon.ts +96 -0
  151. package/src/parser/statements.ts +17 -4
  152. package/dist/tests/e2e/gui.test.d.ts +0 -12
  153. package/dist/tests/e2e/gui.test.js +0 -702
  154. package/dist/tests/e2e/gui.test.js.map +0 -1
@@ -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":"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"}
@@ -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":"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,uBAAuB,EAAE,GAAG,EAAE;IAC3D,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,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CACtC,OAAO,EACP;;;;;;;;8BAQwB,OAAO,CAAC,QAAQ;iCACb,OAAO,CAAC,QAAQ;;;;;;;;;;;;;;;OAe1C,CACF,CAAC;QACF,sEAAsE;QACtE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACvC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,uCAAuC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;QACpF,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,sCAAsC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAC7C,OAAO,EACP;;;;;;;;;;;;;;;;;;;;;;;OAuBC,CACF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAChG,MAAM,WAAW,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QAE9E,0DAA0D;QAC1D,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7C,sDAAsD;QACtD,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACvC,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB;QAC5D,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB;QAC5D,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,oBAAoB;QAC9D,0DAA0D;QAC1D,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CACtC,WAAW,EACX;;;;;;;;;;;;;;;;;;;;;;OAsBC,CACF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAChG,+DAA+D;QAC/D,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -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 {};
@@ -0,0 +1,78 @@
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
+ 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 — scripts", () => {
9
+ beforeAll(() => harness.setup());
10
+ afterAll(() => harness.cleanup());
11
+ it("compiles inline <script> bodies from an imported .html asset", () => {
12
+ const { value } = harness.compileAndRun("script", `
13
+ import { createWindow, run } from "gui";
14
+ import page from "./page.html";
15
+
16
+ const win = createWindow({ title: "script", width: 320, height: 240 });
17
+ win.on("ready", () => {
18
+ const button = win.document.getElementById("b");
19
+ button.click();
20
+ button.click();
21
+ console.log("after=" + win.document.getElementById("count").textContent);
22
+ console.log("button=" + button.textContent);
23
+ win.close();
24
+ });
25
+ win.loadHTML(page);
26
+ run();
27
+ `, {
28
+ "page.html": `<html><body>
29
+ <div id="count">0</div>
30
+ <button id="b">0</button>
31
+ <script lang="ts">
32
+ const button = document.getElementById("b");
33
+ const label = document.getElementById("count");
34
+ let n = 0;
35
+ button.addEventListener("click", () => {
36
+ n = n + 1;
37
+ button.textContent = String(n);
38
+ label.textContent = String(n);
39
+ });
40
+ </script>
41
+ </body></html>`,
42
+ });
43
+ expect(value("after")).toBe("2");
44
+ expect(value("button")).toBe("2");
45
+ });
46
+ it("runs external <script src> modules with imports at load time", () => {
47
+ const { value } = harness.compileAndRun("scriptsrc", `
48
+ import { createWindow, run } from "gui";
49
+ import page from "./page-src.html";
50
+
51
+ const win = createWindow({ title: "scriptsrc", width: 320, height: 240 });
52
+ win.on("ready", () => {
53
+ const button = win.document.getElementById("b");
54
+ button.click();
55
+ button.click();
56
+ console.log("srcAfter=" + win.document.getElementById("b").textContent);
57
+ win.close();
58
+ });
59
+ win.loadHTML(page);
60
+ run();
61
+ `, {
62
+ "page-src.html": `<html><body><button id="b">0</button><script src="./counter.ts"></script></body></html>`,
63
+ "counter.ts": `
64
+ import { double } from "./helper";
65
+ const button = document.getElementById("b");
66
+ let n = 0;
67
+ button.addEventListener("click", () => {
68
+ n = double(n) + 1;
69
+ button.textContent = String(n);
70
+ });
71
+ `,
72
+ "helper.ts": "export function double(n: number) { return n * 2; }",
73
+ });
74
+ /* 0 -> 1 -> 3 proves the hoisted import (double) is linked. */
75
+ expect(value("srcAfter")).toBe("3");
76
+ });
77
+ });
78
+ //# sourceMappingURL=gui-script.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gui-script.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-script.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,yBAAyB,EAAE,GAAG,EAAE;IAC7D,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,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CACrC,QAAQ,EACR;;;;;;;;;;;;;;;OAeC,EACD;YACE,WAAW,EAAE;;;;;;;;;;;;;eAaN;SACR,CACF,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CACrC,WAAW,EACX;;;;;;;;;;;;;;OAcC,EACD;YACE,eAAe,EAAE,yFAAyF;YAC1G,YAAY,EAAE;;;;;;;;CAQrB;YACO,WAAW,EAAE,qDAAqD;SACnE,CACF,CAAC;QACF,+DAA+D;QAC/D,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * End-to-end tests for the `gui` extension — CSS cascade, stateful selectors
3
+ * (`:hover` / `:focus`) and transitions.
4
+ */
5
+ export {};
@@ -0,0 +1,175 @@
1
+ /**
2
+ * End-to-end tests for the `gui` extension — CSS cascade, stateful selectors
3
+ * (`:hover` / `:focus`) and transitions.
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 — style", () => {
9
+ beforeAll(() => harness.setup());
10
+ afterAll(() => harness.cleanup());
11
+ it("parses HTML/CSS and cascades computed styles", () => {
12
+ const { value } = harness.compileAndRun("cascade", `
13
+ import { createWindow, run } from "gui";
14
+
15
+ const HTML = \`
16
+ <html>
17
+ <head>
18
+ <style>
19
+ body { font-size: 16px; color: #111111; }
20
+ div { width: 10%; }
21
+ .box { width: 25%; }
22
+ #main { width: 60%; color: #222222; }
23
+ .box > .inner { padding-top: 3px; }
24
+ .box .inner { padding-top: 5px; }
25
+ li:first-child { color: #555555; }
26
+ li:nth-child(2) { color: #666666; }
27
+ li:not(.skip) { font-weight: bold; }
28
+ [data-role="lead"] { text-align: center; }
29
+ .em { font-size: 2em; }
30
+ .rem { font-size: 2rem; }
31
+ .imp { color: #000000; }
32
+ .imp { color: #ff0000 !important; }
33
+ </style>
34
+ </head>
35
+ <body>
36
+ <div id="main" class="box">
37
+ <div class="inner" style="color: #00ff00">x</div>
38
+ <div class="inner">y</div>
39
+ </div>
40
+ <ul>
41
+ <li>one</li>
42
+ <li>two</li>
43
+ <li class="skip">three</li>
44
+ </ul>
45
+ <p data-role="lead">lead</p>
46
+ <div class="em">em</div>
47
+ <div class="rem">rem</div>
48
+ <div class="imp" style="color: #0000ff">important</div>
49
+ </body>
50
+ </html>
51
+ \`;
52
+
53
+ const win = createWindow({ title: "cascade", width: 800, height: 600 });
54
+ win.on("ready", () => {
55
+ const out = (label, value) => console.log(label + "=" + value);
56
+ out("main-width", win.computedStyle("#main", "width"));
57
+ out("inner-padding", win.computedStyle(".inner", "padding-top"));
58
+ out("inner-color", win.computedStyle(".inner", "color"));
59
+ out("body-color", win.computedStyle("body", "color"));
60
+ out("li1-color", win.computedStyle("li:first-child", "color"));
61
+ out("li2-color", win.computedStyle("li:nth-child(2)", "color"));
62
+ out("li2-weight", win.computedStyle("li:nth-child(2)", "font-weight"));
63
+ out("skip-weight", win.computedStyle("li.skip", "font-weight"));
64
+ out("lead-align", win.computedStyle("[data-role=\\"lead\\"]", "text-align"));
65
+ out("em-size", win.computedStyle(".em", "font-size"));
66
+ out("rem-size", win.computedStyle(".rem", "font-size"));
67
+ out("imp-color", win.computedStyle(".imp", "color"));
68
+ out("li-count", win.queryCount("li"));
69
+ out("lead-count", win.queryCount("[data-role=\\"lead\\"]"));
70
+ });
71
+ win.loadHTML(HTML);
72
+ run();
73
+ `);
74
+ expect(value("main-width")).toBe("60%"); // id beats class and tag
75
+ expect(value("inner-padding")).toBe("5px"); // equal specificity, later rule wins
76
+ expect(value("inner-color")).toBe("rgb(0, 255, 0)"); // inline beats the sheet
77
+ expect(value("body-color")).toBe("rgb(17, 17, 17)");
78
+ expect(value("li1-color")).toBe("rgb(85, 85, 85)"); // :first-child
79
+ expect(value("li2-color")).toBe("rgb(102, 102, 102)"); // :nth-child(2)
80
+ expect(value("li2-weight")).toBe("700"); // :not(.skip)
81
+ expect(value("skip-weight")).toBe("400"); // :not(.skip) excluded it
82
+ expect(value("lead-align")).toBe("center"); // [data-role="lead"]
83
+ expect(value("em-size")).toBe("32px"); // 2em of the 16px parent
84
+ expect(value("rem-size")).toBe("32px"); // 2rem of the 16px root
85
+ expect(value("imp-color")).toBe("rgb(255, 0, 0)"); // !important beats inline
86
+ expect(value("li-count")).toBe("3");
87
+ expect(value("lead-count")).toBe("1");
88
+ });
89
+ it("applies :hover and :focus state to selector matching", () => {
90
+ const { value } = harness.compileAndRun("state", `
91
+ import { createWindow, run } from "gui";
92
+
93
+ const HTML = \`
94
+ <html><head><style>
95
+ body { margin: 0; }
96
+ #box { width: 100px; height: 50px; background: #000000; }
97
+ #box:hover { background: #ff0000; }
98
+ #box:focus { background: #00ff00; }
99
+ #box:hover > #inner { color: #0000ff; }
100
+ #inner { width: 20px; height: 20px; color: #888888; }
101
+ </style></head><body>
102
+ <div id="box"><div id="inner">x</div></div>
103
+ </body></html>
104
+ \`;
105
+
106
+ const win = createWindow({ title: "state", width: 400, height: 300 });
107
+ win.on("ready", () => {
108
+ console.log("initial-bg=" + win.computedStyle("#box", "background-color"));
109
+ console.log("initial-color=" + win.computedStyle("#inner", "color"));
110
+ win.sendEvent("mousemove", { x: 50, y: 40 });
111
+ console.log("hover-bg=" + win.computedStyle("#box", "background-color"));
112
+ win.sendEvent("mousemove", { x: 5, y: 5 });
113
+ console.log("inner-hover-color=" + win.computedStyle("#inner", "color"));
114
+ win.sendEvent("mousedown", { x: 50, y: 40, button: 0 });
115
+ console.log("focus-bg=" + win.computedStyle("#box", "background-color"));
116
+ win.close();
117
+ });
118
+ win.loadHTML(HTML);
119
+ run();
120
+ `);
121
+ expect(value("initial-bg")).toBe("rgb(0, 0, 0)"); // no :hover rule match
122
+ expect(value("initial-color")).toBe("rgb(136, 136, 136)");
123
+ expect(value("hover-bg")).toBe("rgb(255, 0, 0)"); // hovering #box
124
+ // Hover also applies to ancestors of the hovered element.
125
+ expect(value("inner-hover-color")).toBe("rgb(0, 0, 255)");
126
+ expect(value("focus-bg")).toBe("rgb(0, 255, 0)"); // :focus wins over :hover
127
+ });
128
+ it("interpolates CSS transitions as the clock advances", () => {
129
+ const { value } = harness.compileAndRun("transition", `
130
+ import { createWindow, run } from "gui";
131
+
132
+ const HTML = \`
133
+ <html><head><style>
134
+ body { margin: 0; }
135
+ #box {
136
+ width: 100px; height: 50px; background-color: #000000; color: #888888;
137
+ border-radius: 0px;
138
+ transition: background-color 1000ms linear, color 500ms linear, border-radius 1000ms linear;
139
+ }
140
+ #box:hover { background-color: #ffffff; color: #0000ff; border-radius: 10px; }
141
+ </style></head><body>
142
+ <div id="box">hi</div>
143
+ </body></html>
144
+ \`;
145
+
146
+ const win = createWindow({ title: "transition", width: 400, height: 300 });
147
+ win.on("ready", () => {
148
+ win.sendEvent("mousemove", { x: 50, y: 25 });
149
+ console.log("start=" + win.computedStyle("#box", "background-color"));
150
+ win.advance(250);
151
+ console.log("q1-bg=" + win.computedStyle("#box", "background-color"));
152
+ console.log("q1-color=" + win.computedStyle("#box", "color"));
153
+ win.advance(250);
154
+ console.log("q2-bg=" + win.computedStyle("#box", "background-color"));
155
+ console.log("q2-color=" + win.computedStyle("#box", "color"));
156
+ console.log("q2-radius=" + win.computedStyle("#box", "border-radius"));
157
+ win.advance(500);
158
+ console.log("end-bg=" + win.computedStyle("#box", "background-color"));
159
+ console.log("end-radius=" + win.computedStyle("#box", "border-radius"));
160
+ win.close();
161
+ });
162
+ win.loadHTML(HTML);
163
+ run();
164
+ `);
165
+ expect(value("start")).toBe("rgb(0, 0, 0)"); // 0% progress at t=0
166
+ expect(value("q1-bg")).toBe("rgb(64, 64, 64)"); // 25% of black->white
167
+ expect(value("q1-color")).toBe("rgb(68, 68, 196)"); // 50% of the 500ms color run
168
+ expect(value("q2-bg")).toBe("rgb(128, 128, 128)"); // halfway
169
+ expect(value("q2-color")).toBe("rgb(0, 0, 255)"); // finished
170
+ expect(value("q2-radius")).toBe("5px");
171
+ expect(value("end-bg")).toBe("rgb(255, 255, 255)"); // finished
172
+ expect(value("end-radius")).toBe("10px");
173
+ });
174
+ });
175
+ //# sourceMappingURL=gui-style.test.js.map