@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/screen.d.ts CHANGED
@@ -1,66 +1,10 @@
1
- import * as Gtk from "@gtkx/ffi/gtk";
2
- import { type ScreenshotOptions } from "./screenshot.js";
3
- import type { ScreenshotResult } from "./types.js";
4
- /** @internal */
5
- export declare const setScreenRoot: (root: Gtk.Application | null) => void;
1
+ import type { RenderResult, Screen } from "./bound-queries.js";
6
2
  /**
7
- * Global query object for accessing rendered components.
8
- *
9
- * Provides the same query methods as render result, but automatically
10
- * uses the most recently rendered application as the container.
11
- *
12
- * @example
13
- * ```tsx
14
- * import { render, screen } from "@gtkx/testing";
15
- *
16
- * test("finds button", async () => {
17
- * await render(<MyComponent />);
18
- * const button = await screen.findByRole(Gtk.AccessibleRole.BUTTON);
19
- * expect(button).toBeDefined();
20
- * });
21
- * ```
22
- *
23
- * @see {@link render} for rendering components
24
- * @see {@link within} for scoped queries
3
+ * Queries and debug utilities bound to the most recent render, scoped to the
4
+ * current toplevel windows. Accessing it before any render throws.
25
5
  */
26
- export declare const screen: {
27
- /** Print the widget tree to console for debugging */
28
- debug: () => void;
29
- /** Log all accessible roles to console for debugging */
30
- logRoles: () => void;
31
- /**
32
- * Capture a screenshot of the application window, saving it to a temporary file and logging the file path.
33
- *
34
- * @param selector - Window selector: index (number), title substring (string), or title pattern (RegExp).
35
- * If omitted, captures the first window.
36
- * @param options - Optional timeout and interval configuration for waiting on widget rendering.
37
- * @returns Screenshot result containing base64-encoded PNG data
38
- * @throws Error if no windows are available or no matching window is found
39
- *
40
- * @example
41
- * ```tsx
42
- * await screen.screenshot(); // First window
43
- * await screen.screenshot(0); // Window at index 0
44
- * await screen.screenshot("Settings"); // Window with title containing "Settings"
45
- * await screen.screenshot(/^My App/); // Window with title matching regex
46
- * ```
47
- */
48
- screenshot: (selector?: number | string | RegExp, options?: ScreenshotOptions) => Promise<ScreenshotResult>;
49
- queryByRole: (role: Gtk.AccessibleRole, options?: import("./types.js").ByRoleOptions) => Gtk.Widget | null;
50
- queryByLabelText: (text: import("./types.js").TextMatch, options?: import("./types.js").TextMatchOptions) => Gtk.Widget | null;
51
- queryByText: (text: import("./types.js").TextMatch, options?: import("./types.js").TextMatchOptions) => Gtk.Widget | null;
52
- queryByName: (name: import("./types.js").TextMatch, options?: import("./types.js").TextMatchOptions) => Gtk.Widget | null;
53
- queryAllByRole: (role: Gtk.AccessibleRole, options?: import("./types.js").ByRoleOptions) => Gtk.Widget[];
54
- queryAllByLabelText: (text: import("./types.js").TextMatch, options?: import("./types.js").TextMatchOptions) => Gtk.Widget[];
55
- queryAllByText: (text: import("./types.js").TextMatch, options?: import("./types.js").TextMatchOptions) => Gtk.Widget[];
56
- queryAllByName: (name: import("./types.js").TextMatch, options?: import("./types.js").TextMatchOptions) => Gtk.Widget[];
57
- findByRole: (role: Gtk.AccessibleRole, options?: import("./types.js").ByRoleOptions) => Promise<Gtk.Widget>;
58
- findByLabelText: (text: import("./types.js").TextMatch, options?: import("./types.js").TextMatchOptions) => Promise<Gtk.Widget>;
59
- findByText: (text: import("./types.js").TextMatch, options?: import("./types.js").TextMatchOptions) => Promise<Gtk.Widget>;
60
- findByName: (name: import("./types.js").TextMatch, options?: import("./types.js").TextMatchOptions) => Promise<Gtk.Widget>;
61
- findAllByRole: (role: Gtk.AccessibleRole, options?: import("./types.js").ByRoleOptions) => Promise<Gtk.Widget[]>;
62
- findAllByLabelText: (text: import("./types.js").TextMatch, options?: import("./types.js").TextMatchOptions) => Promise<Gtk.Widget[]>;
63
- findAllByText: (text: import("./types.js").TextMatch, options?: import("./types.js").TextMatchOptions) => Promise<Gtk.Widget[]>;
64
- findAllByName: (name: import("./types.js").TextMatch, options?: import("./types.js").TextMatchOptions) => Promise<Gtk.Widget[]>;
65
- };
6
+ declare const screen: Screen;
7
+ declare const setScreen: (result: RenderResult) => void;
8
+ declare const clearScreen: () => void;
9
+ export { screen, setScreen, clearScreen };
66
10
  //# sourceMappingURL=screen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../src/screen.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,eAAe,CAAC;AAIrC,OAAO,EAAE,KAAK,iBAAiB,EAAkC,MAAM,iBAAiB,CAAC;AACzF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAqBnD,gBAAgB;AAChB,eAAO,MAAM,aAAa,GAAI,MAAM,GAAG,CAAC,WAAW,GAAG,IAAI,KAAG,IAE5D,CAAC;AAYF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,MAAM;IAEf,qDAAqD;;IAIrD,wDAAwD;;IAIxD;;;;;;;;;;;;;;;;OAgBG;4BAC2B,MAAM,GAAG,MAAM,GAAG,MAAM,YAAY,iBAAiB,KAAG,OAAO,CAAC,gBAAgB,CAAC;mDAS3G,CAAA;oEAG0B,CAAC;+DAI3B,CAAL;+DAGqC,CAAC;sDAIV,CAAC;uEAGpB,CAAJ;kEAQF,CAAC;kEAAmK,CAAC;kDAAyK,CAAC;mEAA4K,CAAC;8DAAoK,CAAC;8DAAmK,CAAC;qDAAkL,CAAC;sEAAgL,CAAC;iEAAwK,CAAC;iEAAuK,CAAC;CAD//C,CAAC"}
1
+ {"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../src/screen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAgB/D;;;GAGG;AACH,QAAA,MAAM,MAAM,EAAE,MAEZ,CAAC;AAMH,QAAA,MAAM,SAAS,GAAI,QAAQ,YAAY,KAAG,IAEzC,CAAC;AAEF,QAAA,MAAM,WAAW,QAAO,IAEvB,CAAC;AAEF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC"}
package/dist/screen.js CHANGED
@@ -1,114 +1,28 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import * as Gtk from "@gtkx/ffi/gtk";
5
- import { bindQueries } from "./bind-queries.js";
6
- import { prettyWidget } from "./pretty-widget.js";
7
- import { logRoles } from "./role-helpers.js";
8
- import { screenshot as screenshotWidget } from "./screenshot.js";
9
- const getScreenshotDir = () => {
10
- const dir = join(tmpdir(), "gtkx-screenshots");
11
- if (!existsSync(dir)) {
12
- mkdirSync(dir, { recursive: true });
13
- }
14
- return dir;
15
- };
16
- const saveAndLogScreenshot = (result) => {
17
- const dir = getScreenshotDir();
18
- const filename = `${Date.now()}-screenshot.png`;
19
- const filepath = join(dir, filename);
20
- const buffer = Buffer.from(result.data, "base64");
21
- writeFileSync(filepath, buffer);
22
- console.log(`Screenshot saved: file://${filepath}`);
23
- };
24
- let currentRoot = null;
25
- /** @internal */
26
- export const setScreenRoot = (root) => {
27
- currentRoot = root;
28
- };
29
- const getRoot = () => {
30
- if (!currentRoot) {
31
- throw new Error("No render has been performed: call render() before using screen queries");
32
- }
33
- return currentRoot;
34
- };
35
- const boundQueries = bindQueries(getRoot);
36
- /**
37
- * Global query object for accessing rendered components.
38
- *
39
- * Provides the same query methods as render result, but automatically
40
- * uses the most recently rendered application as the container.
41
- *
42
- * @example
43
- * ```tsx
44
- * import { render, screen } from "@gtkx/testing";
45
- *
46
- * test("finds button", async () => {
47
- * await render(<MyComponent />);
48
- * const button = await screen.findByRole(Gtk.AccessibleRole.BUTTON);
49
- * expect(button).toBeDefined();
50
- * });
51
- * ```
52
- *
53
- * @see {@link render} for rendering components
54
- * @see {@link within} for scoped queries
55
- */
56
- export const screen = {
57
- ...boundQueries,
58
- /** Print the widget tree to console for debugging */
59
- debug: () => {
60
- console.log(prettyWidget(getRoot()));
61
- },
62
- /** Log all accessible roles to console for debugging */
63
- logRoles: () => {
64
- logRoles(getRoot());
65
- },
66
- /**
67
- * Capture a screenshot of the application window, saving it to a temporary file and logging the file path.
68
- *
69
- * @param selector - Window selector: index (number), title substring (string), or title pattern (RegExp).
70
- * If omitted, captures the first window.
71
- * @param options - Optional timeout and interval configuration for waiting on widget rendering.
72
- * @returns Screenshot result containing base64-encoded PNG data
73
- * @throws Error if no windows are available or no matching window is found
74
- *
75
- * @example
76
- * ```tsx
77
- * await screen.screenshot(); // First window
78
- * await screen.screenshot(0); // Window at index 0
79
- * await screen.screenshot("Settings"); // Window with title containing "Settings"
80
- * await screen.screenshot(/^My App/); // Window with title matching regex
81
- * ```
82
- */
83
- screenshot: async (selector, options) => {
84
- const windows = Gtk.Window.listToplevels();
85
- if (windows.length === 0) {
86
- throw new Error("No windows available for screenshot");
1
+ const NO_RENDER_MESSAGE = "No render has been performed: call render() before using screen queries";
2
+ const defaultScreen = new Proxy({}, {
3
+ get: (_target, property) => {
4
+ if (property === "baseElement" || property === "container") {
5
+ return throwNoRender();
87
6
  }
88
- let targetWindow;
89
- if (selector === undefined) {
90
- targetWindow = windows[0];
91
- }
92
- else if (typeof selector === "number") {
93
- targetWindow = windows[selector];
94
- if (!targetWindow) {
95
- throw new Error(`Window at index ${selector} not found`);
96
- }
97
- }
98
- else {
99
- const isRegex = selector instanceof RegExp;
100
- targetWindow = windows.find((w) => {
101
- const title = w.getTitle() ?? "";
102
- return isRegex ? selector.test(title) : title.includes(selector);
103
- });
104
- if (!targetWindow) {
105
- const pattern = isRegex ? selector.toString() : `"${selector}"`;
106
- throw new Error(`No window found with title matching ${pattern}`);
107
- }
108
- }
109
- const result = await screenshotWidget(targetWindow, options);
110
- saveAndLogScreenshot(result);
111
- return result;
7
+ return throwNoRender;
112
8
  },
9
+ });
10
+ const activeScreen = { current: defaultScreen };
11
+ /**
12
+ * Queries and debug utilities bound to the most recent render, scoped to the
13
+ * current toplevel windows. Accessing it before any render throws.
14
+ */
15
+ const screen = new Proxy({}, {
16
+ get: (_target, property) => Reflect.get(activeScreen.current, property),
17
+ });
18
+ const throwNoRender = () => {
19
+ throw new Error(NO_RENDER_MESSAGE);
20
+ };
21
+ const setScreen = (result) => {
22
+ activeScreen.current = result;
23
+ };
24
+ const clearScreen = () => {
25
+ activeScreen.current = defaultScreen;
113
26
  };
27
+ export { screen, setScreen, clearScreen };
114
28
  //# sourceMappingURL=screen.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"screen.js","sourceRoot":"","sources":["../src/screen.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"screen.js","sourceRoot":"","sources":["../src/screen.ts"],"names":[],"mappings":"AAEA,MAAM,iBAAiB,GAAG,yEAAyE,CAAC;AAEpG,MAAM,aAAa,GAAiB,IAAI,KAAK,CAAC,EAAkB,EAAE;IAC9D,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;QACvB,IAAI,QAAQ,KAAK,aAAa,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;YACzD,OAAO,aAAa,EAAE,CAAC;QAC3B,CAAC;QAED,OAAO,aAAa,CAAC;IACzB,CAAC;CACJ,CAAC,CAAC;AAEH,MAAM,YAAY,GAA8B,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;AAE3E;;;GAGG;AACH,MAAM,MAAM,GAAW,IAAI,KAAK,CAAC,EAAY,EAAE;IAC3C,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAW,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC;CACnF,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,GAAU,EAAE;IAC9B,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;AACvC,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,CAAC,MAAoB,EAAQ,EAAE;IAC7C,YAAY,CAAC,OAAO,GAAG,MAAM,CAAC;AAClC,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,GAAS,EAAE;IAC3B,YAAY,CAAC,OAAO,GAAG,aAAa,CAAC;AACzC,CAAC,CAAC;AAEF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC","sourcesContent":["import type { RenderResult, Screen } from \"./bound-queries.js\";\n\nconst NO_RENDER_MESSAGE = \"No render has been performed: call render() before using screen queries\";\n\nconst defaultScreen: RenderResult = new Proxy({} as RenderResult, {\n get: (_target, property) => {\n if (property === \"baseElement\" || property === \"container\") {\n return throwNoRender();\n }\n\n return throwNoRender;\n },\n});\n\nconst activeScreen: { current: RenderResult } = { current: defaultScreen };\n\n/**\n * Queries and debug utilities bound to the most recent render, scoped to the\n * current toplevel windows. Accessing it before any render throws.\n */\nconst screen: Screen = new Proxy({} as Screen, {\n get: (_target, property): unknown => Reflect.get(activeScreen.current, property),\n});\n\nconst throwNoRender = (): never => {\n throw new Error(NO_RENDER_MESSAGE);\n};\n\nconst setScreen = (result: RenderResult): void => {\n activeScreen.current = result;\n};\n\nconst clearScreen = (): void => {\n activeScreen.current = defaultScreen;\n};\n\nexport { screen, setScreen, clearScreen };\n"]}
@@ -1,30 +1,28 @@
1
- import * as Gtk from "@gtkx/ffi/gtk";
2
- import type { ScreenshotResult, WaitForOptions } from "./types.js";
1
+ import * as Gtk from "@gtkx/gi/gtk";
2
+ import type { ScreenshotOptions, ScreenshotResult, WindowSelector } from "./types.js";
3
3
  /**
4
- * Options for capturing widget screenshots.
4
+ * Captures a PNG snapshot of a widget, retrying until it has a paintable size.
5
+ *
6
+ * @param widget The widget to render to an image.
7
+ * @param options Optional scale, timeout, and retry interval.
8
+ * @returns The base64-encoded PNG data along with its mime type and dimensions.
5
9
  */
6
- export type ScreenshotOptions = Pick<WaitForOptions, "timeout" | "interval">;
10
+ declare const screenshot: (widget: Gtk.Widget, options?: ScreenshotOptions) => Promise<ScreenshotResult>;
7
11
  /**
8
- * Captures a screenshot of a GTK widget as a PNG image.
9
- *
10
- * This function will retry multiple times if the widget hasn't finished
11
- * rendering, waiting for GTK to complete its paint cycle.
12
- *
13
- * @param widget - The widget to capture (typically a Window)
14
- * @param options - Optional timeout and interval configuration
15
- * @returns Screenshot result containing base64-encoded PNG data and dimensions
16
- * @throws Error if widget has no size, is not realized, or rendering fails after timeout
12
+ * Prints a clickable file URL for a saved screenshot to the console.
17
13
  *
18
- * @example
19
- * ```tsx
20
- * import { render, screenshot } from "@gtkx/testing";
21
- * import * as Gtk from "@gtkx/ffi/gtk";
14
+ * @param filepath Absolute path of the saved screenshot file.
15
+ */
16
+ declare const logScreenshotPath: (filepath: string) => void;
17
+ /**
18
+ * Captures a screenshot of a toplevel window, writes it to a temporary file,
19
+ * logs its path, and returns the image data.
22
20
  *
23
- * const { container } = await render(<MyApp />);
24
- * const window = container.getWindows()[0];
25
- * const result = await screenshot(window);
26
- * console.log(result.mimeType); // "image/png"
27
- * ```
21
+ * @param selector Chooses the window by index, title substring, or regular
22
+ * expression; defaults to the first toplevel.
23
+ * @param options Optional scale, timeout, and retry interval.
24
+ * @returns The base64-encoded PNG data along with its mime type and dimensions.
28
25
  */
29
- export declare const screenshot: (widget: Gtk.Widget, options?: ScreenshotOptions) => Promise<ScreenshotResult>;
26
+ declare const captureAndSaveScreenshot: (selector?: WindowSelector, options?: ScreenshotOptions) => Promise<ScreenshotResult>;
27
+ export { screenshot, logScreenshotPath, captureAndSaveScreenshot };
30
28
  //# sourceMappingURL=screenshot.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,GAAG,MAAM,eAAe,CAAC;AAErC,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAwDnE;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,UAAU,CAAC,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,UAAU,GAAU,QAAQ,GAAG,CAAC,MAAM,EAAE,UAAU,iBAAiB,KAAG,OAAO,CAAC,gBAAgB,CAiB1G,CAAC"}
1
+ {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAIpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAuDtF;;;;;;GAMG;AACH,QAAA,MAAM,UAAU,GAAU,QAAQ,GAAG,CAAC,MAAM,EAAE,UAAU,iBAAiB,KAAG,OAAO,CAAC,gBAAgB,CAWnG,CAAC;AAqEF;;;;GAIG;AACH,QAAA,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,IAE7C,CAAC;AAEF;;;;;;;;GAQG;AACH,QAAA,MAAM,wBAAwB,GAC1B,WAAW,cAAc,EACzB,UAAU,iBAAiB,KAC5B,OAAO,CAAC,gBAAgB,CAM1B,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,CAAC"}
@@ -1,45 +1,44 @@
1
- import { createRef } from "@gtkx/ffi";
2
- import * as Gsk from "@gtkx/ffi/gsk";
3
- import * as Gtk from "@gtkx/ffi/gtk";
4
- import { tick } from "./timing.js";
1
+ import * as Gsk from "@gtkx/gi/gsk";
2
+ import * as Gtk from "@gtkx/gi/gtk";
3
+ import { mkdtempSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
5
6
  import { waitFor } from "./wait-for.js";
7
+ const DEFAULT_SCREENSHOT_INTERVAL = 10;
6
8
  const bytesToBase64 = (bytes) => {
7
9
  return Buffer.from(bytes).toString("base64");
8
10
  };
9
- const DEFAULT_SCREENSHOT_TIMEOUT = 100;
10
- const DEFAULT_SCREENSHOT_INTERVAL = 10;
11
- const captureSnapshot = (widget) => {
12
- const paintable = new Gtk.WidgetPaintable(widget);
11
+ const describeWidgetState = (widget) => `realized=${String(widget.getRealized())} mapped=${String(widget.getMapped())} ` +
12
+ `visible=${String(widget.getVisible())}`;
13
+ const captureSnapshot = (widget, scale) => {
14
+ const paintable = new Gtk.WidgetPaintable({ widget });
13
15
  const width = paintable.getIntrinsicWidth();
14
16
  const height = paintable.getIntrinsicHeight();
15
17
  if (width <= 0 || height <= 0) {
16
- throw new Error("Widget has no size: ensure it is realized and visible");
18
+ throw new Error(`Widget has no size: ensure it is realized and visible (${describeWidgetState(widget)})`);
17
19
  }
18
20
  const snapshot = new Gtk.Snapshot();
21
+ snapshot.scale(scale, scale);
19
22
  paintable.snapshot(snapshot, width, height);
20
23
  const renderNode = snapshot.toNode();
21
24
  if (!renderNode) {
22
- throw new Error("Widget produced no render content");
25
+ throw new Error(`Widget produced no render content (${describeWidgetState(widget)})`);
23
26
  }
24
27
  const display = widget.getDisplay();
25
- if (!display) {
26
- throw new Error("Widget has no display: ensure it is realized");
27
- }
28
28
  const renderer = new Gsk.CairoRenderer();
29
29
  renderer.realizeForDisplay(display);
30
30
  try {
31
- const texture = renderer.renderTexture(renderNode);
31
+ const texture = renderer.renderTexture(renderNode, null);
32
32
  const pngBytes = texture.saveToPngBytes();
33
- const sizeRef = createRef(0);
34
- const data = pngBytes.getData(sizeRef);
33
+ const data = pngBytes.getData();
35
34
  if (!data) {
36
35
  throw new Error("Failed to serialize screenshot to PNG");
37
36
  }
38
37
  return {
39
38
  data: bytesToBase64(data),
40
39
  mimeType: "image/png",
41
- width,
42
- height,
40
+ width: Math.round(width * scale),
41
+ height: Math.round(height * scale),
43
42
  };
44
43
  }
45
44
  finally {
@@ -47,41 +46,90 @@ const captureSnapshot = (widget) => {
47
46
  }
48
47
  };
49
48
  /**
50
- * Captures a screenshot of a GTK widget as a PNG image.
51
- *
52
- * This function will retry multiple times if the widget hasn't finished
53
- * rendering, waiting for GTK to complete its paint cycle.
49
+ * Captures a PNG snapshot of a widget, retrying until it has a paintable size.
54
50
  *
55
- * @param widget - The widget to capture (typically a Window)
56
- * @param options - Optional timeout and interval configuration
57
- * @returns Screenshot result containing base64-encoded PNG data and dimensions
58
- * @throws Error if widget has no size, is not realized, or rendering fails after timeout
59
- *
60
- * @example
61
- * ```tsx
62
- * import { render, screenshot } from "@gtkx/testing";
63
- * import * as Gtk from "@gtkx/ffi/gtk";
64
- *
65
- * const { container } = await render(<MyApp />);
66
- * const window = container.getWindows()[0];
67
- * const result = await screenshot(window);
68
- * console.log(result.mimeType); // "image/png"
69
- * ```
51
+ * @param widget The widget to render to an image.
52
+ * @param options Optional scale, timeout, and retry interval.
53
+ * @returns The base64-encoded PNG data along with its mime type and dimensions.
70
54
  */
71
- export const screenshot = async (widget, options) => {
72
- await tick();
73
- return waitFor(() => captureSnapshot(widget), {
74
- timeout: options?.timeout ?? DEFAULT_SCREENSHOT_TIMEOUT,
55
+ const screenshot = async (widget, options) => {
56
+ const scale = options?.scale ?? 1;
57
+ if (!Number.isFinite(scale) || scale <= 0) {
58
+ throw new Error(`Screenshot scale must be a positive number, got ${String(scale)}`);
59
+ }
60
+ return waitFor(() => captureSnapshot(widget, scale), {
61
+ timeout: options?.timeout,
75
62
  interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,
76
- onTimeout: (error) => {
77
- const paintable = new Gtk.WidgetPaintable(widget);
78
- const width = paintable.getIntrinsicWidth();
79
- const height = paintable.getIntrinsicHeight();
80
- if (width <= 0 || height <= 0) {
81
- return new Error("Widget has no size: ensure it is realized and visible");
82
- }
83
- return new Error(`Widget produced no render content after waiting for paint cycle: ${error.message}`);
84
- },
85
63
  });
86
64
  };
65
+ const firstToplevelWindow = (windows) => {
66
+ const [first] = windows;
67
+ if (!(first instanceof Gtk.Window)) {
68
+ throw new TypeError("First toplevel is not a Window");
69
+ }
70
+ return first;
71
+ };
72
+ const windowAtIndex = (windows, index) => {
73
+ const indexed = windows[index];
74
+ if (!(indexed instanceof Gtk.Window)) {
75
+ throw new TypeError(`Window at index ${String(index)} not found`);
76
+ }
77
+ return indexed;
78
+ };
79
+ const isWindow = (widget) => widget instanceof Gtk.Window;
80
+ const hasMatchingTitle = (window, selector) => {
81
+ const title = window.getTitle() ?? "";
82
+ return selector instanceof RegExp ? selector.test(title) : title.includes(selector);
83
+ };
84
+ const describeTitleSelector = (selector) => selector instanceof RegExp ? selector.toString() : `"${selector}"`;
85
+ const windowByTitle = (windows, selector) => {
86
+ const found = windows.filter(isWindow).find((window) => hasMatchingTitle(window, selector));
87
+ if (!found) {
88
+ throw new Error(`No window found with title matching ${describeTitleSelector(selector)}`);
89
+ }
90
+ return found;
91
+ };
92
+ const resolveWindow = (selector) => {
93
+ const windows = Gtk.Window.listToplevels();
94
+ if (windows.length === 0) {
95
+ throw new Error("No windows available for screenshot");
96
+ }
97
+ if (selector === undefined) {
98
+ return firstToplevelWindow(windows);
99
+ }
100
+ if (typeof selector === "number") {
101
+ return windowAtIndex(windows, selector);
102
+ }
103
+ return windowByTitle(windows, selector);
104
+ };
105
+ const saveScreenshotToTempFile = (result) => {
106
+ const dir = mkdtempSync(join(tmpdir(), "gtkx-screenshots-"));
107
+ const filepath = join(dir, "screenshot.png");
108
+ writeFileSync(filepath, Buffer.from(result.data, "base64"));
109
+ return filepath;
110
+ };
111
+ /**
112
+ * Prints a clickable file URL for a saved screenshot to the console.
113
+ *
114
+ * @param filepath Absolute path of the saved screenshot file.
115
+ */
116
+ const logScreenshotPath = (filepath) => {
117
+ console.log(`Screenshot saved: file://${filepath}`);
118
+ };
119
+ /**
120
+ * Captures a screenshot of a toplevel window, writes it to a temporary file,
121
+ * logs its path, and returns the image data.
122
+ *
123
+ * @param selector Chooses the window by index, title substring, or regular
124
+ * expression; defaults to the first toplevel.
125
+ * @param options Optional scale, timeout, and retry interval.
126
+ * @returns The base64-encoded PNG data along with its mime type and dimensions.
127
+ */
128
+ const captureAndSaveScreenshot = async (selector, options) => {
129
+ const target = resolveWindow(selector);
130
+ const result = await screenshot(target, options);
131
+ logScreenshotPath(saveScreenshotToTempFile(result));
132
+ return result;
133
+ };
134
+ export { screenshot, logScreenshotPath, captureAndSaveScreenshot };
87
135
  //# sourceMappingURL=screenshot.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Gsk from \"@gtkx/gi/gsk\";\nimport * as Gtk from \"@gtkx/gi/gtk\";\nimport { mkdtempSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { ScreenshotOptions, ScreenshotResult, WindowSelector } from \"./types.js\";\nimport { waitFor } from \"./wait-for.js\";\n\nconst DEFAULT_SCREENSHOT_INTERVAL = 10;\n\nconst bytesToBase64 = (bytes: number[]): string => {\n return Buffer.from(bytes).toString(\"base64\");\n};\n\nconst describeWidgetState = (widget: Gtk.Widget): string =>\n `realized=${String(widget.getRealized())} mapped=${String(widget.getMapped())} ` +\n `visible=${String(widget.getVisible())}`;\n\nconst captureSnapshot = (widget: Gtk.Widget, scale: number): ScreenshotResult => {\n const paintable = new Gtk.WidgetPaintable({ widget });\n const width = paintable.getIntrinsicWidth();\n const height = paintable.getIntrinsicHeight();\n\n if (width <= 0 || height <= 0) {\n throw new Error(`Widget has no size: ensure it is realized and visible (${describeWidgetState(widget)})`);\n }\n\n const snapshot = new Gtk.Snapshot();\n snapshot.scale(scale, scale);\n paintable.snapshot(snapshot, width, height);\n const renderNode = snapshot.toNode();\n\n if (!renderNode) {\n throw new Error(`Widget produced no render content (${describeWidgetState(widget)})`);\n }\n\n const display = widget.getDisplay();\n const renderer = new Gsk.CairoRenderer();\n renderer.realizeForDisplay(display);\n\n try {\n const texture = renderer.renderTexture(renderNode, null);\n const pngBytes = texture.saveToPngBytes();\n const data = pngBytes.getData();\n\n if (!data) {\n throw new Error(\"Failed to serialize screenshot to PNG\");\n }\n\n return {\n data: bytesToBase64(data),\n mimeType: \"image/png\",\n width: Math.round(width * scale),\n height: Math.round(height * scale),\n };\n } finally {\n renderer.unrealize();\n }\n};\n\n/**\n * Captures a PNG snapshot of a widget, retrying until it has a paintable size.\n *\n * @param widget The widget to render to an image.\n * @param options Optional scale, timeout, and retry interval.\n * @returns The base64-encoded PNG data along with its mime type and dimensions.\n */\nconst screenshot = async (widget: Gtk.Widget, options?: ScreenshotOptions): Promise<ScreenshotResult> => {\n const scale = options?.scale ?? 1;\n\n if (!Number.isFinite(scale) || scale <= 0) {\n throw new Error(`Screenshot scale must be a positive number, got ${String(scale)}`);\n }\n\n return waitFor(() => captureSnapshot(widget, scale), {\n timeout: options?.timeout,\n interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,\n });\n};\n\nconst firstToplevelWindow = (windows: Gtk.Widget[]): Gtk.Window => {\n const [first] = windows;\n\n if (!(first instanceof Gtk.Window)) {\n throw new TypeError(\"First toplevel is not a Window\");\n }\n\n return first;\n};\n\nconst windowAtIndex = (windows: Gtk.Widget[], index: number): Gtk.Window => {\n const indexed = windows[index];\n\n if (!(indexed instanceof Gtk.Window)) {\n throw new TypeError(`Window at index ${String(index)} not found`);\n }\n\n return indexed;\n};\n\nconst isWindow = (widget: Gtk.Widget): widget is Gtk.Window => widget instanceof Gtk.Window;\n\nconst hasMatchingTitle = (window: Gtk.Window, selector: string | RegExp): boolean => {\n const title = window.getTitle() ?? \"\";\n\n return selector instanceof RegExp ? selector.test(title) : title.includes(selector);\n};\n\nconst describeTitleSelector = (selector: string | RegExp): string =>\n selector instanceof RegExp ? selector.toString() : `\"${selector}\"`;\n\nconst windowByTitle = (windows: Gtk.Widget[], selector: string | RegExp): Gtk.Window => {\n const found = windows.filter(isWindow).find((window) => hasMatchingTitle(window, selector));\n\n if (!found) {\n throw new Error(`No window found with title matching ${describeTitleSelector(selector)}`);\n }\n\n return found;\n};\n\nconst resolveWindow = (selector?: WindowSelector): Gtk.Window => {\n const windows = Gtk.Window.listToplevels();\n\n if (windows.length === 0) {\n throw new Error(\"No windows available for screenshot\");\n }\n\n if (selector === undefined) {\n return firstToplevelWindow(windows);\n }\n\n if (typeof selector === \"number\") {\n return windowAtIndex(windows, selector);\n }\n\n return windowByTitle(windows, selector);\n};\n\nconst saveScreenshotToTempFile = (result: ScreenshotResult): string => {\n const dir = mkdtempSync(join(tmpdir(), \"gtkx-screenshots-\"));\n const filepath = join(dir, \"screenshot.png\");\n writeFileSync(filepath, Buffer.from(result.data, \"base64\"));\n\n return filepath;\n};\n\n/**\n * Prints a clickable file URL for a saved screenshot to the console.\n *\n * @param filepath Absolute path of the saved screenshot file.\n */\nconst logScreenshotPath = (filepath: string): void => {\n console.log(`Screenshot saved: file://${filepath}`);\n};\n\n/**\n * Captures a screenshot of a toplevel window, writes it to a temporary file,\n * logs its path, and returns the image data.\n *\n * @param selector Chooses the window by index, title substring, or regular\n * expression; defaults to the first toplevel.\n * @param options Optional scale, timeout, and retry interval.\n * @returns The base64-encoded PNG data along with its mime type and dimensions.\n */\nconst captureAndSaveScreenshot = async (\n selector?: WindowSelector,\n options?: ScreenshotOptions,\n): Promise<ScreenshotResult> => {\n const target = resolveWindow(selector);\n const result = await screenshot(target, options);\n logScreenshotPath(saveScreenshotToTempFile(result));\n\n return result;\n};\n\nexport { screenshot, logScreenshotPath, captureAndSaveScreenshot };\n"]}
@@ -0,0 +1,27 @@
1
+ import * as Gtk from "@gtkx/gi/gtk";
2
+ /** The query variant a suggestion targets. */
3
+ type Variant = "get" | "getAll" | "query" | "queryAll" | "find" | "findAll";
4
+ /** The query family a suggestion targets. */
5
+ type Method = "Role" | "LabelText" | "PlaceholderText" | "Text" | "DisplayValue" | "Name";
6
+ /**
7
+ * A suggested query for reaching a widget, including its family, variant, method name, and a
8
+ * `toString` that renders the full call.
9
+ */
10
+ type Suggestion = {
11
+ queryName: Method;
12
+ /** The full query function name, such as `getByRole`. */
13
+ queryMethod: string;
14
+ variant: Variant;
15
+ toString: () => string;
16
+ };
17
+ /**
18
+ * Computes the recommended query for reaching the given widget, preferring role, then label text,
19
+ * placeholder text, text, display value, and name.
20
+ * @param widget Widget to build a suggestion for.
21
+ * @param variant Query variant the suggestion should use.
22
+ * @param method Restrict the suggestion to a specific query family instead of choosing by priority.
23
+ * @returns The best available suggestion, or undefined when no query applies.
24
+ */
25
+ declare const getSuggestedQuery: (widget: Gtk.Widget, variant?: Variant, method?: Method) => Suggestion | undefined;
26
+ export { getSuggestedQuery, type Variant, type Method, type Suggestion };
27
+ //# sourceMappingURL=suggestions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggestions.d.ts","sourceRoot":"","sources":["../src/suggestions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAYpC,8CAA8C;AAC9C,KAAK,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;AAC5E,6CAA6C;AAC7C,KAAK,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,iBAAiB,GAAG,MAAM,GAAG,cAAc,GAAG,MAAM,CAAC;AAE1F;;;GAGG;AACH,KAAK,UAAU,GAAG;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,MAAM,CAAC;CAC1B,CAAC;AAsCF;;;;;;;GAOG;AACH,QAAA,MAAM,iBAAiB,GACnB,QAAQ,GAAG,CAAC,MAAM,EAClB,UAAS,OAAe,EACxB,SAAS,MAAM,KAChB,UAAU,GAAG,SA0Bf,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,CAAC"}
@@ -0,0 +1,61 @@
1
+ import * as Gtk from "@gtkx/gi/gtk";
2
+ import { formatRole } from "./role-helpers.js";
3
+ import { getWidgetAccessibleName, getWidgetDisplayValue, getWidgetLabelledByText, getWidgetLabelText, getWidgetName, getWidgetOwnLabel, getWidgetPlaceholderText, } from "./widget-accessible-properties.js";
4
+ const makeSuggestion = (queryName, variant, argsText) => {
5
+ const queryMethod = `${variant}By${queryName}`;
6
+ return {
7
+ queryName,
8
+ queryMethod,
9
+ variant,
10
+ toString: () => `${queryMethod}(${argsText})`,
11
+ };
12
+ };
13
+ const roleSuggestion = (widget, variant) => {
14
+ const role = widget.getAccessibleRole();
15
+ if (role === Gtk.AccessibleRole.NONE || role === Gtk.AccessibleRole.GENERIC) {
16
+ return undefined;
17
+ }
18
+ const roleText = `Gtk.AccessibleRole.${formatRole(role).toUpperCase()}`;
19
+ const name = getWidgetAccessibleName(widget);
20
+ if (name === null) {
21
+ return makeSuggestion("Role", variant, roleText);
22
+ }
23
+ return makeSuggestion("Role", variant, `${roleText}, { name: '${name}' }`);
24
+ };
25
+ const textSuggestion = (queryName, variant, value) => {
26
+ if (value === null) {
27
+ return undefined;
28
+ }
29
+ return makeSuggestion(queryName, variant, `'${value}'`);
30
+ };
31
+ /**
32
+ * Computes the recommended query for reaching the given widget, preferring role, then label text,
33
+ * placeholder text, text, display value, and name.
34
+ * @param widget Widget to build a suggestion for.
35
+ * @param variant Query variant the suggestion should use.
36
+ * @param method Restrict the suggestion to a specific query family instead of choosing by priority.
37
+ * @returns The best available suggestion, or undefined when no query applies.
38
+ */
39
+ const getSuggestedQuery = (widget, variant = "get", method) => {
40
+ const builders = {
41
+ Role: () => roleSuggestion(widget, variant),
42
+ LabelText: () => textSuggestion("LabelText", variant, getWidgetOwnLabel(widget) ?? getWidgetLabelledByText(widget)),
43
+ PlaceholderText: () => textSuggestion("PlaceholderText", variant, getWidgetPlaceholderText(widget)),
44
+ Text: () => textSuggestion("Text", variant, getWidgetLabelText(widget)),
45
+ DisplayValue: () => textSuggestion("DisplayValue", variant, getWidgetDisplayValue(widget)),
46
+ Name: () => textSuggestion("Name", variant, getWidgetName(widget)),
47
+ };
48
+ if (method) {
49
+ return builders[method]();
50
+ }
51
+ const priority = ["Role", "LabelText", "PlaceholderText", "Text", "DisplayValue", "Name"];
52
+ for (const candidate of priority) {
53
+ const suggestion = builders[candidate]();
54
+ if (suggestion) {
55
+ return suggestion;
56
+ }
57
+ }
58
+ return undefined;
59
+ };
60
+ export { getSuggestedQuery };
61
+ //# sourceMappingURL=suggestions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggestions.js","sourceRoot":"","sources":["../src/suggestions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EACH,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,EACvB,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,wBAAwB,GAC3B,MAAM,mCAAmC,CAAC;AAmB3C,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAE,OAAgB,EAAE,QAAgB,EAAc,EAAE;IACzF,MAAM,WAAW,GAAG,GAAG,OAAO,KAAK,SAAS,EAAE,CAAC;IAE/C,OAAO;QACH,SAAS;QACT,WAAW;QACX,OAAO;QACP,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,WAAW,IAAI,QAAQ,GAAG;KAChD,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,MAAkB,EAAE,OAAgB,EAA0B,EAAE;IACpF,MAAM,IAAI,GAAG,MAAM,CAAC,iBAAiB,EAAE,CAAC;IAExC,IAAI,IAAI,KAAK,GAAG,CAAC,cAAc,CAAC,IAAI,IAAI,IAAI,KAAK,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;QAC1E,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,MAAM,QAAQ,GAAG,sBAAsB,UAAU,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;IACxE,MAAM,IAAI,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAE7C,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAChB,OAAO,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,cAAc,IAAI,KAAK,CAAC,CAAC;AAC/E,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAE,OAAgB,EAAE,KAAoB,EAA0B,EAAE;IACzG,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACjB,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,OAAO,cAAc,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,KAAK,GAAG,CAAC,CAAC;AAC5D,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,iBAAiB,GAAG,CACtB,MAAkB,EAClB,UAAmB,KAAK,EACxB,MAAe,EACO,EAAE;IACxB,MAAM,QAAQ,GAAiD;QAC3D,IAAI,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC;QAC3C,SAAS,EAAE,GAAG,EAAE,CACZ,cAAc,CAAC,WAAW,EAAE,OAAO,EAAE,iBAAiB,CAAC,MAAM,CAAC,IAAI,uBAAuB,CAAC,MAAM,CAAC,CAAC;QACtG,eAAe,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,iBAAiB,EAAE,OAAO,EAAE,wBAAwB,CAAC,MAAM,CAAC,CAAC;QACnG,IAAI,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;QACvE,YAAY,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,cAAc,EAAE,OAAO,EAAE,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAC1F,IAAI,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;KACrE,CAAC;IAEF,IAAI,MAAM,EAAE,CAAC;QACT,OAAO,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;IAC9B,CAAC;IAED,MAAM,QAAQ,GAAa,CAAC,MAAM,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;IAEpG,KAAK,MAAM,SAAS,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAEzC,IAAI,UAAU,EAAE,CAAC;YACb,OAAO,UAAU,CAAC;QACtB,CAAC;IACL,CAAC;IAED,OAAO,SAAS,CAAC;AACrB,CAAC,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAA8C,CAAC","sourcesContent":["import * as Gtk from \"@gtkx/gi/gtk\";\nimport { formatRole } from \"./role-helpers.js\";\nimport {\n getWidgetAccessibleName,\n getWidgetDisplayValue,\n getWidgetLabelledByText,\n getWidgetLabelText,\n getWidgetName,\n getWidgetOwnLabel,\n getWidgetPlaceholderText,\n} from \"./widget-accessible-properties.js\";\n\n/** The query variant a suggestion targets. */\ntype Variant = \"get\" | \"getAll\" | \"query\" | \"queryAll\" | \"find\" | \"findAll\";\n/** The query family a suggestion targets. */\ntype Method = \"Role\" | \"LabelText\" | \"PlaceholderText\" | \"Text\" | \"DisplayValue\" | \"Name\";\n\n/**\n * A suggested query for reaching a widget, including its family, variant, method name, and a\n * `toString` that renders the full call.\n */\ntype Suggestion = {\n queryName: Method;\n /** The full query function name, such as `getByRole`. */\n queryMethod: string;\n variant: Variant;\n toString: () => string;\n};\n\nconst makeSuggestion = (queryName: Method, variant: Variant, argsText: string): Suggestion => {\n const queryMethod = `${variant}By${queryName}`;\n\n return {\n queryName,\n queryMethod,\n variant,\n toString: () => `${queryMethod}(${argsText})`,\n };\n};\n\nconst roleSuggestion = (widget: Gtk.Widget, variant: Variant): Suggestion | undefined => {\n const role = widget.getAccessibleRole();\n\n if (role === Gtk.AccessibleRole.NONE || role === Gtk.AccessibleRole.GENERIC) {\n return undefined;\n }\n\n const roleText = `Gtk.AccessibleRole.${formatRole(role).toUpperCase()}`;\n const name = getWidgetAccessibleName(widget);\n\n if (name === null) {\n return makeSuggestion(\"Role\", variant, roleText);\n }\n\n return makeSuggestion(\"Role\", variant, `${roleText}, { name: '${name}' }`);\n};\n\nconst textSuggestion = (queryName: Method, variant: Variant, value: string | null): Suggestion | undefined => {\n if (value === null) {\n return undefined;\n }\n\n return makeSuggestion(queryName, variant, `'${value}'`);\n};\n\n/**\n * Computes the recommended query for reaching the given widget, preferring role, then label text,\n * placeholder text, text, display value, and name.\n * @param widget Widget to build a suggestion for.\n * @param variant Query variant the suggestion should use.\n * @param method Restrict the suggestion to a specific query family instead of choosing by priority.\n * @returns The best available suggestion, or undefined when no query applies.\n */\nconst getSuggestedQuery = (\n widget: Gtk.Widget,\n variant: Variant = \"get\",\n method?: Method,\n): Suggestion | undefined => {\n const builders: Record<Method, () => Suggestion | undefined> = {\n Role: () => roleSuggestion(widget, variant),\n LabelText: () =>\n textSuggestion(\"LabelText\", variant, getWidgetOwnLabel(widget) ?? getWidgetLabelledByText(widget)),\n PlaceholderText: () => textSuggestion(\"PlaceholderText\", variant, getWidgetPlaceholderText(widget)),\n Text: () => textSuggestion(\"Text\", variant, getWidgetLabelText(widget)),\n DisplayValue: () => textSuggestion(\"DisplayValue\", variant, getWidgetDisplayValue(widget)),\n Name: () => textSuggestion(\"Name\", variant, getWidgetName(widget)),\n };\n\n if (method) {\n return builders[method]();\n }\n\n const priority: Method[] = [\"Role\", \"LabelText\", \"PlaceholderText\", \"Text\", \"DisplayValue\", \"Name\"];\n\n for (const candidate of priority) {\n const suggestion = builders[candidate]();\n\n if (suggestion) {\n return suggestion;\n }\n }\n\n return undefined;\n};\n\nexport { getSuggestedQuery, type Variant, type Method, type Suggestion };\n"]}