@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
package/dist/render.js CHANGED
@@ -1,147 +1,164 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { start, stop } from "@gtkx/ffi";
3
- import * as Gio from "@gtkx/ffi/gio";
4
- import { ApplicationContext, GtkApplicationWindow, reconciler } from "@gtkx/react";
5
- import { createRef } from "react";
6
- import { bindQueries } from "./bind-queries.js";
7
- import { prettyWidget } from "./pretty-widget.js";
8
- import { setScreenRoot } from "./screen.js";
9
- import { tick } from "./timing.js";
10
- import { isApplication, traverse } from "./traversal.js";
11
- let application = null;
12
- let container = null;
13
- let lastRenderError = null;
14
- const update = async (instance, element, fiberRoot) => {
15
- lastRenderError = null;
16
- instance.updateContainer(element, fiberRoot, null, () => { });
17
- await tick();
18
- if (lastRenderError) {
19
- const error = lastRenderError;
20
- lastRenderError = null;
21
- throw error;
2
+ import * as Gtk from "@gtkx/gi/gtk";
3
+ import { createReconcilerRoot, isRootElement, setReconcilerErrorHandler, } from "@gtkx/react/internal";
4
+ import { StrictMode } from "react";
5
+ import { runInAct } from "./act.js";
6
+ import { addToCleanupQueue, runCleanup } from "./cleanup-registry.js";
7
+ import { scheduleAfterLayout } from "./frame-sync.js";
8
+ import { logWidget } from "./pretty-widget.js";
9
+ import { logRoles } from "./role-helpers.js";
10
+ import { clearScreen, setScreen } from "./screen.js";
11
+ import { captureAndSaveScreenshot } from "./screenshot.js";
12
+ import { TOPLEVELS, traverse } from "./traversal.js";
13
+ import { resetClipboard } from "./user-event/index.js";
14
+ import { within } from "./within.js";
15
+ const reconcilerErrors = { lastError: null, isHandlerInstalled: false };
16
+ const activeRenders = new Set();
17
+ const HARNESS_WINDOW_WIDTH = 800;
18
+ const HARNESS_WINDOW_HEIGHT = 600;
19
+ const flushLayout = (window) => new Promise((resolve) => {
20
+ scheduleAfterLayout(window, resolve);
21
+ });
22
+ const update = async (element, root) => {
23
+ await runInAct(() => {
24
+ root.update(element);
25
+ });
26
+ if (reconcilerErrors.lastError) {
27
+ const captured = reconcilerErrors.lastError;
28
+ reconcilerErrors.lastError = null;
29
+ throw captured;
30
+ }
31
+ };
32
+ const disposeActiveRender = async (active) => {
33
+ if (!activeRenders.delete(active)) {
34
+ return;
35
+ }
36
+ await active.root.unmount(async (root) => {
37
+ await update(null, root);
38
+ active.window?.destroy();
39
+ });
40
+ };
41
+ const disposeAllActiveRenders = async () => {
42
+ for (const active of activeRenders) {
43
+ await disposeActiveRender(active);
22
44
  }
23
45
  };
24
46
  const handleError = (error) => {
25
- lastRenderError = error;
47
+ reconcilerErrors.lastError = error instanceof Error ? error : new Error(String(error));
26
48
  };
27
- const ensureInitialized = () => {
28
- application = start("org.gtkx.testing", Gio.ApplicationFlags.NON_UNIQUE);
29
- if (!container) {
30
- const instance = reconciler.getInstance();
31
- container = instance.createContainer(application, 1, null, false, null, "", handleError, handleError, () => { }, () => { });
49
+ const installErrorHandler = () => {
50
+ if (reconcilerErrors.isHandlerInstalled) {
51
+ return;
32
52
  }
33
- return { app: application, container };
53
+ setReconcilerErrorHandler(handleError);
54
+ reconcilerErrors.isHandlerInstalled = true;
34
55
  };
35
- const DefaultWrapper = ({ children, ref }) => (_jsx(GtkApplicationWindow, { ref: ref, defaultWidth: 800, defaultHeight: 600, children: children }));
36
- const findFirstWidget = (root) => {
37
- for (const widget of traverse(root)) {
38
- if (isApplication(root))
39
- return widget;
40
- return root;
56
+ const resolveContainer = (container) => {
57
+ if (isRootElement(container)) {
58
+ return { containerInfo: container, window: null };
59
+ }
60
+ if (container instanceof Gtk.Widget) {
61
+ return { containerInfo: container, window: null };
41
62
  }
42
- return null;
63
+ const window = new Gtk.Window({ defaultWidth: HARNESS_WINDOW_WIDTH, defaultHeight: HARNESS_WINDOW_HEIGHT });
64
+ window.setTitlebar(new Gtk.HeaderBar({ showTitleButtons: false }));
65
+ return { containerInfo: window, window };
43
66
  };
44
- const wrapElement = (element, wrapperRef, wrapper) => {
45
- if (wrapper === false || wrapper === undefined)
46
- return element;
47
- const Wrapper = wrapper === true ? DefaultWrapper : wrapper;
48
- return _jsx(Wrapper, { ref: wrapperRef, children: element });
67
+ const firstToplevelWidget = (baseElement) => {
68
+ if (baseElement instanceof Gtk.Widget) {
69
+ return baseElement;
70
+ }
71
+ const [first] = traverse(baseElement);
72
+ if (first) {
73
+ return first;
74
+ }
75
+ throw new Error("render() produced no widgets: ensure the element renders visible content");
49
76
  };
50
- const resolveContainer = (wrapper, wrapperRef, baseElement) => {
51
- if (wrapper !== false && wrapper !== undefined && wrapperRef.current) {
52
- return wrapperRef.current;
77
+ const resolveResultContainer = (resolved, container, baseElement) => {
78
+ if (resolved.window) {
79
+ return resolved.window;
80
+ }
81
+ if (container instanceof Gtk.Widget) {
82
+ return container;
53
83
  }
54
- const firstWidget = findFirstWidget(baseElement);
55
- if (!firstWidget) {
56
- throw new Error("render() produced no widgets: ensure the element renders visible content");
84
+ return firstToplevelWidget(baseElement);
85
+ };
86
+ const renderErrorHandlers = (options) => ({
87
+ onUncaughtError: handleError,
88
+ onCaughtError: (error, errorInfo) => {
89
+ handleError(error);
90
+ options?.onCaughtError?.(error, errorInfo);
91
+ },
92
+ onRecoverableError: (error, errorInfo) => {
93
+ options?.onRecoverableError?.(error, errorInfo);
94
+ },
95
+ });
96
+ const applyEnableAnimations = (areAnimationsEnabled) => {
97
+ const settings = Gtk.Settings.getDefault();
98
+ if (settings) {
99
+ settings.gtkEnableAnimations = areAnimationsEnabled;
57
100
  }
58
- return firstWidget;
59
101
  };
60
102
  /**
61
- * Renders a React element for testing.
62
- *
63
- * Creates a GTK application context and renders the element, returning
64
- * query methods and utilities for interacting with the rendered widgets.
65
- *
66
- * @param element - The React element to render
67
- * @param options - Render options including wrapper configuration
68
- * @returns A promise resolving to query methods and utilities
69
- *
70
- * @example
71
- * ```tsx
72
- * import { render, screen } from "@gtkx/testing";
103
+ * Renders a React element into a GTK4 widget tree and returns queries
104
+ * scoped to it along with controls for rerendering and unmounting. When no
105
+ * container is supplied, a harness window is created and presented.
73
106
  *
74
- * test("button click", async () => {
75
- * await render(<MyButton />);
76
- * const button = await screen.findByRole(Gtk.AccessibleRole.BUTTON);
77
- * await userEvent.click(button);
78
- * });
79
- * ```
80
- *
81
- * @see {@link cleanup} for cleaning up after tests
82
- * @see {@link screen} for global query access
107
+ * @param element The React element to render.
108
+ * @param options Optional container, wrapper, custom queries, and other render settings.
109
+ * @returns A render result with bound queries, debug helpers, and lifecycle controls.
83
110
  */
84
- export const render = async (element, options) => {
85
- const { app: application, container: fiberRoot } = ensureInitialized();
86
- const instance = reconciler.getInstance();
87
- const baseElement = options?.baseElement ?? application;
88
- const wrapper = options?.wrapper ?? true;
89
- const wrapperRef = createRef();
90
- const wrappedElement = wrapElement(element, wrapperRef, wrapper);
91
- const withContext = _jsx(ApplicationContext.Provider, { value: application, children: wrappedElement });
92
- await update(instance, withContext, fiberRoot);
93
- setScreenRoot(application);
94
- return {
95
- container: resolveContainer(wrapper, wrapperRef, baseElement),
111
+ const render = async (element, options) => {
112
+ installErrorHandler();
113
+ applyEnableAnimations(options?.animations === true);
114
+ const baseElement = options?.baseElement ?? TOPLEVELS;
115
+ const Wrapper = options?.wrapper;
116
+ const resolved = resolveContainer(options?.container);
117
+ const root = createReconcilerRoot({
118
+ containerInfo: resolved.containerInfo,
119
+ ...renderErrorHandlers(options),
120
+ });
121
+ const active = { root, window: resolved.window };
122
+ activeRenders.add(active);
123
+ addToCleanupQueue(disposeAllActiveRenders);
124
+ addToCleanupQueue(clearScreen);
125
+ addToCleanupQueue(resetClipboard);
126
+ const wrap = (node) => {
127
+ const wrapped = Wrapper ? _jsx(Wrapper, { children: node }) : node;
128
+ return options?.reactStrictMode ? _jsx(StrictMode, { children: wrapped }) : wrapped;
129
+ };
130
+ await update(wrap(element), root);
131
+ resolved.window?.present();
132
+ await flushLayout(resolved.window);
133
+ const container = resolveResultContainer(resolved, options?.container, baseElement);
134
+ const result = {
135
+ ...within(baseElement, options?.queries),
136
+ container,
96
137
  baseElement,
97
- ...bindQueries(baseElement),
98
- unmount: () => update(instance, null, fiberRoot),
138
+ unmount: async () => {
139
+ await disposeActiveRender(active);
140
+ },
99
141
  rerender: async (newElement) => {
100
- const newWrapperRef = createRef();
101
- const wrapped = wrapElement(newElement, newWrapperRef, wrapper);
102
- const withCtx = _jsx(ApplicationContext.Provider, { value: application, children: wrapped });
103
- await update(instance, withCtx, fiberRoot);
142
+ await update(wrap(newElement), root);
143
+ await flushLayout(resolved.window);
104
144
  },
105
- debug: () => {
106
- console.log(prettyWidget(application));
145
+ debug: (element = baseElement, debugOptions) => {
146
+ logWidget(element, debugOptions);
107
147
  },
148
+ logRoles: () => {
149
+ logRoles(baseElement);
150
+ },
151
+ screenshot: (selector, screenshotOptions) => captureAndSaveScreenshot(selector, screenshotOptions),
108
152
  };
153
+ setScreen(result);
154
+ return result;
109
155
  };
110
156
  /**
111
- * Cleans up the rendered component tree.
112
- *
113
- * Unmounts all rendered components and resets the testing environment.
114
- * Call this in `afterEach` to ensure tests don't affect each other.
115
- *
116
- * @example
117
- * ```tsx
118
- * import { render, cleanup } from "@gtkx/testing";
119
- *
120
- * afterEach(async () => {
121
- * await cleanup();
122
- * });
123
- *
124
- * test("my test", async () => {
125
- * await render(<MyComponent />);
126
- * // ...
127
- * });
128
- * ```
157
+ * Unmounts every active render and runs all registered cleanup callbacks,
158
+ * resetting the screen and clipboard. Called automatically after each test.
129
159
  */
130
- export const cleanup = async () => {
131
- if (container && application) {
132
- const instance = reconciler.getInstance();
133
- await update(instance, null, container);
134
- }
135
- container = null;
136
- setScreenRoot(null);
137
- };
138
- const handleSignal = () => {
139
- try {
140
- stop();
141
- }
142
- catch { }
143
- process.exit(0);
160
+ const cleanup = async () => {
161
+ await runCleanup();
144
162
  };
145
- process.on("SIGTERM", handleSignal);
146
- process.on("SIGINT", handleSignal);
163
+ export { render, cleanup };
147
164
  //# sourceMappingURL=render.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.tsx"],"names":[],"mappings":";AACA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AACpC,OAAO,EACH,oBAAoB,EACpB,aAAa,EAEb,yBAAyB,GAC5B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAkC,UAAU,EAAE,MAAM,OAAO,CAAC;AAGnE,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,SAAS,EAA4B,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAkB,SAAS,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAiBrC,MAAM,gBAAgB,GAAyB,EAAE,SAAS,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC;AAC9F,MAAM,aAAa,GAAsB,IAAI,GAAG,EAAE,CAAC;AACnD,MAAM,oBAAoB,GAAG,GAAG,CAAC;AACjC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAElC,MAAM,WAAW,GAAG,CAAC,MAAyB,EAAiB,EAAE,CAC7D,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACpB,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACzC,CAAC,CAAC,CAAC;AAEP,MAAM,MAAM,GAAG,KAAK,EAAE,OAAkB,EAAE,IAAoB,EAAiB,EAAE;IAC7E,MAAM,QAAQ,CAAC,GAAG,EAAE;QAChB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,IAAI,gBAAgB,CAAC,SAAS,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,SAAS,CAAC;QAC5C,gBAAgB,CAAC,SAAS,GAAG,IAAI,CAAC;QAClC,MAAM,QAAQ,CAAC;IACnB,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,KAAK,EAAE,MAAoB,EAAiB,EAAE;IACtE,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QAChC,OAAO;IACX,CAAC;IAED,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrC,MAAM,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACzB,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;IAC7B,CAAC,CAAC,CAAC;AACP,CAAC,CAAC;AAEF,MAAM,uBAAuB,GAAG,KAAK,IAAmB,EAAE;IACtD,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,KAAc,EAAQ,EAAE;IACzC,gBAAgB,CAAC,SAAS,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3F,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,GAAS,EAAE;IACnC,IAAI,gBAAgB,CAAC,kBAAkB,EAAE,CAAC;QACtC,OAAO;IACX,CAAC;IAED,yBAAyB,CAAC,WAAW,CAAC,CAAC;IACvC,gBAAgB,CAAC,kBAAkB,GAAG,IAAI,CAAC;AAC/C,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,SAAqC,EAAqB,EAAE;IAClF,IAAI,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACtD,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,MAAM,EAAE,CAAC;QAClC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACtD,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,qBAAqB,EAAE,CAAC,CAAC;IAC5G,MAAM,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAEnE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC7C,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,WAAsB,EAAc,EAAE;IAC/D,IAAI,WAAW,YAAY,GAAG,CAAC,MAAM,EAAE,CAAC;QACpC,OAAO,WAAW,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IAEtC,IAAI,KAAK,EAAE,CAAC;QACR,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;AAChG,CAAC,CAAC;AAEF,MAAM,sBAAsB,GAAG,CAC3B,QAA2B,EAC3B,SAAqC,EACrC,WAAsB,EACZ,EAAE;IACZ,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,QAAQ,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,MAAM,EAAE,CAAC;QAClC,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,OAAO,mBAAmB,CAAC,WAAW,CAAC,CAAC;AAC5C,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAqB,OAAqC,EAAE,EAAE,CAAC,CAAC;IACxF,eAAe,EAAE,WAAW;IAC5B,aAAa,EAAE,CAAC,KAAc,EAAE,SAAoB,EAAQ,EAAE;QAC1D,WAAW,CAAC,KAAK,CAAC,CAAC;QACnB,OAAO,EAAE,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAC/C,CAAC;IACD,kBAAkB,EAAE,CAAC,KAAc,EAAE,SAAoB,EAAQ,EAAE;QAC/D,OAAO,EAAE,kBAAkB,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IACpD,CAAC;CACJ,CAAC,CAAC;AAEH,MAAM,qBAAqB,GAAG,CAAC,oBAA6B,EAAQ,EAAE;IAClE,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;IAE3C,IAAI,QAAQ,EAAE,CAAC;QACX,QAAQ,CAAC,mBAAmB,GAAG,oBAAoB,CAAC;IACxD,CAAC;AACL,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,GAAG,KAAK,EAChB,OAAkB,EAClB,OAA0B,EACF,EAAE;IAC1B,mBAAmB,EAAE,CAAC;IACtB,qBAAqB,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC,CAAC;IACpD,MAAM,WAAW,GAAc,OAAO,EAAE,WAAW,IAAI,SAAS,CAAC;IACjE,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,CAAC;IACjC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAEtD,MAAM,IAAI,GAAG,oBAAoB,CAAC;QAC9B,aAAa,EAAE,QAAQ,CAAC,aAAa;QACrC,GAAG,mBAAmB,CAAC,OAAO,CAAC;KAClC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAiB,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;IAC/D,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,iBAAiB,CAAC,uBAAuB,CAAC,CAAC;IAC3C,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAC/B,iBAAiB,CAAC,cAAc,CAAC,CAAC;IAElC,MAAM,IAAI,GAAG,CAAC,IAAe,EAAa,EAAE;QACxC,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,KAAC,OAAO,cAAE,IAAI,GAAW,CAAC,CAAC,CAAC,IAAI,CAAC;QAE3D,OAAO,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,KAAC,UAAU,cAAE,OAAO,GAAc,CAAC,CAAC,CAAC,OAAO,CAAC;IACnF,CAAC,CAAC;IAEF,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;IAC3B,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,SAAS,GAAG,sBAAsB,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;IAEpF,MAAM,MAAM,GAAoB;QAC5B,GAAG,MAAM,CAAC,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC;QACxC,SAAS;QACT,WAAW;QACX,OAAO,EAAE,KAAK,IAAI,EAAE;YAChB,MAAM,mBAAmB,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC;QACD,QAAQ,EAAE,KAAK,EAAE,UAAqB,EAAE,EAAE;YACtC,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;YACrC,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACvC,CAAC;QACD,KAAK,EAAE,CAAC,UAAmC,WAAW,EAAE,YAAkC,EAAE,EAAE;YAC1F,SAAS,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QACrC,CAAC;QACD,QAAQ,EAAE,GAAG,EAAE;YACX,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC1B,CAAC;QACD,UAAU,EAAE,CAAC,QAAyB,EAAE,iBAAqC,EAAE,EAAE,CAC7E,wBAAwB,CAAC,QAAQ,EAAE,iBAAiB,CAAC;KAC5D,CAAC;IAEF,SAAS,CAAC,MAAM,CAAC,CAAC;IAElB,OAAO,MAAM,CAAC;AAClB,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,OAAO,GAAG,KAAK,IAAmB,EAAE;IACtC,MAAM,UAAU,EAAE,CAAC;AACvB,CAAC,CAAC;AAEF,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC","sourcesContent":["import type { RootElement } from \"@gtkx/react\";\nimport * as Gtk from \"@gtkx/gi/gtk\";\nimport {\n createReconcilerRoot,\n isRootElement,\n type ReconcilerRoot,\n setReconcilerErrorHandler,\n} from \"@gtkx/react/internal\";\nimport { type ErrorInfo, type ReactNode, StrictMode } from \"react\";\nimport type { RenderResult } from \"./bound-queries.js\";\nimport type { QueryMap, RenderOptions, ScreenshotOptions, WindowSelector } from \"./types.js\";\nimport { runInAct } from \"./act.js\";\nimport { addToCleanupQueue, runCleanup } from \"./cleanup-registry.js\";\nimport { scheduleAfterLayout } from \"./frame-sync.js\";\nimport { logWidget, type PrettyWidgetOptions } from \"./pretty-widget.js\";\nimport { logRoles } from \"./role-helpers.js\";\nimport { clearScreen, setScreen } from \"./screen.js\";\nimport { captureAndSaveScreenshot } from \"./screenshot.js\";\nimport { type Container, TOPLEVELS, traverse } from \"./traversal.js\";\nimport { resetClipboard } from \"./user-event/index.js\";\nimport { within } from \"./within.js\";\n\ntype ActiveRender = {\n root: ReconcilerRoot;\n window: Gtk.Window | null;\n};\n\ntype ResolvedContainer = {\n containerInfo: Gtk.Widget | RootElement;\n window: Gtk.Window | null;\n};\n\ntype ReconcilerErrorState = {\n lastError: Error | null;\n isHandlerInstalled: boolean;\n};\n\nconst reconcilerErrors: ReconcilerErrorState = { lastError: null, isHandlerInstalled: false };\nconst activeRenders: Set<ActiveRender> = new Set();\nconst HARNESS_WINDOW_WIDTH = 800;\nconst HARNESS_WINDOW_HEIGHT = 600;\n\nconst flushLayout = (window: Gtk.Window | null): Promise<void> =>\n new Promise((resolve) => {\n scheduleAfterLayout(window, resolve);\n });\n\nconst update = async (element: ReactNode, root: ReconcilerRoot): Promise<void> => {\n await runInAct(() => {\n root.update(element);\n });\n\n if (reconcilerErrors.lastError) {\n const captured = reconcilerErrors.lastError;\n reconcilerErrors.lastError = null;\n throw captured;\n }\n};\n\nconst disposeActiveRender = async (active: ActiveRender): Promise<void> => {\n if (!activeRenders.delete(active)) {\n return;\n }\n\n await active.root.unmount(async (root) => {\n await update(null, root);\n active.window?.destroy();\n });\n};\n\nconst disposeAllActiveRenders = async (): Promise<void> => {\n for (const active of activeRenders) {\n await disposeActiveRender(active);\n }\n};\n\nconst handleError = (error: unknown): void => {\n reconcilerErrors.lastError = error instanceof Error ? error : new Error(String(error));\n};\n\nconst installErrorHandler = (): void => {\n if (reconcilerErrors.isHandlerInstalled) {\n return;\n }\n\n setReconcilerErrorHandler(handleError);\n reconcilerErrors.isHandlerInstalled = true;\n};\n\nconst resolveContainer = (container: RenderOptions[\"container\"]): ResolvedContainer => {\n if (isRootElement(container)) {\n return { containerInfo: container, window: null };\n }\n\n if (container instanceof Gtk.Widget) {\n return { containerInfo: container, window: null };\n }\n\n const window = new Gtk.Window({ defaultWidth: HARNESS_WINDOW_WIDTH, defaultHeight: HARNESS_WINDOW_HEIGHT });\n window.setTitlebar(new Gtk.HeaderBar({ showTitleButtons: false }));\n\n return { containerInfo: window, window };\n};\n\nconst firstToplevelWidget = (baseElement: Container): Gtk.Widget => {\n if (baseElement instanceof Gtk.Widget) {\n return baseElement;\n }\n\n const [first] = traverse(baseElement);\n\n if (first) {\n return first;\n }\n\n throw new Error(\"render() produced no widgets: ensure the element renders visible content\");\n};\n\nconst resolveResultContainer = (\n resolved: ResolvedContainer,\n container: RenderOptions[\"container\"],\n baseElement: Container,\n): Gtk.Widget => {\n if (resolved.window) {\n return resolved.window;\n }\n\n if (container instanceof Gtk.Widget) {\n return container;\n }\n\n return firstToplevelWidget(baseElement);\n};\n\nconst renderErrorHandlers = <Q extends QueryMap>(options: RenderOptions<Q> | undefined) => ({\n onUncaughtError: handleError,\n onCaughtError: (error: unknown, errorInfo: ErrorInfo): void => {\n handleError(error);\n options?.onCaughtError?.(error, errorInfo);\n },\n onRecoverableError: (error: unknown, errorInfo: ErrorInfo): void => {\n options?.onRecoverableError?.(error, errorInfo);\n },\n});\n\nconst applyEnableAnimations = (areAnimationsEnabled: boolean): void => {\n const settings = Gtk.Settings.getDefault();\n\n if (settings) {\n settings.gtkEnableAnimations = areAnimationsEnabled;\n }\n};\n\n/**\n * Renders a React element into a GTK4 widget tree and returns queries\n * scoped to it along with controls for rerendering and unmounting. When no\n * container is supplied, a harness window is created and presented.\n *\n * @param element The React element to render.\n * @param options Optional container, wrapper, custom queries, and other render settings.\n * @returns A render result with bound queries, debug helpers, and lifecycle controls.\n */\nconst render = async <Q extends QueryMap = Record<never, never>>(\n element: ReactNode,\n options?: RenderOptions<Q>,\n): Promise<RenderResult<Q>> => {\n installErrorHandler();\n applyEnableAnimations(options?.animations === true);\n const baseElement: Container = options?.baseElement ?? TOPLEVELS;\n const Wrapper = options?.wrapper;\n const resolved = resolveContainer(options?.container);\n\n const root = createReconcilerRoot({\n containerInfo: resolved.containerInfo,\n ...renderErrorHandlers(options),\n });\n\n const active: ActiveRender = { root, window: resolved.window };\n activeRenders.add(active);\n addToCleanupQueue(disposeAllActiveRenders);\n addToCleanupQueue(clearScreen);\n addToCleanupQueue(resetClipboard);\n\n const wrap = (node: ReactNode): ReactNode => {\n const wrapped = Wrapper ? <Wrapper>{node}</Wrapper> : node;\n\n return options?.reactStrictMode ? <StrictMode>{wrapped}</StrictMode> : wrapped;\n };\n\n await update(wrap(element), root);\n resolved.window?.present();\n await flushLayout(resolved.window);\n const container = resolveResultContainer(resolved, options?.container, baseElement);\n\n const result: RenderResult<Q> = {\n ...within(baseElement, options?.queries),\n container,\n baseElement,\n unmount: async () => {\n await disposeActiveRender(active);\n },\n rerender: async (newElement: ReactNode) => {\n await update(wrap(newElement), root);\n await flushLayout(resolved.window);\n },\n debug: (element: Container | Container[] = baseElement, debugOptions?: PrettyWidgetOptions) => {\n logWidget(element, debugOptions);\n },\n logRoles: () => {\n logRoles(baseElement);\n },\n screenshot: (selector?: WindowSelector, screenshotOptions?: ScreenshotOptions) =>\n captureAndSaveScreenshot(selector, screenshotOptions),\n };\n\n setScreen(result);\n\n return result;\n};\n\n/**\n * Unmounts every active render and runs all registered cleanup callbacks,\n * resetting the screen and clipboard. Called automatically after each test.\n */\nconst cleanup = async (): Promise<void> => {\n await runCleanup();\n};\n\nexport { render, cleanup };\n"]}
@@ -1,67 +1,33 @@
1
- import * as Gtk from "@gtkx/ffi/gtk";
1
+ import * as Gtk from "@gtkx/gi/gtk";
2
2
  import { type Container } from "./traversal.js";
3
3
  /**
4
- * Information about a widget and its accessible name.
5
- */
6
- export type RoleInfo = {
7
- widget: Gtk.Widget;
8
- name: string | null;
9
- };
10
- /**
11
- * Formats a GTK accessible role to a lowercase string.
4
+ * Converts an accessible role enum value into its lowercase name, falling back
5
+ * to the numeric value when the role is unknown.
12
6
  *
13
- * @param role - The GTK accessible role
14
- * @returns Lowercase role name (e.g., "button", "checkbox")
7
+ * @param role The accessible role to format.
15
8
  */
16
- export declare const formatRole: (role: Gtk.AccessibleRole | undefined) => string;
9
+ declare const formatRole: (role: Gtk.AccessibleRole) => string;
10
+ declare const formatRoleList: (roles: Iterable<Gtk.AccessibleRole>) => string;
17
11
  /**
18
- * Collects all accessible roles and their widgets from a container.
19
- *
20
- * Returns a Map where keys are role names (lowercase) and values are
21
- * arrays of widgets with that role, including their accessible names.
22
- *
23
- * @param container - The container to scan for roles
24
- * @returns Map of role names to arrays of RoleInfo
12
+ * Groups every widget in a container's tree by its accessible role name.
25
13
  *
26
- * @example
27
- * ```tsx
28
- * import { getRoles } from "@gtkx/testing";
29
- *
30
- * const roles = getRoles(container);
31
- * // Map {
32
- * // "button" => [{ widget: ..., name: "Submit" }, { widget: ..., name: "Cancel" }],
33
- * // "checkbox" => [{ widget: ..., name: "Remember me" }]
34
- * // }
35
- * ```
14
+ * @param container The scope to traverse.
15
+ * @returns A map from role name to the widgets that have that role.
36
16
  */
37
- export declare const getRoles: (container: Container) => Map<string, RoleInfo[]>;
17
+ declare const getRoles: (container: Container) => Map<string, Gtk.Widget[]>;
38
18
  /**
39
- * Formats roles into a readable string for error messages.
19
+ * Formats the accessible roles in a container's tree as a readable string,
20
+ * listing each role together with its widgets and their accessible names.
40
21
  *
41
- * @param container - The container to format roles for
42
- * @returns Formatted string showing all roles and their accessible names
22
+ * @param container The scope to inspect.
43
23
  */
44
- export declare const prettyRoles: (container: Container) => string;
24
+ declare const prettyRoles: (container: Container) => string;
45
25
  /**
46
- * Logs all accessible roles in a container to the console.
47
- *
48
- * Useful for debugging test failures and discovering available roles.
49
- *
50
- * @param container - The container to log roles for
51
- *
52
- * @example
53
- * ```tsx
54
- * import { render, logRoles } from "@gtkx/testing";
26
+ * Prints the accessible roles in a container's tree to the console using
27
+ * {@link prettyRoles}.
55
28
  *
56
- * const { container } = await render(<MyComponent />);
57
- * logRoles(container);
58
- * // Console output:
59
- * // button:
60
- * // Name "Submit": <GtkButton role="button">Submit</GtkButton>
61
- * // Name "Cancel": <GtkButton role="button">Cancel</GtkButton>
62
- * // checkbox:
63
- * // Name "Remember me": <GtkCheckButton role="checkbox">Remember me</GtkCheckButton>
64
- * ```
29
+ * @param container The scope to inspect.
65
30
  */
66
- export declare const logRoles: (container: Container) => void;
31
+ declare const logRoles: (container: Container) => void;
32
+ export { formatRole, formatRoleList, getRoles, prettyRoles, logRoles };
67
33
  //# sourceMappingURL=role-helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"role-helpers.d.ts","sourceRoot":"","sources":["../src/role-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,gBAAgB,CAAC;AAG1D;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACnB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,GAAG,CAAC,cAAc,GAAG,SAAS,KAAG,MAKjE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,QAAQ,GAAI,WAAW,SAAS,KAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,CAoBrE,CAAC;AASF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,WAAW,SAAS,KAAG,MAoBlD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,QAAQ,GAAI,WAAW,SAAS,KAAG,IAE/C,CAAC"}
1
+ {"version":3,"file":"role-helpers.d.ts","sourceRoot":"","sources":["../src/role-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAEpC,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,gBAAgB,CAAC;AAa1D;;;;;GAKG;AACH,QAAA,MAAM,UAAU,GAAI,MAAM,GAAG,CAAC,cAAc,KAAG,MAQ9C,CAAC;AAEF,QAAA,MAAM,cAAc,GAAI,OAAO,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,KAAG,MAe7D,CAAC;AAEF;;;;;GAKG;AACH,QAAA,MAAM,QAAQ,GAAI,WAAW,SAAS,KAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAehE,CAAC;AAUF;;;;;GAKG;AACH,QAAA,MAAM,WAAW,GAAI,WAAW,SAAS,KAAG,MAqB3C,CAAC;AAEF;;;;;GAKG;AACH,QAAA,MAAM,QAAQ,GAAI,WAAW,SAAS,KAAG,IAExC,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC"}
@@ -1,109 +1,94 @@
1
- import * as Gtk from "@gtkx/ffi/gtk";
1
+ import * as Gtk from "@gtkx/gi/gtk";
2
+ import { sortStringsBy } from "@gtkx/utils";
2
3
  import { traverse } from "./traversal.js";
3
- import { getWidgetAccessibleName } from "./widget-text.js";
4
+ import { getWidgetAccessibleName } from "./widget-accessible-properties.js";
5
+ const ROLE_NAMES_BY_VALUE = enumNamesByValue(Gtk.AccessibleRole);
6
+ function enumNamesByValue(enumObject) {
7
+ return new Map(Object.entries(enumObject)
8
+ .filter((entry) => typeof entry[1] === "number")
9
+ .map(([name, value]) => [value, name]));
10
+ }
4
11
  /**
5
- * Formats a GTK accessible role to a lowercase string.
12
+ * Converts an accessible role enum value into its lowercase name, falling back
13
+ * to the numeric value when the role is unknown.
6
14
  *
7
- * @param role - The GTK accessible role
8
- * @returns Lowercase role name (e.g., "button", "checkbox")
15
+ * @param role The accessible role to format.
9
16
  */
10
- export const formatRole = (role) => {
11
- if (role === undefined)
12
- return "unknown";
13
- const name = Gtk.AccessibleRole[role];
14
- if (!name)
17
+ const formatRole = (role) => {
18
+ const name = ROLE_NAMES_BY_VALUE.get(role);
19
+ if (!name) {
15
20
  return String(role);
21
+ }
16
22
  return name.toLowerCase();
17
23
  };
24
+ const formatRoleList = (roles) => {
25
+ const names = [...roles].map((role) => formatRole(role).toUpperCase());
26
+ if (names.length <= 1) {
27
+ return names.join("");
28
+ }
29
+ if (names.length === 2) {
30
+ return names.join(" or ");
31
+ }
32
+ const head = names.slice(0, -1);
33
+ const last = names.at(-1) ?? "";
34
+ return `${head.join(", ")}, or ${last}`;
35
+ };
18
36
  /**
19
- * Collects all accessible roles and their widgets from a container.
20
- *
21
- * Returns a Map where keys are role names (lowercase) and values are
22
- * arrays of widgets with that role, including their accessible names.
37
+ * Groups every widget in a container's tree by its accessible role name.
23
38
  *
24
- * @param container - The container to scan for roles
25
- * @returns Map of role names to arrays of RoleInfo
26
- *
27
- * @example
28
- * ```tsx
29
- * import { getRoles } from "@gtkx/testing";
30
- *
31
- * const roles = getRoles(container);
32
- * // Map {
33
- * // "button" => [{ widget: ..., name: "Submit" }, { widget: ..., name: "Cancel" }],
34
- * // "checkbox" => [{ widget: ..., name: "Remember me" }]
35
- * // }
36
- * ```
39
+ * @param container The scope to traverse.
40
+ * @returns A map from role name to the widgets that have that role.
37
41
  */
38
- export const getRoles = (container) => {
42
+ const getRoles = (container) => {
39
43
  const roles = new Map();
40
44
  for (const widget of traverse(container)) {
41
- const role = widget.getAccessibleRole?.();
42
- if (role === undefined)
43
- continue;
44
- const roleName = formatRole(role);
45
- const name = getWidgetAccessibleName(widget);
46
- const info = { widget, name };
45
+ const roleName = formatRole(widget.getAccessibleRole());
47
46
  const existing = roles.get(roleName);
48
47
  if (existing) {
49
- existing.push(info);
48
+ existing.push(widget);
50
49
  }
51
50
  else {
52
- roles.set(roleName, [info]);
51
+ roles.set(roleName, [widget]);
53
52
  }
54
53
  }
55
54
  return roles;
56
55
  };
57
56
  const formatWidgetPreview = (widget, name) => {
58
57
  const tagName = widget.constructor.name;
59
- const roleAttr = formatRole(widget.getAccessibleRole?.());
58
+ const roleAttr = formatRole(widget.getAccessibleRole());
60
59
  const nameDisplay = name ? `Name "${name}"` : 'Name ""';
61
60
  return `${nameDisplay}: <${tagName} role="${roleAttr}">${name ?? ""}</${tagName}>`;
62
61
  };
63
62
  /**
64
- * Formats roles into a readable string for error messages.
63
+ * Formats the accessible roles in a container's tree as a readable string,
64
+ * listing each role together with its widgets and their accessible names.
65
65
  *
66
- * @param container - The container to format roles for
67
- * @returns Formatted string showing all roles and their accessible names
66
+ * @param container The scope to inspect.
68
67
  */
69
- export const prettyRoles = (container) => {
68
+ const prettyRoles = (container) => {
70
69
  const roles = getRoles(container);
71
70
  if (roles.size === 0) {
72
71
  return "No accessible roles found in the widget tree.";
73
72
  }
74
73
  const lines = [];
75
- const sortedRoles = [...roles.entries()].sort(([a], [b]) => a.localeCompare(b));
74
+ const sortedRoles = sortStringsBy([...roles], ([roleName]) => roleName);
76
75
  for (const [roleName, widgets] of sortedRoles) {
77
76
  lines.push(`${roleName}:`);
78
- for (const { widget, name } of widgets) {
79
- lines.push(` ${formatWidgetPreview(widget, name)}`);
77
+ for (const widget of widgets) {
78
+ lines.push(` ${formatWidgetPreview(widget, getWidgetAccessibleName(widget))}`);
80
79
  }
81
80
  lines.push("");
82
81
  }
83
82
  return lines.join("\n").trimEnd();
84
83
  };
85
84
  /**
86
- * Logs all accessible roles in a container to the console.
87
- *
88
- * Useful for debugging test failures and discovering available roles.
89
- *
90
- * @param container - The container to log roles for
91
- *
92
- * @example
93
- * ```tsx
94
- * import { render, logRoles } from "@gtkx/testing";
85
+ * Prints the accessible roles in a container's tree to the console using
86
+ * {@link prettyRoles}.
95
87
  *
96
- * const { container } = await render(<MyComponent />);
97
- * logRoles(container);
98
- * // Console output:
99
- * // button:
100
- * // Name "Submit": <GtkButton role="button">Submit</GtkButton>
101
- * // Name "Cancel": <GtkButton role="button">Cancel</GtkButton>
102
- * // checkbox:
103
- * // Name "Remember me": <GtkCheckButton role="checkbox">Remember me</GtkCheckButton>
104
- * ```
88
+ * @param container The scope to inspect.
105
89
  */
106
- export const logRoles = (container) => {
90
+ const logRoles = (container) => {
107
91
  console.log(prettyRoles(container));
108
92
  };
93
+ export { formatRole, formatRoleList, getRoles, prettyRoles, logRoles };
109
94
  //# sourceMappingURL=role-helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"role-helpers.js","sourceRoot":"","sources":["../src/role-helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"role-helpers.js","sourceRoot":"","sources":["../src/role-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAkB,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAE5E,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAEjE,SAAS,gBAAgB,CAAC,UAA2C;IACjE,OAAO,IAAI,GAAG,CACV,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC;SACrB,MAAM,CAAC,CAAC,KAAK,EAA6B,EAAE,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC;SAC1E,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAC7C,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,GAAG,CAAC,IAAwB,EAAU,EAAE;IACpD,MAAM,IAAI,GAAG,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE3C,IAAI,CAAC,IAAI,EAAE,CAAC;QACR,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAED,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC9B,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,KAAmC,EAAU,EAAE;IACnE,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAEvE,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACpB,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAEhC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;AAC5C,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,QAAQ,GAAG,CAAC,SAAoB,EAA6B,EAAE;IACjE,MAAM,KAAK,GAA8B,IAAI,GAAG,EAAE,CAAC;IAEnD,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACvC,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAErC,IAAI,QAAQ,EAAE,CAAC;YACX,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;aAAM,CAAC;YACJ,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;QAClC,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,MAAkB,EAAE,IAAmB,EAAU,EAAE;IAC5E,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC;IACxC,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IAExD,OAAO,GAAG,WAAW,MAAM,OAAO,UAAU,QAAQ,KAAK,IAAI,IAAI,EAAE,KAAK,OAAO,GAAG,CAAC;AACvF,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,GAAG,CAAC,SAAoB,EAAU,EAAE;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IAElC,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACnB,OAAO,+CAA+C,CAAC;IAC3D,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC;IAExE,KAAK,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,WAAW,EAAE,CAAC;QAC5C,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,GAAG,CAAC,CAAC;QAE3B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,KAAK,mBAAmB,CAAC,MAAM,EAAE,uBAAuB,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;QACpF,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;AACtC,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,QAAQ,GAAG,CAAC,SAAoB,EAAQ,EAAE;IAC5C,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;AACxC,CAAC,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC","sourcesContent":["import * as Gtk from \"@gtkx/gi/gtk\";\nimport { sortStringsBy } from \"@gtkx/utils\";\nimport { type Container, traverse } from \"./traversal.js\";\nimport { getWidgetAccessibleName } from \"./widget-accessible-properties.js\";\n\nconst ROLE_NAMES_BY_VALUE = enumNamesByValue(Gtk.AccessibleRole);\n\nfunction enumNamesByValue(enumObject: Record<string, string | number>): Map<number, string> {\n return new Map<number, string>(\n Object.entries(enumObject)\n .filter((entry): entry is [string, number] => typeof entry[1] === \"number\")\n .map(([name, value]) => [value, name]),\n );\n}\n\n/**\n * Converts an accessible role enum value into its lowercase name, falling back\n * to the numeric value when the role is unknown.\n *\n * @param role The accessible role to format.\n */\nconst formatRole = (role: Gtk.AccessibleRole): string => {\n const name = ROLE_NAMES_BY_VALUE.get(role);\n\n if (!name) {\n return String(role);\n }\n\n return name.toLowerCase();\n};\n\nconst formatRoleList = (roles: Iterable<Gtk.AccessibleRole>): string => {\n const names = [...roles].map((role) => formatRole(role).toUpperCase());\n\n if (names.length <= 1) {\n return names.join(\"\");\n }\n\n if (names.length === 2) {\n return names.join(\" or \");\n }\n\n const head = names.slice(0, -1);\n const last = names.at(-1) ?? \"\";\n\n return `${head.join(\", \")}, or ${last}`;\n};\n\n/**\n * Groups every widget in a container's tree by its accessible role name.\n *\n * @param container The scope to traverse.\n * @returns A map from role name to the widgets that have that role.\n */\nconst getRoles = (container: Container): Map<string, Gtk.Widget[]> => {\n const roles: Map<string, Gtk.Widget[]> = new Map();\n\n for (const widget of traverse(container)) {\n const roleName = formatRole(widget.getAccessibleRole());\n const existing = roles.get(roleName);\n\n if (existing) {\n existing.push(widget);\n } else {\n roles.set(roleName, [widget]);\n }\n }\n\n return roles;\n};\n\nconst formatWidgetPreview = (widget: Gtk.Widget, name: string | null): string => {\n const tagName = widget.constructor.name;\n const roleAttr = formatRole(widget.getAccessibleRole());\n const nameDisplay = name ? `Name \"${name}\"` : 'Name \"\"';\n\n return `${nameDisplay}: <${tagName} role=\"${roleAttr}\">${name ?? \"\"}</${tagName}>`;\n};\n\n/**\n * Formats the accessible roles in a container's tree as a readable string,\n * listing each role together with its widgets and their accessible names.\n *\n * @param container The scope to inspect.\n */\nconst prettyRoles = (container: Container): string => {\n const roles = getRoles(container);\n\n if (roles.size === 0) {\n return \"No accessible roles found in the widget tree.\";\n }\n\n const lines: string[] = [];\n const sortedRoles = sortStringsBy([...roles], ([roleName]) => roleName);\n\n for (const [roleName, widgets] of sortedRoles) {\n lines.push(`${roleName}:`);\n\n for (const widget of widgets) {\n lines.push(` ${formatWidgetPreview(widget, getWidgetAccessibleName(widget))}`);\n }\n\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\").trimEnd();\n};\n\n/**\n * Prints the accessible roles in a container's tree to the console using\n * {@link prettyRoles}.\n *\n * @param container The scope to inspect.\n */\nconst logRoles = (container: Container): void => {\n console.log(prettyRoles(container));\n};\n\nexport { formatRole, formatRoleList, getRoles, prettyRoles, logRoles };\n"]}