@gtkx/testing 2.0.0-beta.5 → 2.0.0-beta.7

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 (38) hide show
  1. package/README.md +43 -40
  2. package/dist/accessible-native.d.ts +1 -1
  3. package/dist/accessible-native.d.ts.map +1 -1
  4. package/dist/accessible-native.js +29 -8
  5. package/dist/accessible-native.js.map +1 -1
  6. package/dist/queries.d.ts.map +1 -1
  7. package/dist/queries.js +4 -3
  8. package/dist/queries.js.map +1 -1
  9. package/dist/screenshot.d.ts.map +1 -1
  10. package/dist/screenshot.js +22 -4
  11. package/dist/screenshot.js.map +1 -1
  12. package/dist/traversal.d.ts +1 -1
  13. package/dist/traversal.d.ts.map +1 -1
  14. package/dist/traversal.js +24 -6
  15. package/dist/traversal.js.map +1 -1
  16. package/dist/user-event/click.d.ts +17 -4
  17. package/dist/user-event/click.d.ts.map +1 -1
  18. package/dist/user-event/click.js +26 -7
  19. package/dist/user-event/click.js.map +1 -1
  20. package/dist/user-event/keyboard.d.ts.map +1 -1
  21. package/dist/user-event/keyboard.js +23 -2
  22. package/dist/user-event/keyboard.js.map +1 -1
  23. package/dist/user-event/pointer.d.ts.map +1 -1
  24. package/dist/user-event/pointer.js +9 -11
  25. package/dist/user-event/pointer.js.map +1 -1
  26. package/dist/widget-accessible-properties.d.ts +2 -1
  27. package/dist/widget-accessible-properties.d.ts.map +1 -1
  28. package/dist/widget-accessible-properties.js +23 -18
  29. package/dist/widget-accessible-properties.js.map +1 -1
  30. package/package.json +8 -6
  31. package/src/accessible-native.ts +45 -9
  32. package/src/queries.ts +5 -3
  33. package/src/screenshot.ts +45 -4
  34. package/src/traversal.ts +31 -7
  35. package/src/user-event/click.ts +35 -15
  36. package/src/user-event/keyboard.ts +32 -3
  37. package/src/user-event/pointer.ts +11 -15
  38. package/src/widget-accessible-properties.ts +34 -22
@@ -11,6 +11,7 @@ type PressPoint = { x: number; y: number };
11
11
  type ClickPhase = "pressed" | "released";
12
12
  type ClickSite = { gestures: Gtk.GestureClick[]; point: PressPoint };
13
13
  type ClickScope = { isClicked: boolean; isInternalAllowed: boolean };
14
+ type DirectClick = { sites: ClickSite[]; outcome: NativeClick | null };
14
15
 
15
16
  type ClickTarget = {
16
17
  widget: Gtk.Widget;
@@ -59,15 +60,6 @@ const emitGesture = (
59
60
  }
60
61
  };
61
62
 
62
- const emitClickPhase = (
63
- widget: Gtk.Widget,
64
- controllers: Gtk.GestureClick[],
65
- nPress: number,
66
- phase: ClickPhase,
67
- ): void => {
68
- emitGesture(getCenterPoint(widget), controllers, nPress, phase);
69
- };
70
-
71
63
  const emitSitePhase = (sites: ClickSite[], nPress: number, phase: ClickPhase): void => {
72
64
  for (const site of sites) {
73
65
  emitGesture(site.point, site.gestures, nPress, phase);
@@ -97,14 +89,23 @@ const clickGestures = (widget: Gtk.Widget): Gtk.GestureClick[] =>
97
89
  const getAuthoredClickGestures = (widget: Gtk.Widget): Gtk.GestureClick[] =>
98
90
  clickGestures(widget).filter((gesture) => hasClickHandlers(gesture));
99
91
 
100
- const isClaimingTarget = (widget: Gtk.Widget): boolean =>
101
- widget instanceof Gtk.Button || containerFor(widget) !== null;
102
-
103
92
  const gestureTargetFor = (widget: Gtk.Widget, isInternalAllowed: boolean): ClickTarget | null => {
104
93
  const gestures = clickGestures(widget);
105
94
 
106
- if (isClaimingTarget(widget)) {
107
- return { widget, container: containerFor(widget), gestures, isClaiming: true, native: null };
95
+ if (widget instanceof Gtk.Button) {
96
+ return { widget, container: null, gestures, isClaiming: true, native: null };
97
+ }
98
+
99
+ const container = containerFor(widget);
100
+
101
+ if (container !== null) {
102
+ return {
103
+ widget,
104
+ container,
105
+ gestures: gestures.filter((gesture) => hasClickHandlers(gesture)),
106
+ isClaiming: true,
107
+ native: null,
108
+ };
108
109
  }
109
110
 
110
111
  const authored = gestures.filter((gesture) => hasClickHandlers(gesture));
@@ -223,6 +224,24 @@ const applyClickOutcome = (target: ClickTarget, nPress: number): void => {
223
224
  target.native(target.widget, nPress);
224
225
  };
225
226
 
227
+ const applyContainerClick: NativeClick = (widget, nPress) => {
228
+ const container = containerFor(widget);
229
+
230
+ if (container !== null) {
231
+ applyContainerOutcome({ widget, container, gestures: [], isClaiming: true, native: null }, nPress);
232
+ }
233
+ };
234
+
235
+ const directClickFor = (widget: Gtk.Widget): DirectClick => {
236
+ const native = nativeClickFor(widget, true);
237
+ const container = native === null ? containerFor(widget) : null;
238
+ const gestures =
239
+ native !== null || container !== null ? getAuthoredClickGestures(widget) : clickGestures(widget);
240
+ const target: ClickTarget = { widget, container, gestures, isClaiming: true, native };
241
+
242
+ return { sites: targetSites(widget, target), outcome: native ?? (container === null ? null : applyContainerClick) };
243
+ };
244
+
226
245
  const applyClickOutcomes = (targets: ClickTarget[], nPress: number): void => {
227
246
  for (const target of targets) {
228
247
  applyClickOutcome(target, nPress);
@@ -266,4 +285,5 @@ const dblClick = (widget: Gtk.Widget): Promise<void> => deliverClick(widget, 2);
266
285
  /** Delivers a triple-click gesture. */
267
286
  const tripleClick = (widget: Gtk.Widget): Promise<void> => deliverClick(widget, 3);
268
287
 
269
- export { clickGestures, emitClickPhase, getAuthoredClickGestures, click, dblClick, tripleClick };
288
+ export { directClickFor, emitSitePhase, click, dblClick, tripleClick };
289
+ export type { DirectClick };
@@ -18,6 +18,7 @@ type KeyAction = { keyval: number; isPress: boolean };
18
18
  type ParseStep = { actions: KeyAction[]; next: number };
19
19
  type KeyStopController = Gtk.EventControllerKey | Gtk.ShortcutController;
20
20
  type KeyStop = { widget: Gtk.Widget; controller: KeyStopController };
21
+ type KeyTarget = { root: Gtk.Root | null; focus: Gtk.Widget | null };
21
22
 
22
23
  const KEY_MAP: Record<string, number> = {
23
24
  Enter: Gdk.KEY_Return,
@@ -366,9 +367,37 @@ const didStopHandlePress = (stop: KeyStop, keyval: number, modifiers: Gdk.Modifi
366
367
  return controller.emit("key-pressed", keyval, 0, modifiers);
367
368
  };
368
369
 
369
- const didHandlePress = (widget: Gtk.Widget, keyval: number, modifiers: Gdk.ModifierType): boolean =>
370
- didDispatchApplicationAccels(widget, keyval, modifiers) ||
371
- keyPropagationChain(widget).some((stop) => didStopHandlePress(stop, keyval, modifiers));
370
+ const keyTargetFor = (widget: Gtk.Widget): KeyTarget => {
371
+ const root = widget.getRoot();
372
+
373
+ return { root, focus: root instanceof Gtk.Window ? root.getFocus() : null };
374
+ };
375
+
376
+ const isKeyTargetInvalidated = (widget: Gtk.Widget, target: KeyTarget): boolean => {
377
+ const current = keyTargetFor(widget);
378
+
379
+ return current.root !== target.root || current.focus !== target.focus || !widget.getMapped();
380
+ };
381
+
382
+ const didHandlePress = (widget: Gtk.Widget, keyval: number, modifiers: Gdk.ModifierType): boolean => {
383
+ const target = keyTargetFor(widget);
384
+
385
+ if (didDispatchApplicationAccels(widget, keyval, modifiers)) {
386
+ return true;
387
+ }
388
+
389
+ if (isKeyTargetInvalidated(widget, target)) {
390
+ return true;
391
+ }
392
+
393
+ for (const stop of keyPropagationChain(widget)) {
394
+ if (didStopHandlePress(stop, keyval, modifiers) || isKeyTargetInvalidated(widget, target)) {
395
+ return true;
396
+ }
397
+ }
398
+
399
+ return false;
400
+ };
372
401
 
373
402
  const emitKeyReleased = (widget: Gtk.Widget, keyval: number, modifiers: Gdk.ModifierType): void => {
374
403
  for (const stop of keyPropagationChain(widget)) {
@@ -1,16 +1,15 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
- import type { NativeClick } from "./native-click.js";
3
2
  import type { UserEventState } from "./state.js";
4
- import { clickGestures, emitClickPhase, getAuthoredClickGestures } from "./click.js";
3
+ import { type DirectClick, directClickFor, emitSitePhase } from "./click.js";
5
4
  import { wrapEvent } from "./event-wrapper.js";
6
- import { isClickTransparent, nativeClickFor } from "./native-click.js";
5
+ import { isClickTransparent } from "./native-click.js";
7
6
 
8
7
  /**
9
8
  * A pointer action token: a full click (`click`, `[MouseLeft]`), a button press (`down`,
10
9
  * `[MouseLeft>]`), or a button release (`up`, `[/MouseLeft]`).
11
10
  */
12
11
  type PointerInput = "click" | "down" | "up" | "[MouseLeft]" | "[MouseLeft>]" | "[/MouseLeft]";
13
- type PointerTarget = { widget: Gtk.Widget; gestures: Gtk.GestureClick[]; native: NativeClick | null };
12
+ type PointerTarget = { widget: Gtk.Widget; click: DirectClick };
14
13
 
15
14
  const PRESS_INPUTS: Set<PointerInput> = new Set(["[MouseLeft>]", "down"]);
16
15
  const RELEASE_INPUTS: Set<PointerInput> = new Set(["[/MouseLeft]", "up"]);
@@ -32,26 +31,23 @@ const pointerWidgetFor = (widget: Gtk.Widget): Gtk.Widget => {
32
31
  return current;
33
32
  };
34
33
 
35
- const pointerGesturesFor = (widget: Gtk.Widget, native: NativeClick | null): Gtk.GestureClick[] =>
36
- native === null ? clickGestures(widget) : getAuthoredClickGestures(widget);
37
-
38
34
  const pointerTargetFor = (widget: Gtk.Widget): PointerTarget => {
39
35
  const target = pointerWidgetFor(widget);
40
- const native = nativeClickFor(target, true);
41
36
 
42
- return { widget: target, gestures: pointerGesturesFor(target, native), native };
37
+ return { widget: target, click: directClickFor(target) };
43
38
  };
44
39
 
45
- const pressPointer = ({ widget, gestures }: PointerTarget): void => {
46
- emitClickPhase(widget, gestures, 1, "pressed");
40
+ const pressPointer = ({ click }: PointerTarget): void => {
41
+ emitSitePhase(click.sites, 1, "pressed");
47
42
  };
48
43
 
49
- const releasePointer = ({ widget, gestures, native }: PointerTarget): void => {
50
- emitClickPhase(widget, gestures, 1, "released");
51
- native?.(widget, 1);
44
+ const releasePointer = ({ widget, click }: PointerTarget): void => {
45
+ emitSitePhase(click.sites, 1, "released");
46
+ click.outcome?.(widget, 1);
52
47
  };
53
48
 
54
- const isPointerPressable = (target: PointerTarget): boolean => target.native !== null || target.gestures.length > 0;
49
+ const isPointerPressable = (target: PointerTarget): boolean =>
50
+ target.click.outcome !== null || target.click.sites.some((site) => site.gestures.length > 0);
55
51
 
56
52
  const applyPointerInput = (widget: Gtk.Widget, state: UserEventState, input: PointerInput): void => {
57
53
  const target = pointerTargetFor(widget);
@@ -401,22 +401,22 @@ const isSelfLabelling = (widget: Gtk.Widget, targets: Gtk.Widget[]): boolean =>
401
401
  return targets.every((target) => own.has(target));
402
402
  };
403
403
 
404
- const getWidgetLabelledByText = (widget: Gtk.Widget): string | null => {
405
- const targets = readAccessibleWidgets(widget, Gtk.AccessibleRelation.LABELLED_BY);
406
-
407
- if (targets === null) {
408
- return null;
409
- }
410
-
404
+ const labelledByText = (targets: Gtk.Widget[]): string | null => {
411
405
  const texts = collectAccessibleNames(targets);
412
406
 
413
407
  return texts.length > 0 ? texts.join(" ") : null;
414
408
  };
415
409
 
410
+ const getWidgetLabelledByText = (widget: Gtk.Widget): string | null => {
411
+ const targets = readAccessibleWidgets(widget, Gtk.AccessibleRelation.LABELLED_BY);
412
+
413
+ return targets === null ? null : labelledByText(targets);
414
+ };
415
+
416
416
  const getWidgetExternalLabelText = (widget: Gtk.Widget): string | null => {
417
417
  const targets = readAccessibleWidgets(widget, Gtk.AccessibleRelation.LABELLED_BY);
418
418
 
419
- return targets === null || isSelfLabelling(widget, targets) ? null : getWidgetLabelledByText(widget);
419
+ return targets === null || isSelfLabelling(widget, targets) ? null : labelledByText(targets);
420
420
  };
421
421
 
422
422
  const isTooltipUsedAsName = (widget: Gtk.Widget): boolean =>
@@ -441,28 +441,40 @@ const getWidgetDescribedByText = (widget: Gtk.Widget): string | null => {
441
441
  return isTooltipUsedAsName(widget) ? null : callStringGetter(widget, "getTooltipText");
442
442
  };
443
443
 
444
+ const isHiddenWithin = (widget: Gtk.Widget, hiddenWidgets: Map<Gtk.Widget, boolean>): boolean => {
445
+ const cached = hiddenWidgets.get(widget);
446
+
447
+ if (cached !== undefined) {
448
+ return cached;
449
+ }
450
+
451
+ const parent = widget.getParent();
452
+
453
+ const isHidden =
454
+ readAccessibleBoolean(widget, Gtk.AccessibleState.HIDDEN) === true ||
455
+ (parent !== null && isHiddenWithin(parent, hiddenWidgets));
456
+
457
+ hiddenWidgets.set(widget, isHidden);
458
+
459
+ return isHidden;
460
+ };
461
+
462
+ const buildInaccessibilityCheck = (): ((target: Gtk.Accessible) => boolean) => {
463
+ const hiddenWidgets: Map<Gtk.Widget, boolean> = new Map();
464
+
465
+ return (target) => isHiddenWithin(requireWidget(target), hiddenWidgets);
466
+ };
467
+
444
468
  /**
445
469
  * Returns whether a widget is excluded from the accessibility tree, because it or one of its
446
470
  * ancestors is marked hidden.
447
471
  *
448
472
  * @param widget The widget to test.
449
473
  */
450
- const isInaccessible = (target: Gtk.Accessible): boolean => {
451
- const widget = requireWidget(target);
452
- let current: Gtk.Widget | null = widget;
453
-
454
- while (current) {
455
- if (readAccessibleBoolean(current, Gtk.AccessibleState.HIDDEN) === true) {
456
- return true;
457
- }
458
-
459
- current = current.getParent();
460
- }
461
-
462
- return false;
463
- };
474
+ const isInaccessible = (target: Gtk.Accessible): boolean => buildInaccessibilityCheck()(target);
464
475
 
465
476
  export {
477
+ buildInaccessibilityCheck,
466
478
  namingLabelText,
467
479
  getWidgetText,
468
480
  getWidgetTextContent,