@gtkx/testing 2.0.0-beta.3 → 2.0.0-beta.4

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 (88) hide show
  1. package/dist/build-queries.d.ts +9 -9
  2. package/dist/build-queries.d.ts.map +1 -1
  3. package/dist/build-queries.js +2 -1
  4. package/dist/build-queries.js.map +1 -1
  5. package/dist/fire-event.d.ts +3 -3
  6. package/dist/fire-event.d.ts.map +1 -1
  7. package/dist/fire-event.js +14 -4
  8. package/dist/fire-event.js.map +1 -1
  9. package/dist/matchers.d.ts +6 -6
  10. package/dist/matchers.d.ts.map +1 -1
  11. package/dist/matchers.js +3 -2
  12. package/dist/matchers.js.map +1 -1
  13. package/dist/queries.d.ts +7 -7
  14. package/dist/queries.d.ts.map +1 -1
  15. package/dist/queries.js +16 -9
  16. package/dist/queries.js.map +1 -1
  17. package/dist/query-helpers.d.ts +2 -2
  18. package/dist/query-helpers.d.ts.map +1 -1
  19. package/dist/query-helpers.js +2 -1
  20. package/dist/query-helpers.js.map +1 -1
  21. package/dist/render.d.ts.map +1 -1
  22. package/dist/render.js +4 -0
  23. package/dist/render.js.map +1 -1
  24. package/dist/role-helpers.d.ts +1 -1
  25. package/dist/role-helpers.d.ts.map +1 -1
  26. package/dist/role-helpers.js +2 -1
  27. package/dist/role-helpers.js.map +1 -1
  28. package/dist/screenshot.d.ts +1 -1
  29. package/dist/screenshot.d.ts.map +1 -1
  30. package/dist/screenshot.js +3 -1
  31. package/dist/screenshot.js.map +1 -1
  32. package/dist/suggestions.d.ts +1 -1
  33. package/dist/suggestions.d.ts.map +1 -1
  34. package/dist/suggestions.js +3 -1
  35. package/dist/suggestions.js.map +1 -1
  36. package/dist/traversal.d.ts +3 -3
  37. package/dist/traversal.d.ts.map +1 -1
  38. package/dist/traversal.js +1 -1
  39. package/dist/traversal.js.map +1 -1
  40. package/dist/types.d.ts +8 -8
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.js.map +1 -1
  43. package/dist/user-event/controller.d.ts +5 -5
  44. package/dist/user-event/controller.d.ts.map +1 -1
  45. package/dist/user-event/controller.js +5 -2
  46. package/dist/user-event/controller.js.map +1 -1
  47. package/dist/user-event/index.d.ts +22 -22
  48. package/dist/user-event/index.d.ts.map +1 -1
  49. package/dist/user-event/index.js +28 -25
  50. package/dist/user-event/index.js.map +1 -1
  51. package/dist/user-event/selection.d.ts +2 -2
  52. package/dist/user-event/selection.d.ts.map +1 -1
  53. package/dist/user-event/selection.js +8 -2
  54. package/dist/user-event/selection.js.map +1 -1
  55. package/dist/wait-for.d.ts +2 -2
  56. package/dist/wait-for.d.ts.map +1 -1
  57. package/dist/wait-for.js +6 -4
  58. package/dist/wait-for.js.map +1 -1
  59. package/dist/widget-accessible-properties.d.ts +2 -2
  60. package/dist/widget-accessible-properties.d.ts.map +1 -1
  61. package/dist/widget-accessible-properties.js +3 -1
  62. package/dist/widget-accessible-properties.js.map +1 -1
  63. package/dist/widget-getters.d.ts +7 -9
  64. package/dist/widget-getters.d.ts.map +1 -1
  65. package/dist/widget-getters.js.map +1 -1
  66. package/dist/widget-target.d.ts +4 -0
  67. package/dist/widget-target.d.ts.map +1 -0
  68. package/dist/widget-target.js +9 -0
  69. package/dist/widget-target.js.map +1 -0
  70. package/package.json +5 -5
  71. package/src/build-queries.ts +60 -30
  72. package/src/fire-event.ts +32 -8
  73. package/src/matchers.ts +13 -12
  74. package/src/queries.ts +61 -29
  75. package/src/query-helpers.ts +9 -7
  76. package/src/render.tsx +5 -0
  77. package/src/role-helpers.ts +3 -1
  78. package/src/screenshot.ts +3 -1
  79. package/src/suggestions.ts +3 -1
  80. package/src/traversal.ts +4 -4
  81. package/src/types.ts +12 -8
  82. package/src/user-event/controller.ts +8 -5
  83. package/src/user-event/index.ts +133 -47
  84. package/src/user-event/selection.ts +12 -4
  85. package/src/wait-for.ts +9 -6
  86. package/src/widget-accessible-properties.ts +7 -5
  87. package/src/widget-getters.ts +11 -13
  88. package/src/widget-target.ts +11 -0
@@ -5,18 +5,28 @@ import { getConfig } from "./config.js";
5
5
  import { runWithExpensiveErrorDiagnosticsDisabled, suggestionError } from "./errors.js";
6
6
  import { getSuggestedQuery, type Variant } from "./suggestions.js";
7
7
  import { waitFor } from "./wait-for.js";
8
+ import { requireWidget } from "./widget-target.js";
8
9
 
9
10
  /**
10
11
  * The `queryAllBy` function a query family is built from, taking the scope to search followed by the
11
12
  * family's own matcher arguments. Its name determines the family name, so `queryAllByTestId` builds
12
13
  * a `TestId` family.
13
14
  */
14
- type QueryAllBy<Args extends unknown[]> = (container: Container, ...args: Args) => Gtk.Widget[];
15
+ type QueryAllBy<
16
+ Args extends unknown[],
17
+ Element extends Gtk.Accessible = Gtk.Accessible,
18
+ > = (
19
+ container: Container,
20
+ ...args: Args
21
+ ) => Element[];
15
22
 
16
23
  /** Builds the error thrown when a single-match query finds more than one widget. */
17
- type MultipleErrorBuilder<Args extends unknown[]> = (
24
+ type MultipleErrorBuilder<
25
+ Args extends unknown[],
26
+ Element extends Gtk.Accessible = Gtk.Accessible,
27
+ > = (
18
28
  container: Container,
19
- matches: Gtk.Widget[],
29
+ matches: Element[],
20
30
  ...args: Args
21
31
  ) => Error;
22
32
 
@@ -24,16 +34,22 @@ type MultipleErrorBuilder<Args extends unknown[]> = (
24
34
  type MissingErrorBuilder<Args extends unknown[]> = (container: Container, ...args: Args) => Error;
25
35
 
26
36
  /** The variants derived from one `queryAllBy` function, in the order DOM Testing Library returns them. */
27
- type BuiltQueries<Args extends unknown[]> = [
28
- queryBy: (container: Container, ...args: Args) => Gtk.Widget | null,
29
- getAllBy: QueryAllBy<Args>,
30
- getBy: (container: Container, ...args: Args) => Gtk.Widget,
31
- findAllBy: (container: Container, ...args: Args) => Promise<Gtk.Widget[]>,
32
- findBy: (container: Container, ...args: Args) => Promise<Gtk.Widget>,
37
+ type BuiltQueries<
38
+ Args extends unknown[],
39
+ Element extends Gtk.Accessible = Gtk.Accessible,
40
+ > = [
41
+ queryBy: (container: Container, ...args: Args) => Element | null,
42
+ getAllBy: QueryAllBy<Args, Element>,
43
+ getBy: (container: Container, ...args: Args) => Element,
44
+ findAllBy: (container: Container, ...args: Args) => Promise<Element[]>,
45
+ findBy: (container: Container, ...args: Args) => Promise<Element>,
33
46
  ];
34
47
 
35
48
  type FindQuery<Args extends unknown[]> = (container: Container, ...args: Args) => unknown;
36
- type SingleQuery<Args extends unknown[]> = (container: Container, ...args: Args) => Gtk.Widget | null;
49
+ type SingleQuery<Args extends unknown[], Element extends Gtk.Accessible> = (
50
+ container: Container,
51
+ ...args: Args
52
+ ) => Element | null;
37
53
 
38
54
  const getTrailingOptions = (args: unknown[]): MatcherOptions | undefined => {
39
55
  const last = args.at(-1);
@@ -50,7 +66,7 @@ const getQueryFamily = (queryAllBy: { name: string }): string => queryAllBy.name
50
66
 
51
67
  const maybeThrowSuggestion = (options: {
52
68
  container: Container;
53
- match: Gtk.Widget;
69
+ match: object;
54
70
  queryName: string;
55
71
  variant: Variant;
56
72
  shouldSuggest: boolean | undefined;
@@ -61,7 +77,7 @@ const maybeThrowSuggestion = (options: {
61
77
  return;
62
78
  }
63
79
 
64
- const suggestion = getSuggestedQuery(match, variant);
80
+ const suggestion = getSuggestedQuery(requireWidget(match), variant);
65
81
 
66
82
  if (suggestion && suggestion.queryName !== queryName) {
67
83
  throw suggestionError(suggestion.toString(), container);
@@ -101,10 +117,10 @@ const findOptions = <Args extends unknown[]>(
101
117
  };
102
118
 
103
119
  const singleFrom =
104
- <Args extends unknown[]>(
105
- allQuery: QueryAllBy<Args>,
106
- getMultipleError: MultipleErrorBuilder<Args>,
107
- ): SingleQuery<Args> =>
120
+ <Args extends unknown[], Element extends Gtk.Accessible>(
121
+ allQuery: QueryAllBy<Args, Element>,
122
+ getMultipleError: MultipleErrorBuilder<Args, Element>,
123
+ ): SingleQuery<Args, Element> =>
108
124
  (container, ...args) => {
109
125
  const matches = allQuery(container, ...args);
110
126
 
@@ -116,10 +132,10 @@ const singleFrom =
116
132
  };
117
133
 
118
134
  const allOrThrow =
119
- <Args extends unknown[]>(
120
- allQuery: QueryAllBy<Args>,
135
+ <Args extends unknown[], Element extends Gtk.Accessible>(
136
+ allQuery: QueryAllBy<Args, Element>,
121
137
  getMissingError: MissingErrorBuilder<Args>,
122
- ): QueryAllBy<Args> =>
138
+ ): QueryAllBy<Args, Element> =>
123
139
  (container, ...args) => {
124
140
  const matches = allQuery(container, ...args);
125
141
 
@@ -131,7 +147,11 @@ const allOrThrow =
131
147
  };
132
148
 
133
149
  const wrapSingleWithSuggestion =
134
- <Args extends unknown[]>(query: SingleQuery<Args>, queryName: string, variant: Variant): SingleQuery<Args> =>
150
+ <Args extends unknown[], Element extends Gtk.Accessible>(
151
+ query: SingleQuery<Args, Element>,
152
+ queryName: string,
153
+ variant: Variant,
154
+ ): SingleQuery<Args, Element> =>
135
155
  (container, ...args) => {
136
156
  const match = query(container, ...args);
137
157
 
@@ -149,7 +169,11 @@ const wrapSingleWithSuggestion =
149
169
  };
150
170
 
151
171
  const wrapAllWithSuggestion =
152
- <Args extends unknown[]>(query: QueryAllBy<Args>, queryName: string, variant: Variant): QueryAllBy<Args> =>
172
+ <Args extends unknown[], Element extends Gtk.Accessible>(
173
+ query: QueryAllBy<Args, Element>,
174
+ queryName: string,
175
+ variant: Variant,
176
+ ): QueryAllBy<Args, Element> =>
153
177
  (container, ...args) => {
154
178
  const matches = query(container, ...args);
155
179
  const [first] = matches;
@@ -168,8 +192,11 @@ const wrapAllWithSuggestion =
168
192
  };
169
193
 
170
194
  const requireSingle =
171
- <Args extends unknown[]>(query: SingleQuery<Args>, getMissingError: MissingErrorBuilder<Args>) =>
172
- (container: Container, ...args: Args): Gtk.Widget => {
195
+ <Args extends unknown[], Element extends Gtk.Accessible>(
196
+ query: SingleQuery<Args, Element>,
197
+ getMissingError: MissingErrorBuilder<Args>,
198
+ ) =>
199
+ (container: Container, ...args: Args): Element => {
173
200
  const match = query(container, ...args);
174
201
 
175
202
  if (match === null) {
@@ -189,11 +216,14 @@ const requireSingle =
189
216
  * @param getMissingError Builds the error thrown when a required match is missing.
190
217
  * @returns The variants, in the order `[queryBy, getAllBy, getBy, findAllBy, findBy]`.
191
218
  */
192
- const buildQueries = <Args extends unknown[]>(
193
- queryAllBy: QueryAllBy<Args>,
194
- getMultipleError: MultipleErrorBuilder<Args>,
219
+ const buildQueries = <
220
+ Args extends unknown[],
221
+ Element extends Gtk.Accessible = Gtk.Accessible,
222
+ >(
223
+ queryAllBy: QueryAllBy<Args, Element>,
224
+ getMultipleError: MultipleErrorBuilder<Args, Element>,
195
225
  getMissingError: MissingErrorBuilder<Args>,
196
- ): BuiltQueries<Args> => {
226
+ ): BuiltQueries<Args, Element> => {
197
227
  const queryName = getQueryFamily(queryAllBy);
198
228
  const getAllBy = allOrThrow(queryAllBy, getMissingError);
199
229
  const getBy = requireSingle(singleFrom(getAllBy, getMultipleError), getMissingError);
@@ -206,7 +236,7 @@ const buildQueries = <Args extends unknown[]>(
206
236
 
207
237
  const getAllByWithSuggestion = wrapAllWithSuggestion(getAllBy, queryName, "getAll");
208
238
 
209
- const getByWithSuggestion: (container: Container, ...args: Args) => Gtk.Widget = (container, ...args) => {
239
+ const getByWithSuggestion: (container: Container, ...args: Args) => Element = (container, ...args) => {
210
240
  const match = getBy(container, ...args);
211
241
 
212
242
  maybeThrowSuggestion({
@@ -220,13 +250,13 @@ const buildQueries = <Args extends unknown[]>(
220
250
  return match;
221
251
  };
222
252
 
223
- const findAllBy = (container: Container, ...args: Args): Promise<Gtk.Widget[]> =>
253
+ const findAllBy = (container: Container, ...args: Args): Promise<Element[]> =>
224
254
  waitFor(
225
255
  () => runWithExpensiveErrorDiagnosticsDisabled(() => getAllByWithSuggestion(container, ...args)),
226
256
  findOptions(getAllByWithSuggestion, container, args),
227
257
  );
228
258
 
229
- const findBy = (container: Container, ...args: Args): Promise<Gtk.Widget> =>
259
+ const findBy = (container: Container, ...args: Args): Promise<Element> =>
230
260
  waitFor(
231
261
  () => runWithExpensiveErrorDiagnosticsDisabled(() => getByWithSuggestion(container, ...args)),
232
262
  findOptions(getByWithSuggestion, container, args),
package/src/fire-event.ts CHANGED
@@ -7,13 +7,21 @@ import { runInAct } from "./act.js";
7
7
  */
8
8
  type WidgetEvent = {
9
9
  /** The GObject instance the signal is emitted on. */
10
- target: GObject.Object;
10
+ target: Pick<GObject.Object, "__type__">;
11
11
  /** Name of the signal to emit. */
12
12
  signalName: string;
13
13
  /** Arguments passed to the signal handlers. */
14
14
  args: unknown[];
15
15
  };
16
16
 
17
+ const requireObject = (target: object): GObject.Object => {
18
+ if (!(target instanceof GObject.Object)) {
19
+ throw new TypeError("Expected a GObject instance");
20
+ }
21
+
22
+ return target;
23
+ };
24
+
17
25
  /**
18
26
  * Records a signal emission without delivering it, so it can be inspected or held and passed to
19
27
  * {@link fireEvent} later.
@@ -23,18 +31,28 @@ type WidgetEvent = {
23
31
  * @param args Arguments to pass to the signal handlers.
24
32
  * @returns The recorded emission.
25
33
  */
26
- const createEvent = (target: GObject.Object, signalName: string, ...args: unknown[]): WidgetEvent => ({
27
- target,
34
+ const createEvent = (
35
+ target: Pick<GObject.Object, "__type__">,
36
+ signalName: string,
37
+ ...args: unknown[]
38
+ ): WidgetEvent => ({
39
+ target: requireObject(target),
28
40
  signalName,
29
41
  args,
30
42
  });
31
43
 
32
44
  const dispatchEvent = async (event: WidgetEvent): Promise<void> => {
45
+ const target = requireObject(event.target);
46
+
33
47
  await runInAct(() => {
34
- Reflect.apply(GObject.signalEmit, undefined, [event.target, event.signalName, ...event.args]);
48
+ Reflect.apply(GObject.signalEmit, undefined, [target, event.signalName, ...event.args]);
35
49
  });
36
50
  };
37
51
 
52
+ const isWidgetEvent = (value: object): value is WidgetEvent =>
53
+ "target" in value && "signalName" in value && "args" in value &&
54
+ typeof value.signalName === "string" && Array.isArray(value.args);
55
+
38
56
  /**
39
57
  * Emits a recorded signal emission inside React's act environment, so any resulting state updates are
40
58
  * flushed before the promise resolves.
@@ -50,14 +68,18 @@ function fireEvent(event: WidgetEvent): Promise<void>;
50
68
  * @param signalName Name of the signal to emit.
51
69
  * @param args Arguments passed to the signal handlers.
52
70
  */
53
- function fireEvent(target: GObject.Object, signalName: string, ...args: unknown[]): Promise<void>;
71
+ function fireEvent(
72
+ target: Pick<GObject.Object, "__type__">,
73
+ signalName: string,
74
+ ...args: unknown[]
75
+ ): Promise<void>;
54
76
 
55
77
  function fireEvent(
56
- eventOrTarget: WidgetEvent | GObject.Object,
78
+ eventOrTarget: WidgetEvent | Pick<GObject.Object, "__type__">,
57
79
  signalName?: string,
58
80
  ...args: unknown[]
59
81
  ): Promise<void> {
60
- if (!(eventOrTarget instanceof GObject.Object)) {
82
+ if (signalName === undefined && isWidgetEvent(eventOrTarget)) {
61
83
  return dispatchEvent(eventOrTarget);
62
84
  }
63
85
 
@@ -65,7 +87,9 @@ function fireEvent(
65
87
  throw new Error("Unable to fire an event: pass a signal name, or an event built by createEvent.");
66
88
  }
67
89
 
68
- return dispatchEvent(createEvent(eventOrTarget, signalName, ...args));
90
+ const target = requireObject(eventOrTarget);
91
+
92
+ return dispatchEvent({ target, signalName, args });
69
93
  }
70
94
 
71
95
  export { createEvent, fireEvent, type WidgetEvent };
package/src/matchers.ts CHANGED
@@ -77,7 +77,7 @@ type TextQueryArgs = [text: Matcher, options?: MatcherOptions];
77
77
  type ContainmentMatcher<Args extends unknown[]> = (received: unknown, ...args: Args) => MatcherResult;
78
78
  type TextMatcherContext = { matcherName: string; widget: Gtk.Widget; actual: string | null };
79
79
  type ClassArguments = { expected: ClassExpectation[]; isExact: boolean };
80
- type QueryAll<Args extends unknown[]> = (container: Container, ...args: Args) => Gtk.Widget[];
80
+ type QueryAll<Args extends unknown[]> = (container: Container, ...args: Args) => object[];
81
81
 
82
82
  type BooleanAccessibleState =
83
83
  Gtk.AccessibleState.BUSY |
@@ -195,11 +195,11 @@ type MatcherImplementations = {
195
195
  /** Asserts the widget's accessible role. */
196
196
  toHaveRole: (received: unknown, expected: Gtk.AccessibleRole) => MatcherResult;
197
197
  /** Asserts the widget is, or is an ancestor of, the given widget. */
198
- toContainElement: (received: unknown, descendant: Gtk.Widget | null) => MatcherResult;
198
+ toContainElement: (received: unknown, descendant: Gtk.Accessible | null) => MatcherResult;
199
199
  /** Asserts the widget comes before the given widget in the widget tree, neither containing the other. */
200
- toAppearBefore: (received: unknown, other: Gtk.Widget) => MatcherResult;
200
+ toAppearBefore: (received: unknown, other: Gtk.Accessible) => MatcherResult;
201
201
  /** Asserts the widget comes after the given widget in the widget tree, neither containing the other. */
202
- toAppearAfter: (received: unknown, other: Gtk.Widget) => MatcherResult;
202
+ toAppearAfter: (received: unknown, other: Gtk.Accessible) => MatcherResult;
203
203
  /** Asserts at least one widget under the received one matches the `ByRole` query. */
204
204
  toContainAnyByRole: ContainmentMatcher<RoleQueryArgs>;
205
205
  /** Asserts exactly one widget under the received one matches the `ByRole` query. */
@@ -783,14 +783,14 @@ function toBePartiallyPressed(received: unknown): MatcherResult {
783
783
  return stateResult(widget, "partially pressed", state === MIXED_TRISTATE);
784
784
  }
785
785
 
786
- function toAppearBefore(received: unknown, other: Gtk.Widget): MatcherResult {
786
+ function toAppearBefore(received: unknown, other: Gtk.Accessible): MatcherResult {
787
787
  const widget = asWidget(received, "toAppearBefore");
788
788
  const target = asWidget(other, "toAppearBefore");
789
789
 
790
790
  return orderResult(widget, target, "before", isAppearingBefore(widget, target));
791
791
  }
792
792
 
793
- function toAppearAfter(received: unknown, other: Gtk.Widget): MatcherResult {
793
+ function toAppearAfter(received: unknown, other: Gtk.Accessible): MatcherResult {
794
794
  const widget = asWidget(received, "toAppearAfter");
795
795
  const target = asWidget(other, "toAppearAfter");
796
796
 
@@ -922,15 +922,16 @@ function toHaveRole(received: unknown, expected: Gtk.AccessibleRole): MatcherRes
922
922
  };
923
923
  }
924
924
 
925
- function toContainElement(received: unknown, descendant: Gtk.Widget | null): MatcherResult {
925
+ function toContainElement(received: unknown, descendant: Gtk.Accessible | null): MatcherResult {
926
926
  const widget = asWidget(received, "toContainElement");
927
- const isPass = descendant !== null && (descendant === widget || descendant.isAncestor(widget));
927
+ const target = descendant === null ? null : asWidget(descendant, "toContainElement");
928
+ const isPass = target !== null && (target === widget || target.isAncestor(widget));
928
929
 
929
930
  return {
930
931
  pass: isPass,
931
932
  message: () =>
932
933
  `expected widget ${negationPrefix(isPass)}to contain ` +
933
- `${descendant === null ? "null" : describeWidget(descendant)}\n${describeWidget(widget)}`,
934
+ `${target === null ? "null" : describeWidget(target)}\n${describeWidget(widget)}`,
934
935
  };
935
936
  }
936
937
 
@@ -1037,9 +1038,9 @@ declare module "@vitest/expect" {
1037
1038
  toHaveFocus(): void;
1038
1039
  toHaveValue(expected?: number | string): void;
1039
1040
  toHaveRole(expected: Gtk.AccessibleRole): void;
1040
- toContainElement(descendant: Gtk.Widget | null): void;
1041
- toAppearBefore(other: Gtk.Widget): void;
1042
- toAppearAfter(other: Gtk.Widget): void;
1041
+ toContainElement(descendant: Gtk.Accessible | null): void;
1042
+ toAppearBefore(other: Gtk.Accessible): void;
1043
+ toAppearAfter(other: Gtk.Accessible): void;
1043
1044
  toContainAnyByRole(...args: RoleQueryArgs): void;
1044
1045
  toContainOneByRole(...args: RoleQueryArgs): void;
1045
1046
  toContainAnyByText(...args: TextQueryArgs): void;
package/src/queries.ts CHANGED
@@ -39,7 +39,7 @@ type BuiltinQueries = QueryFamilies<[container: Container]>;
39
39
  const roleQueries = nameQueryFamily(
40
40
  "Role",
41
41
  queryAllByRole,
42
- buildQueries<[role: Gtk.AccessibleRole, options?: ByRoleOptions]>(
42
+ buildQueries<[role: Gtk.AccessibleRole, options?: ByRoleOptions<Gtk.Widget>], Gtk.Widget>(
43
43
  queryAllByRole,
44
44
  (container, matches, role, options) =>
45
45
  multipleFoundError(container, { queryType: "role", role, options }, matches),
@@ -50,7 +50,7 @@ const roleQueries = nameQueryFamily(
50
50
  const labelTextQueries = nameQueryFamily(
51
51
  "LabelText",
52
52
  queryAllByLabelText,
53
- buildQueries<[text: Matcher, options?: MatcherOptions]>(
53
+ buildQueries<[text: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
54
54
  queryAllByLabelText,
55
55
  (container, matches, text) => multipleFoundError(container, { queryType: "labelText", text }, matches),
56
56
  (container, text) => notFoundError(container, { queryType: "labelText", text }),
@@ -60,7 +60,7 @@ const labelTextQueries = nameQueryFamily(
60
60
  const textQueries = nameQueryFamily(
61
61
  "Text",
62
62
  queryAllByText,
63
- buildQueries<[text: Matcher, options?: MatcherOptions]>(
63
+ buildQueries<[text: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
64
64
  queryAllByText,
65
65
  (container, matches, text) => multipleFoundError(container, { queryType: "text", text }, matches),
66
66
  (container, text) => notFoundError(container, { queryType: "text", text }),
@@ -70,7 +70,7 @@ const textQueries = nameQueryFamily(
70
70
  const nameQueries = nameQueryFamily(
71
71
  "Name",
72
72
  queryAllByName,
73
- buildQueries<[name: Matcher, options?: MatcherOptions]>(
73
+ buildQueries<[name: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
74
74
  queryAllByName,
75
75
  (container, matches, name) => multipleFoundError(container, { queryType: "name", name }, matches),
76
76
  (container, name) => notFoundError(container, { queryType: "name", name }),
@@ -80,7 +80,7 @@ const nameQueries = nameQueryFamily(
80
80
  const placeholderTextQueries = nameQueryFamily(
81
81
  "PlaceholderText",
82
82
  queryAllByPlaceholderText,
83
- buildQueries<[text: Matcher, options?: MatcherOptions]>(
83
+ buildQueries<[text: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
84
84
  queryAllByPlaceholderText,
85
85
  (container, matches, text) => multipleFoundError(container, { queryType: "placeholderText", text }, matches),
86
86
  (container, text) => notFoundError(container, { queryType: "placeholderText", text }),
@@ -90,7 +90,7 @@ const placeholderTextQueries = nameQueryFamily(
90
90
  const displayValueQueries = nameQueryFamily(
91
91
  "DisplayValue",
92
92
  queryAllByDisplayValue,
93
- buildQueries<[value: Matcher, options?: MatcherOptions]>(
93
+ buildQueries<[value: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
94
94
  queryAllByDisplayValue,
95
95
  (container, matches, value) => multipleFoundError(container, { queryType: "displayValue", value }, matches),
96
96
  (container, value) => notFoundError(container, { queryType: "displayValue", value }),
@@ -284,25 +284,27 @@ const hasMatchingAccessibleStates = (widget: Gtk.Widget, options: ByRoleOptions)
284
284
  hasMatchingDescriptionState(widget, options) &&
285
285
  hasMatchingValueState(widget, options);
286
286
 
287
- const isMatchingWidgetType = (widget: Gtk.Widget, options?: MatcherOptions): boolean =>
287
+ const isMatchingWidgetType = <T extends Gtk.Accessible>(
288
+ widget: Gtk.Widget,
289
+ options?: MatcherOptions<T>,
290
+ ): widget is Gtk.Widget & T =>
288
291
  options?.as === undefined || widget instanceof options.as;
289
292
 
290
- const hasMatchingByRoleOptions = (widget: Gtk.Widget, options?: ByRoleOptions): boolean => {
293
+ const hasMatchingAccessibleOptions = (widget: Gtk.Widget, options?: ByRoleOptions): boolean => {
291
294
  if (!options) {
292
295
  return true;
293
296
  }
294
297
 
295
298
  return (
296
- isMatchingWidgetType(widget, options) &&
297
299
  hasMatchingAccessibleName(widget, options) &&
298
300
  hasMatchingAccessibleStates(widget, options)
299
301
  );
300
302
  };
301
303
 
302
- function nameQueryFamily<Args extends unknown[]>(
304
+ function nameQueryFamily<Args extends unknown[], Element extends Gtk.Accessible>(
303
305
  suffix: string,
304
- queryAllBy: QueryAllBy<Args>,
305
- built: BuiltQueries<Args>,
306
+ queryAllBy: QueryAllBy<Args, Element>,
307
+ built: BuiltQueries<Args, Element>,
306
308
  ): Record<string, unknown> {
307
309
  const [queryBy, getAllBy, getBy, findAllBy, findBy] = built;
308
310
 
@@ -325,12 +327,12 @@ function nameQueryFamily<Args extends unknown[]>(
325
327
  * @param options Additional accessible name, state, and value constraints.
326
328
  * @returns Every matching widget, or an empty array when none match.
327
329
  */
328
- function queryAllByRole(
330
+ function queryAllByRole<T extends Gtk.Accessible = Gtk.Widget>(
329
331
  container: Container,
330
332
  role: Gtk.AccessibleRole,
331
- options?: ByRoleOptions,
332
- ): Gtk.Widget[] {
333
- return findAll(container, (widget) => {
333
+ options?: ByRoleOptions<T>,
334
+ ): T[] {
335
+ const matches = findAll(container, (widget) => {
334
336
  if (widget.getAccessibleRole() !== role) {
335
337
  return false;
336
338
  }
@@ -339,8 +341,14 @@ function queryAllByRole(
339
341
  return false;
340
342
  }
341
343
 
342
- return hasMatchingByRoleOptions(widget, options);
344
+ if (!isMatchingWidgetType(widget, options)) {
345
+ return false;
346
+ }
347
+
348
+ return hasMatchingAccessibleOptions(widget, options);
343
349
  });
350
+
351
+ return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
344
352
  }
345
353
 
346
354
  const collectMnemonicMatch = (
@@ -394,14 +402,18 @@ const collectLabelMatches = (
394
402
  * @param options Text matching options.
395
403
  * @returns Every matching widget, or an empty array when none match.
396
404
  */
397
- function queryAllByLabelText(container: Container, text: Matcher, options?: MatcherOptions): Gtk.Widget[] {
405
+ function queryAllByLabelText<T extends Gtk.Accessible = Gtk.Widget>(
406
+ container: Container,
407
+ text: Matcher,
408
+ options?: MatcherOptions<T>,
409
+ ): T[] {
398
410
  const results: Set<Gtk.Widget> = new Set();
399
411
 
400
412
  for (const widget of traverse(container)) {
401
413
  collectLabelMatches(results, widget, text, options);
402
414
  }
403
415
 
404
- return [...results].filter((widget) => isMatchingWidgetType(widget, options));
416
+ return [...results].filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
405
417
  }
406
418
 
407
419
  /**
@@ -411,12 +423,18 @@ function queryAllByLabelText(container: Container, text: Matcher, options?: Matc
411
423
  * @param options Text matching options.
412
424
  * @returns Every matching widget, or an empty array when none match.
413
425
  */
414
- function queryAllByText(container: Container, text: Matcher, options?: MatcherOptions): Gtk.Widget[] {
415
- return findAll(
426
+ function queryAllByText<T extends Gtk.Accessible = Gtk.Widget>(
427
+ container: Container,
428
+ text: Matcher,
429
+ options?: MatcherOptions<T>,
430
+ ): T[] {
431
+ const matches = findAll(
416
432
  container,
417
433
  (widget) =>
418
434
  isMatchingWidgetType(widget, options) && isTextMatch(getWidgetLabelText(widget), text, widget, options),
419
435
  );
436
+
437
+ return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
420
438
  }
421
439
 
422
440
  /**
@@ -426,11 +444,17 @@ function queryAllByText(container: Container, text: Matcher, options?: MatcherOp
426
444
  * @param options Text matching options.
427
445
  * @returns Every matching widget, or an empty array when none match.
428
446
  */
429
- function queryAllByName(container: Container, name: Matcher, options?: MatcherOptions): Gtk.Widget[] {
430
- return findAll(
447
+ function queryAllByName<T extends Gtk.Accessible = Gtk.Widget>(
448
+ container: Container,
449
+ name: Matcher,
450
+ options?: MatcherOptions<T>,
451
+ ): T[] {
452
+ const matches = findAll(
431
453
  container,
432
454
  (widget) => isMatchingWidgetType(widget, options) && isTextMatch(getWidgetName(widget), name, widget, options),
433
455
  );
456
+
457
+ return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
434
458
  }
435
459
 
436
460
  /**
@@ -440,17 +464,19 @@ function queryAllByName(container: Container, name: Matcher, options?: MatcherOp
440
464
  * @param options Text matching options.
441
465
  * @returns Every matching widget, or an empty array when none match.
442
466
  */
443
- function queryAllByPlaceholderText(
467
+ function queryAllByPlaceholderText<T extends Gtk.Accessible = Gtk.Widget>(
444
468
  container: Container,
445
469
  text: Matcher,
446
- options?: MatcherOptions,
447
- ): Gtk.Widget[] {
448
- return findAll(
470
+ options?: MatcherOptions<T>,
471
+ ): T[] {
472
+ const matches = findAll(
449
473
  container,
450
474
  (widget) =>
451
475
  isMatchingWidgetType(widget, options) &&
452
476
  isTextMatch(getWidgetPlaceholderText(widget), text, widget, options),
453
477
  );
478
+
479
+ return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
454
480
  }
455
481
 
456
482
  /**
@@ -460,13 +486,19 @@ function queryAllByPlaceholderText(
460
486
  * @param options Text matching options.
461
487
  * @returns Every matching widget, or an empty array when none match.
462
488
  */
463
- function queryAllByDisplayValue(container: Container, value: Matcher, options?: MatcherOptions): Gtk.Widget[] {
464
- return findAll(
489
+ function queryAllByDisplayValue<T extends Gtk.Accessible = Gtk.Widget>(
490
+ container: Container,
491
+ value: Matcher,
492
+ options?: MatcherOptions<T>,
493
+ ): T[] {
494
+ const matches = findAll(
465
495
  container,
466
496
  (widget) =>
467
497
  isMatchingWidgetType(widget, options) &&
468
498
  isTextMatch(getWidgetDisplayValue(widget), value, widget, options),
469
499
  );
500
+
501
+ return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
470
502
  }
471
503
 
472
504
  export {
@@ -57,18 +57,20 @@ const readObjectProperty = (widget: Gtk.Widget, property: string): string | null
57
57
  * @param options Text matching options.
58
58
  * @returns Every matching widget, or an empty array when none match.
59
59
  */
60
- function queryAllByObjectProperty(
60
+ function queryAllByObjectProperty<T extends Gtk.Accessible = Gtk.Widget>(
61
61
  property: string,
62
62
  container: Container,
63
63
  text: Matcher,
64
- options?: MatcherOptions,
65
- ): Gtk.Widget[] {
66
- return findAll(
64
+ options?: MatcherOptions<T>,
65
+ ): T[] {
66
+ const matches = findAll(
67
67
  container,
68
68
  (widget) =>
69
69
  isMatchingWidgetType(widget, options) &&
70
70
  isTextMatch(readObjectProperty(widget, property), text, widget, options),
71
71
  );
72
+
73
+ return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
72
74
  }
73
75
 
74
76
  /**
@@ -82,12 +84,12 @@ function queryAllByObjectProperty(
82
84
  * @returns The matching widget, or null when none match.
83
85
  * @throws When more than one widget matches.
84
86
  */
85
- function queryByObjectProperty(
87
+ function queryByObjectProperty<T extends Gtk.Accessible = Gtk.Widget>(
86
88
  property: string,
87
89
  container: Container,
88
90
  text: Matcher,
89
- options?: MatcherOptions,
90
- ): Gtk.Widget | null {
91
+ options?: MatcherOptions<T>,
92
+ ): T | null {
91
93
  const matches = queryAllByObjectProperty(property, container, text, options);
92
94
 
93
95
  if (matches.length > 1) {
package/src/render.tsx CHANGED
@@ -21,6 +21,7 @@ import { clearScreen, setScreen } from "./screen.js";
21
21
  import { captureScreen } from "./screenshot.js";
22
22
  import { type Container, roots, TOPLEVELS } from "./traversal.js";
23
23
  import { resetClipboard } from "./user-event/index.js";
24
+ import { requireWidget } from "./widget-target.js";
24
25
  import { findPresentedWindowFailure, findRenderedWindowFailure, mappedToplevels } from "./window-state.js";
25
26
  import { within } from "./within.js";
26
27
 
@@ -125,6 +126,10 @@ const resolveContainer = (container: RenderOptions["container"]): ResolvedContai
125
126
  return { containerInfo: container, window: null };
126
127
  }
127
128
 
129
+ if (container !== undefined) {
130
+ requireWidget(container);
131
+ }
132
+
128
133
  const window = createHarnessWindow();
129
134
  window.setDecorated(false);
130
135
 
@@ -3,6 +3,7 @@ import { sortStringsBy } from "@gtkx/utils";
3
3
  import { type Container, traverse } from "./traversal.js";
4
4
  import { getWidgetAccessibleName, getWidgetLevel } from "./widget-accessible-properties.js";
5
5
  import { getTypeTag } from "./widget-getters.js";
6
+ import { requireWidget } from "./widget-target.js";
6
7
 
7
8
  const ROLE_NAMES_BY_VALUE = enumNamesByValue(Gtk.AccessibleRole);
8
9
 
@@ -125,6 +126,7 @@ const logRoles = (container: Container): void => {
125
126
  *
126
127
  * @param widget The widget to read the level from.
127
128
  */
128
- const computeHeadingLevel = (widget: Gtk.Widget): number | undefined => getWidgetLevel(widget) ?? undefined;
129
+ const computeHeadingLevel = (widget: Gtk.Accessible): number | undefined =>
130
+ getWidgetLevel(requireWidget(widget)) ?? undefined;
129
131
 
130
132
  export { computeHeadingLevel, formatRole, formatRoleList, getRoles, prettyRoles, logRoles };