@gtkx/testing 0.21.0 → 1.0.0-rc.2

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 (216) hide show
  1. package/README.md +137 -34
  2. package/dist/act.d.ts +19 -0
  3. package/dist/act.d.ts.map +1 -0
  4. package/dist/act.js +56 -0
  5. package/dist/act.js.map +1 -0
  6. package/dist/bound-queries.d.ts +33 -0
  7. package/dist/bound-queries.d.ts.map +1 -0
  8. package/dist/bound-queries.js +2 -0
  9. package/dist/bound-queries.js.map +1 -0
  10. package/dist/build-queries.d.ts +16 -0
  11. package/dist/build-queries.d.ts.map +1 -0
  12. package/dist/build-queries.js +115 -0
  13. package/dist/build-queries.js.map +1 -0
  14. package/dist/cleanup-registry.d.ts +5 -0
  15. package/dist/cleanup-registry.d.ts.map +1 -0
  16. package/dist/cleanup-registry.js +21 -0
  17. package/dist/cleanup-registry.js.map +1 -0
  18. package/dist/config.d.ts +20 -52
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +17 -46
  21. package/dist/config.js.map +1 -1
  22. package/dist/editable.d.ts +8 -0
  23. package/dist/editable.d.ts.map +1 -0
  24. package/dist/editable.js +26 -0
  25. package/dist/editable.js.map +1 -0
  26. package/dist/errors.d.ts +30 -0
  27. package/dist/errors.d.ts.map +1 -0
  28. package/dist/errors.js +135 -0
  29. package/dist/errors.js.map +1 -0
  30. package/dist/fire-event.d.ts +8 -25
  31. package/dist/fire-event.d.ts.map +1 -1
  32. package/dist/fire-event.js +11 -30
  33. package/dist/fire-event.js.map +1 -1
  34. package/dist/frame-sync.d.ts +4 -0
  35. package/dist/frame-sync.d.ts.map +1 -0
  36. package/dist/frame-sync.js +37 -0
  37. package/dist/frame-sync.js.map +1 -0
  38. package/dist/index.d.ts +17 -12
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +11 -7
  41. package/dist/index.js.map +1 -1
  42. package/dist/matchers.d.ts +59 -0
  43. package/dist/matchers.d.ts.map +1 -0
  44. package/dist/matchers.js +117 -0
  45. package/dist/matchers.js.map +1 -0
  46. package/dist/pretty-widget.d.ts +24 -26
  47. package/dist/pretty-widget.d.ts.map +1 -1
  48. package/dist/pretty-widget.js +111 -117
  49. package/dist/pretty-widget.js.map +1 -1
  50. package/dist/queries.d.ts +190 -180
  51. package/dist/queries.d.ts.map +1 -1
  52. package/dist/queries.js +291 -320
  53. package/dist/queries.js.map +1 -1
  54. package/dist/register-test-hooks.d.ts +2 -0
  55. package/dist/register-test-hooks.d.ts.map +1 -0
  56. package/dist/register-test-hooks.js +16 -0
  57. package/dist/register-test-hooks.js.map +1 -0
  58. package/dist/render-hook.d.ts +15 -35
  59. package/dist/render-hook.d.ts.map +1 -1
  60. package/dist/render-hook.js +9 -45
  61. package/dist/render-hook.js.map +1 -1
  62. package/dist/render.d.ts +13 -42
  63. package/dist/render.d.ts.map +1 -1
  64. package/dist/render.js +138 -121
  65. package/dist/render.js.map +1 -1
  66. package/dist/role-helpers.d.ts +19 -53
  67. package/dist/role-helpers.d.ts.map +1 -1
  68. package/dist/role-helpers.js +48 -63
  69. package/dist/role-helpers.js.map +1 -1
  70. package/dist/screen.d.ts +7 -63
  71. package/dist/screen.d.ts.map +1 -1
  72. package/dist/screen.js +24 -110
  73. package/dist/screen.js.map +1 -1
  74. package/dist/screenshot.d.ts +21 -23
  75. package/dist/screenshot.d.ts.map +1 -1
  76. package/dist/screenshot.js +98 -50
  77. package/dist/screenshot.js.map +1 -1
  78. package/dist/suggestions.d.ts +27 -0
  79. package/dist/suggestions.d.ts.map +1 -0
  80. package/dist/suggestions.js +61 -0
  81. package/dist/suggestions.js.map +1 -0
  82. package/dist/traversal.d.ts +12 -10
  83. package/dist/traversal.d.ts.map +1 -1
  84. package/dist/traversal.js +36 -15
  85. package/dist/traversal.js.map +1 -1
  86. package/dist/types.d.ts +115 -169
  87. package/dist/types.d.ts.map +1 -1
  88. package/dist/types.js.map +1 -1
  89. package/dist/user-event/adjustment.d.ts +10 -0
  90. package/dist/user-event/adjustment.d.ts.map +1 -0
  91. package/dist/user-event/adjustment.js +35 -0
  92. package/dist/user-event/adjustment.js.map +1 -0
  93. package/dist/user-event/click.d.ts +8 -0
  94. package/dist/user-event/click.d.ts.map +1 -0
  95. package/dist/user-event/click.js +73 -0
  96. package/dist/user-event/click.js.map +1 -0
  97. package/dist/user-event/controller.d.ts +8 -0
  98. package/dist/user-event/controller.d.ts.map +1 -0
  99. package/dist/user-event/controller.js +31 -0
  100. package/dist/user-event/controller.js.map +1 -0
  101. package/dist/user-event/dispatch.d.ts +7 -0
  102. package/dist/user-event/dispatch.d.ts.map +1 -0
  103. package/dist/user-event/dispatch.js +11 -0
  104. package/dist/user-event/dispatch.js.map +1 -0
  105. package/dist/user-event/event-wrapper.d.ts +4 -0
  106. package/dist/user-event/event-wrapper.d.ts.map +1 -0
  107. package/dist/user-event/event-wrapper.js +59 -0
  108. package/dist/user-event/event-wrapper.js.map +1 -0
  109. package/dist/user-event/gesture.d.ts +35 -0
  110. package/dist/user-event/gesture.d.ts.map +1 -0
  111. package/dist/user-event/gesture.js +138 -0
  112. package/dist/user-event/gesture.js.map +1 -0
  113. package/dist/user-event/index.d.ts +51 -0
  114. package/dist/user-event/index.d.ts.map +1 -0
  115. package/dist/user-event/index.js +42 -0
  116. package/dist/user-event/index.js.map +1 -0
  117. package/dist/user-event/keyboard.d.ts +10 -0
  118. package/dist/user-event/keyboard.d.ts.map +1 -0
  119. package/dist/user-event/keyboard.js +204 -0
  120. package/dist/user-event/keyboard.js.map +1 -0
  121. package/dist/user-event/pointer.d.ts +10 -0
  122. package/dist/user-event/pointer.d.ts.map +1 -0
  123. package/dist/user-event/pointer.js +30 -0
  124. package/dist/user-event/pointer.js.map +1 -0
  125. package/dist/user-event/selection.d.ts +5 -0
  126. package/dist/user-event/selection.d.ts.map +1 -0
  127. package/dist/user-event/selection.js +105 -0
  128. package/dist/user-event/selection.js.map +1 -0
  129. package/dist/user-event/state.d.ts +7 -0
  130. package/dist/user-event/state.d.ts.map +1 -0
  131. package/dist/user-event/state.js +3 -0
  132. package/dist/user-event/state.js.map +1 -0
  133. package/dist/user-event/text.d.ts +18 -0
  134. package/dist/user-event/text.d.ts.map +1 -0
  135. package/dist/user-event/text.js +113 -0
  136. package/dist/user-event/text.js.map +1 -0
  137. package/dist/wait-for.d.ts +16 -36
  138. package/dist/wait-for.d.ts.map +1 -1
  139. package/dist/wait-for.js +74 -70
  140. package/dist/wait-for.js.map +1 -1
  141. package/dist/widget-accessible-properties.d.ts +34 -0
  142. package/dist/widget-accessible-properties.d.ts.map +1 -0
  143. package/dist/widget-accessible-properties.js +260 -0
  144. package/dist/widget-accessible-properties.js.map +1 -0
  145. package/dist/within.d.ts +10 -26
  146. package/dist/within.d.ts.map +1 -1
  147. package/dist/within.js +16 -25
  148. package/dist/within.js.map +1 -1
  149. package/package.json +23 -12
  150. package/src/act.ts +77 -0
  151. package/src/bound-queries.ts +37 -0
  152. package/src/build-queries.ts +226 -0
  153. package/src/cleanup-registry.ts +27 -0
  154. package/src/config.ts +35 -62
  155. package/src/editable.ts +37 -0
  156. package/src/errors.ts +191 -0
  157. package/src/fire-event.ts +13 -38
  158. package/src/frame-sync.ts +51 -0
  159. package/src/index.ts +58 -15
  160. package/src/matchers.ts +251 -0
  161. package/src/pretty-widget.ts +163 -130
  162. package/src/queries.ts +485 -396
  163. package/src/register-test-hooks.ts +19 -0
  164. package/src/render-hook.tsx +38 -45
  165. package/src/render.tsx +192 -155
  166. package/src/role-helpers.ts +65 -72
  167. package/src/screen.ts +26 -114
  168. package/src/screenshot.ts +128 -56
  169. package/src/suggestions.ts +106 -0
  170. package/src/traversal.ts +56 -22
  171. package/src/types.ts +144 -172
  172. package/src/user-event/adjustment.ts +62 -0
  173. package/src/user-event/click.ts +99 -0
  174. package/src/user-event/controller.ts +58 -0
  175. package/src/user-event/dispatch.ts +25 -0
  176. package/src/user-event/event-wrapper.ts +80 -0
  177. package/src/user-event/gesture.ts +239 -0
  178. package/src/user-event/index.ts +81 -0
  179. package/src/user-event/keyboard.ts +290 -0
  180. package/src/user-event/pointer.ts +46 -0
  181. package/src/user-event/selection.ts +159 -0
  182. package/src/user-event/state.ts +8 -0
  183. package/src/user-event/text.ts +172 -0
  184. package/src/wait-for.ts +117 -78
  185. package/src/widget-accessible-properties.ts +388 -0
  186. package/src/within.ts +29 -27
  187. package/dist/bind-queries.d.ts +0 -14
  188. package/dist/bind-queries.d.ts.map +0 -1
  189. package/dist/bind-queries.js +0 -29
  190. package/dist/bind-queries.js.map +0 -1
  191. package/dist/error-builder.d.ts +0 -28
  192. package/dist/error-builder.d.ts.map +0 -1
  193. package/dist/error-builder.js +0 -81
  194. package/dist/error-builder.js.map +0 -1
  195. package/dist/timing.d.ts +0 -18
  196. package/dist/timing.d.ts.map +0 -1
  197. package/dist/timing.js +0 -18
  198. package/dist/timing.js.map +0 -1
  199. package/dist/user-event.d.ts +0 -138
  200. package/dist/user-event.d.ts.map +0 -1
  201. package/dist/user-event.js +0 -411
  202. package/dist/user-event.js.map +0 -1
  203. package/dist/widget-text.d.ts +0 -69
  204. package/dist/widget-text.d.ts.map +0 -1
  205. package/dist/widget-text.js +0 -177
  206. package/dist/widget-text.js.map +0 -1
  207. package/dist/widget.d.ts +0 -3
  208. package/dist/widget.d.ts.map +0 -1
  209. package/dist/widget.js +0 -13
  210. package/dist/widget.js.map +0 -1
  211. package/src/bind-queries.ts +0 -52
  212. package/src/error-builder.ts +0 -102
  213. package/src/timing.ts +0 -17
  214. package/src/user-event.ts +0 -492
  215. package/src/widget-text.ts +0 -202
  216. package/src/widget.ts +0 -15
@@ -1,201 +1,218 @@
1
- import * as Gtk from "@gtkx/ffi/gtk";
1
+ import type * as Gtk from "@gtkx/gi/gtk";
2
+ import { sortStringsBy } from "@gtkx/utils";
2
3
  import { formatRole } from "./role-helpers.js";
3
- import { type Container, isApplication } from "./traversal.js";
4
- import { getWidgetPropertyText } from "./widget-text.js";
4
+ import { type Container, roots } from "./traversal.js";
5
+ import { getWidgetNodeText } from "./widget-accessible-properties.js";
5
6
 
6
- const DEFAULT_MAX_LENGTH = 7000;
7
- const INDENT = " ";
8
-
9
- const debugIdMap = new WeakMap<Gtk.Widget, string>();
10
- let nextDebugId = 0;
11
-
12
- const getWidgetDebugId = (widget: Gtk.Widget): string => {
13
- let id = debugIdMap.get(widget);
14
- if (!id) {
15
- id = String(nextDebugId++);
16
- debugIdMap.set(widget, id);
17
- }
18
- return id;
19
- };
7
+ type WidgetIdResolver = (widget: Gtk.Widget) => string;
20
8
 
21
9
  /**
22
- * Options for {@link prettyWidget}.
10
+ * Options controlling how a widget tree is rendered to a string by
11
+ * {@link prettyWidget} and {@link logWidget}.
23
12
  */
24
- export type PrettyWidgetOptions = {
25
- /** Maximum output length before truncation (default: 7000) */
13
+ type PrettyWidgetOptions = {
14
+ /** Truncates the output once it exceeds this many characters. */
26
15
  maxLength?: number;
27
- /** Enable ANSI color highlighting (default: auto-detect) */
16
+ /** Whether to apply ANSI color highlighting; defaults to the terminal capabilities. */
28
17
  highlight?: boolean;
29
- /** Include widget IDs for MCP/agentic interactions (default: false) */
30
- includeIds?: boolean;
18
+ /** Resolves an `id` attribute to show for each widget. */
19
+ getId?: WidgetIdResolver;
20
+ /** Stops descending past this depth, replacing deeper children with a summary line. */
21
+ maxDepth?: number;
31
22
  };
32
23
 
33
- type HighlightColors = {
24
+ type Colors = {
34
25
  tag: (s: string) => string;
35
26
  attr: (s: string) => string;
36
27
  value: (s: string) => string;
37
- text: (s: string) => string;
38
- reset: string;
39
28
  };
40
29
 
41
- const shouldHighlight = (): boolean => {
42
- if (typeof process === "undefined") return false;
43
- if (process.env.COLORS === "false" || process.env.NO_COLOR) return false;
44
- if (process.env.COLORS === "true" || process.env.FORCE_COLOR) return true;
45
- return process.stdout?.isTTY ?? false;
30
+ type FormatContext = {
31
+ getId: WidgetIdResolver | undefined;
32
+ colors: Colors;
33
+ maxDepth: number | undefined;
46
34
  };
47
35
 
48
- const ansi = {
49
- cyan: "\x1b[36m",
50
- yellow: "\x1b[33m",
51
- green: "\x1b[32m",
52
- reset: "\x1b[0m",
53
- };
54
-
55
- const createColors = (enabled: boolean): HighlightColors => {
56
- if (!enabled) {
57
- const identity = (s: string): string => s;
58
- return { tag: identity, attr: identity, value: identity, text: identity, reset: "" };
59
- }
60
- return {
61
- tag: (s) => `${ansi.cyan}${s}${ansi.reset}`,
62
- attr: (s) => `${ansi.yellow}${s}${ansi.reset}`,
63
- value: (s) => `${ansi.green}${s}${ansi.reset}`,
64
- text: (s) => s,
65
- reset: ansi.reset,
66
- };
67
- };
68
-
69
- const formatTagName = (widget: Gtk.Widget): string => {
70
- return widget.constructor.name;
71
- };
36
+ const DEFAULT_MAX_LENGTH = 7000;
37
+ const INDENT = " ";
72
38
 
73
- const escapeAttrValue = (value: string): string => {
74
- return value.replace(/"/g, "&quot;");
39
+ const ansi = {
40
+ cyan: "\u{1B}[36m",
41
+ yellow: "\u{1B}[33m",
42
+ green: "\u{1B}[32m",
43
+ reset: "\u{1B}[0m",
75
44
  };
76
45
 
77
- const formatAttributes = (widget: Gtk.Widget, colors: HighlightColors, includeIds: boolean): string => {
46
+ const buildAttrs = (widget: Gtk.Widget, getId: WidgetIdResolver | undefined): [string, string][] => {
78
47
  const attrs: [string, string][] = [];
79
48
 
80
- if (includeIds) {
81
- attrs.push(["id", getWidgetDebugId(widget)]);
49
+ if (getId) {
50
+ attrs.push(["id", getId(widget)]);
82
51
  }
83
52
 
84
53
  const name = widget.getName();
54
+
85
55
  if (name) {
86
56
  attrs.push(["name", name]);
87
57
  }
88
58
 
89
- const role = widget.getAccessibleRole();
90
- if (role !== undefined) {
91
- attrs.push(["role", formatRole(role)]);
92
- }
59
+ attrs.push(["role", formatRole(widget.getAccessibleRole())]);
93
60
 
94
61
  if (!widget.getSensitive()) {
95
- attrs.push(["aria-disabled", "true"]);
62
+ attrs.push(["accessible-disabled", "true"]);
96
63
  }
97
64
 
98
65
  if (!widget.getVisible()) {
99
- attrs.push(["aria-hidden", "true"]);
66
+ attrs.push(["accessible-hidden", "true"]);
100
67
  }
101
68
 
102
- if (attrs.length === 0) return "";
69
+ const idAttrs = attrs.filter(([key]) => key === "id");
70
+
71
+ const otherAttrs = sortStringsBy(
72
+ attrs.filter(([key]) => key !== "id"),
73
+ ([key]) => key,
74
+ );
103
75
 
104
- return attrs
105
- .sort(([a], [b]) => {
106
- if (a === "id") return -1;
107
- if (b === "id") return 1;
108
- return a.localeCompare(b);
109
- })
110
- .map(([key, value]) => ` ${colors.attr(key)}=${colors.value(`"${escapeAttrValue(value)}"`)}`)
111
- .join("");
76
+ return [...idAttrs, ...otherAttrs];
112
77
  };
113
78
 
114
- const hasChildren = (widget: Gtk.Widget): boolean => {
115
- return widget.getFirstChild() !== null;
79
+ const shouldHighlight = (): boolean => {
80
+ if (typeof process === "undefined") {
81
+ return false;
82
+ }
83
+
84
+ if (process.env.COLORS === "false" || process.env.NO_COLOR) {
85
+ return false;
86
+ }
87
+
88
+ if (process.env.COLORS === "true" || process.env.FORCE_COLOR) {
89
+ return true;
90
+ }
91
+
92
+ return process.stdout.isTTY;
116
93
  };
117
94
 
118
- const printWidget = (widget: Gtk.Widget, colors: HighlightColors, depth: number, includeIds: boolean): string => {
119
- const indent = INDENT.repeat(depth);
120
- const tagName = formatTagName(widget);
121
- const attributes = formatAttributes(widget, colors, includeIds);
122
- const text = getWidgetPropertyText(widget);
123
- const children: string[] = [];
95
+ const createColors = (isEnabled: boolean): Colors => {
96
+ if (!isEnabled) {
97
+ const identity = (s: string): string => s;
98
+
99
+ return { tag: identity, attr: identity, value: identity };
100
+ }
101
+
102
+ return {
103
+ tag: (s) => `${ansi.cyan}${s}${ansi.reset}`,
104
+ attr: (s) => `${ansi.yellow}${s}${ansi.reset}`,
105
+ value: (s) => `${ansi.green}${s}${ansi.reset}`,
106
+ };
107
+ };
108
+
109
+ const escapeAttrValue = (value: string): string => value.replaceAll('"', "&quot;");
110
+
111
+ const formatAttr = (key: string, value: string, colors: Colors): string => {
112
+ const quoted = `"${escapeAttrValue(value)}"`;
113
+
114
+ return ` ${colors.attr(key)}=${colors.value(quoted)}`;
115
+ };
124
116
 
117
+ const formatAttrs = (attrs: [string, string][], colors: Colors): string =>
118
+ attrs.map(([key, value]) => formatAttr(key, value, colors)).join("");
119
+
120
+ const countChildren = (widget: Gtk.Widget): number => {
121
+ let count = 0;
125
122
  let child = widget.getFirstChild();
123
+
126
124
  while (child) {
127
- children.push(printWidget(child, colors, depth + 1, includeIds));
125
+ count += 1;
128
126
  child = child.getNextSibling();
129
127
  }
130
128
 
131
- const openTag = `${colors.tag("<")}${colors.tag(tagName)}${attributes}${colors.tag(">")}`;
129
+ return count;
130
+ };
132
131
 
133
- if (!hasChildren(widget) && !text) {
134
- return `${indent}${openTag}\n`;
132
+ const formatHiddenChildrenLine = (widget: Gtk.Widget, depth: number, ctx: FormatContext): string => {
133
+ const { getId, colors } = ctx;
134
+ const indent = INDENT.repeat(depth);
135
+ const count = countChildren(widget);
136
+ const hint = getId ? ` (pass rootId="${getId(widget)}" or raise maxDepth to expand)` : "";
137
+ const plural = count === 1 ? "" : "s";
138
+ const summary = `… ${String(count)} child widget${plural} hidden${hint}`;
139
+
140
+ return `${indent}${INDENT}${colors.tag(summary)}\n`;
141
+ };
142
+
143
+ const formatChildren = (widget: Gtk.Widget, depth: number, ctx: FormatContext): string => {
144
+ let output = "";
145
+ let child = widget.getFirstChild();
146
+
147
+ while (child) {
148
+ output += formatWidget(child, depth + 1, ctx);
149
+ child = child.getNextSibling();
135
150
  }
136
151
 
137
- const closeTag = `${colors.tag("</")}${colors.tag(tagName)}${colors.tag(">")}`;
152
+ return output;
153
+ };
138
154
 
139
- if (text && !hasChildren(widget)) {
140
- const textContent = colors.text(text);
141
- return `${indent}${openTag}\n${indent}${INDENT}${textContent}\n${indent}${closeTag}\n`;
155
+ const formatWidget = (widget: Gtk.Widget, depth: number, ctx: FormatContext): string => {
156
+ const { getId, colors, maxDepth } = ctx;
157
+ const indent = INDENT.repeat(depth);
158
+ const tag = widget.constructor.name;
159
+ const attrs = formatAttrs(buildAttrs(widget, getId), colors);
160
+ const openTag = `${colors.tag("<")}${colors.tag(tag)}${attrs}${colors.tag(">")}`;
161
+ const closeTag = `${colors.tag("</")}${colors.tag(tag)}${colors.tag(">")}`;
162
+ const text = getWidgetNodeText(widget);
163
+ const firstChild = widget.getFirstChild();
164
+
165
+ if (!text && !firstChild) {
166
+ return `${indent}${openTag}\n`;
142
167
  }
143
168
 
144
- let result = `${indent}${openTag}\n`;
169
+ let output = `${indent}${openTag}\n`;
170
+
145
171
  if (text) {
146
- result += `${indent}${INDENT}${colors.text(text)}\n`;
172
+ output += `${indent}${INDENT}${text}\n`;
147
173
  }
148
- for (const childOutput of children) {
149
- result += childOutput;
174
+
175
+ if (firstChild && maxDepth !== undefined && depth >= maxDepth) {
176
+ output += formatHiddenChildrenLine(widget, depth, ctx);
177
+ output += `${indent}${closeTag}\n`;
178
+
179
+ return output;
150
180
  }
151
- result += `${indent}${closeTag}\n`;
152
181
 
153
- return result;
182
+ output += formatChildren(widget, depth, ctx);
183
+ output += `${indent}${closeTag}\n`;
184
+
185
+ return output;
154
186
  };
155
187
 
156
- const printContainer = (container: Container, colors: HighlightColors, includeIds: boolean): string => {
157
- if (isApplication(container)) {
158
- const windows = Gtk.Window.listToplevels();
159
- return windows.map((window) => printWidget(window, colors, 0, includeIds)).join("");
160
- }
161
- return printWidget(container, colors, 0, includeIds);
188
+ const resolveMaxLength = (options: PrettyWidgetOptions): number => {
189
+ const envLimit = process.env.DEBUG_PRINT_LIMIT ? Number(process.env.DEBUG_PRINT_LIMIT) : DEFAULT_MAX_LENGTH;
190
+
191
+ return options.maxLength ?? envLimit;
162
192
  };
163
193
 
164
194
  /**
165
- * Formats a widget tree as a readable string for debugging.
166
- *
167
- * Renders the widget hierarchy in an HTML-like format with accessibility
168
- * attributes like role, name, and text content.
169
- *
170
- * @param container - The container widget or application to format
171
- * @param options - Formatting options for length and highlighting
172
- * @returns Formatted string representation of the widget tree
195
+ * Renders a widget tree as an indented, HTML-like string showing each widget's
196
+ * tag, accessible attributes, and text content.
173
197
  *
174
- * @example
175
- * ```tsx
176
- * import { prettyWidget } from "@gtkx/testing";
177
- *
178
- * console.log(prettyWidget(application));
179
- * // Output:
180
- * // <GtkApplicationWindow role="window">
181
- * // <GtkButton role="button">
182
- * // Click me
183
- * // </GtkButton>
184
- * // </GtkApplicationWindow>
185
- * ```
198
+ * @param container The scope whose widget tree is formatted.
199
+ * @param options Formatting options such as truncation length and highlighting.
200
+ * @returns The formatted representation of the tree.
186
201
  */
187
- export const prettyWidget = (container: Container, options: PrettyWidgetOptions = {}): string => {
188
- const envLimit = process.env.DEBUG_PRINT_LIMIT ? Number(process.env.DEBUG_PRINT_LIMIT) : DEFAULT_MAX_LENGTH;
189
- const maxLength = options.maxLength ?? envLimit;
190
- const highlight = options.highlight ?? shouldHighlight();
191
- const includeIds = options.includeIds ?? false;
202
+ const prettyWidget = (container: Container, options: PrettyWidgetOptions = {}): string => {
203
+ const maxLength = resolveMaxLength(options);
192
204
 
193
205
  if (maxLength === 0) {
194
206
  return "";
195
207
  }
196
208
 
197
- const colors = createColors(highlight);
198
- const output = printContainer(container, colors, includeIds);
209
+ const isHighlight = options.highlight ?? shouldHighlight();
210
+ const colors = createColors(isHighlight);
211
+ let output = "";
212
+
213
+ for (const root of roots(container)) {
214
+ output += formatWidget(root, 0, { getId: options.getId, colors, maxDepth: options.maxDepth });
215
+ }
199
216
 
200
217
  if (output.length > maxLength) {
201
218
  return `${output.slice(0, maxLength)}...`;
@@ -203,3 +220,19 @@ export const prettyWidget = (container: Container, options: PrettyWidgetOptions
203
220
 
204
221
  return output.trimEnd();
205
222
  };
223
+
224
+ /**
225
+ * Prints one or more widget trees to the console using {@link prettyWidget}.
226
+ *
227
+ * @param container A single scope or an array of scopes to format and print.
228
+ * @param options Formatting options passed through to {@link prettyWidget}.
229
+ */
230
+ const logWidget = (container: Container | Container[], options?: PrettyWidgetOptions): void => {
231
+ const containers: Container[] = Array.isArray(container) ? container : [container];
232
+
233
+ for (const target of containers) {
234
+ console.log(prettyWidget(target, options));
235
+ }
236
+ };
237
+
238
+ export { prettyWidget, logWidget, type PrettyWidgetOptions };