@gtkx/testing 1.0.0-rc.1 → 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 (182) hide show
  1. package/dist/act.d.ts +6 -6
  2. package/dist/act.d.ts.map +1 -1
  3. package/dist/act.js +34 -26
  4. package/dist/act.js.map +1 -1
  5. package/dist/bound-queries.d.ts +4 -3
  6. package/dist/bound-queries.d.ts.map +1 -1
  7. package/dist/bound-queries.js.map +1 -1
  8. package/dist/build-queries.d.ts +5 -5
  9. package/dist/build-queries.d.ts.map +1 -1
  10. package/dist/build-queries.js +21 -9
  11. package/dist/build-queries.js.map +1 -1
  12. package/dist/cleanup-registry.d.ts +4 -3
  13. package/dist/cleanup-registry.d.ts.map +1 -1
  14. package/dist/cleanup-registry.js +13 -3
  15. package/dist/cleanup-registry.js.map +1 -1
  16. package/dist/config.d.ts +5 -4
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +11 -9
  19. package/dist/config.js.map +1 -1
  20. package/dist/editable.d.ts +6 -5
  21. package/dist/editable.d.ts.map +1 -1
  22. package/dist/editable.js +7 -5
  23. package/dist/editable.js.map +1 -1
  24. package/dist/errors.d.ts +7 -6
  25. package/dist/errors.d.ts.map +1 -1
  26. package/dist/errors.js +64 -48
  27. package/dist/errors.js.map +1 -1
  28. package/dist/fire-event.d.ts +2 -1
  29. package/dist/fire-event.d.ts.map +1 -1
  30. package/dist/fire-event.js +2 -1
  31. package/dist/fire-event.js.map +1 -1
  32. package/dist/frame-sync.d.ts +2 -1
  33. package/dist/frame-sync.d.ts.map +1 -1
  34. package/dist/frame-sync.js +28 -21
  35. package/dist/frame-sync.js.map +1 -1
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/matchers.d.ts +38 -34
  41. package/dist/matchers.d.ts.map +1 -1
  42. package/dist/matchers.js +72 -58
  43. package/dist/matchers.js.map +1 -1
  44. package/dist/pretty-widget.d.ts +6 -4
  45. package/dist/pretty-widget.d.ts.map +1 -1
  46. package/dist/pretty-widget.js +64 -24
  47. package/dist/pretty-widget.js.map +1 -1
  48. package/dist/queries.d.ts +143 -86
  49. package/dist/queries.d.ts.map +1 -1
  50. package/dist/queries.js +231 -147
  51. package/dist/queries.js.map +1 -1
  52. package/dist/register-test-hooks.js +2 -1
  53. package/dist/register-test-hooks.js.map +1 -1
  54. package/dist/render-hook.d.ts +3 -2
  55. package/dist/render-hook.d.ts.map +1 -1
  56. package/dist/render-hook.js +2 -1
  57. package/dist/render-hook.js.map +1 -1
  58. package/dist/render.d.ts +3 -2
  59. package/dist/render.d.ts.map +1 -1
  60. package/dist/render.js +24 -18
  61. package/dist/render.js.map +1 -1
  62. package/dist/role-helpers.d.ts +6 -5
  63. package/dist/role-helpers.d.ts.map +1 -1
  64. package/dist/role-helpers.js +16 -12
  65. package/dist/role-helpers.js.map +1 -1
  66. package/dist/screen.d.ts +4 -3
  67. package/dist/screen.d.ts.map +1 -1
  68. package/dist/screen.js +13 -11
  69. package/dist/screen.js.map +1 -1
  70. package/dist/screenshot.d.ts +4 -3
  71. package/dist/screenshot.d.ts.map +1 -1
  72. package/dist/screenshot.js +43 -36
  73. package/dist/screenshot.js.map +1 -1
  74. package/dist/suggestions.d.ts +11 -6
  75. package/dist/suggestions.d.ts.map +1 -1
  76. package/dist/suggestions.js +10 -5
  77. package/dist/suggestions.js.map +1 -1
  78. package/dist/traversal.d.ts +7 -7
  79. package/dist/traversal.d.ts.map +1 -1
  80. package/dist/traversal.js +17 -11
  81. package/dist/traversal.js.map +1 -1
  82. package/dist/types.d.ts +39 -24
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/types.js.map +1 -1
  85. package/dist/user-event/adjustment.d.ts +5 -3
  86. package/dist/user-event/adjustment.d.ts.map +1 -1
  87. package/dist/user-event/adjustment.js +6 -4
  88. package/dist/user-event/adjustment.js.map +1 -1
  89. package/dist/user-event/click.d.ts +6 -5
  90. package/dist/user-event/click.d.ts.map +1 -1
  91. package/dist/user-event/click.js +29 -21
  92. package/dist/user-event/click.js.map +1 -1
  93. package/dist/user-event/controller.d.ts +6 -5
  94. package/dist/user-event/controller.d.ts.map +1 -1
  95. package/dist/user-event/controller.js +9 -6
  96. package/dist/user-event/controller.js.map +1 -1
  97. package/dist/user-event/dispatch.d.ts +3 -3
  98. package/dist/user-event/dispatch.d.ts.map +1 -1
  99. package/dist/user-event/dispatch.js +3 -2
  100. package/dist/user-event/dispatch.js.map +1 -1
  101. package/dist/user-event/event-wrapper.d.ts +2 -1
  102. package/dist/user-event/event-wrapper.d.ts.map +1 -1
  103. package/dist/user-event/event-wrapper.js +27 -16
  104. package/dist/user-event/event-wrapper.js.map +1 -1
  105. package/dist/user-event/gesture.d.ts +18 -14
  106. package/dist/user-event/gesture.d.ts.map +1 -1
  107. package/dist/user-event/gesture.js +70 -37
  108. package/dist/user-event/gesture.js.map +1 -1
  109. package/dist/user-event/index.d.ts +17 -10
  110. package/dist/user-event/index.d.ts.map +1 -1
  111. package/dist/user-event/index.js +7 -3
  112. package/dist/user-event/index.js.map +1 -1
  113. package/dist/user-event/keyboard.d.ts +4 -3
  114. package/dist/user-event/keyboard.d.ts.map +1 -1
  115. package/dist/user-event/keyboard.js +89 -58
  116. package/dist/user-event/keyboard.js.map +1 -1
  117. package/dist/user-event/pointer.d.ts +7 -3
  118. package/dist/user-event/pointer.d.ts.map +1 -1
  119. package/dist/user-event/pointer.js +4 -1
  120. package/dist/user-event/pointer.js.map +1 -1
  121. package/dist/user-event/selection.d.ts +3 -2
  122. package/dist/user-event/selection.d.ts.map +1 -1
  123. package/dist/user-event/selection.js +26 -27
  124. package/dist/user-event/selection.js.map +1 -1
  125. package/dist/user-event/state.d.ts +3 -2
  126. package/dist/user-event/state.d.ts.map +1 -1
  127. package/dist/user-event/state.js +2 -1
  128. package/dist/user-event/state.js.map +1 -1
  129. package/dist/user-event/text.d.ts +8 -7
  130. package/dist/user-event/text.d.ts.map +1 -1
  131. package/dist/user-event/text.js +39 -33
  132. package/dist/user-event/text.js.map +1 -1
  133. package/dist/wait-for.d.ts +5 -5
  134. package/dist/wait-for.d.ts.map +1 -1
  135. package/dist/wait-for.js +45 -30
  136. package/dist/wait-for.js.map +1 -1
  137. package/dist/widget-accessible-properties.d.ts +26 -25
  138. package/dist/widget-accessible-properties.d.ts.map +1 -1
  139. package/dist/widget-accessible-properties.js +106 -61
  140. package/dist/widget-accessible-properties.js.map +1 -1
  141. package/dist/within.d.ts +2 -1
  142. package/dist/within.d.ts.map +1 -1
  143. package/dist/within.js +2 -1
  144. package/dist/within.js.map +1 -1
  145. package/package.json +11 -9
  146. package/src/act.ts +44 -35
  147. package/src/bound-queries.ts +5 -4
  148. package/src/build-queries.ts +84 -43
  149. package/src/cleanup-registry.ts +18 -5
  150. package/src/config.ts +16 -12
  151. package/src/editable.ts +13 -6
  152. package/src/errors.ts +106 -45
  153. package/src/fire-event.ts +3 -1
  154. package/src/frame-sync.ts +38 -21
  155. package/src/index.ts +2 -1
  156. package/src/matchers.ts +158 -107
  157. package/src/pretty-widget.ts +114 -42
  158. package/src/queries.ts +422 -233
  159. package/src/register-test-hooks.ts +4 -1
  160. package/src/render-hook.tsx +10 -4
  161. package/src/render.tsx +58 -33
  162. package/src/role-helpers.ts +29 -16
  163. package/src/screen.ts +17 -12
  164. package/src/screenshot.ts +61 -38
  165. package/src/suggestions.ts +29 -10
  166. package/src/traversal.ts +35 -15
  167. package/src/types.ts +65 -32
  168. package/src/user-event/adjustment.ts +23 -10
  169. package/src/user-event/click.ts +49 -20
  170. package/src/user-event/controller.ts +21 -7
  171. package/src/user-event/dispatch.ts +4 -2
  172. package/src/user-event/event-wrapper.ts +42 -16
  173. package/src/user-event/gesture.ts +135 -50
  174. package/src/user-event/index.ts +19 -12
  175. package/src/user-event/keyboard.ts +147 -61
  176. package/src/user-event/pointer.ts +20 -8
  177. package/src/user-event/selection.ts +69 -32
  178. package/src/user-event/state.ts +4 -2
  179. package/src/user-event/text.ts +73 -38
  180. package/src/wait-for.ts +77 -36
  181. package/src/widget-accessible-properties.ts +218 -74
  182. package/src/within.ts +7 -4
@@ -11,13 +11,15 @@ import {
11
11
  } from "./widget-accessible-properties.js";
12
12
 
13
13
  /** The query variant a suggestion targets. */
14
- export type Variant = "get" | "getAll" | "query" | "queryAll" | "find" | "findAll";
15
-
14
+ type Variant = "get" | "getAll" | "query" | "queryAll" | "find" | "findAll";
16
15
  /** The query family a suggestion targets. */
17
- export type Method = "Role" | "LabelText" | "PlaceholderText" | "Text" | "DisplayValue" | "Name";
16
+ type Method = "Role" | "LabelText" | "PlaceholderText" | "Text" | "DisplayValue" | "Name";
18
17
 
19
- /** A suggested query for reaching a widget, including its family, variant, method name, and a `toString` that renders the full call. */
20
- export type Suggestion = {
18
+ /**
19
+ * A suggested query for reaching a widget, including its family, variant, method name, and a
20
+ * `toString` that renders the full call.
21
+ */
22
+ type Suggestion = {
21
23
  queryName: Method;
22
24
  /** The full query function name, such as `getByRole`. */
23
25
  queryMethod: string;
@@ -27,6 +29,7 @@ export type Suggestion = {
27
29
 
28
30
  const makeSuggestion = (queryName: Method, variant: Variant, argsText: string): Suggestion => {
29
31
  const queryMethod = `${variant}By${queryName}`;
32
+
30
33
  return {
31
34
  queryName,
32
35
  queryMethod,
@@ -37,29 +40,38 @@ const makeSuggestion = (queryName: Method, variant: Variant, argsText: string):
37
40
 
38
41
  const roleSuggestion = (widget: Gtk.Widget, variant: Variant): Suggestion | undefined => {
39
42
  const role = widget.getAccessibleRole();
40
- if (role === Gtk.AccessibleRole.NONE || role === Gtk.AccessibleRole.GENERIC) return undefined;
43
+
44
+ if (role === Gtk.AccessibleRole.NONE || role === Gtk.AccessibleRole.GENERIC) {
45
+ return undefined;
46
+ }
41
47
 
42
48
  const roleText = `Gtk.AccessibleRole.${formatRole(role).toUpperCase()}`;
43
49
  const name = getWidgetAccessibleName(widget);
50
+
44
51
  if (name === null) {
45
52
  return makeSuggestion("Role", variant, roleText);
46
53
  }
54
+
47
55
  return makeSuggestion("Role", variant, `${roleText}, { name: '${name}' }`);
48
56
  };
49
57
 
50
58
  const textSuggestion = (queryName: Method, variant: Variant, value: string | null): Suggestion | undefined => {
51
- if (value === null) return undefined;
59
+ if (value === null) {
60
+ return undefined;
61
+ }
62
+
52
63
  return makeSuggestion(queryName, variant, `'${value}'`);
53
64
  };
54
65
 
55
66
  /**
56
- * Computes the recommended query for reaching the given widget, preferring role, then label text, placeholder text, text, display value, and name.
67
+ * Computes the recommended query for reaching the given widget, preferring role, then label text,
68
+ * placeholder text, text, display value, and name.
57
69
  * @param widget Widget to build a suggestion for.
58
70
  * @param variant Query variant the suggestion should use.
59
71
  * @param method Restrict the suggestion to a specific query family instead of choosing by priority.
60
72
  * @returns The best available suggestion, or undefined when no query applies.
61
73
  */
62
- export const getSuggestedQuery = (
74
+ const getSuggestedQuery = (
63
75
  widget: Gtk.Widget,
64
76
  variant: Variant = "get",
65
77
  method?: Method,
@@ -79,9 +91,16 @@ export const getSuggestedQuery = (
79
91
  }
80
92
 
81
93
  const priority: Method[] = ["Role", "LabelText", "PlaceholderText", "Text", "DisplayValue", "Name"];
94
+
82
95
  for (const candidate of priority) {
83
96
  const suggestion = builders[candidate]();
84
- if (suggestion) return suggestion;
97
+
98
+ if (suggestion) {
99
+ return suggestion;
100
+ }
85
101
  }
102
+
86
103
  return undefined;
87
104
  };
105
+
106
+ export { getSuggestedQuery, type Variant, type Method, type Suggestion };
package/src/traversal.ts CHANGED
@@ -1,63 +1,83 @@
1
1
  import * as Gtk from "@gtkx/gi/gtk";
2
2
 
3
- export const TOPLEVELS: unique symbol = Symbol("gtkx.toplevels");
4
-
5
3
  type QueryContainer = Gtk.Widget | Gtk.EventController | Gtk.LayoutManager | Gtk.ListItem | Gtk.ListHeader;
6
-
7
4
  /**
8
5
  * A scope that queries and traversal can run against: a widget, an event
9
6
  * controller, a layout manager, a list item or header, an application, or the
10
7
  * sentinel representing all current toplevel windows.
11
8
  */
12
- export type Container = QueryContainer | Gtk.Application | typeof TOPLEVELS;
9
+ type Container = QueryContainer | Gtk.Application | typeof TOPLEVELS;
10
+
11
+ const TOPLEVELS: unique symbol = Symbol("gtkx.toplevels");
13
12
 
14
13
  const isApplication = (container: Container): container is Gtk.Application => container instanceof Gtk.Application;
15
14
 
16
15
  const traverseWidgetTree = function* (root: Gtk.Widget): Generator<Gtk.Widget> {
17
16
  yield root;
18
-
19
17
  let child = root.getFirstChild();
18
+
20
19
  while (child) {
21
20
  yield* traverseWidgetTree(child);
22
21
  child = child.getNextSibling();
23
22
  }
24
23
  };
25
24
 
26
- export const descendants = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {
25
+ const descendants = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {
27
26
  const tree = traverseWidgetTree(widget);
28
27
  tree.next();
29
28
  yield* tree;
30
29
  };
31
30
 
32
31
  const resolveRoot = (container: QueryContainer): Gtk.Widget | null => {
33
- if (container instanceof Gtk.Widget) return container;
34
- if (container instanceof Gtk.EventController) return container.getWidget();
35
- if (container instanceof Gtk.LayoutManager) return container.getWidget();
36
- if (container instanceof Gtk.ListItem || container instanceof Gtk.ListHeader) return container.getChild();
32
+ if (container instanceof Gtk.Widget) {
33
+ return container;
34
+ }
35
+
36
+ if (container instanceof Gtk.EventController) {
37
+ return container.getWidget();
38
+ }
39
+
40
+ if (container instanceof Gtk.LayoutManager) {
41
+ return container.getWidget();
42
+ }
43
+
44
+ if (container instanceof Gtk.ListItem || container instanceof Gtk.ListHeader) {
45
+ return container.getChild();
46
+ }
47
+
37
48
  return null;
38
49
  };
39
50
 
40
- export const roots = function* (container: Container): Generator<Gtk.Widget> {
51
+ const roots = function* (container: Container): Generator<Gtk.Widget> {
41
52
  if (container === TOPLEVELS || isApplication(container)) {
42
53
  yield* Gtk.Window.listToplevels();
54
+
43
55
  return;
44
56
  }
57
+
45
58
  const root = resolveRoot(container);
46
- if (root) yield root;
59
+
60
+ if (root) {
61
+ yield root;
62
+ }
47
63
  };
48
64
 
49
- export const traverse = function* (container: Container): Generator<Gtk.Widget> {
65
+ const traverse = function* (container: Container): Generator<Gtk.Widget> {
50
66
  for (const root of roots(container)) {
51
67
  yield* traverseWidgetTree(root);
52
68
  }
53
69
  };
54
70
 
55
- export const findAll = (container: Container, predicate: (node: Gtk.Widget) => boolean): Gtk.Widget[] => {
71
+ const findAll = (container: Container, isMatch: (node: Gtk.Widget) => boolean): Gtk.Widget[] => {
56
72
  const results: Gtk.Widget[] = [];
73
+
57
74
  for (const node of traverse(container)) {
58
- if (predicate(node)) {
75
+ if (isMatch(node)) {
59
76
  results.push(node);
60
77
  }
61
78
  }
79
+
62
80
  return results;
63
81
  };
82
+
83
+ export { TOPLEVELS, descendants, roots, traverse, findAll, type Container };
package/src/types.ts CHANGED
@@ -4,17 +4,18 @@ import type { ComponentType, ErrorInfo, ReactNode } from "react";
4
4
  import type { PrettyWidgetOptions } from "./pretty-widget.js";
5
5
  import type { Container } from "./traversal.js";
6
6
 
7
- /** A custom matcher predicate: given a candidate widget's normalized text content and the widget itself, returns whether it matches. */
8
- export type MatcherFunction = (content: string, widget: Gtk.Widget) => boolean;
9
-
7
+ /**
8
+ * A custom matcher predicate: given a candidate widget's normalized text content and the widget
9
+ * itself, returns whether it matches.
10
+ */
11
+ type MatcherFunction = (content: string, widget: Gtk.Widget) => boolean;
10
12
  /** A value used to match widget text: a string or number, a regular expression, or a custom {@link MatcherFunction}. */
11
- export type Matcher = string | number | RegExp | MatcherFunction;
12
-
13
+ type Matcher = string | number | RegExp | MatcherFunction;
13
14
  /** Normalizes a widget's text before it is compared against a matcher. */
14
- export type NormalizerFn = (text: string) => string;
15
+ type NormalizerFn = (text: string) => string;
15
16
 
16
17
  /** Options controlling the default text normalizer. */
17
- export type NormalizerOptions = {
18
+ type NormalizerOptions = {
18
19
  /** Trim leading and trailing whitespace. */
19
20
  trim?: boolean | undefined;
20
21
  /** Collapse runs of whitespace into a single space. */
@@ -22,7 +23,7 @@ export type NormalizerOptions = {
22
23
  };
23
24
 
24
25
  /** Options controlling how asynchronous queries and waits poll for a condition. */
25
- export type WaitForOptions = {
26
+ type WaitForOptions = {
26
27
  /** Maximum time in milliseconds to keep retrying before failing. */
27
28
  timeout?: number | undefined;
28
29
  /** Delay in milliseconds between retries. */
@@ -34,7 +35,7 @@ export type WaitForOptions = {
34
35
  };
35
36
 
36
37
  /** Options controlling text matching and, for asynchronous queries, polling behavior. */
37
- export type MatcherOptions = {
38
+ type MatcherOptions = {
38
39
  /** When true (the default), require an exact match; when false, match case-insensitively as a substring. */
39
40
  exact?: boolean | undefined;
40
41
  /** Custom normalizer replacing the default; cannot be combined with `trim` or `collapseWhitespace`. */
@@ -48,7 +49,7 @@ export type MatcherOptions = {
48
49
  } & WaitForOptions;
49
50
 
50
51
  /** Constraints on a widget's numeric range value used by role queries. */
51
- export type ByRoleValue = {
52
+ type ByRoleValue = {
52
53
  /** The current value. */
53
54
  now?: number | undefined;
54
55
  min?: number | undefined;
@@ -58,7 +59,7 @@ export type ByRoleValue = {
58
59
  };
59
60
 
60
61
  /** Options for role queries: an accessible name matcher plus accessible state and value constraints. */
61
- export type ByRoleOptions = MatcherOptions & {
62
+ type ByRoleOptions = MatcherOptions & {
62
63
  name?: Matcher | undefined;
63
64
  checked?: boolean | undefined;
64
65
  pressed?: boolean | undefined;
@@ -74,22 +75,24 @@ export type ByRoleOptions = MatcherOptions & {
74
75
  };
75
76
 
76
77
  /** A React component that wraps rendered content, receiving it as its children. */
77
- export type WrapperComponent = ComponentType<{
78
+ type WrapperComponent = ComponentType<{
78
79
  children: ReactNode;
79
80
  }>;
80
81
 
81
82
  type Query = (container: Container, ...args: never[]) => unknown;
82
-
83
- export type QueryMap = Record<string, Query>;
83
+ type QueryMap = Record<string, Query>;
84
84
 
85
85
  type BoundQuery<Q extends Query> = Q extends (container: Container, ...args: infer A) => infer R
86
86
  ? (...args: A) => R
87
87
  : never;
88
88
 
89
- export type BoundCustomQueries<Q extends QueryMap> = { [K in keyof Q]: BoundQuery<Q[K]> };
89
+ type BoundCustomQueries<Q extends QueryMap> = { [K in keyof Q]: BoundQuery<Q[K]> };
90
90
 
91
- /** Options for {@link render}: the container and base element to mount into, an optional wrapper, React behavior toggles, error callbacks, and custom queries to bind. */
92
- export type RenderOptions<Q extends QueryMap = Record<never, never>> = {
91
+ /**
92
+ * Options for {@link render}: the container and base element to mount into, an optional wrapper,
93
+ * React behavior toggles, error callbacks, and custom queries to bind.
94
+ */
95
+ type RenderOptions<Q extends QueryMap = Record<never, never>> = {
93
96
  container?: Gtk.Widget | RootElement | undefined;
94
97
  /** Root of the subtree that bound queries search. */
95
98
  baseElement?: Container | undefined;
@@ -106,14 +109,14 @@ export type RenderOptions<Q extends QueryMap = Record<never, never>> = {
106
109
  queries?: Q | undefined;
107
110
  };
108
111
 
109
- export type DebugUtilities = {
112
+ type DebugUtilities = {
110
113
  debug: (element?: Container | Container[], options?: PrettyWidgetOptions) => void;
111
114
  logRoles: () => void;
112
115
  screenshot: (selector?: WindowSelector, options?: ScreenshotOptions) => Promise<ScreenshotResult>;
113
116
  };
114
117
 
115
118
  /** A captured screenshot: base64-encoded image data, its MIME type, and pixel dimensions. */
116
- export type ScreenshotResult = {
119
+ type ScreenshotResult = {
117
120
  data: string;
118
121
  mimeType: string;
119
122
  width: number;
@@ -121,29 +124,59 @@ export type ScreenshotResult = {
121
124
  };
122
125
 
123
126
  /** Options for capturing a screenshot: the poll timeout and interval, plus a rendering scale factor. */
124
- export type ScreenshotOptions = Pick<WaitForOptions, "timeout" | "interval"> & {
127
+ type ScreenshotOptions = Pick<WaitForOptions, "timeout" | "interval"> & {
125
128
  /** Device scale factor applied when rendering. */
126
129
  scale?: number;
127
130
  };
128
131
 
129
- /** Selects the window to screenshot by index, or by title (exact string or regular expression); undefined targets the default window. */
130
- export type WindowSelector = number | string | RegExp | undefined;
131
-
132
- /** Options for {@link renderHook}: an optional wrapper and the initial props (required unless the props type permits undefined). */
133
- export type RenderHookOptions<Props> = {
132
+ /**
133
+ * Selects the window to screenshot by index, or by title (exact string or regular expression);
134
+ * undefined targets the default window.
135
+ */
136
+ type WindowSelector = number | string | RegExp | undefined;
137
+
138
+ /**
139
+ * Options for {@link renderHook}: an optional wrapper and the initial props (required unless the
140
+ * props type permits undefined).
141
+ */
142
+ type RenderHookOptions<Props> = {
134
143
  wrapper?: WrapperComponent;
135
144
  } & (undefined extends Props
136
145
  ? {
137
- initialProps?: Props;
138
- }
146
+ initialProps?: Props;
147
+ }
139
148
  : {
140
- initialProps: Props;
141
- });
142
-
143
- /** The result of {@link renderHook}: the latest hook return value plus functions to rerender with new props and to unmount. */
144
- export type RenderHookResult<Result, Props> = {
149
+ initialProps: Props;
150
+ });
151
+
152
+ /**
153
+ * The result of {@link renderHook}: the latest hook return value plus functions to rerender with new
154
+ * props and to unmount.
155
+ */
156
+ type RenderHookResult<Result, Props> = {
145
157
  /** Holds the most recent value returned by the hook under `current`. */
146
158
  result: { current: Result };
147
159
  rerender: (newProps?: Props) => Promise<void>;
148
160
  unmount: () => Promise<void>;
149
161
  };
162
+
163
+ export {
164
+ type MatcherFunction,
165
+ type Matcher,
166
+ type NormalizerFn,
167
+ type NormalizerOptions,
168
+ type WaitForOptions,
169
+ type MatcherOptions,
170
+ type ByRoleValue,
171
+ type ByRoleOptions,
172
+ type WrapperComponent,
173
+ type QueryMap,
174
+ type BoundCustomQueries,
175
+ type RenderOptions,
176
+ type DebugUtilities,
177
+ type ScreenshotResult,
178
+ type ScreenshotOptions,
179
+ type WindowSelector,
180
+ type RenderHookOptions,
181
+ type RenderHookResult,
182
+ };
@@ -1,49 +1,62 @@
1
1
  import * as Gtk from "@gtkx/gi/gtk";
2
2
  import { wrapEvent } from "./event-wrapper.js";
3
3
 
4
- export type ScrollDelta = {
4
+ /** A scroll distance in pixels along each axis. */
5
+ type ScrollDelta = {
5
6
  x?: number;
6
7
  y?: number;
7
8
  };
8
9
 
9
- export const slide = (widget: Gtk.Widget, value: number): Promise<void> =>
10
+ type ScrollAdjustments = {
11
+ horizontal: Gtk.Adjustment | null;
12
+ vertical: Gtk.Adjustment | null;
13
+ };
14
+
15
+ const slide = (widget: Gtk.Widget, value: number): Promise<void> =>
10
16
  wrapEvent(widget, () => {
11
17
  if (!(widget instanceof Gtk.Range)) {
12
- throw new Error(`userEvent.slide requires a Gtk.Range (e.g. Gtk.Scale), got ${widget.constructor.name}`);
18
+ throw new TypeError(
19
+ `userEvent.slide requires a Gtk.Range (e.g. Gtk.Scale), got ${widget.constructor.name}`,
20
+ );
13
21
  }
22
+
14
23
  widget.emit("change-value", Gtk.ScrollType.JUMP, value);
15
24
  });
16
25
 
17
- type ScrollAdjustments = {
18
- horizontal: Gtk.Adjustment | null;
19
- vertical: Gtk.Adjustment | null;
20
- };
21
-
22
26
  const resolveScrollAdjustments = (widget: Gtk.Widget): ScrollAdjustments | null => {
23
27
  for (let current: Gtk.Widget | null = widget; current; current = current.getParent()) {
24
28
  if (current instanceof Gtk.ScrolledWindow) {
25
29
  return { horizontal: current.getHadjustment(), vertical: current.getVadjustment() };
26
30
  }
31
+
27
32
  if (current instanceof Gtk.Scrollable) {
28
33
  return { horizontal: current.getHadjustment(), vertical: current.getVadjustment() };
29
34
  }
30
35
  }
36
+
31
37
  return null;
32
38
  };
33
39
 
34
40
  const applyScrollDelta = (adjustment: Gtk.Adjustment | null, delta: number): void => {
35
- if (!adjustment || delta === 0) return;
41
+ if (!adjustment || delta === 0) {
42
+ return;
43
+ }
44
+
36
45
  adjustment.setValue(adjustment.getValue() + delta);
37
46
  };
38
47
 
39
- export const scroll = (widget: Gtk.Widget, delta: ScrollDelta): Promise<void> =>
48
+ const scroll = (widget: Gtk.Widget, delta: ScrollDelta): Promise<void> =>
40
49
  wrapEvent(widget, () => {
41
50
  const adjustments = resolveScrollAdjustments(widget);
51
+
42
52
  if (!adjustments) {
43
53
  throw new Error(
44
54
  "userEvent.scroll: no Gtk.ScrolledWindow or Gtk.Scrollable found on the widget or its ancestors",
45
55
  );
46
56
  }
57
+
47
58
  applyScrollDelta(adjustments.horizontal, delta.x ?? 0);
48
59
  applyScrollDelta(adjustments.vertical, delta.y ?? 0);
49
60
  });
61
+
62
+ export { slide, scroll, type ScrollDelta };
@@ -2,25 +2,31 @@ import * as Gtk from "@gtkx/gi/gtk";
2
2
  import { getOrCreateControllers, queryController } from "./controller.js";
3
3
  import { wrapEvent } from "./event-wrapper.js";
4
4
 
5
- const pressPointOf = (widget: Gtk.Widget): { x: number; y: number } => {
5
+ const getPressPoint = (widget: Gtk.Widget): { x: number; y: number } => {
6
6
  const width = widget.getWidth();
7
7
  const height = widget.getHeight();
8
- if (width > 0 && height > 0) return { x: width / 2, y: height / 2 };
8
+
9
+ if (width > 0 && height > 0) {
10
+ return { x: width / 2, y: height / 2 };
11
+ }
12
+
9
13
  return { x: 0, y: 0 };
10
14
  };
11
15
 
12
- export const emitPress = (widget: Gtk.Widget, controllers: Gtk.GestureClick[], nPress: number): void => {
13
- const { x, y } = pressPointOf(widget);
16
+ const emitGesture = (widget: Gtk.Widget, controllers: Gtk.GestureClick[], nPress: number, signal: string): void => {
17
+ const { x, y } = getPressPoint(widget);
18
+
14
19
  for (const controller of controllers) {
15
- controller.emit("pressed", nPress, x, y);
20
+ controller.emit(signal, nPress, x, y);
16
21
  }
17
22
  };
18
23
 
19
- export const emitRelease = (widget: Gtk.Widget, controllers: Gtk.GestureClick[], nPress: number): void => {
20
- const { x, y } = pressPointOf(widget);
21
- for (const controller of controllers) {
22
- controller.emit("released", nPress, x, y);
23
- }
24
+ const emitPress = (widget: Gtk.Widget, controllers: Gtk.GestureClick[], nPress: number): void => {
25
+ emitGesture(widget, controllers, nPress, "pressed");
26
+ };
27
+
28
+ const emitRelease = (widget: Gtk.Widget, controllers: Gtk.GestureClick[], nPress: number): void => {
29
+ emitGesture(widget, controllers, nPress, "released");
24
30
  };
25
31
 
26
32
  const emitClickSequence = (widget: Gtk.Widget, target: Gtk.Widget, nPress: number): Promise<void> =>
@@ -35,36 +41,59 @@ const emitClickSequence = (widget: Gtk.Widget, target: Gtk.Widget, nPress: numbe
35
41
 
36
42
  const findClickableAncestor = (widget: Gtk.Widget): Gtk.Widget | null => {
37
43
  let current = widget.getParent();
44
+
38
45
  while (current) {
39
46
  if (current instanceof Gtk.Button || queryController(current, Gtk.GestureClick) !== null) {
40
47
  return current;
41
48
  }
49
+
42
50
  current = current.getParent();
43
51
  }
52
+
44
53
  return null;
45
54
  };
46
55
 
47
- export const click = async (widget: Gtk.Widget): Promise<void> => {
56
+ const tryActivate = async (widget: Gtk.Widget): Promise<boolean> => {
57
+ if (widget.getAccessibleRole() === Gtk.AccessibleRole.LABEL) {
58
+ return false;
59
+ }
60
+
61
+ let isActivated = false;
62
+
63
+ await wrapEvent(widget, () => {
64
+ isActivated = widget.activate();
65
+ });
66
+
67
+ return isActivated;
68
+ };
69
+
70
+ const click = async (widget: Gtk.Widget): Promise<void> => {
48
71
  if (widget instanceof Gtk.Button) {
49
72
  await emitClickSequence(widget, widget, 1);
73
+
50
74
  return;
51
75
  }
76
+
52
77
  if (widget instanceof Gtk.Switch) {
53
78
  await wrapEvent(widget, () => {
54
79
  widget.setActive(!widget.getActive());
55
80
  });
81
+
56
82
  return;
57
83
  }
58
- if (widget.getAccessibleRole() !== Gtk.AccessibleRole.LABEL) {
59
- let activated = false;
60
- await wrapEvent(widget, () => {
61
- activated = widget.activate();
62
- });
63
- if (activated) return;
84
+
85
+ if (await tryActivate(widget)) {
86
+ return;
64
87
  }
88
+
65
89
  const target = findClickableAncestor(widget);
66
- if (target) await emitClickSequence(widget, target, 1);
90
+
91
+ if (target) {
92
+ await emitClickSequence(widget, target, 1);
93
+ }
67
94
  };
68
95
 
69
- export const dblClick = (widget: Gtk.Widget): Promise<void> => emitClickSequence(widget, widget, 2);
70
- export const tripleClick = (widget: Gtk.Widget): Promise<void> => emitClickSequence(widget, widget, 3);
96
+ const dblClick = (widget: Gtk.Widget): Promise<void> => emitClickSequence(widget, widget, 2);
97
+ const tripleClick = (widget: Gtk.Widget): Promise<void> => emitClickSequence(widget, widget, 3);
98
+
99
+ export { emitPress, emitRelease, click, dblClick, tripleClick };
@@ -1,44 +1,58 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
2
 
3
- export type ControllerConstructor<T extends Gtk.EventController> = new () => T;
3
+ type ControllerConstructor<T extends Gtk.EventController> = new () => T;
4
4
 
5
- export const queryAllControllers = <T extends Gtk.EventController>(
5
+ const queryAllControllers = <T extends Gtk.EventController>(
6
6
  widget: Gtk.Widget,
7
7
  controllerType: ControllerConstructor<T>,
8
8
  ): T[] => {
9
9
  const controllers = widget.observeControllers();
10
10
  const nItems = controllers.getNItems();
11
11
  const matches: T[] = [];
12
+
12
13
  for (let i = 0; i < nItems; i++) {
13
14
  const controller = controllers.getItem(i);
14
- if (controller instanceof controllerType) matches.push(controller);
15
+
16
+ if (controller instanceof controllerType) {
17
+ matches.push(controller);
18
+ }
15
19
  }
20
+
16
21
  return matches;
17
22
  };
18
23
 
19
- export const queryController = <T extends Gtk.EventController>(
24
+ const queryController = <T extends Gtk.EventController>(
20
25
  widget: Gtk.Widget,
21
26
  controllerType: ControllerConstructor<T>,
22
27
  ): T | null => queryAllControllers(widget, controllerType)[0] ?? null;
23
28
 
24
- export const getAllControllers = <T extends Gtk.EventController>(
29
+ const getAllControllers = <T extends Gtk.EventController>(
25
30
  widget: Gtk.Widget,
26
31
  controllerType: ControllerConstructor<T>,
27
32
  ): T[] => {
28
33
  const controllers = queryAllControllers(widget, controllerType);
34
+
29
35
  if (controllers.length === 0) {
30
36
  throw new Error(`No ${controllerType.name} controller is attached to the widget`);
31
37
  }
38
+
32
39
  return controllers;
33
40
  };
34
41
 
35
- export const getOrCreateControllers = <T extends Gtk.EventController>(
42
+ const getOrCreateControllers = <T extends Gtk.EventController>(
36
43
  widget: Gtk.Widget,
37
44
  controllerType: ControllerConstructor<T>,
38
45
  ): T[] => {
39
46
  const existing = queryAllControllers(widget, controllerType);
40
- if (existing.length > 0) return existing;
47
+
48
+ if (existing.length > 0) {
49
+ return existing;
50
+ }
51
+
41
52
  const controller = new controllerType();
42
53
  widget.addController(controller);
54
+
43
55
  return [controller];
44
56
  };
57
+
58
+ export { queryAllControllers, queryController, getAllControllers, getOrCreateControllers, type ControllerConstructor };
@@ -10,14 +10,16 @@ const emitOnAll = async <T extends Gtk.EventController>(controllers: T[], emit:
10
10
  }
11
11
  };
12
12
 
13
- export const dispatchOnOrCreateControllers = <T extends Gtk.EventController>(
13
+ const dispatchOnOrCreateControllers = <T extends Gtk.EventController>(
14
14
  widget: Gtk.Widget,
15
15
  controllerType: ControllerConstructor<T>,
16
16
  emit: ControllerEmit<T>,
17
17
  ): Promise<void> => wrapEvent(widget, () => emitOnAll(getOrCreateControllers(widget, controllerType), emit));
18
18
 
19
- export const dispatchOnControllers = <T extends Gtk.EventController>(
19
+ const dispatchOnControllers = <T extends Gtk.EventController>(
20
20
  widget: Gtk.Widget,
21
21
  controllerType: ControllerConstructor<T>,
22
22
  emit: ControllerEmit<T>,
23
23
  ): Promise<void> => wrapEvent(widget, () => emitOnAll(getAllControllers(widget, controllerType), emit));
24
+
25
+ export { dispatchOnOrCreateControllers, dispatchOnControllers };