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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/dist/act.d.ts +6 -6
  2. package/dist/act.d.ts.map +1 -1
  3. package/dist/act.js +34 -26
  4. package/dist/act.js.map +1 -1
  5. package/dist/bound-queries.d.ts +4 -3
  6. package/dist/bound-queries.d.ts.map +1 -1
  7. package/dist/bound-queries.js.map +1 -1
  8. package/dist/build-queries.d.ts +5 -5
  9. package/dist/build-queries.d.ts.map +1 -1
  10. package/dist/build-queries.js +21 -9
  11. package/dist/build-queries.js.map +1 -1
  12. package/dist/cleanup-registry.d.ts +4 -3
  13. package/dist/cleanup-registry.d.ts.map +1 -1
  14. package/dist/cleanup-registry.js +13 -3
  15. package/dist/cleanup-registry.js.map +1 -1
  16. package/dist/config.d.ts +5 -4
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +11 -9
  19. package/dist/config.js.map +1 -1
  20. package/dist/editable.d.ts +6 -5
  21. package/dist/editable.d.ts.map +1 -1
  22. package/dist/editable.js +7 -5
  23. package/dist/editable.js.map +1 -1
  24. package/dist/errors.d.ts +7 -6
  25. package/dist/errors.d.ts.map +1 -1
  26. package/dist/errors.js +64 -48
  27. package/dist/errors.js.map +1 -1
  28. package/dist/fire-event.d.ts +2 -1
  29. package/dist/fire-event.d.ts.map +1 -1
  30. package/dist/fire-event.js +2 -1
  31. package/dist/fire-event.js.map +1 -1
  32. package/dist/frame-sync.d.ts +2 -1
  33. package/dist/frame-sync.d.ts.map +1 -1
  34. package/dist/frame-sync.js +28 -21
  35. package/dist/frame-sync.js.map +1 -1
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/matchers.d.ts +38 -34
  41. package/dist/matchers.d.ts.map +1 -1
  42. package/dist/matchers.js +72 -58
  43. package/dist/matchers.js.map +1 -1
  44. package/dist/pretty-widget.d.ts +6 -4
  45. package/dist/pretty-widget.d.ts.map +1 -1
  46. package/dist/pretty-widget.js +64 -24
  47. package/dist/pretty-widget.js.map +1 -1
  48. package/dist/queries.d.ts +143 -86
  49. package/dist/queries.d.ts.map +1 -1
  50. package/dist/queries.js +231 -147
  51. package/dist/queries.js.map +1 -1
  52. package/dist/register-test-hooks.js +2 -1
  53. package/dist/register-test-hooks.js.map +1 -1
  54. package/dist/render-hook.d.ts +3 -2
  55. package/dist/render-hook.d.ts.map +1 -1
  56. package/dist/render-hook.js +2 -1
  57. package/dist/render-hook.js.map +1 -1
  58. package/dist/render.d.ts +3 -2
  59. package/dist/render.d.ts.map +1 -1
  60. package/dist/render.js +24 -18
  61. package/dist/render.js.map +1 -1
  62. package/dist/role-helpers.d.ts +6 -5
  63. package/dist/role-helpers.d.ts.map +1 -1
  64. package/dist/role-helpers.js +16 -12
  65. package/dist/role-helpers.js.map +1 -1
  66. package/dist/screen.d.ts +4 -3
  67. package/dist/screen.d.ts.map +1 -1
  68. package/dist/screen.js +13 -11
  69. package/dist/screen.js.map +1 -1
  70. package/dist/screenshot.d.ts +4 -3
  71. package/dist/screenshot.d.ts.map +1 -1
  72. package/dist/screenshot.js +43 -36
  73. package/dist/screenshot.js.map +1 -1
  74. package/dist/suggestions.d.ts +11 -6
  75. package/dist/suggestions.d.ts.map +1 -1
  76. package/dist/suggestions.js +10 -5
  77. package/dist/suggestions.js.map +1 -1
  78. package/dist/traversal.d.ts +7 -7
  79. package/dist/traversal.d.ts.map +1 -1
  80. package/dist/traversal.js +17 -11
  81. package/dist/traversal.js.map +1 -1
  82. package/dist/types.d.ts +39 -24
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/types.js.map +1 -1
  85. package/dist/user-event/adjustment.d.ts +5 -3
  86. package/dist/user-event/adjustment.d.ts.map +1 -1
  87. package/dist/user-event/adjustment.js +6 -4
  88. package/dist/user-event/adjustment.js.map +1 -1
  89. package/dist/user-event/click.d.ts +6 -5
  90. package/dist/user-event/click.d.ts.map +1 -1
  91. package/dist/user-event/click.js +29 -21
  92. package/dist/user-event/click.js.map +1 -1
  93. package/dist/user-event/controller.d.ts +6 -5
  94. package/dist/user-event/controller.d.ts.map +1 -1
  95. package/dist/user-event/controller.js +9 -6
  96. package/dist/user-event/controller.js.map +1 -1
  97. package/dist/user-event/dispatch.d.ts +3 -3
  98. package/dist/user-event/dispatch.d.ts.map +1 -1
  99. package/dist/user-event/dispatch.js +3 -2
  100. package/dist/user-event/dispatch.js.map +1 -1
  101. package/dist/user-event/event-wrapper.d.ts +2 -1
  102. package/dist/user-event/event-wrapper.d.ts.map +1 -1
  103. package/dist/user-event/event-wrapper.js +27 -16
  104. package/dist/user-event/event-wrapper.js.map +1 -1
  105. package/dist/user-event/gesture.d.ts +18 -14
  106. package/dist/user-event/gesture.d.ts.map +1 -1
  107. package/dist/user-event/gesture.js +70 -37
  108. package/dist/user-event/gesture.js.map +1 -1
  109. package/dist/user-event/index.d.ts +17 -10
  110. package/dist/user-event/index.d.ts.map +1 -1
  111. package/dist/user-event/index.js +7 -3
  112. package/dist/user-event/index.js.map +1 -1
  113. package/dist/user-event/keyboard.d.ts +4 -3
  114. package/dist/user-event/keyboard.d.ts.map +1 -1
  115. package/dist/user-event/keyboard.js +89 -58
  116. package/dist/user-event/keyboard.js.map +1 -1
  117. package/dist/user-event/pointer.d.ts +7 -3
  118. package/dist/user-event/pointer.d.ts.map +1 -1
  119. package/dist/user-event/pointer.js +4 -1
  120. package/dist/user-event/pointer.js.map +1 -1
  121. package/dist/user-event/selection.d.ts +3 -2
  122. package/dist/user-event/selection.d.ts.map +1 -1
  123. package/dist/user-event/selection.js +26 -27
  124. package/dist/user-event/selection.js.map +1 -1
  125. package/dist/user-event/state.d.ts +3 -2
  126. package/dist/user-event/state.d.ts.map +1 -1
  127. package/dist/user-event/state.js +2 -1
  128. package/dist/user-event/state.js.map +1 -1
  129. package/dist/user-event/text.d.ts +8 -7
  130. package/dist/user-event/text.d.ts.map +1 -1
  131. package/dist/user-event/text.js +39 -33
  132. package/dist/user-event/text.js.map +1 -1
  133. package/dist/wait-for.d.ts +5 -5
  134. package/dist/wait-for.d.ts.map +1 -1
  135. package/dist/wait-for.js +45 -30
  136. package/dist/wait-for.js.map +1 -1
  137. package/dist/widget-accessible-properties.d.ts +26 -25
  138. package/dist/widget-accessible-properties.d.ts.map +1 -1
  139. package/dist/widget-accessible-properties.js +106 -61
  140. package/dist/widget-accessible-properties.js.map +1 -1
  141. package/dist/within.d.ts +2 -1
  142. package/dist/within.d.ts.map +1 -1
  143. package/dist/within.js +2 -1
  144. package/dist/within.js.map +1 -1
  145. package/package.json +11 -9
  146. package/src/act.ts +44 -35
  147. package/src/bound-queries.ts +5 -4
  148. package/src/build-queries.ts +84 -43
  149. package/src/cleanup-registry.ts +18 -5
  150. package/src/config.ts +16 -12
  151. package/src/editable.ts +13 -6
  152. package/src/errors.ts +106 -45
  153. package/src/fire-event.ts +3 -1
  154. package/src/frame-sync.ts +38 -21
  155. package/src/index.ts +2 -1
  156. package/src/matchers.ts +158 -107
  157. package/src/pretty-widget.ts +114 -42
  158. package/src/queries.ts +422 -233
  159. package/src/register-test-hooks.ts +4 -1
  160. package/src/render-hook.tsx +10 -4
  161. package/src/render.tsx +58 -33
  162. package/src/role-helpers.ts +29 -16
  163. package/src/screen.ts +17 -12
  164. package/src/screenshot.ts +61 -38
  165. package/src/suggestions.ts +29 -10
  166. package/src/traversal.ts +35 -15
  167. package/src/types.ts +65 -32
  168. package/src/user-event/adjustment.ts +23 -10
  169. package/src/user-event/click.ts +49 -20
  170. package/src/user-event/controller.ts +21 -7
  171. package/src/user-event/dispatch.ts +4 -2
  172. package/src/user-event/event-wrapper.ts +42 -16
  173. package/src/user-event/gesture.ts +135 -50
  174. package/src/user-event/index.ts +19 -12
  175. package/src/user-event/keyboard.ts +147 -61
  176. package/src/user-event/pointer.ts +20 -8
  177. package/src/user-event/selection.ts +69 -32
  178. package/src/user-event/state.ts +4 -2
  179. package/src/user-event/text.ts +73 -38
  180. package/src/wait-for.ts +77 -36
  181. package/src/widget-accessible-properties.ts +218 -74
  182. package/src/within.ts +7 -4
package/dist/matchers.js CHANGED
@@ -1,6 +1,27 @@
1
1
  /// <reference types="@vitest/expect" />
2
2
  import * as Gtk from "@gtkx/gi/gtk";
3
3
  import { getWidgetAccessibleName, getWidgetCheckedState, getWidgetDisplayValue, getWidgetExpandedState, getWidgetNodeText, getWidgetPlaceholderText, getWidgetPressedState, getWidgetSelectedState, getWidgetValue, } from "./widget-accessible-properties.js";
4
+ const toHaveDisplayValue = textMatcher("toHaveDisplayValue", getWidgetDisplayValue, "exact");
5
+ const toHaveTextContent = textMatcher("toHaveTextContent", (widget) => getWidgetNodeText(widget) ?? getWidgetAccessibleName(widget), "substring");
6
+ const toHaveAccessibleName = textMatcher("toHaveAccessibleName", getWidgetAccessibleName, "exact");
7
+ const toHavePlaceholderText = textMatcher("toHavePlaceholderText", getWidgetPlaceholderText, "exact");
8
+ const toBeChecked = booleanStateMatcher("toBeChecked", "checked", getWidgetCheckedState);
9
+ const toBePressed = booleanStateMatcher("toBePressed", "pressed", getWidgetPressedState);
10
+ const toBeExpanded = booleanStateMatcher("toBeExpanded", "expanded", getWidgetExpandedState);
11
+ const toBeSelected = booleanStateMatcher("toBeSelected", "selected", getWidgetSelectedState);
12
+ /** The widget assertion matchers keyed by name, suitable for passing to `expect.extend`. */
13
+ const matchers = {
14
+ toHaveDisplayValue,
15
+ toHaveTextContent,
16
+ toHaveAccessibleName,
17
+ toHavePlaceholderText,
18
+ toBeChecked,
19
+ toBePressed,
20
+ toBeExpanded,
21
+ toBeSelected,
22
+ toHaveValue,
23
+ };
24
+ const registration = { isRegistered: false };
4
25
  const asWidget = (received, matcherName) => {
5
26
  if (!(received instanceof Gtk.Widget)) {
6
27
  throw new TypeError(`${matcherName}: received value must be a Gtk.Widget, got ${typeof received}`);
@@ -10,9 +31,9 @@ const asWidget = (received, matcherName) => {
10
31
  const describeWidget = (widget) => {
11
32
  const role = Gtk.AccessibleRole[widget.getAccessibleRole()];
12
33
  const name = getWidgetAccessibleName(widget);
13
- return name !== null ? `<${role} name=${JSON.stringify(name)}>` : `<${role}>`;
34
+ return name === null ? `<${role}>` : `<${role} name=${JSON.stringify(name)}>`;
14
35
  };
15
- const matchesText = (actual, expected, mode) => {
36
+ const isTextMatch = (actual, expected, mode) => {
16
37
  if (expected instanceof RegExp) {
17
38
  expected.lastIndex = 0;
18
39
  return expected.test(actual);
@@ -20,68 +41,59 @@ const matchesText = (actual, expected, mode) => {
20
41
  return mode === "exact" ? actual === expected : actual.includes(expected);
21
42
  };
22
43
  const describeExpected = (expected) => expected instanceof RegExp ? String(expected) : JSON.stringify(expected);
23
- const textMatcher = (matcherName, read, mode) => function (received, expected) {
24
- const widget = asWidget(received, matcherName);
25
- const actual = read(widget);
26
- if (expected === undefined) {
27
- const pass = actual !== null && actual !== "";
28
- return {
29
- pass,
30
- message: () => `expected widget ${this.isNot ? "not " : ""}to have a non-empty value for ${matcherName}, ` +
31
- `but got ${JSON.stringify(actual)}\n${describeWidget(widget)}`,
32
- };
33
- }
34
- const pass = actual !== null && matchesText(actual, expected, mode);
44
+ const negationPrefix = (isPass) => (isPass ? "not " : "");
45
+ const nonEmptyResult = ({ matcherName, widget, actual }) => {
46
+ const isPass = actual !== null && actual !== "";
35
47
  return {
36
- pass,
37
- message: () => `expected widget ${this.isNot ? "not " : ""}${matcherName} ${describeExpected(expected)}, ` +
38
- `but received ${JSON.stringify(actual)}\n${describeWidget(widget)}`,
48
+ pass: isPass,
49
+ message: () => `expected widget ${negationPrefix(isPass)}to have a non-empty value for ${matcherName}, ` +
50
+ `but got ${JSON.stringify(actual)}\n${describeWidget(widget)}`,
39
51
  };
40
52
  };
41
- const booleanStateMatcher = (matcherName, stateName, read) => function (received) {
42
- const widget = asWidget(received, matcherName);
43
- const state = read(widget);
44
- if (state === null) {
45
- throw new Error(`${matcherName}: widget does not expose a ${stateName} state ` +
46
- `(role ${Gtk.AccessibleRole[widget.getAccessibleRole()]})\n${describeWidget(widget)}`);
47
- }
48
- return {
49
- pass: state,
50
- message: () => `expected widget ${this.isNot ? "not " : ""}to be ${stateName}\n${describeWidget(widget)}`,
53
+ const matchedResult = (context, expected, isPass) => ({
54
+ pass: isPass,
55
+ message: () => `expected widget ${negationPrefix(isPass)}${context.matcherName} ${describeExpected(expected)}, ` +
56
+ `but received ${JSON.stringify(context.actual)}\n${describeWidget(context.widget)}`,
57
+ });
58
+ function textMatcher(matcherName, read, mode) {
59
+ return (received, expected) => {
60
+ const widget = asWidget(received, matcherName);
61
+ const actual = read(widget);
62
+ const context = { matcherName, widget, actual };
63
+ if (expected === undefined) {
64
+ return nonEmptyResult(context);
65
+ }
66
+ return matchedResult(context, expected, actual !== null && isTextMatch(actual, expected, mode));
51
67
  };
52
- };
53
- export const toHaveDisplayValue = textMatcher("toHaveDisplayValue", getWidgetDisplayValue, "exact");
54
- export const toHaveTextContent = textMatcher("toHaveTextContent", (widget) => getWidgetNodeText(widget) ?? getWidgetAccessibleName(widget), "substring");
55
- export const toHaveAccessibleName = textMatcher("toHaveAccessibleName", getWidgetAccessibleName, "exact");
56
- export const toHavePlaceholderText = textMatcher("toHavePlaceholderText", getWidgetPlaceholderText, "exact");
57
- export const toBeChecked = booleanStateMatcher("toBeChecked", "checked", getWidgetCheckedState);
58
- export const toBePressed = booleanStateMatcher("toBePressed", "pressed", getWidgetPressedState);
59
- export const toBeExpanded = booleanStateMatcher("toBeExpanded", "expanded", getWidgetExpandedState);
60
- export const toBeSelected = booleanStateMatcher("toBeSelected", "selected", getWidgetSelectedState);
61
- export const toHaveValue = function (received, expected) {
68
+ }
69
+ function booleanStateMatcher(matcherName, stateName, read) {
70
+ return (received) => {
71
+ const widget = asWidget(received, matcherName);
72
+ const state = read(widget);
73
+ if (state === null) {
74
+ throw new Error(`${matcherName}: widget does not expose a ${stateName} state ` +
75
+ `(role ${Gtk.AccessibleRole[widget.getAccessibleRole()]})\n${describeWidget(widget)}`);
76
+ }
77
+ return {
78
+ pass: state,
79
+ message: () => `expected widget ${negationPrefix(state)}to be ${stateName}\n${describeWidget(widget)}`,
80
+ };
81
+ };
82
+ }
83
+ function toHaveValue(received, expected) {
62
84
  const widget = asWidget(received, "toHaveValue");
63
85
  const actual = getWidgetValue(widget).now;
64
86
  if (actual === null) {
65
- throw new Error(`toHaveValue: widget does not expose a numeric value ` +
87
+ throw new Error("toHaveValue: widget does not expose a numeric value " +
66
88
  `(role ${Gtk.AccessibleRole[widget.getAccessibleRole()]})\n${describeWidget(widget)}`);
67
89
  }
90
+ const isPass = actual === expected;
68
91
  return {
69
- pass: actual === expected,
70
- message: () => `expected widget ${this.isNot ? "not " : ""}to have value ${expected}, but received ${actual}\n${describeWidget(widget)}`,
92
+ pass: isPass,
93
+ message: () => `expected widget ${negationPrefix(isPass)}to have value ${String(expected)}, ` +
94
+ `but received ${String(actual)}\n${describeWidget(widget)}`,
71
95
  };
72
- };
73
- /** The widget assertion matchers keyed by name, suitable for passing to `expect.extend`. */
74
- export const matchers = {
75
- toHaveDisplayValue,
76
- toHaveTextContent,
77
- toHaveAccessibleName,
78
- toHavePlaceholderText,
79
- toBeChecked,
80
- toBePressed,
81
- toBeExpanded,
82
- toBeSelected,
83
- toHaveValue,
84
- };
96
+ }
85
97
  const globalExpect = () => {
86
98
  const candidate = Reflect.get(globalThis, "expect");
87
99
  if (candidate && typeof candidate.extend === "function") {
@@ -89,15 +101,17 @@ const globalExpect = () => {
89
101
  }
90
102
  return null;
91
103
  };
92
- let registered = false;
93
104
  /** Registers the widget matchers on the global `expect`, when one is available. Safe to call more than once. */
94
- export const registerMatchers = () => {
95
- if (registered)
105
+ const registerMatchers = () => {
106
+ if (registration.isRegistered) {
96
107
  return;
108
+ }
97
109
  const expect = globalExpect();
98
- if (!expect)
110
+ if (!expect) {
99
111
  return;
112
+ }
100
113
  expect.extend(matchers);
101
- registered = true;
114
+ registration.isRegistered = true;
102
115
  };
116
+ export { toHaveDisplayValue, toHaveTextContent, toHaveAccessibleName, toHavePlaceholderText, toBeChecked, toBePressed, toBeExpanded, toBeSelected, matchers, toHaveValue, registerMatchers, };
103
117
  //# sourceMappingURL=matchers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"matchers.js","sourceRoot":"","sources":["../src/matchers.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AACpC,OAAO,EACH,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,wBAAwB,EACxB,qBAAqB,EACrB,sBAAsB,EACtB,cAAc,GACjB,MAAM,mCAAmC,CAAC;AAe3C,MAAM,QAAQ,GAAG,CAAC,QAAiB,EAAE,WAAmB,EAAc,EAAE;IACpE,IAAI,CAAC,CAAC,QAAQ,YAAY,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,SAAS,CAAC,GAAG,WAAW,8CAA8C,OAAO,QAAQ,EAAE,CAAC,CAAC;IACvG,CAAC;IACD,OAAO,QAAQ,CAAC;AACpB,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,MAAkB,EAAU,EAAE;IAClD,MAAM,IAAI,GAAG,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAC7C,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,SAAS,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC;AAClF,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,MAAc,EAAE,QAAyB,EAAE,IAA2B,EAAW,EAAE;IACpG,IAAI,QAAQ,YAAY,MAAM,EAAE,CAAC;QAC7B,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC;QACvB,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC9E,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,QAAyB,EAAU,EAAE,CAC3D,QAAQ,YAAY,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE7E,MAAM,WAAW,GAAG,CAChB,WAAmB,EACnB,IAA2C,EAC3C,IAA2B,EAChB,EAAE,CACb,UAAgC,QAAiB,EAAE,QAA0B;IACzE,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAE5B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,EAAE,CAAC;QAC9C,OAAO;YACH,IAAI;YACJ,OAAO,EAAE,GAAG,EAAE,CACV,mBAAmB,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,iCAAiC,WAAW,IAAI;gBAC3F,WAAW,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,cAAc,CAAC,MAAM,CAAC,EAAE;SACrE,CAAC;IACN,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,KAAK,IAAI,IAAI,WAAW,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;IACpE,OAAO;QACH,IAAI;QACJ,OAAO,EAAE,GAAG,EAAE,CACV,mBAAmB,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,WAAW,IAAI,gBAAgB,CAAC,QAAQ,CAAC,IAAI;YAC3F,gBAAgB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,cAAc,CAAC,MAAM,CAAC,EAAE;KAC1E,CAAC;AACN,CAAC,CAAC;AAEN,MAAM,mBAAmB,GAAG,CACxB,WAAmB,EACnB,SAAiB,EACjB,IAA4C,EAChC,EAAE,CACd,UAAgC,QAAiB;IAC7C,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACX,GAAG,WAAW,8BAA8B,SAAS,SAAS;YAC1D,SAAS,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,MAAM,cAAc,CAAC,MAAM,CAAC,EAAE,CAC5F,CAAC;IACN,CAAC;IACD,OAAO;QACH,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,GAAG,EAAE,CAAC,mBAAmB,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,SAAS,KAAK,cAAc,CAAC,MAAM,CAAC,EAAE;KAC5G,CAAC;AACN,CAAC,CAAC;AAEN,MAAM,CAAC,MAAM,kBAAkB,GAAgB,WAAW,CAAC,oBAAoB,EAAE,qBAAqB,EAAE,OAAO,CAAC,CAAC;AACjH,MAAM,CAAC,MAAM,iBAAiB,GAAgB,WAAW,CACrD,mBAAmB,EACnB,CAAC,MAAM,EAAE,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,IAAI,uBAAuB,CAAC,MAAM,CAAC,EACxE,WAAW,CACd,CAAC;AACF,MAAM,CAAC,MAAM,oBAAoB,GAAgB,WAAW,CAAC,sBAAsB,EAAE,uBAAuB,EAAE,OAAO,CAAC,CAAC;AACvH,MAAM,CAAC,MAAM,qBAAqB,GAAgB,WAAW,CACzD,uBAAuB,EACvB,wBAAwB,EACxB,OAAO,CACV,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAiB,mBAAmB,CAAC,aAAa,EAAE,SAAS,EAAE,qBAAqB,CAAC,CAAC;AAC9G,MAAM,CAAC,MAAM,WAAW,GAAiB,mBAAmB,CAAC,aAAa,EAAE,SAAS,EAAE,qBAAqB,CAAC,CAAC;AAC9G,MAAM,CAAC,MAAM,YAAY,GAAiB,mBAAmB,CAAC,cAAc,EAAE,UAAU,EAAE,sBAAsB,CAAC,CAAC;AAClH,MAAM,CAAC,MAAM,YAAY,GAAiB,mBAAmB,CAAC,cAAc,EAAE,UAAU,EAAE,sBAAsB,CAAC,CAAC;AAElH,MAAM,CAAC,MAAM,WAAW,GAAiB,UAErC,QAAiB,EACjB,QAAgB;IAEhB,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC;IAC1C,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACX,sDAAsD;YAClD,SAAS,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,MAAM,cAAc,CAAC,MAAM,CAAC,EAAE,CAC5F,CAAC;IACN,CAAC;IACD,OAAO;QACH,IAAI,EAAE,MAAM,KAAK,QAAQ;QACzB,OAAO,EAAE,GAAG,EAAE,CACV,mBAAmB,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,iBAAiB,QAAQ,kBAAkB,MAAM,KAAK,cAAc,CAAC,MAAM,CAAC,EAAE;KAChI,CAAC;AACN,CAAC,CAAC;AAcF,4FAA4F;AAC5F,MAAM,CAAC,MAAM,QAAQ,GAA2B;IAC5C,kBAAkB;IAClB,iBAAiB;IACjB,oBAAoB;IACpB,qBAAqB;IACrB,WAAW;IACX,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,WAAW;CACd,CAAC;AAIF,MAAM,YAAY,GAAG,GAAwB,EAAE;IAC3C,MAAM,SAAS,GAAY,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC7D,IAAI,SAAS,IAAI,OAAQ,SAA0B,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACxE,OAAO,SAAyB,CAAC;IACrC,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,IAAI,UAAU,GAAG,KAAK,CAAC;AAEvB,gHAAgH;AAChH,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAS,EAAE;IACvC,IAAI,UAAU;QAAE,OAAO;IACvB,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,IAAI,CAAC,MAAM;QAAE,OAAO;IACpB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACxB,UAAU,GAAG,IAAI,CAAC;AACtB,CAAC,CAAC","sourcesContent":["/// <reference types=\"@vitest/expect\" />\nimport * as Gtk from \"@gtkx/gi/gtk\";\nimport {\n getWidgetAccessibleName,\n getWidgetCheckedState,\n getWidgetDisplayValue,\n getWidgetExpandedState,\n getWidgetNodeText,\n getWidgetPlaceholderText,\n getWidgetPressedState,\n getWidgetSelectedState,\n getWidgetValue,\n} from \"./widget-accessible-properties.js\";\n\n/** The expected value for a text matcher: an exact string or a regular expression. */\nexport type TextExpectation = string | RegExp;\n\ntype MatcherResult = { pass: boolean; message: () => string };\n\ninterface MatcherContext {\n isNot: boolean;\n}\n\ntype TextMatcher = (this: MatcherContext, received: unknown, expected?: TextExpectation) => MatcherResult;\ntype StateMatcher = (this: MatcherContext, received: unknown) => MatcherResult;\ntype ValueMatcher = (this: MatcherContext, received: unknown, expected: number) => MatcherResult;\n\nconst asWidget = (received: unknown, matcherName: string): Gtk.Widget => {\n if (!(received instanceof Gtk.Widget)) {\n throw new TypeError(`${matcherName}: received value must be a Gtk.Widget, got ${typeof received}`);\n }\n return received;\n};\n\nconst describeWidget = (widget: Gtk.Widget): string => {\n const role = Gtk.AccessibleRole[widget.getAccessibleRole()];\n const name = getWidgetAccessibleName(widget);\n return name !== null ? `<${role} name=${JSON.stringify(name)}>` : `<${role}>`;\n};\n\nconst matchesText = (actual: string, expected: TextExpectation, mode: \"exact\" | \"substring\"): boolean => {\n if (expected instanceof RegExp) {\n expected.lastIndex = 0;\n return expected.test(actual);\n }\n return mode === \"exact\" ? actual === expected : actual.includes(expected);\n};\n\nconst describeExpected = (expected: TextExpectation): string =>\n expected instanceof RegExp ? String(expected) : JSON.stringify(expected);\n\nconst textMatcher = (\n matcherName: string,\n read: (widget: Gtk.Widget) => string | null,\n mode: \"exact\" | \"substring\",\n): TextMatcher =>\n function (this: MatcherContext, received: unknown, expected?: TextExpectation): MatcherResult {\n const widget = asWidget(received, matcherName);\n const actual = read(widget);\n\n if (expected === undefined) {\n const pass = actual !== null && actual !== \"\";\n return {\n pass,\n message: () =>\n `expected widget ${this.isNot ? \"not \" : \"\"}to have a non-empty value for ${matcherName}, ` +\n `but got ${JSON.stringify(actual)}\\n${describeWidget(widget)}`,\n };\n }\n\n const pass = actual !== null && matchesText(actual, expected, mode);\n return {\n pass,\n message: () =>\n `expected widget ${this.isNot ? \"not \" : \"\"}${matcherName} ${describeExpected(expected)}, ` +\n `but received ${JSON.stringify(actual)}\\n${describeWidget(widget)}`,\n };\n };\n\nconst booleanStateMatcher = (\n matcherName: string,\n stateName: string,\n read: (widget: Gtk.Widget) => boolean | null,\n): StateMatcher =>\n function (this: MatcherContext, received: unknown): MatcherResult {\n const widget = asWidget(received, matcherName);\n const state = read(widget);\n if (state === null) {\n throw new Error(\n `${matcherName}: widget does not expose a ${stateName} state ` +\n `(role ${Gtk.AccessibleRole[widget.getAccessibleRole()]})\\n${describeWidget(widget)}`,\n );\n }\n return {\n pass: state,\n message: () => `expected widget ${this.isNot ? \"not \" : \"\"}to be ${stateName}\\n${describeWidget(widget)}`,\n };\n };\n\nexport const toHaveDisplayValue: TextMatcher = textMatcher(\"toHaveDisplayValue\", getWidgetDisplayValue, \"exact\");\nexport const toHaveTextContent: TextMatcher = textMatcher(\n \"toHaveTextContent\",\n (widget) => getWidgetNodeText(widget) ?? getWidgetAccessibleName(widget),\n \"substring\",\n);\nexport const toHaveAccessibleName: TextMatcher = textMatcher(\"toHaveAccessibleName\", getWidgetAccessibleName, \"exact\");\nexport const toHavePlaceholderText: TextMatcher = textMatcher(\n \"toHavePlaceholderText\",\n getWidgetPlaceholderText,\n \"exact\",\n);\n\nexport const toBeChecked: StateMatcher = booleanStateMatcher(\"toBeChecked\", \"checked\", getWidgetCheckedState);\nexport const toBePressed: StateMatcher = booleanStateMatcher(\"toBePressed\", \"pressed\", getWidgetPressedState);\nexport const toBeExpanded: StateMatcher = booleanStateMatcher(\"toBeExpanded\", \"expanded\", getWidgetExpandedState);\nexport const toBeSelected: StateMatcher = booleanStateMatcher(\"toBeSelected\", \"selected\", getWidgetSelectedState);\n\nexport const toHaveValue: ValueMatcher = function (\n this: MatcherContext,\n received: unknown,\n expected: number,\n): MatcherResult {\n const widget = asWidget(received, \"toHaveValue\");\n const actual = getWidgetValue(widget).now;\n if (actual === null) {\n throw new Error(\n `toHaveValue: widget does not expose a numeric value ` +\n `(role ${Gtk.AccessibleRole[widget.getAccessibleRole()]})\\n${describeWidget(widget)}`,\n );\n }\n return {\n pass: actual === expected,\n message: () =>\n `expected widget ${this.isNot ? \"not \" : \"\"}to have value ${expected}, but received ${actual}\\n${describeWidget(widget)}`,\n };\n};\n\ninterface MatcherImplementations {\n toHaveDisplayValue: TextMatcher;\n toHaveTextContent: TextMatcher;\n toHaveAccessibleName: TextMatcher;\n toHavePlaceholderText: TextMatcher;\n toBeChecked: StateMatcher;\n toBePressed: StateMatcher;\n toBeExpanded: StateMatcher;\n toBeSelected: StateMatcher;\n toHaveValue: ValueMatcher;\n}\n\n/** The widget assertion matchers keyed by name, suitable for passing to `expect.extend`. */\nexport const matchers: MatcherImplementations = {\n toHaveDisplayValue,\n toHaveTextContent,\n toHaveAccessibleName,\n toHavePlaceholderText,\n toBeChecked,\n toBePressed,\n toBeExpanded,\n toBeSelected,\n toHaveValue,\n};\n\ntype ExpectExtend = { extend: (m: MatcherImplementations) => void };\n\nconst globalExpect = (): ExpectExtend | null => {\n const candidate: unknown = Reflect.get(globalThis, \"expect\");\n if (candidate && typeof (candidate as ExpectExtend).extend === \"function\") {\n return candidate as ExpectExtend;\n }\n return null;\n};\n\nlet registered = false;\n\n/** Registers the widget matchers on the global `expect`, when one is available. Safe to call more than once. */\nexport const registerMatchers = (): void => {\n if (registered) return;\n const expect = globalExpect();\n if (!expect) return;\n expect.extend(matchers);\n registered = true;\n};\n\ninterface WidgetMatchers<R = void> {\n toHaveDisplayValue(expected?: TextExpectation): R;\n toHaveTextContent(expected?: TextExpectation): R;\n toHaveAccessibleName(expected?: TextExpectation): R;\n toHavePlaceholderText(expected?: TextExpectation): R;\n toBeChecked(): R;\n toBePressed(): R;\n toBeExpanded(): R;\n toBeSelected(): R;\n toHaveValue(expected: number): R;\n}\n\ndeclare module \"@vitest/expect\" {\n // biome-ignore lint/suspicious/noExplicitAny: must match the `Assertion<T>` signature it augments for declaration merging\n interface Assertion<T = any> extends WidgetMatchers {}\n interface AsymmetricMatchersContaining extends WidgetMatchers {}\n}\n"]}
1
+ {"version":3,"file":"matchers.js","sourceRoot":"","sources":["../src/matchers.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference types=\"@vitest/expect\" />\nimport * as Gtk from \"@gtkx/gi/gtk\";\nimport {\n getWidgetAccessibleName,\n getWidgetCheckedState,\n getWidgetDisplayValue,\n getWidgetExpandedState,\n getWidgetNodeText,\n getWidgetPlaceholderText,\n getWidgetPressedState,\n getWidgetSelectedState,\n getWidgetValue,\n} from \"./widget-accessible-properties.js\";\n\n/** The expected value for a text matcher: an exact string or a regular expression. */\ntype TextExpectation = string | RegExp;\ntype MatcherResult = { pass: boolean; message: () => string };\ntype TextMatcher = (received: unknown, expected?: TextExpectation) => MatcherResult;\ntype StateMatcher = (received: unknown) => MatcherResult;\ntype ValueMatcher = (received: unknown, expected: number) => MatcherResult;\ntype TextMatcherContext = { matcherName: string; widget: Gtk.Widget; actual: string | null };\n\ntype MatcherImplementations = {\n toHaveDisplayValue: TextMatcher;\n toHaveTextContent: TextMatcher;\n toHaveAccessibleName: TextMatcher;\n toHavePlaceholderText: TextMatcher;\n toBeChecked: StateMatcher;\n toBePressed: StateMatcher;\n toBeExpanded: StateMatcher;\n toBeSelected: StateMatcher;\n toHaveValue: ValueMatcher;\n};\n\ntype ExpectExtend = { extend: (m: MatcherImplementations) => void };\n\nconst toHaveDisplayValue: TextMatcher = textMatcher(\"toHaveDisplayValue\", getWidgetDisplayValue, \"exact\");\n\nconst toHaveTextContent: TextMatcher = textMatcher(\n \"toHaveTextContent\",\n (widget) => getWidgetNodeText(widget) ?? getWidgetAccessibleName(widget),\n \"substring\",\n);\n\nconst toHaveAccessibleName: TextMatcher = textMatcher(\"toHaveAccessibleName\", getWidgetAccessibleName, \"exact\");\n\nconst toHavePlaceholderText: TextMatcher = textMatcher(\n \"toHavePlaceholderText\",\n getWidgetPlaceholderText,\n \"exact\",\n);\n\nconst toBeChecked: StateMatcher = booleanStateMatcher(\"toBeChecked\", \"checked\", getWidgetCheckedState);\nconst toBePressed: StateMatcher = booleanStateMatcher(\"toBePressed\", \"pressed\", getWidgetPressedState);\nconst toBeExpanded: StateMatcher = booleanStateMatcher(\"toBeExpanded\", \"expanded\", getWidgetExpandedState);\nconst toBeSelected: StateMatcher = booleanStateMatcher(\"toBeSelected\", \"selected\", getWidgetSelectedState);\n\n/** The widget assertion matchers keyed by name, suitable for passing to `expect.extend`. */\nconst matchers: MatcherImplementations = {\n toHaveDisplayValue,\n toHaveTextContent,\n toHaveAccessibleName,\n toHavePlaceholderText,\n toBeChecked,\n toBePressed,\n toBeExpanded,\n toBeSelected,\n toHaveValue,\n};\n\nconst registration = { isRegistered: false };\n\nconst asWidget = (received: unknown, matcherName: string): Gtk.Widget => {\n if (!(received instanceof Gtk.Widget)) {\n throw new TypeError(`${matcherName}: received value must be a Gtk.Widget, got ${typeof received}`);\n }\n\n return received;\n};\n\nconst describeWidget = (widget: Gtk.Widget): string => {\n const role = Gtk.AccessibleRole[widget.getAccessibleRole()];\n const name = getWidgetAccessibleName(widget);\n\n return name === null ? `<${role}>` : `<${role} name=${JSON.stringify(name)}>`;\n};\n\nconst isTextMatch = (actual: string, expected: TextExpectation, mode: \"exact\" | \"substring\"): boolean => {\n if (expected instanceof RegExp) {\n expected.lastIndex = 0;\n\n return expected.test(actual);\n }\n\n return mode === \"exact\" ? actual === expected : actual.includes(expected);\n};\n\nconst describeExpected = (expected: TextExpectation): string =>\n expected instanceof RegExp ? String(expected) : JSON.stringify(expected);\n\nconst negationPrefix = (isPass: boolean): string => (isPass ? \"not \" : \"\");\n\nconst nonEmptyResult = ({ matcherName, widget, actual }: TextMatcherContext): MatcherResult => {\n const isPass = actual !== null && actual !== \"\";\n\n return {\n pass: isPass,\n message: () =>\n `expected widget ${negationPrefix(isPass)}to have a non-empty value for ${matcherName}, ` +\n `but got ${JSON.stringify(actual)}\\n${describeWidget(widget)}`,\n };\n};\n\nconst matchedResult = (context: TextMatcherContext, expected: TextExpectation, isPass: boolean): MatcherResult => ({\n pass: isPass,\n message: () =>\n `expected widget ${negationPrefix(isPass)}${context.matcherName} ${describeExpected(expected)}, ` +\n `but received ${JSON.stringify(context.actual)}\\n${describeWidget(context.widget)}`,\n});\n\nfunction textMatcher(\n matcherName: string,\n read: (widget: Gtk.Widget) => string | null,\n mode: \"exact\" | \"substring\",\n): TextMatcher {\n return (received: unknown, expected?: TextExpectation): MatcherResult => {\n const widget = asWidget(received, matcherName);\n const actual = read(widget);\n const context: TextMatcherContext = { matcherName, widget, actual };\n\n if (expected === undefined) {\n return nonEmptyResult(context);\n }\n\n return matchedResult(context, expected, actual !== null && isTextMatch(actual, expected, mode));\n };\n}\n\nfunction booleanStateMatcher(\n matcherName: string,\n stateName: string,\n read: (widget: Gtk.Widget) => boolean | null,\n): StateMatcher {\n return (received: unknown): MatcherResult => {\n const widget = asWidget(received, matcherName);\n const state = read(widget);\n\n if (state === null) {\n throw new Error(\n `${matcherName}: widget does not expose a ${stateName} state ` +\n `(role ${Gtk.AccessibleRole[widget.getAccessibleRole()]})\\n${describeWidget(widget)}`,\n );\n }\n\n return {\n pass: state,\n message: () =>\n `expected widget ${negationPrefix(state)}to be ${stateName}\\n${describeWidget(widget)}`,\n };\n };\n}\n\nfunction toHaveValue(received: unknown, expected: number): MatcherResult {\n const widget = asWidget(received, \"toHaveValue\");\n const actual = getWidgetValue(widget).now;\n\n if (actual === null) {\n throw new Error(\n \"toHaveValue: widget does not expose a numeric value \" +\n `(role ${Gtk.AccessibleRole[widget.getAccessibleRole()]})\\n${describeWidget(widget)}`,\n );\n }\n\n const isPass = actual === expected;\n\n return {\n pass: isPass,\n message: () =>\n `expected widget ${negationPrefix(isPass)}to have value ${String(expected)}, ` +\n `but received ${String(actual)}\\n${describeWidget(widget)}`,\n };\n}\n\nconst globalExpect = (): ExpectExtend | null => {\n const candidate: unknown = Reflect.get(globalThis, \"expect\");\n\n if (candidate && typeof (candidate as ExpectExtend).extend === \"function\") {\n return candidate as ExpectExtend;\n }\n\n return null;\n};\n\n/** Registers the widget matchers on the global `expect`, when one is available. Safe to call more than once. */\nconst registerMatchers = (): void => {\n if (registration.isRegistered) {\n return;\n }\n\n const expect = globalExpect();\n\n if (!expect) {\n return;\n }\n\n expect.extend(matchers);\n registration.isRegistered = true;\n};\n\ndeclare module \"@vitest/expect\" {\n /* eslint-disable @typescript-eslint/consistent-type-definitions -- declaration merging requires interfaces */\n interface Assertion {\n toHaveDisplayValue(expected?: TextExpectation): void;\n toHaveTextContent(expected?: TextExpectation): void;\n toHaveAccessibleName(expected?: TextExpectation): void;\n toHavePlaceholderText(expected?: TextExpectation): void;\n toBeChecked(): void;\n toBePressed(): void;\n toBeExpanded(): void;\n toBeSelected(): void;\n toHaveValue(expected: number): void;\n }\n\n interface AsymmetricMatchersContaining {\n toHaveDisplayValue(expected?: TextExpectation): void;\n toHaveTextContent(expected?: TextExpectation): void;\n toHaveAccessibleName(expected?: TextExpectation): void;\n toHavePlaceholderText(expected?: TextExpectation): void;\n toBeChecked(): void;\n toBePressed(): void;\n toBeExpanded(): void;\n toBeSelected(): void;\n toHaveValue(expected: number): void;\n }\n /* eslint-enable @typescript-eslint/consistent-type-definitions */\n}\n\nexport {\n toHaveDisplayValue,\n toHaveTextContent,\n toHaveAccessibleName,\n toHavePlaceholderText,\n toBeChecked,\n toBePressed,\n toBeExpanded,\n toBeSelected,\n matchers,\n toHaveValue,\n registerMatchers,\n type TextExpectation,\n};\n"]}
@@ -5,13 +5,15 @@ type WidgetIdResolver = (widget: Gtk.Widget) => string;
5
5
  * Options controlling how a widget tree is rendered to a string by
6
6
  * {@link prettyWidget} and {@link logWidget}.
7
7
  */
8
- export type PrettyWidgetOptions = {
8
+ type PrettyWidgetOptions = {
9
9
  /** Truncates the output once it exceeds this many characters. */
10
10
  maxLength?: number;
11
11
  /** Whether to apply ANSI color highlighting; defaults to the terminal capabilities. */
12
12
  highlight?: boolean;
13
13
  /** Resolves an `id` attribute to show for each widget. */
14
14
  getId?: WidgetIdResolver;
15
+ /** Stops descending past this depth, replacing deeper children with a summary line. */
16
+ maxDepth?: number;
15
17
  };
16
18
  /**
17
19
  * Renders a widget tree as an indented, HTML-like string showing each widget's
@@ -21,13 +23,13 @@ export type PrettyWidgetOptions = {
21
23
  * @param options Formatting options such as truncation length and highlighting.
22
24
  * @returns The formatted representation of the tree.
23
25
  */
24
- export declare const prettyWidget: (container: Container, options?: PrettyWidgetOptions) => string;
26
+ declare const prettyWidget: (container: Container, options?: PrettyWidgetOptions) => string;
25
27
  /**
26
28
  * Prints one or more widget trees to the console using {@link prettyWidget}.
27
29
  *
28
30
  * @param container A single scope or an array of scopes to format and print.
29
31
  * @param options Formatting options passed through to {@link prettyWidget}.
30
32
  */
31
- export declare const logWidget: (container: Container | Container[], options?: PrettyWidgetOptions) => void;
32
- export {};
33
+ declare const logWidget: (container: Container | Container[], options?: PrettyWidgetOptions) => void;
34
+ export { prettyWidget, logWidget, type PrettyWidgetOptions };
33
35
  //# sourceMappingURL=pretty-widget.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pretty-widget.d.ts","sourceRoot":"","sources":["../src/pretty-widget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,cAAc,CAAC;AAGzC,OAAO,EAAE,KAAK,SAAS,EAAS,MAAM,gBAAgB,CAAC;AAMvD,KAAK,gBAAgB,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC;AAEvD;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAC9B,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,gBAAgB,CAAC;CAC5B,CAAC;AAqGF;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,cAAe,SAAS,YAAW,mBAAmB,KAAQ,MAqBtF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,SAAS,cAAe,SAAS,GAAG,SAAS,EAAE,YAAY,mBAAmB,KAAG,IAK7F,CAAC"}
1
+ {"version":3,"file":"pretty-widget.d.ts","sourceRoot":"","sources":["../src/pretty-widget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,cAAc,CAAC;AAGzC,OAAO,EAAE,KAAK,SAAS,EAAS,MAAM,gBAAgB,CAAC;AAGvD,KAAK,gBAAgB,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC;AAEvD;;;GAGG;AACH,KAAK,mBAAmB,GAAG;IACvB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AA4KF;;;;;;;GAOG;AACH,QAAA,MAAM,YAAY,GAAI,WAAW,SAAS,EAAE,UAAS,mBAAwB,KAAG,MAoB/E,CAAC;AAEF;;;;;GAKG;AACH,QAAA,MAAM,SAAS,GAAI,WAAW,SAAS,GAAG,SAAS,EAAE,EAAE,UAAU,mBAAmB,KAAG,IAMtF,CAAC;AAEF,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,mBAAmB,EAAE,CAAC"}
@@ -4,6 +4,12 @@ import { roots } from "./traversal.js";
4
4
  import { getWidgetNodeText } from "./widget-accessible-properties.js";
5
5
  const DEFAULT_MAX_LENGTH = 7000;
6
6
  const INDENT = " ";
7
+ const ansi = {
8
+ cyan: "\u{1B}[36m",
9
+ yellow: "\u{1B}[33m",
10
+ green: "\u{1B}[32m",
11
+ reset: "\u{1B}[0m",
12
+ };
7
13
  const buildAttrs = (widget, getId) => {
8
14
  const attrs = [];
9
15
  if (getId) {
@@ -24,23 +30,20 @@ const buildAttrs = (widget, getId) => {
24
30
  const otherAttrs = sortStringsBy(attrs.filter(([key]) => key !== "id"), ([key]) => key);
25
31
  return [...idAttrs, ...otherAttrs];
26
32
  };
27
- const ansi = {
28
- cyan: "\x1b[36m",
29
- yellow: "\x1b[33m",
30
- green: "\x1b[32m",
31
- reset: "\x1b[0m",
32
- };
33
33
  const shouldHighlight = () => {
34
- if (typeof process === "undefined")
34
+ if (typeof process === "undefined") {
35
35
  return false;
36
- if (process.env.COLORS === "false" || process.env.NO_COLOR)
36
+ }
37
+ if (process.env.COLORS === "false" || process.env.NO_COLOR) {
37
38
  return false;
38
- if (process.env.COLORS === "true" || process.env.FORCE_COLOR)
39
+ }
40
+ if (process.env.COLORS === "true" || process.env.FORCE_COLOR) {
39
41
  return true;
42
+ }
40
43
  return process.stdout.isTTY;
41
44
  };
42
- const createColors = (enabled) => {
43
- if (!enabled) {
45
+ const createColors = (isEnabled) => {
46
+ if (!isEnabled) {
44
47
  const identity = (s) => s;
45
48
  return { tag: identity, attr: identity, value: identity };
46
49
  }
@@ -51,8 +54,40 @@ const createColors = (enabled) => {
51
54
  };
52
55
  };
53
56
  const escapeAttrValue = (value) => value.replaceAll('"', "&quot;");
54
- const formatAttrs = (attrs, colors) => attrs.map(([key, value]) => ` ${colors.attr(key)}=${colors.value(`"${escapeAttrValue(value)}"`)}`).join("");
55
- const formatWidget = (widget, depth, getId, colors) => {
57
+ const formatAttr = (key, value, colors) => {
58
+ const quoted = `"${escapeAttrValue(value)}"`;
59
+ return ` ${colors.attr(key)}=${colors.value(quoted)}`;
60
+ };
61
+ const formatAttrs = (attrs, colors) => attrs.map(([key, value]) => formatAttr(key, value, colors)).join("");
62
+ const countChildren = (widget) => {
63
+ let count = 0;
64
+ let child = widget.getFirstChild();
65
+ while (child) {
66
+ count += 1;
67
+ child = child.getNextSibling();
68
+ }
69
+ return count;
70
+ };
71
+ const formatHiddenChildrenLine = (widget, depth, ctx) => {
72
+ const { getId, colors } = ctx;
73
+ const indent = INDENT.repeat(depth);
74
+ const count = countChildren(widget);
75
+ const hint = getId ? ` (pass rootId="${getId(widget)}" or raise maxDepth to expand)` : "";
76
+ const plural = count === 1 ? "" : "s";
77
+ const summary = `… ${String(count)} child widget${plural} hidden${hint}`;
78
+ return `${indent}${INDENT}${colors.tag(summary)}\n`;
79
+ };
80
+ const formatChildren = (widget, depth, ctx) => {
81
+ let output = "";
82
+ let child = widget.getFirstChild();
83
+ while (child) {
84
+ output += formatWidget(child, depth + 1, ctx);
85
+ child = child.getNextSibling();
86
+ }
87
+ return output;
88
+ };
89
+ const formatWidget = (widget, depth, ctx) => {
90
+ const { getId, colors, maxDepth } = ctx;
56
91
  const indent = INDENT.repeat(depth);
57
92
  const tag = widget.constructor.name;
58
93
  const attrs = formatAttrs(buildAttrs(widget, getId), colors);
@@ -67,14 +102,19 @@ const formatWidget = (widget, depth, getId, colors) => {
67
102
  if (text) {
68
103
  output += `${indent}${INDENT}${text}\n`;
69
104
  }
70
- let child = firstChild;
71
- while (child) {
72
- output += formatWidget(child, depth + 1, getId, colors);
73
- child = child.getNextSibling();
105
+ if (firstChild && maxDepth !== undefined && depth >= maxDepth) {
106
+ output += formatHiddenChildrenLine(widget, depth, ctx);
107
+ output += `${indent}${closeTag}\n`;
108
+ return output;
74
109
  }
110
+ output += formatChildren(widget, depth, ctx);
75
111
  output += `${indent}${closeTag}\n`;
76
112
  return output;
77
113
  };
114
+ const resolveMaxLength = (options) => {
115
+ const envLimit = process.env.DEBUG_PRINT_LIMIT ? Number(process.env.DEBUG_PRINT_LIMIT) : DEFAULT_MAX_LENGTH;
116
+ return options.maxLength ?? envLimit;
117
+ };
78
118
  /**
79
119
  * Renders a widget tree as an indented, HTML-like string showing each widget's
80
120
  * tag, accessible attributes, and text content.
@@ -83,17 +123,16 @@ const formatWidget = (widget, depth, getId, colors) => {
83
123
  * @param options Formatting options such as truncation length and highlighting.
84
124
  * @returns The formatted representation of the tree.
85
125
  */
86
- export const prettyWidget = (container, options = {}) => {
87
- const envLimit = process.env.DEBUG_PRINT_LIMIT ? Number(process.env.DEBUG_PRINT_LIMIT) : DEFAULT_MAX_LENGTH;
88
- const maxLength = options.maxLength ?? envLimit;
126
+ const prettyWidget = (container, options = {}) => {
127
+ const maxLength = resolveMaxLength(options);
89
128
  if (maxLength === 0) {
90
129
  return "";
91
130
  }
92
- const highlight = options.highlight ?? shouldHighlight();
93
- const colors = createColors(highlight);
131
+ const isHighlight = options.highlight ?? shouldHighlight();
132
+ const colors = createColors(isHighlight);
94
133
  let output = "";
95
134
  for (const root of roots(container)) {
96
- output += formatWidget(root, 0, options.getId, colors);
135
+ output += formatWidget(root, 0, { getId: options.getId, colors, maxDepth: options.maxDepth });
97
136
  }
98
137
  if (output.length > maxLength) {
99
138
  return `${output.slice(0, maxLength)}...`;
@@ -106,10 +145,11 @@ export const prettyWidget = (container, options = {}) => {
106
145
  * @param container A single scope or an array of scopes to format and print.
107
146
  * @param options Formatting options passed through to {@link prettyWidget}.
108
147
  */
109
- export const logWidget = (container, options) => {
148
+ const logWidget = (container, options) => {
110
149
  const containers = Array.isArray(container) ? container : [container];
111
150
  for (const target of containers) {
112
151
  console.log(prettyWidget(target, options));
113
152
  }
114
153
  };
154
+ export { prettyWidget, logWidget };
115
155
  //# sourceMappingURL=pretty-widget.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pretty-widget.js","sourceRoot":"","sources":["../src/pretty-widget.ts"],"names":[],"mappings":"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","sourcesContent":["import type * as Gtk from \"@gtkx/gi/gtk\";\nimport { sortStringsBy } from \"@gtkx/utils\";\nimport { formatRole } from \"./role-helpers.js\";\nimport { type Container, roots } from \"./traversal.js\";\nimport { getWidgetNodeText } from \"./widget-accessible-properties.js\";\n\nconst DEFAULT_MAX_LENGTH = 7000;\nconst INDENT = \" \";\n\ntype WidgetIdResolver = (widget: Gtk.Widget) => string;\n\n/**\n * Options controlling how a widget tree is rendered to a string by\n * {@link prettyWidget} and {@link logWidget}.\n */\nexport type PrettyWidgetOptions = {\n /** Truncates the output once it exceeds this many characters. */\n maxLength?: number;\n /** Whether to apply ANSI color highlighting; defaults to the terminal capabilities. */\n highlight?: boolean;\n /** Resolves an `id` attribute to show for each widget. */\n getId?: WidgetIdResolver;\n};\n\nconst buildAttrs = (widget: Gtk.Widget, getId: WidgetIdResolver | undefined): [string, string][] => {\n const attrs: [string, string][] = [];\n\n if (getId) {\n attrs.push([\"id\", getId(widget)]);\n }\n\n const name = widget.getName();\n if (name) {\n attrs.push([\"name\", name]);\n }\n\n attrs.push([\"role\", formatRole(widget.getAccessibleRole())]);\n\n if (!widget.getSensitive()) {\n attrs.push([\"accessible-disabled\", \"true\"]);\n }\n\n if (!widget.getVisible()) {\n attrs.push([\"accessible-hidden\", \"true\"]);\n }\n\n const idAttrs = attrs.filter(([key]) => key === \"id\");\n const otherAttrs = sortStringsBy(\n attrs.filter(([key]) => key !== \"id\"),\n ([key]) => key,\n );\n return [...idAttrs, ...otherAttrs];\n};\n\ntype Colors = {\n tag: (s: string) => string;\n attr: (s: string) => string;\n value: (s: string) => string;\n};\n\nconst ansi = {\n cyan: \"\\x1b[36m\",\n yellow: \"\\x1b[33m\",\n green: \"\\x1b[32m\",\n reset: \"\\x1b[0m\",\n};\n\nconst shouldHighlight = (): boolean => {\n if (typeof process === \"undefined\") return false;\n if (process.env.COLORS === \"false\" || process.env.NO_COLOR) return false;\n if (process.env.COLORS === \"true\" || process.env.FORCE_COLOR) return true;\n return process.stdout.isTTY;\n};\n\nconst createColors = (enabled: boolean): Colors => {\n if (!enabled) {\n const identity = (s: string): string => s;\n return { tag: identity, attr: identity, value: identity };\n }\n return {\n tag: (s) => `${ansi.cyan}${s}${ansi.reset}`,\n attr: (s) => `${ansi.yellow}${s}${ansi.reset}`,\n value: (s) => `${ansi.green}${s}${ansi.reset}`,\n };\n};\n\nconst escapeAttrValue = (value: string): string => value.replaceAll('\"', \"&quot;\");\n\nconst formatAttrs = (attrs: [string, string][], colors: Colors): string =>\n attrs.map(([key, value]) => ` ${colors.attr(key)}=${colors.value(`\"${escapeAttrValue(value)}\"`)}`).join(\"\");\n\nconst formatWidget = (\n widget: Gtk.Widget,\n depth: number,\n getId: WidgetIdResolver | undefined,\n colors: Colors,\n): string => {\n const indent = INDENT.repeat(depth);\n const tag = widget.constructor.name;\n const attrs = formatAttrs(buildAttrs(widget, getId), colors);\n const openTag = `${colors.tag(\"<\")}${colors.tag(tag)}${attrs}${colors.tag(\">\")}`;\n const closeTag = `${colors.tag(\"</\")}${colors.tag(tag)}${colors.tag(\">\")}`;\n\n const text = getWidgetNodeText(widget);\n const firstChild = widget.getFirstChild();\n\n if (!text && !firstChild) {\n return `${indent}${openTag}\\n`;\n }\n\n let output = `${indent}${openTag}\\n`;\n if (text) {\n output += `${indent}${INDENT}${text}\\n`;\n }\n let child = firstChild;\n while (child) {\n output += formatWidget(child, depth + 1, getId, colors);\n child = child.getNextSibling();\n }\n output += `${indent}${closeTag}\\n`;\n return output;\n};\n\n/**\n * Renders a widget tree as an indented, HTML-like string showing each widget's\n * tag, accessible attributes, and text content.\n *\n * @param container The scope whose widget tree is formatted.\n * @param options Formatting options such as truncation length and highlighting.\n * @returns The formatted representation of the tree.\n */\nexport const prettyWidget = (container: Container, options: PrettyWidgetOptions = {}): string => {\n const envLimit = process.env.DEBUG_PRINT_LIMIT ? Number(process.env.DEBUG_PRINT_LIMIT) : DEFAULT_MAX_LENGTH;\n const maxLength = options.maxLength ?? envLimit;\n\n if (maxLength === 0) {\n return \"\";\n }\n\n const highlight = options.highlight ?? shouldHighlight();\n const colors = createColors(highlight);\n\n let output = \"\";\n for (const root of roots(container)) {\n output += formatWidget(root, 0, options.getId, colors);\n }\n\n if (output.length > maxLength) {\n return `${output.slice(0, maxLength)}...`;\n }\n\n return output.trimEnd();\n};\n\n/**\n * Prints one or more widget trees to the console using {@link prettyWidget}.\n *\n * @param container A single scope or an array of scopes to format and print.\n * @param options Formatting options passed through to {@link prettyWidget}.\n */\nexport const logWidget = (container: Container | Container[], options?: PrettyWidgetOptions): void => {\n const containers: Container[] = Array.isArray(container) ? container : [container];\n for (const target of containers) {\n console.log(prettyWidget(target, options));\n }\n};\n"]}
1
+ {"version":3,"file":"pretty-widget.js","sourceRoot":"","sources":["../src/pretty-widget.ts"],"names":[],"mappings":"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","sourcesContent":["import type * as Gtk from \"@gtkx/gi/gtk\";\nimport { sortStringsBy } from \"@gtkx/utils\";\nimport { formatRole } from \"./role-helpers.js\";\nimport { type Container, roots } from \"./traversal.js\";\nimport { getWidgetNodeText } from \"./widget-accessible-properties.js\";\n\ntype WidgetIdResolver = (widget: Gtk.Widget) => string;\n\n/**\n * Options controlling how a widget tree is rendered to a string by\n * {@link prettyWidget} and {@link logWidget}.\n */\ntype PrettyWidgetOptions = {\n /** Truncates the output once it exceeds this many characters. */\n maxLength?: number;\n /** Whether to apply ANSI color highlighting; defaults to the terminal capabilities. */\n highlight?: boolean;\n /** Resolves an `id` attribute to show for each widget. */\n getId?: WidgetIdResolver;\n /** Stops descending past this depth, replacing deeper children with a summary line. */\n maxDepth?: number;\n};\n\ntype Colors = {\n tag: (s: string) => string;\n attr: (s: string) => string;\n value: (s: string) => string;\n};\n\ntype FormatContext = {\n getId: WidgetIdResolver | undefined;\n colors: Colors;\n maxDepth: number | undefined;\n};\n\nconst DEFAULT_MAX_LENGTH = 7000;\nconst INDENT = \" \";\n\nconst ansi = {\n cyan: \"\\u{1B}[36m\",\n yellow: \"\\u{1B}[33m\",\n green: \"\\u{1B}[32m\",\n reset: \"\\u{1B}[0m\",\n};\n\nconst buildAttrs = (widget: Gtk.Widget, getId: WidgetIdResolver | undefined): [string, string][] => {\n const attrs: [string, string][] = [];\n\n if (getId) {\n attrs.push([\"id\", getId(widget)]);\n }\n\n const name = widget.getName();\n\n if (name) {\n attrs.push([\"name\", name]);\n }\n\n attrs.push([\"role\", formatRole(widget.getAccessibleRole())]);\n\n if (!widget.getSensitive()) {\n attrs.push([\"accessible-disabled\", \"true\"]);\n }\n\n if (!widget.getVisible()) {\n attrs.push([\"accessible-hidden\", \"true\"]);\n }\n\n const idAttrs = attrs.filter(([key]) => key === \"id\");\n\n const otherAttrs = sortStringsBy(\n attrs.filter(([key]) => key !== \"id\"),\n ([key]) => key,\n );\n\n return [...idAttrs, ...otherAttrs];\n};\n\nconst shouldHighlight = (): boolean => {\n if (typeof process === \"undefined\") {\n return false;\n }\n\n if (process.env.COLORS === \"false\" || process.env.NO_COLOR) {\n return false;\n }\n\n if (process.env.COLORS === \"true\" || process.env.FORCE_COLOR) {\n return true;\n }\n\n return process.stdout.isTTY;\n};\n\nconst createColors = (isEnabled: boolean): Colors => {\n if (!isEnabled) {\n const identity = (s: string): string => s;\n\n return { tag: identity, attr: identity, value: identity };\n }\n\n return {\n tag: (s) => `${ansi.cyan}${s}${ansi.reset}`,\n attr: (s) => `${ansi.yellow}${s}${ansi.reset}`,\n value: (s) => `${ansi.green}${s}${ansi.reset}`,\n };\n};\n\nconst escapeAttrValue = (value: string): string => value.replaceAll('\"', \"&quot;\");\n\nconst formatAttr = (key: string, value: string, colors: Colors): string => {\n const quoted = `\"${escapeAttrValue(value)}\"`;\n\n return ` ${colors.attr(key)}=${colors.value(quoted)}`;\n};\n\nconst formatAttrs = (attrs: [string, string][], colors: Colors): string =>\n attrs.map(([key, value]) => formatAttr(key, value, colors)).join(\"\");\n\nconst countChildren = (widget: Gtk.Widget): number => {\n let count = 0;\n let child = widget.getFirstChild();\n\n while (child) {\n count += 1;\n child = child.getNextSibling();\n }\n\n return count;\n};\n\nconst formatHiddenChildrenLine = (widget: Gtk.Widget, depth: number, ctx: FormatContext): string => {\n const { getId, colors } = ctx;\n const indent = INDENT.repeat(depth);\n const count = countChildren(widget);\n const hint = getId ? ` (pass rootId=\"${getId(widget)}\" or raise maxDepth to expand)` : \"\";\n const plural = count === 1 ? \"\" : \"s\";\n const summary = `… ${String(count)} child widget${plural} hidden${hint}`;\n\n return `${indent}${INDENT}${colors.tag(summary)}\\n`;\n};\n\nconst formatChildren = (widget: Gtk.Widget, depth: number, ctx: FormatContext): string => {\n let output = \"\";\n let child = widget.getFirstChild();\n\n while (child) {\n output += formatWidget(child, depth + 1, ctx);\n child = child.getNextSibling();\n }\n\n return output;\n};\n\nconst formatWidget = (widget: Gtk.Widget, depth: number, ctx: FormatContext): string => {\n const { getId, colors, maxDepth } = ctx;\n const indent = INDENT.repeat(depth);\n const tag = widget.constructor.name;\n const attrs = formatAttrs(buildAttrs(widget, getId), colors);\n const openTag = `${colors.tag(\"<\")}${colors.tag(tag)}${attrs}${colors.tag(\">\")}`;\n const closeTag = `${colors.tag(\"</\")}${colors.tag(tag)}${colors.tag(\">\")}`;\n const text = getWidgetNodeText(widget);\n const firstChild = widget.getFirstChild();\n\n if (!text && !firstChild) {\n return `${indent}${openTag}\\n`;\n }\n\n let output = `${indent}${openTag}\\n`;\n\n if (text) {\n output += `${indent}${INDENT}${text}\\n`;\n }\n\n if (firstChild && maxDepth !== undefined && depth >= maxDepth) {\n output += formatHiddenChildrenLine(widget, depth, ctx);\n output += `${indent}${closeTag}\\n`;\n\n return output;\n }\n\n output += formatChildren(widget, depth, ctx);\n output += `${indent}${closeTag}\\n`;\n\n return output;\n};\n\nconst resolveMaxLength = (options: PrettyWidgetOptions): number => {\n const envLimit = process.env.DEBUG_PRINT_LIMIT ? Number(process.env.DEBUG_PRINT_LIMIT) : DEFAULT_MAX_LENGTH;\n\n return options.maxLength ?? envLimit;\n};\n\n/**\n * Renders a widget tree as an indented, HTML-like string showing each widget's\n * tag, accessible attributes, and text content.\n *\n * @param container The scope whose widget tree is formatted.\n * @param options Formatting options such as truncation length and highlighting.\n * @returns The formatted representation of the tree.\n */\nconst prettyWidget = (container: Container, options: PrettyWidgetOptions = {}): string => {\n const maxLength = resolveMaxLength(options);\n\n if (maxLength === 0) {\n return \"\";\n }\n\n const isHighlight = options.highlight ?? shouldHighlight();\n const colors = createColors(isHighlight);\n let output = \"\";\n\n for (const root of roots(container)) {\n output += formatWidget(root, 0, { getId: options.getId, colors, maxDepth: options.maxDepth });\n }\n\n if (output.length > maxLength) {\n return `${output.slice(0, maxLength)}...`;\n }\n\n return output.trimEnd();\n};\n\n/**\n * Prints one or more widget trees to the console using {@link prettyWidget}.\n *\n * @param container A single scope or an array of scopes to format and print.\n * @param options Formatting options passed through to {@link prettyWidget}.\n */\nconst logWidget = (container: Container | Container[], options?: PrettyWidgetOptions): void => {\n const containers: Container[] = Array.isArray(container) ? container : [container];\n\n for (const target of containers) {\n console.log(prettyWidget(target, options));\n }\n};\n\nexport { prettyWidget, logWidget, type PrettyWidgetOptions };\n"]}