@gtkx/testing 1.0.0-rc.4 → 1.0.0

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/README.md +5 -5
  2. package/dist/accessible-native.d.ts +16 -0
  3. package/dist/accessible-native.d.ts.map +1 -0
  4. package/dist/accessible-native.js +206 -0
  5. package/dist/accessible-native.js.map +1 -0
  6. package/dist/build-queries.d.ts +27 -10
  7. package/dist/build-queries.d.ts.map +1 -1
  8. package/dist/build-queries.js +22 -24
  9. package/dist/build-queries.js.map +1 -1
  10. package/dist/cleanup-registry.d.ts +1 -1
  11. package/dist/cleanup-registry.d.ts.map +1 -1
  12. package/dist/cleanup-registry.js.map +1 -1
  13. package/dist/errors.d.ts +10 -1
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +10 -1
  16. package/dist/errors.js.map +1 -1
  17. package/dist/fire-event.d.ts +35 -6
  18. package/dist/fire-event.d.ts.map +1 -1
  19. package/dist/fire-event.js +23 -7
  20. package/dist/fire-event.js.map +1 -1
  21. package/dist/frame-sync.d.ts +2 -1
  22. package/dist/frame-sync.d.ts.map +1 -1
  23. package/dist/frame-sync.js +15 -5
  24. package/dist/frame-sync.js.map +1 -1
  25. package/dist/harness-window.d.ts +4 -0
  26. package/dist/harness-window.d.ts.map +1 -0
  27. package/dist/harness-window.js +6 -0
  28. package/dist/harness-window.js.map +1 -0
  29. package/dist/index.d.ts +14 -7
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +9 -5
  32. package/dist/index.js.map +1 -1
  33. package/dist/internal.d.ts +4 -0
  34. package/dist/internal.d.ts.map +1 -0
  35. package/dist/internal.js +4 -0
  36. package/dist/internal.js.map +1 -0
  37. package/dist/matchers.d.ts +88 -46
  38. package/dist/matchers.d.ts.map +1 -1
  39. package/dist/matchers.js +156 -18
  40. package/dist/matchers.js.map +1 -1
  41. package/dist/pretty-format.d.ts +2 -0
  42. package/dist/pretty-format.d.ts.map +1 -0
  43. package/dist/pretty-format.js +2 -0
  44. package/dist/pretty-format.js.map +1 -0
  45. package/dist/pretty-widget.d.ts +5 -2
  46. package/dist/pretty-widget.d.ts.map +1 -1
  47. package/dist/pretty-widget.js +73 -56
  48. package/dist/pretty-widget.js.map +1 -1
  49. package/dist/production.d.ts +8 -0
  50. package/dist/production.d.ts.map +1 -0
  51. package/dist/production.js +34 -0
  52. package/dist/production.js.map +1 -0
  53. package/dist/queries.d.ts +98 -56
  54. package/dist/queries.d.ts.map +1 -1
  55. package/dist/queries.js +123 -83
  56. package/dist/queries.js.map +1 -1
  57. package/dist/query-helpers.d.ts +44 -0
  58. package/dist/query-helpers.d.ts.map +1 -0
  59. package/dist/query-helpers.js +57 -0
  60. package/dist/query-helpers.js.map +1 -0
  61. package/dist/render-children.d.ts +9 -0
  62. package/dist/render-children.d.ts.map +1 -0
  63. package/dist/render-children.js +11 -0
  64. package/dist/render-children.js.map +1 -0
  65. package/dist/render.d.ts.map +1 -1
  66. package/dist/render.js +11 -9
  67. package/dist/render.js.map +1 -1
  68. package/dist/role-helpers.d.ts +12 -3
  69. package/dist/role-helpers.d.ts.map +1 -1
  70. package/dist/role-helpers.js +13 -4
  71. package/dist/role-helpers.js.map +1 -1
  72. package/dist/role-naming.d.ts +5 -0
  73. package/dist/role-naming.d.ts.map +1 -0
  74. package/dist/role-naming.js +96 -0
  75. package/dist/role-naming.js.map +1 -0
  76. package/dist/screenshot.d.ts +12 -20
  77. package/dist/screenshot.d.ts.map +1 -1
  78. package/dist/screenshot.js +183 -91
  79. package/dist/screenshot.js.map +1 -1
  80. package/dist/traversal.d.ts +4 -1
  81. package/dist/traversal.d.ts.map +1 -1
  82. package/dist/traversal.js +25 -6
  83. package/dist/traversal.js.map +1 -1
  84. package/dist/types.d.ts +26 -14
  85. package/dist/types.d.ts.map +1 -1
  86. package/dist/types.js.map +1 -1
  87. package/dist/user-event/adjustment.d.ts +3 -0
  88. package/dist/user-event/adjustment.d.ts.map +1 -1
  89. package/dist/user-event/adjustment.js +23 -2
  90. package/dist/user-event/adjustment.js.map +1 -1
  91. package/dist/user-event/click.d.ts +43 -8
  92. package/dist/user-event/click.d.ts.map +1 -1
  93. package/dist/user-event/click.js +175 -44
  94. package/dist/user-event/click.js.map +1 -1
  95. package/dist/user-event/column-header-click.d.ts +4 -0
  96. package/dist/user-event/column-header-click.d.ts.map +1 -0
  97. package/dist/user-event/column-header-click.js +65 -0
  98. package/dist/user-event/column-header-click.js.map +1 -0
  99. package/dist/user-event/event-wrapper.d.ts.map +1 -1
  100. package/dist/user-event/event-wrapper.js +2 -1
  101. package/dist/user-event/event-wrapper.js.map +1 -1
  102. package/dist/user-event/index.d.ts +51 -10
  103. package/dist/user-event/index.d.ts.map +1 -1
  104. package/dist/user-event/index.js +43 -26
  105. package/dist/user-event/index.js.map +1 -1
  106. package/dist/user-event/indexed-children.d.ts +10 -0
  107. package/dist/user-event/indexed-children.d.ts.map +1 -0
  108. package/dist/user-event/indexed-children.js +50 -0
  109. package/dist/user-event/indexed-children.js.map +1 -0
  110. package/dist/user-event/keyboard.d.ts.map +1 -1
  111. package/dist/user-event/keyboard.js +33 -4
  112. package/dist/user-event/keyboard.js.map +1 -1
  113. package/dist/user-event/list-row-click.d.ts +4 -0
  114. package/dist/user-event/list-row-click.d.ts.map +1 -0
  115. package/dist/user-event/list-row-click.js +39 -0
  116. package/dist/user-event/list-row-click.js.map +1 -0
  117. package/dist/user-event/native-click.d.ts +6 -0
  118. package/dist/user-event/native-click.d.ts.map +1 -0
  119. package/dist/user-event/native-click.js +32 -0
  120. package/dist/user-event/native-click.js.map +1 -0
  121. package/dist/user-event/pointer.d.ts +1 -1
  122. package/dist/user-event/pointer.d.ts.map +1 -1
  123. package/dist/user-event/pointer.js +33 -9
  124. package/dist/user-event/pointer.js.map +1 -1
  125. package/dist/user-event/selection.d.ts +11 -8
  126. package/dist/user-event/selection.d.ts.map +1 -1
  127. package/dist/user-event/selection.js +49 -38
  128. package/dist/user-event/selection.js.map +1 -1
  129. package/dist/user-event/text.d.ts +6 -2
  130. package/dist/user-event/text.d.ts.map +1 -1
  131. package/dist/user-event/text.js +68 -18
  132. package/dist/user-event/text.js.map +1 -1
  133. package/dist/widget-accessible-properties.d.ts +14 -26
  134. package/dist/widget-accessible-properties.d.ts.map +1 -1
  135. package/dist/widget-accessible-properties.js +135 -190
  136. package/dist/widget-accessible-properties.js.map +1 -1
  137. package/dist/widget-getters.d.ts +8 -0
  138. package/dist/widget-getters.d.ts.map +1 -0
  139. package/dist/widget-getters.js +19 -0
  140. package/dist/widget-getters.js.map +1 -0
  141. package/dist/within.d.ts +3 -2
  142. package/dist/within.d.ts.map +1 -1
  143. package/dist/within.js +3 -2
  144. package/dist/within.js.map +1 -1
  145. package/package.json +12 -7
  146. package/src/accessible-native.ts +333 -0
  147. package/src/build-queries.ts +47 -41
  148. package/src/cleanup-registry.ts +1 -1
  149. package/src/errors.ts +12 -1
  150. package/src/fire-event.ts +61 -8
  151. package/src/frame-sync.ts +21 -5
  152. package/src/harness-window.ts +9 -0
  153. package/src/index.ts +18 -11
  154. package/src/internal.ts +11 -0
  155. package/src/matchers.ts +428 -70
  156. package/src/pretty-format.ts +1 -0
  157. package/src/pretty-widget.ts +97 -70
  158. package/src/production.ts +52 -0
  159. package/src/queries.ts +127 -89
  160. package/src/query-helpers.ts +97 -0
  161. package/src/render-children.ts +25 -0
  162. package/src/render.tsx +16 -11
  163. package/src/role-helpers.ts +14 -4
  164. package/src/role-naming.ts +99 -0
  165. package/src/screenshot.ts +272 -103
  166. package/src/traversal.ts +44 -6
  167. package/src/types.ts +28 -16
  168. package/src/user-event/adjustment.ts +29 -2
  169. package/src/user-event/click.ts +247 -52
  170. package/src/user-event/column-header-click.ts +90 -0
  171. package/src/user-event/event-wrapper.ts +3 -1
  172. package/src/user-event/index.ts +100 -37
  173. package/src/user-event/indexed-children.ts +75 -0
  174. package/src/user-event/keyboard.ts +52 -4
  175. package/src/user-event/list-row-click.ts +57 -0
  176. package/src/user-event/native-click.ts +46 -0
  177. package/src/user-event/pointer.ts +48 -9
  178. package/src/user-event/selection.ts +67 -41
  179. package/src/user-event/text.ts +92 -21
  180. package/src/widget-accessible-properties.ts +183 -230
  181. package/src/widget-getters.ts +32 -0
  182. package/src/within.ts +3 -2
@@ -1,11 +1,23 @@
1
1
  import * as Gtk from "@gtkx/gi/gtk";
2
2
  import { formatRoleList } from "../role-helpers.js";
3
+ import { getCallableMethod } from "../widget-getters.js";
3
4
  import { wrapEvent } from "./event-wrapper.js";
5
+ import {
6
+ getChildAtIndex,
7
+ hasIndexedChildren,
8
+ isChildSelected,
9
+ selectContainerChild,
10
+ unselectContainerChild,
11
+ } from "./indexed-children.js";
4
12
 
5
13
  type CollectionWidget = Gtk.ListView | Gtk.GridView | Gtk.ColumnView;
6
- type ListBoxRowAction = (listBox: Gtk.ListBox, row: Gtk.ListBoxRow) => void;
14
+ type IndexedChildAction = (container: Gtk.Widget, child: Gtk.Widget) => void;
7
15
 
8
- const SELECTABLE_ROLES: Set<Gtk.AccessibleRole> = new Set([Gtk.AccessibleRole.COMBO_BOX, Gtk.AccessibleRole.LIST]);
16
+ const SELECTABLE_ROLES: Set<Gtk.AccessibleRole> = new Set([
17
+ Gtk.AccessibleRole.COMBO_BOX,
18
+ Gtk.AccessibleRole.GRID,
19
+ Gtk.AccessibleRole.LIST,
20
+ ]);
9
21
 
10
22
  const isSelectable = (widget: Gtk.Widget): boolean => {
11
23
  return SELECTABLE_ROLES.has(widget.getAccessibleRole());
@@ -44,42 +56,40 @@ const selectListViewItems = (
44
56
  const isListView = (widget: Gtk.Widget): widget is CollectionWidget =>
45
57
  widget instanceof Gtk.ListView || widget instanceof Gtk.GridView || widget instanceof Gtk.ColumnView;
46
58
 
47
- const selectComboBoxOption = (widget: Gtk.Widget, valueArray: number[]): void => {
48
- if (valueArray.length > 1) {
49
- throw new Error("Cannot select multiple options: ComboBox only supports single selection");
50
- }
59
+ const getIndexSelector = (widget: Gtk.Widget): ((position: number) => void) | null =>
60
+ getCallableMethod<[number], unknown>(widget, "setSelected");
51
61
 
52
- const [selection] = valueArray;
62
+ const selectDropDownOption = (widget: Gtk.Widget, valueArray: number[]): void => {
63
+ const setSelected = getIndexSelector(widget);
53
64
 
54
- if (selection === undefined) {
55
- return;
65
+ if (!setSelected) {
66
+ throw new TypeError("Cannot select options: the COMBO_BOX role needs a widget exposing setSelected");
56
67
  }
57
68
 
58
- if (widget instanceof Gtk.DropDown) {
59
- widget.setSelected(selection);
60
- } else if (widget instanceof Gtk.ComboBox) {
61
- widget.setActive(selection);
69
+ if (valueArray.length > 1) {
70
+ throw new Error("Cannot select multiple options: a drop-down only supports single selection");
62
71
  }
63
- };
64
72
 
65
- const selectListBoxRow = (listBox: Gtk.ListBox, row: Gtk.ListBoxRow): void => {
66
- listBox.selectRow(row);
67
- row.activate();
73
+ const [selection] = valueArray;
74
+
75
+ if (selection !== undefined) {
76
+ setSelected(selection);
77
+ }
68
78
  };
69
79
 
70
- const unselectListBoxRow = (listBox: Gtk.ListBox, row: Gtk.ListBoxRow): void => {
71
- listBox.unselectRow(row);
80
+ const unselectChildRow = (container: Gtk.Widget, child: Gtk.Widget): void => {
81
+ if (isChildSelected(child)) {
82
+ unselectContainerChild(container, child);
83
+ }
72
84
  };
73
85
 
74
- const applyListBoxRows = (listBox: Gtk.ListBox, valueArray: number[], apply: ListBoxRowAction): void => {
86
+ const applyIndexedChildren = (widget: Gtk.Widget, valueArray: number[], apply: IndexedChildAction): void => {
75
87
  for (const value of valueArray) {
76
- const row = listBox.getRowAtIndex(value);
88
+ const child = getChildAtIndex(widget, value);
77
89
 
78
- if (!row) {
79
- continue;
90
+ if (child) {
91
+ apply(widget, child);
80
92
  }
81
-
82
- apply(listBox, row);
83
93
  }
84
94
  };
85
95
 
@@ -102,18 +112,29 @@ const selectInListView = (widget: CollectionWidget, valueArray: number[]): void
102
112
  selectListViewItems(selectionModel, valueArray, !isMultiSelection);
103
113
  };
104
114
 
105
- const selectByRole = (widget: Gtk.Widget, valueArray: number[]): void => {
115
+ const requireSelectableRole = (widget: Gtk.Widget, verb: string): void => {
106
116
  if (!isSelectable(widget)) {
107
- throw new Error(`Cannot select options: expected selectable widget (${formatRoleList(SELECTABLE_ROLES)})`);
117
+ throw new Error(
118
+ `Cannot ${verb} options: expected selectable widget (${formatRoleList(SELECTABLE_ROLES)})`,
119
+ );
108
120
  }
121
+ };
109
122
 
123
+ const selectByRole = (widget: Gtk.Widget, valueArray: number[]): void => {
124
+ requireSelectableRole(widget, "select");
110
125
  const role = widget.getAccessibleRole();
111
126
 
112
127
  if (role === Gtk.AccessibleRole.COMBO_BOX) {
113
- selectComboBoxOption(widget, valueArray);
114
- } else if (widget instanceof Gtk.ListBox) {
115
- applyListBoxRows(widget, valueArray, selectListBoxRow);
128
+ selectDropDownOption(widget, valueArray);
129
+
130
+ return;
131
+ }
132
+
133
+ if (!hasIndexedChildren(widget)) {
134
+ throw new TypeError("Cannot select options: the widget exposes no children to select by index");
116
135
  }
136
+
137
+ applyIndexedChildren(widget, valueArray, selectContainerChild);
117
138
  };
118
139
 
119
140
  const runSelectionEvent = (
@@ -136,11 +157,13 @@ const runSelectionEvent = (
136
157
 
137
158
  /**
138
159
  * Selects the items at the given positions through a list, grid, or column view's selection model,
139
- * by setting the active item of a drop-down or combo box, or by selecting list box rows. An empty
140
- * array clears a view's selection.
160
+ * by setting the selected position of a widget with the COMBO_BOX role, or by selecting the indexed
161
+ * children of a list box or flow box through their container. An empty array clears a view's
162
+ * selection. The children are never activated, so no row-activated handler runs.
141
163
  *
142
- * @throws When a view has no selection model, when a combo box is given more than one position, or
143
- * when the widget is neither a view nor something with the combo box or list role.
164
+ * @throws When a view has no selection model, when a COMBO_BOX-role widget is given more than one
165
+ * position or exposes no `setSelected`, or when the widget's role is none of COMBO_BOX, GRID, or
166
+ * LIST.
144
167
  */
145
168
  const selectOptions = (widget: Gtk.Widget, values: number | number[]): Promise<void> =>
146
169
  runSelectionEvent(widget, values, selectInListView, selectByRole);
@@ -154,19 +177,22 @@ const deselectInListView = (widget: Gtk.ListView | Gtk.GridView | Gtk.ColumnView
154
177
  };
155
178
 
156
179
  const deselectByRole = (widget: Gtk.Widget, valueArray: number[]): void => {
157
- if (!(widget instanceof Gtk.ListBox)) {
158
- throw new TypeError("Cannot deselect options: only ListBox supports deselection");
180
+ requireSelectableRole(widget, "deselect");
181
+
182
+ if (!hasIndexedChildren(widget)) {
183
+ throw new TypeError("Cannot deselect options: the widget exposes no children to deselect by index");
159
184
  }
160
185
 
161
- applyListBoxRows(widget, valueArray, unselectListBoxRow);
186
+ applyIndexedChildren(widget, valueArray, unselectChildRow);
162
187
  };
163
188
 
164
189
  /**
165
- * Unselects the items at the given positions in a list, grid, or column view, or the rows at those
166
- * indices in a Gtk.ListBox.
190
+ * Unselects the items at the given positions in a list, grid, or column view, or unselects the
191
+ * indexed children of a list box or flow box through their container. A child that is not selected
192
+ * is left alone, and no child is activated.
167
193
  *
168
- * @throws When a view has no selection model, or when the widget is neither one of those views nor
169
- * a Gtk.ListBox.
194
+ * @throws When a view has no selection model, when the widget's role is none of COMBO_BOX, GRID, or
195
+ * LIST, or when it exposes no indexed children.
170
196
  */
171
197
  const deselectOptions = (widget: Gtk.Widget, values: number | number[]): Promise<void> =>
172
198
  runSelectionEvent(widget, values, deselectInListView, deselectByRole);
@@ -1,10 +1,17 @@
1
1
  import * as Gdk from "@gtkx/gi/gdk";
2
2
  import * as GObject from "@gtkx/gi/gobject";
3
3
  import * as Gtk from "@gtkx/gi/gtk";
4
- import { EDITABLE_ROLES, type EditableTarget, getEditableDelegate, isEditable } from "../editable.js";
4
+ import { EDITABLE_ROLES, type EditableTarget, getEditableDelegate, isEditable, readEditableText } from "../editable.js";
5
5
  import { formatRoleList } from "../role-helpers.js";
6
+ import { getWidgetSelection } from "../widget-accessible-properties.js";
7
+ import { callBooleanGetter } from "../widget-getters.js";
6
8
  import { wrapEvent } from "./event-wrapper.js";
7
9
 
10
+ type SignalWatchTarget = {
11
+ on(signal: string, handler: (...args: unknown[]) => unknown, isAfter?: boolean): unknown;
12
+ off(signal: string, handler: (...args: unknown[]) => unknown): unknown;
13
+ };
14
+
8
15
  /** Options for `userEvent.type`. */
9
16
  type TypeOptions = {
10
17
  /** Focus the widget before typing; defaults to true. */
@@ -16,6 +23,7 @@ type TypeOptions = {
16
23
  };
17
24
 
18
25
  const EDITABLE_REQUIRED = `expected editable widget (${formatRoleList(EDITABLE_ROLES)})`;
26
+ const CLEAR_REFUSED = "Cannot clear element: the widget refuses to delete part of its text";
19
27
 
20
28
  const insertEditableText = (widget: EditableTarget, text: string): void => {
21
29
  if (widget instanceof Gtk.TextView) {
@@ -37,18 +45,7 @@ const insertEditableText = (widget: EditableTarget, text: string): void => {
37
45
  widget.setPosition(newPosition);
38
46
  };
39
47
 
40
- const readSelection = (widget: EditableTarget): string => {
41
- if (widget instanceof Gtk.TextView) {
42
- const buffer = widget.getBuffer();
43
- const [hasSelection, start, end] = buffer.getSelectionBounds();
44
-
45
- return hasSelection ? buffer.getText(start, end, true) : "";
46
- }
47
-
48
- const [hasSelection, start, end] = widget.getSelectionBounds();
49
-
50
- return hasSelection ? widget.getChars(start, end) : "";
51
- };
48
+ const readSelection = (widget: EditableTarget): string => getWidgetSelection(widget) ?? "";
52
49
 
53
50
  const deleteSelection = (widget: EditableTarget): void => {
54
51
  if (widget instanceof Gtk.TextView) {
@@ -60,14 +57,15 @@ const deleteSelection = (widget: EditableTarget): void => {
60
57
  widget.deleteSelection();
61
58
  };
62
59
 
63
- const setEditableText = (widget: EditableTarget, text: string): void => {
60
+ const deleteAllText = (widget: EditableTarget): void => {
64
61
  if (widget instanceof Gtk.TextView) {
65
- widget.getBuffer().setText(text, text.length);
62
+ const buffer = widget.getBuffer();
63
+ buffer.deleteInteractive(buffer.getStartIter(), buffer.getEndIter(), widget.getEditable());
66
64
 
67
65
  return;
68
66
  }
69
67
 
70
- widget.setText(text);
68
+ widget.deleteText(0, -1);
71
69
  };
72
70
 
73
71
  const applyTextViewSelection = (widget: Gtk.TextView, start: number, end: number): void => {
@@ -147,15 +145,88 @@ const type = (widget: Gtk.Widget, text: string, options?: TypeOptions): Promise<
147
145
  insertEditableText(editable, text);
148
146
  });
149
147
 
148
+ const isWidgetEditable = (widget: Gtk.Widget): boolean => callBooleanGetter(widget, "getEditable") ?? true;
149
+
150
+ const hasNonEditableChar = (iter: Gtk.TextIter, isDefaultEditable: boolean): boolean => {
151
+ while (!iter.isEnd()) {
152
+ if (!iter.editable(isDefaultEditable)) {
153
+ return true;
154
+ }
155
+
156
+ if (!iter.forwardChar()) {
157
+ return false;
158
+ }
159
+ }
160
+
161
+ return false;
162
+ };
163
+
164
+ const hasProtectedText = (widget: EditableTarget): boolean =>
165
+ widget instanceof Gtk.TextView && hasNonEditableChar(widget.getBuffer().getStartIter(), widget.getEditable());
166
+
167
+ const requireClearableText = (editable: EditableTarget): void => {
168
+ if (!isWidgetEditable(editable)) {
169
+ throw new Error("Cannot clear element: the widget is not editable");
170
+ }
171
+
172
+ if (hasProtectedText(editable)) {
173
+ throw new Error(CLEAR_REFUSED);
174
+ }
175
+ };
176
+
177
+ const deletionSignalFor = (widget: EditableTarget): [SignalWatchTarget, string] =>
178
+ widget instanceof Gtk.TextView ? [widget.getBuffer(), "delete-range"] : [widget, "delete-text"];
179
+
180
+ const isDeletionApplied = (emitter: SignalWatchTarget, signal: string, apply: () => void): boolean => {
181
+ let isApplied = false;
182
+
183
+ const watch = (): void => {
184
+ isApplied = true;
185
+ };
186
+
187
+ emitter.on(signal, watch, true);
188
+
189
+ try {
190
+ apply();
191
+ } finally {
192
+ emitter.off(signal, watch);
193
+ }
194
+
195
+ return isApplied;
196
+ };
197
+
198
+ const didDeleteAllText = (widget: EditableTarget): boolean => {
199
+ const [emitter, signal] = deletionSignalFor(widget);
200
+
201
+ return isDeletionApplied(emitter, signal, () => {
202
+ deleteAllText(widget);
203
+ });
204
+ };
205
+
206
+ const clearEditable = (editable: EditableTarget): void => {
207
+ requireClearableText(editable);
208
+ editable.grabFocus();
209
+
210
+ if (readEditableText(editable) === "") {
211
+ return;
212
+ }
213
+
214
+ if (!didDeleteAllText(editable)) {
215
+ throw new Error(CLEAR_REFUSED);
216
+ }
217
+ };
218
+
150
219
  /**
151
- * Replaces an editable widget's text with the empty string.
220
+ * Focuses an editable widget and deletes its whole text through the widget's own editing API, so the
221
+ * deletion emits the signals an edit made by hand emits. Text a tag protects is detected before the
222
+ * deletion runs, leaving such a widget untouched. A widget that writes a new value back in response
223
+ * to the deletion, as an input mask does, is cleared successfully and keeps whatever it wrote.
152
224
  *
153
- * @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView.
225
+ * @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView, when it refuses edits, when
226
+ * a tag protects part of its text, or when a handler blocks the deletion.
154
227
  */
155
228
  const clear = (widget: Gtk.Widget): Promise<void> =>
156
- runEditableEvent(widget, "Cannot clear element", (editable) => {
157
- setEditableText(editable, "");
158
- });
229
+ runEditableEvent(widget, "Cannot clear element", clearEditable);
159
230
 
160
231
  /**
161
232
  * Writes an editable widget's selected text to its clipboard, or the empty string when nothing is selected.