@gtkx/testing 1.0.0-rc.1 → 1.0.0-rc.3

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 (187) 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 +5 -5
  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 +9 -6
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +5 -3
  39. package/dist/index.js.map +1 -1
  40. package/dist/matchers.d.ts +99 -35
  41. package/dist/matchers.d.ts.map +1 -1
  42. package/dist/matchers.js +314 -63
  43. package/dist/matchers.js.map +1 -1
  44. package/dist/normalize.d.ts +10 -0
  45. package/dist/normalize.d.ts.map +1 -0
  46. package/dist/normalize.js +20 -0
  47. package/dist/normalize.js.map +1 -0
  48. package/dist/pretty-widget.d.ts +6 -4
  49. package/dist/pretty-widget.d.ts.map +1 -1
  50. package/dist/pretty-widget.js +64 -24
  51. package/dist/pretty-widget.js.map +1 -1
  52. package/dist/queries.d.ts +129 -90
  53. package/dist/queries.d.ts.map +1 -1
  54. package/dist/queries.js +235 -164
  55. package/dist/queries.js.map +1 -1
  56. package/dist/register-test-hooks.js +2 -1
  57. package/dist/register-test-hooks.js.map +1 -1
  58. package/dist/render-hook.d.ts +3 -2
  59. package/dist/render-hook.d.ts.map +1 -1
  60. package/dist/render-hook.js +2 -1
  61. package/dist/render-hook.js.map +1 -1
  62. package/dist/render.d.ts +3 -2
  63. package/dist/render.d.ts.map +1 -1
  64. package/dist/render.js +25 -19
  65. package/dist/render.js.map +1 -1
  66. package/dist/role-helpers.d.ts +6 -5
  67. package/dist/role-helpers.d.ts.map +1 -1
  68. package/dist/role-helpers.js +16 -12
  69. package/dist/role-helpers.js.map +1 -1
  70. package/dist/screen.d.ts +4 -3
  71. package/dist/screen.d.ts.map +1 -1
  72. package/dist/screen.js +13 -11
  73. package/dist/screen.js.map +1 -1
  74. package/dist/screenshot.d.ts +4 -3
  75. package/dist/screenshot.d.ts.map +1 -1
  76. package/dist/screenshot.js +43 -36
  77. package/dist/screenshot.js.map +1 -1
  78. package/dist/suggestions.d.ts +11 -6
  79. package/dist/suggestions.d.ts.map +1 -1
  80. package/dist/suggestions.js +10 -5
  81. package/dist/suggestions.js.map +1 -1
  82. package/dist/traversal.d.ts +7 -7
  83. package/dist/traversal.d.ts.map +1 -1
  84. package/dist/traversal.js +17 -11
  85. package/dist/traversal.js.map +1 -1
  86. package/dist/types.d.ts +65 -24
  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 +5 -3
  90. package/dist/user-event/adjustment.d.ts.map +1 -1
  91. package/dist/user-event/adjustment.js +6 -4
  92. package/dist/user-event/adjustment.js.map +1 -1
  93. package/dist/user-event/click.d.ts +6 -5
  94. package/dist/user-event/click.d.ts.map +1 -1
  95. package/dist/user-event/click.js +29 -21
  96. package/dist/user-event/click.js.map +1 -1
  97. package/dist/user-event/controller.d.ts +7 -5
  98. package/dist/user-event/controller.d.ts.map +1 -1
  99. package/dist/user-event/controller.js +16 -6
  100. package/dist/user-event/controller.js.map +1 -1
  101. package/dist/user-event/dispatch.d.ts +3 -3
  102. package/dist/user-event/dispatch.d.ts.map +1 -1
  103. package/dist/user-event/dispatch.js +3 -2
  104. package/dist/user-event/dispatch.js.map +1 -1
  105. package/dist/user-event/event-wrapper.d.ts +2 -1
  106. package/dist/user-event/event-wrapper.d.ts.map +1 -1
  107. package/dist/user-event/event-wrapper.js +27 -16
  108. package/dist/user-event/event-wrapper.js.map +1 -1
  109. package/dist/user-event/gesture.d.ts +18 -14
  110. package/dist/user-event/gesture.d.ts.map +1 -1
  111. package/dist/user-event/gesture.js +70 -37
  112. package/dist/user-event/gesture.js.map +1 -1
  113. package/dist/user-event/index.d.ts +17 -10
  114. package/dist/user-event/index.d.ts.map +1 -1
  115. package/dist/user-event/index.js +7 -3
  116. package/dist/user-event/index.js.map +1 -1
  117. package/dist/user-event/keyboard.d.ts +4 -3
  118. package/dist/user-event/keyboard.d.ts.map +1 -1
  119. package/dist/user-event/keyboard.js +89 -58
  120. package/dist/user-event/keyboard.js.map +1 -1
  121. package/dist/user-event/pointer.d.ts +7 -3
  122. package/dist/user-event/pointer.d.ts.map +1 -1
  123. package/dist/user-event/pointer.js +4 -1
  124. package/dist/user-event/pointer.js.map +1 -1
  125. package/dist/user-event/selection.d.ts +3 -2
  126. package/dist/user-event/selection.d.ts.map +1 -1
  127. package/dist/user-event/selection.js +26 -27
  128. package/dist/user-event/selection.js.map +1 -1
  129. package/dist/user-event/state.d.ts +3 -2
  130. package/dist/user-event/state.d.ts.map +1 -1
  131. package/dist/user-event/state.js +2 -1
  132. package/dist/user-event/state.js.map +1 -1
  133. package/dist/user-event/text.d.ts +8 -7
  134. package/dist/user-event/text.d.ts.map +1 -1
  135. package/dist/user-event/text.js +39 -33
  136. package/dist/user-event/text.js.map +1 -1
  137. package/dist/wait-for.d.ts +5 -5
  138. package/dist/wait-for.d.ts.map +1 -1
  139. package/dist/wait-for.js +45 -30
  140. package/dist/wait-for.js.map +1 -1
  141. package/dist/widget-accessible-properties.d.ts +36 -25
  142. package/dist/widget-accessible-properties.d.ts.map +1 -1
  143. package/dist/widget-accessible-properties.js +215 -68
  144. package/dist/widget-accessible-properties.js.map +1 -1
  145. package/dist/within.d.ts +2 -1
  146. package/dist/within.d.ts.map +1 -1
  147. package/dist/within.js +2 -1
  148. package/dist/within.js.map +1 -1
  149. package/package.json +11 -9
  150. package/src/act.ts +44 -35
  151. package/src/bound-queries.ts +6 -6
  152. package/src/build-queries.ts +84 -43
  153. package/src/cleanup-registry.ts +18 -5
  154. package/src/config.ts +16 -12
  155. package/src/editable.ts +13 -6
  156. package/src/errors.ts +106 -45
  157. package/src/fire-event.ts +3 -1
  158. package/src/frame-sync.ts +38 -21
  159. package/src/index.ts +13 -3
  160. package/src/matchers.ts +568 -114
  161. package/src/normalize.ts +28 -0
  162. package/src/pretty-widget.ts +114 -42
  163. package/src/queries.ts +418 -256
  164. package/src/register-test-hooks.ts +4 -1
  165. package/src/render-hook.tsx +10 -4
  166. package/src/render.tsx +59 -34
  167. package/src/role-helpers.ts +29 -16
  168. package/src/screen.ts +17 -12
  169. package/src/screenshot.ts +61 -38
  170. package/src/suggestions.ts +29 -10
  171. package/src/traversal.ts +35 -15
  172. package/src/types.ts +111 -32
  173. package/src/user-event/adjustment.ts +23 -10
  174. package/src/user-event/click.ts +49 -20
  175. package/src/user-event/controller.ts +41 -7
  176. package/src/user-event/dispatch.ts +4 -2
  177. package/src/user-event/event-wrapper.ts +42 -16
  178. package/src/user-event/gesture.ts +135 -50
  179. package/src/user-event/index.ts +19 -12
  180. package/src/user-event/keyboard.ts +147 -61
  181. package/src/user-event/pointer.ts +20 -8
  182. package/src/user-event/selection.ts +69 -32
  183. package/src/user-event/state.ts +4 -2
  184. package/src/user-event/text.ts +73 -38
  185. package/src/wait-for.ts +77 -36
  186. package/src/widget-accessible-properties.ts +385 -79
  187. package/src/within.ts +8 -5
@@ -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,23 @@ 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;
16
+ /**
17
+ * A widget class usable as a query's `as` constraint, such as `Gtk.Button`. Abstract classes and
18
+ * generated GInterface pseudo-classes are accepted.
19
+ */
20
+ type WidgetType<T extends Gtk.Widget = Gtk.Widget> = abstract new (...args: never[]) => T;
15
21
 
16
22
  /** Options controlling the default text normalizer. */
17
- export type NormalizerOptions = {
23
+ type NormalizerOptions = {
18
24
  /** Trim leading and trailing whitespace. */
19
25
  trim?: boolean | undefined;
20
26
  /** Collapse runs of whitespace into a single space. */
@@ -22,7 +28,7 @@ export type NormalizerOptions = {
22
28
  };
23
29
 
24
30
  /** Options controlling how asynchronous queries and waits poll for a condition. */
25
- export type WaitForOptions = {
31
+ type WaitForOptions = {
26
32
  /** Maximum time in milliseconds to keep retrying before failing. */
27
33
  timeout?: number | undefined;
28
34
  /** Delay in milliseconds between retries. */
@@ -34,7 +40,7 @@ export type WaitForOptions = {
34
40
  };
35
41
 
36
42
  /** Options controlling text matching and, for asynchronous queries, polling behavior. */
37
- export type MatcherOptions = {
43
+ type MatcherOptions<T extends Gtk.Widget = Gtk.Widget> = {
38
44
  /** When true (the default), require an exact match; when false, match case-insensitively as a substring. */
39
45
  exact?: boolean | undefined;
40
46
  /** Custom normalizer replacing the default; cannot be combined with `trim` or `collapseWhitespace`. */
@@ -45,10 +51,12 @@ export type MatcherOptions = {
45
51
  collapseWhitespace?: boolean | undefined;
46
52
  /** Whether to include a suggested better query in error messages. */
47
53
  suggest?: boolean | undefined;
54
+ /** Restricts matches to instances of this widget class, and narrows the query's return type to it. */
55
+ as?: WidgetType<T> | undefined;
48
56
  } & WaitForOptions;
49
57
 
50
58
  /** Constraints on a widget's numeric range value used by role queries. */
51
- export type ByRoleValue = {
59
+ type ByRoleValue = {
52
60
  /** The current value. */
53
61
  now?: number | undefined;
54
62
  min?: number | undefined;
@@ -58,7 +66,7 @@ export type ByRoleValue = {
58
66
  };
59
67
 
60
68
  /** Options for role queries: an accessible name matcher plus accessible state and value constraints. */
61
- export type ByRoleOptions = MatcherOptions & {
69
+ type ByRoleOptions<T extends Gtk.Widget = Gtk.Widget> = MatcherOptions<T> & {
62
70
  name?: Matcher | undefined;
63
71
  checked?: boolean | undefined;
64
72
  pressed?: boolean | undefined;
@@ -74,22 +82,61 @@ export type ByRoleOptions = MatcherOptions & {
74
82
  };
75
83
 
76
84
  /** A React component that wraps rendered content, receiving it as its children. */
77
- export type WrapperComponent = ComponentType<{
85
+ type WrapperComponent = ComponentType<{
78
86
  children: ReactNode;
79
87
  }>;
80
88
 
81
89
  type Query = (container: Container, ...args: never[]) => unknown;
82
-
83
- export type QueryMap = Record<string, Query>;
90
+ type QueryMap = Record<string, Query>;
84
91
 
85
92
  type BoundQuery<Q extends Query> = Q extends (container: Container, ...args: infer A) => infer R
86
93
  ? (...args: A) => R
87
94
  : never;
88
95
 
89
- export type BoundCustomQueries<Q extends QueryMap> = { [K in keyof Q]: BoundQuery<Q[K]> };
96
+ type BoundCustomQueries<Q extends QueryMap> = { [K in keyof Q]: BoundQuery<Q[K]> };
90
97
 
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>> = {
98
+ type QueryFamilyReturns<T extends Gtk.Widget> = {
99
+ queryBy: T | null;
100
+ queryAllBy: T[];
101
+ getBy: T;
102
+ getAllBy: T[];
103
+ findBy: Promise<T>;
104
+ findAllBy: Promise<T[]>;
105
+ };
106
+
107
+ type QueryKind = "role" | "text" | "name" | "value";
108
+
109
+ type QueryArgs<Kind extends QueryKind, T extends Gtk.Widget> = Kind extends "role"
110
+ ? [role: Gtk.AccessibleRole, options?: ByRoleOptions<T>]
111
+ : Kind extends "name"
112
+ ? [name: Matcher, options?: MatcherOptions<T>]
113
+ : Kind extends "value"
114
+ ? [value: Matcher, options?: MatcherOptions<T>]
115
+ : [text: Matcher, options?: MatcherOptions<T>];
116
+
117
+ /**
118
+ * One query family (`queryBy`, `getBy`, `findBy` and their `All` variants) for a single suffix.
119
+ * Each member takes an explicit widget type, as Testing Library's queries do, and also infers it
120
+ * from an `as` option.
121
+ */
122
+ type QueryFamily<Suffix extends string, Kind extends QueryKind, Head extends unknown[]> = {
123
+ [K in keyof QueryFamilyReturns<Gtk.Widget> as `${K & string}${Suffix}`]: <T extends Gtk.Widget = Gtk.Widget>(
124
+ ...args: [...Head, ...QueryArgs<Kind, T>]
125
+ ) => QueryFamilyReturns<T>[K];
126
+ };
127
+
128
+ type QueryFamilies<Head extends unknown[]> = QueryFamily<"Role", "role", Head> &
129
+ QueryFamily<"LabelText", "text", Head> &
130
+ QueryFamily<"Text", "text", Head> &
131
+ QueryFamily<"Name", "name", Head> &
132
+ QueryFamily<"PlaceholderText", "text", Head> &
133
+ QueryFamily<"DisplayValue", "value", Head>;
134
+
135
+ /**
136
+ * Options for {@link render}: the container and base element to mount into, an optional wrapper,
137
+ * React behavior toggles, error callbacks, and custom queries to bind.
138
+ */
139
+ type RenderOptions<Q extends QueryMap = Record<never, never>> = {
93
140
  container?: Gtk.Widget | RootElement | undefined;
94
141
  /** Root of the subtree that bound queries search. */
95
142
  baseElement?: Container | undefined;
@@ -106,14 +153,14 @@ export type RenderOptions<Q extends QueryMap = Record<never, never>> = {
106
153
  queries?: Q | undefined;
107
154
  };
108
155
 
109
- export type DebugUtilities = {
156
+ type DebugUtilities = {
110
157
  debug: (element?: Container | Container[], options?: PrettyWidgetOptions) => void;
111
158
  logRoles: () => void;
112
159
  screenshot: (selector?: WindowSelector, options?: ScreenshotOptions) => Promise<ScreenshotResult>;
113
160
  };
114
161
 
115
162
  /** A captured screenshot: base64-encoded image data, its MIME type, and pixel dimensions. */
116
- export type ScreenshotResult = {
163
+ type ScreenshotResult = {
117
164
  data: string;
118
165
  mimeType: string;
119
166
  width: number;
@@ -121,29 +168,61 @@ export type ScreenshotResult = {
121
168
  };
122
169
 
123
170
  /** Options for capturing a screenshot: the poll timeout and interval, plus a rendering scale factor. */
124
- export type ScreenshotOptions = Pick<WaitForOptions, "timeout" | "interval"> & {
171
+ type ScreenshotOptions = Pick<WaitForOptions, "timeout" | "interval"> & {
125
172
  /** Device scale factor applied when rendering. */
126
173
  scale?: number;
127
174
  };
128
175
 
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> = {
176
+ /**
177
+ * Selects the window to screenshot by index, or by title (exact string or regular expression);
178
+ * undefined targets the default window.
179
+ */
180
+ type WindowSelector = number | string | RegExp | undefined;
181
+
182
+ /**
183
+ * Options for {@link renderHook}: an optional wrapper and the initial props (required unless the
184
+ * props type permits undefined).
185
+ */
186
+ type RenderHookOptions<Props> = {
134
187
  wrapper?: WrapperComponent;
135
188
  } & (undefined extends Props
136
189
  ? {
137
- initialProps?: Props;
138
- }
190
+ initialProps?: Props;
191
+ }
139
192
  : {
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> = {
193
+ initialProps: Props;
194
+ });
195
+
196
+ /**
197
+ * The result of {@link renderHook}: the latest hook return value plus functions to rerender with new
198
+ * props and to unmount.
199
+ */
200
+ type RenderHookResult<Result, Props> = {
145
201
  /** Holds the most recent value returned by the hook under `current`. */
146
202
  result: { current: Result };
147
203
  rerender: (newProps?: Props) => Promise<void>;
148
204
  unmount: () => Promise<void>;
149
205
  };
206
+
207
+ export {
208
+ type MatcherFunction,
209
+ type Matcher,
210
+ type NormalizerFn,
211
+ type NormalizerOptions,
212
+ type WaitForOptions,
213
+ type MatcherOptions,
214
+ type ByRoleValue,
215
+ type ByRoleOptions,
216
+ type WidgetType,
217
+ type WrapperComponent,
218
+ type QueryMap,
219
+ type BoundCustomQueries,
220
+ type QueryFamilies,
221
+ type RenderOptions,
222
+ type DebugUtilities,
223
+ type ScreenshotResult,
224
+ type ScreenshotOptions,
225
+ type WindowSelector,
226
+ type RenderHookOptions,
227
+ type RenderHookResult,
228
+ };
@@ -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 };