@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.
- package/dist/act.d.ts +6 -6
- package/dist/act.d.ts.map +1 -1
- package/dist/act.js +34 -26
- package/dist/act.js.map +1 -1
- package/dist/bound-queries.d.ts +4 -3
- package/dist/bound-queries.d.ts.map +1 -1
- package/dist/bound-queries.js.map +1 -1
- package/dist/build-queries.d.ts +5 -5
- package/dist/build-queries.d.ts.map +1 -1
- package/dist/build-queries.js +21 -9
- package/dist/build-queries.js.map +1 -1
- package/dist/cleanup-registry.d.ts +4 -3
- package/dist/cleanup-registry.d.ts.map +1 -1
- package/dist/cleanup-registry.js +13 -3
- package/dist/cleanup-registry.js.map +1 -1
- package/dist/config.d.ts +5 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -9
- package/dist/config.js.map +1 -1
- package/dist/editable.d.ts +6 -5
- package/dist/editable.d.ts.map +1 -1
- package/dist/editable.js +7 -5
- package/dist/editable.js.map +1 -1
- package/dist/errors.d.ts +7 -6
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +64 -48
- package/dist/errors.js.map +1 -1
- package/dist/fire-event.d.ts +2 -1
- package/dist/fire-event.d.ts.map +1 -1
- package/dist/fire-event.js +2 -1
- package/dist/fire-event.js.map +1 -1
- package/dist/frame-sync.d.ts +2 -1
- package/dist/frame-sync.d.ts.map +1 -1
- package/dist/frame-sync.js +28 -21
- package/dist/frame-sync.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/matchers.d.ts +38 -34
- package/dist/matchers.d.ts.map +1 -1
- package/dist/matchers.js +72 -58
- package/dist/matchers.js.map +1 -1
- package/dist/pretty-widget.d.ts +6 -4
- package/dist/pretty-widget.d.ts.map +1 -1
- package/dist/pretty-widget.js +64 -24
- package/dist/pretty-widget.js.map +1 -1
- package/dist/queries.d.ts +143 -86
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +231 -147
- package/dist/queries.js.map +1 -1
- package/dist/register-test-hooks.js +2 -1
- package/dist/register-test-hooks.js.map +1 -1
- package/dist/render-hook.d.ts +3 -2
- package/dist/render-hook.d.ts.map +1 -1
- package/dist/render-hook.js +2 -1
- package/dist/render-hook.js.map +1 -1
- package/dist/render.d.ts +3 -2
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +24 -18
- package/dist/render.js.map +1 -1
- package/dist/role-helpers.d.ts +6 -5
- package/dist/role-helpers.d.ts.map +1 -1
- package/dist/role-helpers.js +16 -12
- package/dist/role-helpers.js.map +1 -1
- package/dist/screen.d.ts +4 -3
- package/dist/screen.d.ts.map +1 -1
- package/dist/screen.js +13 -11
- package/dist/screen.js.map +1 -1
- package/dist/screenshot.d.ts +4 -3
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +43 -36
- package/dist/screenshot.js.map +1 -1
- package/dist/suggestions.d.ts +11 -6
- package/dist/suggestions.d.ts.map +1 -1
- package/dist/suggestions.js +10 -5
- package/dist/suggestions.js.map +1 -1
- package/dist/traversal.d.ts +7 -7
- package/dist/traversal.d.ts.map +1 -1
- package/dist/traversal.js +17 -11
- package/dist/traversal.js.map +1 -1
- package/dist/types.d.ts +39 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/user-event/adjustment.d.ts +5 -3
- package/dist/user-event/adjustment.d.ts.map +1 -1
- package/dist/user-event/adjustment.js +6 -4
- package/dist/user-event/adjustment.js.map +1 -1
- package/dist/user-event/click.d.ts +6 -5
- package/dist/user-event/click.d.ts.map +1 -1
- package/dist/user-event/click.js +29 -21
- package/dist/user-event/click.js.map +1 -1
- package/dist/user-event/controller.d.ts +6 -5
- package/dist/user-event/controller.d.ts.map +1 -1
- package/dist/user-event/controller.js +9 -6
- package/dist/user-event/controller.js.map +1 -1
- package/dist/user-event/dispatch.d.ts +3 -3
- package/dist/user-event/dispatch.d.ts.map +1 -1
- package/dist/user-event/dispatch.js +3 -2
- package/dist/user-event/dispatch.js.map +1 -1
- package/dist/user-event/event-wrapper.d.ts +2 -1
- package/dist/user-event/event-wrapper.d.ts.map +1 -1
- package/dist/user-event/event-wrapper.js +27 -16
- package/dist/user-event/event-wrapper.js.map +1 -1
- package/dist/user-event/gesture.d.ts +18 -14
- package/dist/user-event/gesture.d.ts.map +1 -1
- package/dist/user-event/gesture.js +70 -37
- package/dist/user-event/gesture.js.map +1 -1
- package/dist/user-event/index.d.ts +17 -10
- package/dist/user-event/index.d.ts.map +1 -1
- package/dist/user-event/index.js +7 -3
- package/dist/user-event/index.js.map +1 -1
- package/dist/user-event/keyboard.d.ts +4 -3
- package/dist/user-event/keyboard.d.ts.map +1 -1
- package/dist/user-event/keyboard.js +89 -58
- package/dist/user-event/keyboard.js.map +1 -1
- package/dist/user-event/pointer.d.ts +7 -3
- package/dist/user-event/pointer.d.ts.map +1 -1
- package/dist/user-event/pointer.js +4 -1
- package/dist/user-event/pointer.js.map +1 -1
- package/dist/user-event/selection.d.ts +3 -2
- package/dist/user-event/selection.d.ts.map +1 -1
- package/dist/user-event/selection.js +26 -27
- package/dist/user-event/selection.js.map +1 -1
- package/dist/user-event/state.d.ts +3 -2
- package/dist/user-event/state.d.ts.map +1 -1
- package/dist/user-event/state.js +2 -1
- package/dist/user-event/state.js.map +1 -1
- package/dist/user-event/text.d.ts +8 -7
- package/dist/user-event/text.d.ts.map +1 -1
- package/dist/user-event/text.js +39 -33
- package/dist/user-event/text.js.map +1 -1
- package/dist/wait-for.d.ts +5 -5
- package/dist/wait-for.d.ts.map +1 -1
- package/dist/wait-for.js +45 -30
- package/dist/wait-for.js.map +1 -1
- package/dist/widget-accessible-properties.d.ts +26 -25
- package/dist/widget-accessible-properties.d.ts.map +1 -1
- package/dist/widget-accessible-properties.js +106 -61
- package/dist/widget-accessible-properties.js.map +1 -1
- package/dist/within.d.ts +2 -1
- package/dist/within.d.ts.map +1 -1
- package/dist/within.js +2 -1
- package/dist/within.js.map +1 -1
- package/package.json +11 -9
- package/src/act.ts +44 -35
- package/src/bound-queries.ts +5 -4
- package/src/build-queries.ts +84 -43
- package/src/cleanup-registry.ts +18 -5
- package/src/config.ts +16 -12
- package/src/editable.ts +13 -6
- package/src/errors.ts +106 -45
- package/src/fire-event.ts +3 -1
- package/src/frame-sync.ts +38 -21
- package/src/index.ts +2 -1
- package/src/matchers.ts +158 -107
- package/src/pretty-widget.ts +114 -42
- package/src/queries.ts +422 -233
- package/src/register-test-hooks.ts +4 -1
- package/src/render-hook.tsx +10 -4
- package/src/render.tsx +58 -33
- package/src/role-helpers.ts +29 -16
- package/src/screen.ts +17 -12
- package/src/screenshot.ts +61 -38
- package/src/suggestions.ts +29 -10
- package/src/traversal.ts +35 -15
- package/src/types.ts +65 -32
- package/src/user-event/adjustment.ts +23 -10
- package/src/user-event/click.ts +49 -20
- package/src/user-event/controller.ts +21 -7
- package/src/user-event/dispatch.ts +4 -2
- package/src/user-event/event-wrapper.ts +42 -16
- package/src/user-event/gesture.ts +135 -50
- package/src/user-event/index.ts +19 -12
- package/src/user-event/keyboard.ts +147 -61
- package/src/user-event/pointer.ts +20 -8
- package/src/user-event/selection.ts +69 -32
- package/src/user-event/state.ts +4 -2
- package/src/user-event/text.ts +73 -38
- package/src/wait-for.ts +77 -36
- package/src/widget-accessible-properties.ts +218 -74
- package/src/within.ts +7 -4
package/dist/screen.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ import type { RenderResult, Screen } from "./bound-queries.js";
|
|
|
3
3
|
* Queries and debug utilities bound to the most recent render, scoped to the
|
|
4
4
|
* current toplevel windows. Accessing it before any render throws.
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
declare const screen: Screen;
|
|
7
|
+
declare const setScreen: (result: RenderResult) => void;
|
|
8
|
+
declare const clearScreen: () => void;
|
|
9
|
+
export { screen, setScreen, clearScreen };
|
|
9
10
|
//# sourceMappingURL=screen.d.ts.map
|
package/dist/screen.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../src/screen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../src/screen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAgB/D;;;GAGG;AACH,QAAA,MAAM,MAAM,EAAE,MAEZ,CAAC;AAMH,QAAA,MAAM,SAAS,GAAI,QAAQ,YAAY,KAAG,IAEzC,CAAC;AAEF,QAAA,MAAM,WAAW,QAAO,IAEvB,CAAC;AAEF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/screen.js
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
const NO_RENDER_MESSAGE = "No render has been performed: call render() before using screen queries";
|
|
2
|
-
const throwNoRender = () => {
|
|
3
|
-
throw new Error(NO_RENDER_MESSAGE);
|
|
4
|
-
};
|
|
5
2
|
const defaultScreen = new Proxy({}, {
|
|
6
3
|
get: (_target, property) => {
|
|
7
|
-
if (property === "baseElement" || property === "container")
|
|
4
|
+
if (property === "baseElement" || property === "container") {
|
|
8
5
|
return throwNoRender();
|
|
6
|
+
}
|
|
9
7
|
return throwNoRender;
|
|
10
8
|
},
|
|
11
9
|
});
|
|
12
|
-
|
|
10
|
+
const activeScreen = { current: defaultScreen };
|
|
13
11
|
/**
|
|
14
12
|
* Queries and debug utilities bound to the most recent render, scoped to the
|
|
15
13
|
* current toplevel windows. Accessing it before any render throws.
|
|
16
14
|
*/
|
|
17
|
-
|
|
18
|
-
get: (_target, property) => Reflect.get(activeScreen, property),
|
|
15
|
+
const screen = new Proxy({}, {
|
|
16
|
+
get: (_target, property) => Reflect.get(activeScreen.current, property),
|
|
19
17
|
});
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
const throwNoRender = () => {
|
|
19
|
+
throw new Error(NO_RENDER_MESSAGE);
|
|
20
|
+
};
|
|
21
|
+
const setScreen = (result) => {
|
|
22
|
+
activeScreen.current = result;
|
|
22
23
|
};
|
|
23
|
-
|
|
24
|
-
activeScreen = defaultScreen;
|
|
24
|
+
const clearScreen = () => {
|
|
25
|
+
activeScreen.current = defaultScreen;
|
|
25
26
|
};
|
|
27
|
+
export { screen, setScreen, clearScreen };
|
|
26
28
|
//# sourceMappingURL=screen.js.map
|
package/dist/screen.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen.js","sourceRoot":"","sources":["../src/screen.ts"],"names":[],"mappings":"AAEA,MAAM,iBAAiB,GAAG,yEAAyE,CAAC;AAEpG,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"screen.js","sourceRoot":"","sources":["../src/screen.ts"],"names":[],"mappings":"AAEA,MAAM,iBAAiB,GAAG,yEAAyE,CAAC;AAEpG,MAAM,aAAa,GAAiB,IAAI,KAAK,CAAC,EAAkB,EAAE;IAC9D,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;QACvB,IAAI,QAAQ,KAAK,aAAa,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;YACzD,OAAO,aAAa,EAAE,CAAC;QAC3B,CAAC;QAED,OAAO,aAAa,CAAC;IACzB,CAAC;CACJ,CAAC,CAAC;AAEH,MAAM,YAAY,GAA8B,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;AAE3E;;;GAGG;AACH,MAAM,MAAM,GAAW,IAAI,KAAK,CAAC,EAAY,EAAE;IAC3C,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAW,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC;CACnF,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,GAAU,EAAE;IAC9B,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;AACvC,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,CAAC,MAAoB,EAAQ,EAAE;IAC7C,YAAY,CAAC,OAAO,GAAG,MAAM,CAAC;AAClC,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,GAAS,EAAE;IAC3B,YAAY,CAAC,OAAO,GAAG,aAAa,CAAC;AACzC,CAAC,CAAC;AAEF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC","sourcesContent":["import type { RenderResult, Screen } from \"./bound-queries.js\";\n\nconst NO_RENDER_MESSAGE = \"No render has been performed: call render() before using screen queries\";\n\nconst defaultScreen: RenderResult = new Proxy({} as RenderResult, {\n get: (_target, property) => {\n if (property === \"baseElement\" || property === \"container\") {\n return throwNoRender();\n }\n\n return throwNoRender;\n },\n});\n\nconst activeScreen: { current: RenderResult } = { current: defaultScreen };\n\n/**\n * Queries and debug utilities bound to the most recent render, scoped to the\n * current toplevel windows. Accessing it before any render throws.\n */\nconst screen: Screen = new Proxy({} as Screen, {\n get: (_target, property): unknown => Reflect.get(activeScreen.current, property),\n});\n\nconst throwNoRender = (): never => {\n throw new Error(NO_RENDER_MESSAGE);\n};\n\nconst setScreen = (result: RenderResult): void => {\n activeScreen.current = result;\n};\n\nconst clearScreen = (): void => {\n activeScreen.current = defaultScreen;\n};\n\nexport { screen, setScreen, clearScreen };\n"]}
|
package/dist/screenshot.d.ts
CHANGED
|
@@ -7,13 +7,13 @@ import type { ScreenshotOptions, ScreenshotResult, WindowSelector } from "./type
|
|
|
7
7
|
* @param options Optional scale, timeout, and retry interval.
|
|
8
8
|
* @returns The base64-encoded PNG data along with its mime type and dimensions.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
declare const screenshot: (widget: Gtk.Widget, options?: ScreenshotOptions) => Promise<ScreenshotResult>;
|
|
11
11
|
/**
|
|
12
12
|
* Prints a clickable file URL for a saved screenshot to the console.
|
|
13
13
|
*
|
|
14
14
|
* @param filepath Absolute path of the saved screenshot file.
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
declare const logScreenshotPath: (filepath: string) => void;
|
|
17
17
|
/**
|
|
18
18
|
* Captures a screenshot of a toplevel window, writes it to a temporary file,
|
|
19
19
|
* logs its path, and returns the image data.
|
|
@@ -23,5 +23,6 @@ export declare const logScreenshotPath: (filepath: string) => void;
|
|
|
23
23
|
* @param options Optional scale, timeout, and retry interval.
|
|
24
24
|
* @returns The base64-encoded PNG data along with its mime type and dimensions.
|
|
25
25
|
*/
|
|
26
|
-
|
|
26
|
+
declare const captureAndSaveScreenshot: (selector?: WindowSelector, options?: ScreenshotOptions) => Promise<ScreenshotResult>;
|
|
27
|
+
export { screenshot, logScreenshotPath, captureAndSaveScreenshot };
|
|
27
28
|
//# sourceMappingURL=screenshot.d.ts.map
|
package/dist/screenshot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAIpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAuDtF;;;;;;GAMG;AACH,QAAA,MAAM,UAAU,GAAU,QAAQ,GAAG,CAAC,MAAM,EAAE,UAAU,iBAAiB,KAAG,OAAO,CAAC,gBAAgB,CAWnG,CAAC;AAqEF;;;;GAIG;AACH,QAAA,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,IAE7C,CAAC;AAEF;;;;;;;;GAQG;AACH,QAAA,MAAM,wBAAwB,GAC1B,WAAW,cAAc,EACzB,UAAU,iBAAiB,KAC5B,OAAO,CAAC,gBAAgB,CAM1B,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,CAAC"}
|
package/dist/screenshot.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
1
|
import * as Gsk from "@gtkx/gi/gsk";
|
|
5
2
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
3
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
6
|
import { waitFor } from "./wait-for.js";
|
|
7
|
+
const DEFAULT_SCREENSHOT_INTERVAL = 10;
|
|
7
8
|
const bytesToBase64 = (bytes) => {
|
|
8
9
|
return Buffer.from(bytes).toString("base64");
|
|
9
10
|
};
|
|
10
|
-
const
|
|
11
|
-
|
|
11
|
+
const describeWidgetState = (widget) => `realized=${String(widget.getRealized())} mapped=${String(widget.getMapped())} ` +
|
|
12
|
+
`visible=${String(widget.getVisible())}`;
|
|
12
13
|
const captureSnapshot = (widget, scale) => {
|
|
13
14
|
const paintable = new Gtk.WidgetPaintable({ widget });
|
|
14
15
|
const width = paintable.getIntrinsicWidth();
|
|
@@ -51,54 +52,59 @@ const captureSnapshot = (widget, scale) => {
|
|
|
51
52
|
* @param options Optional scale, timeout, and retry interval.
|
|
52
53
|
* @returns The base64-encoded PNG data along with its mime type and dimensions.
|
|
53
54
|
*/
|
|
54
|
-
|
|
55
|
+
const screenshot = async (widget, options) => {
|
|
55
56
|
const scale = options?.scale ?? 1;
|
|
56
57
|
if (!Number.isFinite(scale) || scale <= 0) {
|
|
57
|
-
throw new Error(`Screenshot scale must be a positive number, got ${scale}`);
|
|
58
|
+
throw new Error(`Screenshot scale must be a positive number, got ${String(scale)}`);
|
|
58
59
|
}
|
|
59
60
|
return waitFor(() => captureSnapshot(widget, scale), {
|
|
60
61
|
timeout: options?.timeout,
|
|
61
62
|
interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,
|
|
62
63
|
});
|
|
63
64
|
};
|
|
65
|
+
const firstToplevelWindow = (windows) => {
|
|
66
|
+
const [first] = windows;
|
|
67
|
+
if (!(first instanceof Gtk.Window)) {
|
|
68
|
+
throw new TypeError("First toplevel is not a Window");
|
|
69
|
+
}
|
|
70
|
+
return first;
|
|
71
|
+
};
|
|
72
|
+
const windowAtIndex = (windows, index) => {
|
|
73
|
+
const indexed = windows[index];
|
|
74
|
+
if (!(indexed instanceof Gtk.Window)) {
|
|
75
|
+
throw new TypeError(`Window at index ${String(index)} not found`);
|
|
76
|
+
}
|
|
77
|
+
return indexed;
|
|
78
|
+
};
|
|
79
|
+
const isWindow = (widget) => widget instanceof Gtk.Window;
|
|
80
|
+
const hasMatchingTitle = (window, selector) => {
|
|
81
|
+
const title = window.getTitle() ?? "";
|
|
82
|
+
return selector instanceof RegExp ? selector.test(title) : title.includes(selector);
|
|
83
|
+
};
|
|
84
|
+
const describeTitleSelector = (selector) => selector instanceof RegExp ? selector.toString() : `"${selector}"`;
|
|
85
|
+
const windowByTitle = (windows, selector) => {
|
|
86
|
+
const found = windows.filter(isWindow).find((window) => hasMatchingTitle(window, selector));
|
|
87
|
+
if (!found) {
|
|
88
|
+
throw new Error(`No window found with title matching ${describeTitleSelector(selector)}`);
|
|
89
|
+
}
|
|
90
|
+
return found;
|
|
91
|
+
};
|
|
64
92
|
const resolveWindow = (selector) => {
|
|
65
93
|
const windows = Gtk.Window.listToplevels();
|
|
66
94
|
if (windows.length === 0) {
|
|
67
95
|
throw new Error("No windows available for screenshot");
|
|
68
96
|
}
|
|
69
97
|
if (selector === undefined) {
|
|
70
|
-
|
|
71
|
-
if (!(first instanceof Gtk.Window)) {
|
|
72
|
-
throw new TypeError("First toplevel is not a Window");
|
|
73
|
-
}
|
|
74
|
-
return first;
|
|
98
|
+
return firstToplevelWindow(windows);
|
|
75
99
|
}
|
|
76
100
|
if (typeof selector === "number") {
|
|
77
|
-
|
|
78
|
-
if (!(indexed instanceof Gtk.Window)) {
|
|
79
|
-
throw new TypeError(`Window at index ${selector} not found`);
|
|
80
|
-
}
|
|
81
|
-
return indexed;
|
|
101
|
+
return windowAtIndex(windows, selector);
|
|
82
102
|
}
|
|
83
|
-
|
|
84
|
-
const found = windows.find((w) => {
|
|
85
|
-
if (!(w instanceof Gtk.Window))
|
|
86
|
-
return false;
|
|
87
|
-
const title = w.getTitle() ?? "";
|
|
88
|
-
return isRegex ? selector.test(title) : title.includes(selector);
|
|
89
|
-
});
|
|
90
|
-
if (!found) {
|
|
91
|
-
const pattern = isRegex ? selector.toString() : `"${selector}"`;
|
|
92
|
-
throw new Error(`No window found with title matching ${pattern}`);
|
|
93
|
-
}
|
|
94
|
-
return found;
|
|
103
|
+
return windowByTitle(windows, selector);
|
|
95
104
|
};
|
|
96
105
|
const saveScreenshotToTempFile = (result) => {
|
|
97
|
-
const dir = join(tmpdir(), "gtkx-screenshots");
|
|
98
|
-
|
|
99
|
-
mkdirSync(dir, { recursive: true });
|
|
100
|
-
}
|
|
101
|
-
const filepath = join(dir, `${Date.now()}-screenshot.png`);
|
|
106
|
+
const dir = mkdtempSync(join(tmpdir(), "gtkx-screenshots-"));
|
|
107
|
+
const filepath = join(dir, "screenshot.png");
|
|
102
108
|
writeFileSync(filepath, Buffer.from(result.data, "base64"));
|
|
103
109
|
return filepath;
|
|
104
110
|
};
|
|
@@ -107,7 +113,7 @@ const saveScreenshotToTempFile = (result) => {
|
|
|
107
113
|
*
|
|
108
114
|
* @param filepath Absolute path of the saved screenshot file.
|
|
109
115
|
*/
|
|
110
|
-
|
|
116
|
+
const logScreenshotPath = (filepath) => {
|
|
111
117
|
console.log(`Screenshot saved: file://${filepath}`);
|
|
112
118
|
};
|
|
113
119
|
/**
|
|
@@ -119,10 +125,11 @@ export const logScreenshotPath = (filepath) => {
|
|
|
119
125
|
* @param options Optional scale, timeout, and retry interval.
|
|
120
126
|
* @returns The base64-encoded PNG data along with its mime type and dimensions.
|
|
121
127
|
*/
|
|
122
|
-
|
|
128
|
+
const captureAndSaveScreenshot = async (selector, options) => {
|
|
123
129
|
const target = resolveWindow(selector);
|
|
124
130
|
const result = await screenshot(target, options);
|
|
125
131
|
logScreenshotPath(saveScreenshotToTempFile(result));
|
|
126
132
|
return result;
|
|
127
133
|
};
|
|
134
|
+
export { screenshot, logScreenshotPath, captureAndSaveScreenshot };
|
|
128
135
|
//# sourceMappingURL=screenshot.js.map
|
package/dist/screenshot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport * as Gsk from \"@gtkx/gi/gsk\";\nimport * as Gtk from \"@gtkx/gi/gtk\";\nimport type { ScreenshotOptions, ScreenshotResult, WindowSelector } from \"./types.js\";\nimport { waitFor } from \"./wait-for.js\";\n\nconst bytesToBase64 = (bytes: number[]): string => {\n return Buffer.from(bytes).toString(\"base64\");\n};\n\nconst DEFAULT_SCREENSHOT_INTERVAL = 10;\n\nconst describeWidgetState = (widget: Gtk.Widget): string =>\n `realized=${widget.getRealized()} mapped=${widget.getMapped()} visible=${widget.getVisible()}`;\n\nconst captureSnapshot = (widget: Gtk.Widget, scale: number): ScreenshotResult => {\n const paintable = new Gtk.WidgetPaintable({ widget });\n const width = paintable.getIntrinsicWidth();\n const height = paintable.getIntrinsicHeight();\n\n if (width <= 0 || height <= 0) {\n throw new Error(`Widget has no size: ensure it is realized and visible (${describeWidgetState(widget)})`);\n }\n\n const snapshot = new Gtk.Snapshot();\n snapshot.scale(scale, scale);\n paintable.snapshot(snapshot, width, height);\n const renderNode = snapshot.toNode();\n\n if (!renderNode) {\n throw new Error(`Widget produced no render content (${describeWidgetState(widget)})`);\n }\n\n const display = widget.getDisplay();\n\n const renderer = new Gsk.CairoRenderer();\n renderer.realizeForDisplay(display);\n\n try {\n const texture = renderer.renderTexture(renderNode, null);\n const pngBytes = texture.saveToPngBytes();\n const data = pngBytes.getData();\n\n if (!data) {\n throw new Error(\"Failed to serialize screenshot to PNG\");\n }\n\n return {\n data: bytesToBase64(data),\n mimeType: \"image/png\",\n width: Math.round(width * scale),\n height: Math.round(height * scale),\n };\n } finally {\n renderer.unrealize();\n }\n};\n\n/**\n * Captures a PNG snapshot of a widget, retrying until it has a paintable size.\n *\n * @param widget The widget to render to an image.\n * @param options Optional scale, timeout, and retry interval.\n * @returns The base64-encoded PNG data along with its mime type and dimensions.\n */\nexport const screenshot = async (widget: Gtk.Widget, options?: ScreenshotOptions): Promise<ScreenshotResult> => {\n const scale = options?.scale ?? 1;\n\n if (!Number.isFinite(scale) || scale <= 0) {\n throw new Error(`Screenshot scale must be a positive number, got ${scale}`);\n }\n\n return waitFor(() => captureSnapshot(widget, scale), {\n timeout: options?.timeout,\n interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,\n });\n};\n\nconst resolveWindow = (selector?: WindowSelector): Gtk.Window => {\n const windows = Gtk.Window.listToplevels();\n\n if (windows.length === 0) {\n throw new Error(\"No windows available for screenshot\");\n }\n\n if (selector === undefined) {\n const [first] = windows;\n if (!(first instanceof Gtk.Window)) {\n throw new TypeError(\"First toplevel is not a Window\");\n }\n return first;\n }\n\n if (typeof selector === \"number\") {\n const indexed = windows[selector];\n if (!(indexed instanceof Gtk.Window)) {\n throw new TypeError(`Window at index ${selector} not found`);\n }\n return indexed;\n }\n\n const isRegex = selector instanceof RegExp;\n const found = windows.find((w): w is Gtk.Window => {\n if (!(w instanceof Gtk.Window)) return false;\n const title = w.getTitle() ?? \"\";\n return isRegex ? selector.test(title) : title.includes(selector);\n });\n\n if (!found) {\n const pattern = isRegex ? selector.toString() : `\"${selector}\"`;\n throw new Error(`No window found with title matching ${pattern}`);\n }\n return found;\n};\n\nconst saveScreenshotToTempFile = (result: ScreenshotResult): string => {\n const dir = join(tmpdir(), \"gtkx-screenshots\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n const filepath = join(dir, `${Date.now()}-screenshot.png`);\n writeFileSync(filepath, Buffer.from(result.data, \"base64\"));\n return filepath;\n};\n\n/**\n * Prints a clickable file URL for a saved screenshot to the console.\n *\n * @param filepath Absolute path of the saved screenshot file.\n */\nexport const logScreenshotPath = (filepath: string): void => {\n console.log(`Screenshot saved: file://${filepath}`);\n};\n\n/**\n * Captures a screenshot of a toplevel window, writes it to a temporary file,\n * logs its path, and returns the image data.\n *\n * @param selector Chooses the window by index, title substring, or regular\n * expression; defaults to the first toplevel.\n * @param options Optional scale, timeout, and retry interval.\n * @returns The base64-encoded PNG data along with its mime type and dimensions.\n */\nexport const captureAndSaveScreenshot = async (\n selector?: WindowSelector,\n options?: ScreenshotOptions,\n): Promise<ScreenshotResult> => {\n const target = resolveWindow(selector);\n const result = await screenshot(target, options);\n logScreenshotPath(saveScreenshotToTempFile(result));\n return result;\n};\n"]}
|
|
1
|
+
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Gsk from \"@gtkx/gi/gsk\";\nimport * as Gtk from \"@gtkx/gi/gtk\";\nimport { mkdtempSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { ScreenshotOptions, ScreenshotResult, WindowSelector } from \"./types.js\";\nimport { waitFor } from \"./wait-for.js\";\n\nconst DEFAULT_SCREENSHOT_INTERVAL = 10;\n\nconst bytesToBase64 = (bytes: number[]): string => {\n return Buffer.from(bytes).toString(\"base64\");\n};\n\nconst describeWidgetState = (widget: Gtk.Widget): string =>\n `realized=${String(widget.getRealized())} mapped=${String(widget.getMapped())} ` +\n `visible=${String(widget.getVisible())}`;\n\nconst captureSnapshot = (widget: Gtk.Widget, scale: number): ScreenshotResult => {\n const paintable = new Gtk.WidgetPaintable({ widget });\n const width = paintable.getIntrinsicWidth();\n const height = paintable.getIntrinsicHeight();\n\n if (width <= 0 || height <= 0) {\n throw new Error(`Widget has no size: ensure it is realized and visible (${describeWidgetState(widget)})`);\n }\n\n const snapshot = new Gtk.Snapshot();\n snapshot.scale(scale, scale);\n paintable.snapshot(snapshot, width, height);\n const renderNode = snapshot.toNode();\n\n if (!renderNode) {\n throw new Error(`Widget produced no render content (${describeWidgetState(widget)})`);\n }\n\n const display = widget.getDisplay();\n const renderer = new Gsk.CairoRenderer();\n renderer.realizeForDisplay(display);\n\n try {\n const texture = renderer.renderTexture(renderNode, null);\n const pngBytes = texture.saveToPngBytes();\n const data = pngBytes.getData();\n\n if (!data) {\n throw new Error(\"Failed to serialize screenshot to PNG\");\n }\n\n return {\n data: bytesToBase64(data),\n mimeType: \"image/png\",\n width: Math.round(width * scale),\n height: Math.round(height * scale),\n };\n } finally {\n renderer.unrealize();\n }\n};\n\n/**\n * Captures a PNG snapshot of a widget, retrying until it has a paintable size.\n *\n * @param widget The widget to render to an image.\n * @param options Optional scale, timeout, and retry interval.\n * @returns The base64-encoded PNG data along with its mime type and dimensions.\n */\nconst screenshot = async (widget: Gtk.Widget, options?: ScreenshotOptions): Promise<ScreenshotResult> => {\n const scale = options?.scale ?? 1;\n\n if (!Number.isFinite(scale) || scale <= 0) {\n throw new Error(`Screenshot scale must be a positive number, got ${String(scale)}`);\n }\n\n return waitFor(() => captureSnapshot(widget, scale), {\n timeout: options?.timeout,\n interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,\n });\n};\n\nconst firstToplevelWindow = (windows: Gtk.Widget[]): Gtk.Window => {\n const [first] = windows;\n\n if (!(first instanceof Gtk.Window)) {\n throw new TypeError(\"First toplevel is not a Window\");\n }\n\n return first;\n};\n\nconst windowAtIndex = (windows: Gtk.Widget[], index: number): Gtk.Window => {\n const indexed = windows[index];\n\n if (!(indexed instanceof Gtk.Window)) {\n throw new TypeError(`Window at index ${String(index)} not found`);\n }\n\n return indexed;\n};\n\nconst isWindow = (widget: Gtk.Widget): widget is Gtk.Window => widget instanceof Gtk.Window;\n\nconst hasMatchingTitle = (window: Gtk.Window, selector: string | RegExp): boolean => {\n const title = window.getTitle() ?? \"\";\n\n return selector instanceof RegExp ? selector.test(title) : title.includes(selector);\n};\n\nconst describeTitleSelector = (selector: string | RegExp): string =>\n selector instanceof RegExp ? selector.toString() : `\"${selector}\"`;\n\nconst windowByTitle = (windows: Gtk.Widget[], selector: string | RegExp): Gtk.Window => {\n const found = windows.filter(isWindow).find((window) => hasMatchingTitle(window, selector));\n\n if (!found) {\n throw new Error(`No window found with title matching ${describeTitleSelector(selector)}`);\n }\n\n return found;\n};\n\nconst resolveWindow = (selector?: WindowSelector): Gtk.Window => {\n const windows = Gtk.Window.listToplevels();\n\n if (windows.length === 0) {\n throw new Error(\"No windows available for screenshot\");\n }\n\n if (selector === undefined) {\n return firstToplevelWindow(windows);\n }\n\n if (typeof selector === \"number\") {\n return windowAtIndex(windows, selector);\n }\n\n return windowByTitle(windows, selector);\n};\n\nconst saveScreenshotToTempFile = (result: ScreenshotResult): string => {\n const dir = mkdtempSync(join(tmpdir(), \"gtkx-screenshots-\"));\n const filepath = join(dir, \"screenshot.png\");\n writeFileSync(filepath, Buffer.from(result.data, \"base64\"));\n\n return filepath;\n};\n\n/**\n * Prints a clickable file URL for a saved screenshot to the console.\n *\n * @param filepath Absolute path of the saved screenshot file.\n */\nconst logScreenshotPath = (filepath: string): void => {\n console.log(`Screenshot saved: file://${filepath}`);\n};\n\n/**\n * Captures a screenshot of a toplevel window, writes it to a temporary file,\n * logs its path, and returns the image data.\n *\n * @param selector Chooses the window by index, title substring, or regular\n * expression; defaults to the first toplevel.\n * @param options Optional scale, timeout, and retry interval.\n * @returns The base64-encoded PNG data along with its mime type and dimensions.\n */\nconst captureAndSaveScreenshot = async (\n selector?: WindowSelector,\n options?: ScreenshotOptions,\n): Promise<ScreenshotResult> => {\n const target = resolveWindow(selector);\n const result = await screenshot(target, options);\n logScreenshotPath(saveScreenshotToTempFile(result));\n\n return result;\n};\n\nexport { screenshot, logScreenshotPath, captureAndSaveScreenshot };\n"]}
|
package/dist/suggestions.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
2
|
/** The query variant a suggestion targets. */
|
|
3
|
-
|
|
3
|
+
type Variant = "get" | "getAll" | "query" | "queryAll" | "find" | "findAll";
|
|
4
4
|
/** The query family a suggestion targets. */
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
|
|
5
|
+
type Method = "Role" | "LabelText" | "PlaceholderText" | "Text" | "DisplayValue" | "Name";
|
|
6
|
+
/**
|
|
7
|
+
* A suggested query for reaching a widget, including its family, variant, method name, and a
|
|
8
|
+
* `toString` that renders the full call.
|
|
9
|
+
*/
|
|
10
|
+
type Suggestion = {
|
|
8
11
|
queryName: Method;
|
|
9
12
|
/** The full query function name, such as `getByRole`. */
|
|
10
13
|
queryMethod: string;
|
|
@@ -12,11 +15,13 @@ export type Suggestion = {
|
|
|
12
15
|
toString: () => string;
|
|
13
16
|
};
|
|
14
17
|
/**
|
|
15
|
-
* Computes the recommended query for reaching the given widget, preferring role, then label text,
|
|
18
|
+
* Computes the recommended query for reaching the given widget, preferring role, then label text,
|
|
19
|
+
* placeholder text, text, display value, and name.
|
|
16
20
|
* @param widget Widget to build a suggestion for.
|
|
17
21
|
* @param variant Query variant the suggestion should use.
|
|
18
22
|
* @param method Restrict the suggestion to a specific query family instead of choosing by priority.
|
|
19
23
|
* @returns The best available suggestion, or undefined when no query applies.
|
|
20
24
|
*/
|
|
21
|
-
|
|
25
|
+
declare const getSuggestedQuery: (widget: Gtk.Widget, variant?: Variant, method?: Method) => Suggestion | undefined;
|
|
26
|
+
export { getSuggestedQuery, type Variant, type Method, type Suggestion };
|
|
22
27
|
//# sourceMappingURL=suggestions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suggestions.d.ts","sourceRoot":"","sources":["../src/suggestions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAYpC,8CAA8C;AAC9C,
|
|
1
|
+
{"version":3,"file":"suggestions.d.ts","sourceRoot":"","sources":["../src/suggestions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAYpC,8CAA8C;AAC9C,KAAK,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;AAC5E,6CAA6C;AAC7C,KAAK,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,iBAAiB,GAAG,MAAM,GAAG,cAAc,GAAG,MAAM,CAAC;AAE1F;;;GAGG;AACH,KAAK,UAAU,GAAG;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,MAAM,CAAC;CAC1B,CAAC;AAsCF;;;;;;;GAOG;AACH,QAAA,MAAM,iBAAiB,GACnB,QAAQ,GAAG,CAAC,MAAM,EAClB,UAAS,OAAe,EACxB,SAAS,MAAM,KAChB,UAAU,GAAG,SA0Bf,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,CAAC"}
|
package/dist/suggestions.js
CHANGED
|
@@ -12,8 +12,9 @@ const makeSuggestion = (queryName, variant, argsText) => {
|
|
|
12
12
|
};
|
|
13
13
|
const roleSuggestion = (widget, variant) => {
|
|
14
14
|
const role = widget.getAccessibleRole();
|
|
15
|
-
if (role === Gtk.AccessibleRole.NONE || role === Gtk.AccessibleRole.GENERIC)
|
|
15
|
+
if (role === Gtk.AccessibleRole.NONE || role === Gtk.AccessibleRole.GENERIC) {
|
|
16
16
|
return undefined;
|
|
17
|
+
}
|
|
17
18
|
const roleText = `Gtk.AccessibleRole.${formatRole(role).toUpperCase()}`;
|
|
18
19
|
const name = getWidgetAccessibleName(widget);
|
|
19
20
|
if (name === null) {
|
|
@@ -22,18 +23,20 @@ const roleSuggestion = (widget, variant) => {
|
|
|
22
23
|
return makeSuggestion("Role", variant, `${roleText}, { name: '${name}' }`);
|
|
23
24
|
};
|
|
24
25
|
const textSuggestion = (queryName, variant, value) => {
|
|
25
|
-
if (value === null)
|
|
26
|
+
if (value === null) {
|
|
26
27
|
return undefined;
|
|
28
|
+
}
|
|
27
29
|
return makeSuggestion(queryName, variant, `'${value}'`);
|
|
28
30
|
};
|
|
29
31
|
/**
|
|
30
|
-
* Computes the recommended query for reaching the given widget, preferring role, then label text,
|
|
32
|
+
* Computes the recommended query for reaching the given widget, preferring role, then label text,
|
|
33
|
+
* placeholder text, text, display value, and name.
|
|
31
34
|
* @param widget Widget to build a suggestion for.
|
|
32
35
|
* @param variant Query variant the suggestion should use.
|
|
33
36
|
* @param method Restrict the suggestion to a specific query family instead of choosing by priority.
|
|
34
37
|
* @returns The best available suggestion, or undefined when no query applies.
|
|
35
38
|
*/
|
|
36
|
-
|
|
39
|
+
const getSuggestedQuery = (widget, variant = "get", method) => {
|
|
37
40
|
const builders = {
|
|
38
41
|
Role: () => roleSuggestion(widget, variant),
|
|
39
42
|
LabelText: () => textSuggestion("LabelText", variant, getWidgetOwnLabel(widget) ?? getWidgetLabelledByText(widget)),
|
|
@@ -48,9 +51,11 @@ export const getSuggestedQuery = (widget, variant = "get", method) => {
|
|
|
48
51
|
const priority = ["Role", "LabelText", "PlaceholderText", "Text", "DisplayValue", "Name"];
|
|
49
52
|
for (const candidate of priority) {
|
|
50
53
|
const suggestion = builders[candidate]();
|
|
51
|
-
if (suggestion)
|
|
54
|
+
if (suggestion) {
|
|
52
55
|
return suggestion;
|
|
56
|
+
}
|
|
53
57
|
}
|
|
54
58
|
return undefined;
|
|
55
59
|
};
|
|
60
|
+
export { getSuggestedQuery };
|
|
56
61
|
//# sourceMappingURL=suggestions.js.map
|
package/dist/suggestions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suggestions.js","sourceRoot":"","sources":["../src/suggestions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EACH,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,EACvB,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,wBAAwB,GAC3B,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"suggestions.js","sourceRoot":"","sources":["../src/suggestions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EACH,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,EACvB,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,wBAAwB,GAC3B,MAAM,mCAAmC,CAAC;AAmB3C,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAE,OAAgB,EAAE,QAAgB,EAAc,EAAE;IACzF,MAAM,WAAW,GAAG,GAAG,OAAO,KAAK,SAAS,EAAE,CAAC;IAE/C,OAAO;QACH,SAAS;QACT,WAAW;QACX,OAAO;QACP,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,WAAW,IAAI,QAAQ,GAAG;KAChD,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,MAAkB,EAAE,OAAgB,EAA0B,EAAE;IACpF,MAAM,IAAI,GAAG,MAAM,CAAC,iBAAiB,EAAE,CAAC;IAExC,IAAI,IAAI,KAAK,GAAG,CAAC,cAAc,CAAC,IAAI,IAAI,IAAI,KAAK,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;QAC1E,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,MAAM,QAAQ,GAAG,sBAAsB,UAAU,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;IACxE,MAAM,IAAI,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAE7C,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAChB,OAAO,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,cAAc,IAAI,KAAK,CAAC,CAAC;AAC/E,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAE,OAAgB,EAAE,KAAoB,EAA0B,EAAE;IACzG,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACjB,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,OAAO,cAAc,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,KAAK,GAAG,CAAC,CAAC;AAC5D,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,iBAAiB,GAAG,CACtB,MAAkB,EAClB,UAAmB,KAAK,EACxB,MAAe,EACO,EAAE;IACxB,MAAM,QAAQ,GAAiD;QAC3D,IAAI,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC;QAC3C,SAAS,EAAE,GAAG,EAAE,CACZ,cAAc,CAAC,WAAW,EAAE,OAAO,EAAE,iBAAiB,CAAC,MAAM,CAAC,IAAI,uBAAuB,CAAC,MAAM,CAAC,CAAC;QACtG,eAAe,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,iBAAiB,EAAE,OAAO,EAAE,wBAAwB,CAAC,MAAM,CAAC,CAAC;QACnG,IAAI,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;QACvE,YAAY,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,cAAc,EAAE,OAAO,EAAE,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAC1F,IAAI,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;KACrE,CAAC;IAEF,IAAI,MAAM,EAAE,CAAC;QACT,OAAO,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;IAC9B,CAAC;IAED,MAAM,QAAQ,GAAa,CAAC,MAAM,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;IAEpG,KAAK,MAAM,SAAS,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAEzC,IAAI,UAAU,EAAE,CAAC;YACb,OAAO,UAAU,CAAC;QACtB,CAAC;IACL,CAAC;IAED,OAAO,SAAS,CAAC;AACrB,CAAC,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAA8C,CAAC","sourcesContent":["import * as Gtk from \"@gtkx/gi/gtk\";\nimport { formatRole } from \"./role-helpers.js\";\nimport {\n getWidgetAccessibleName,\n getWidgetDisplayValue,\n getWidgetLabelledByText,\n getWidgetLabelText,\n getWidgetName,\n getWidgetOwnLabel,\n getWidgetPlaceholderText,\n} from \"./widget-accessible-properties.js\";\n\n/** The query variant a suggestion targets. */\ntype Variant = \"get\" | \"getAll\" | \"query\" | \"queryAll\" | \"find\" | \"findAll\";\n/** The query family a suggestion targets. */\ntype Method = \"Role\" | \"LabelText\" | \"PlaceholderText\" | \"Text\" | \"DisplayValue\" | \"Name\";\n\n/**\n * A suggested query for reaching a widget, including its family, variant, method name, and a\n * `toString` that renders the full call.\n */\ntype Suggestion = {\n queryName: Method;\n /** The full query function name, such as `getByRole`. */\n queryMethod: string;\n variant: Variant;\n toString: () => string;\n};\n\nconst makeSuggestion = (queryName: Method, variant: Variant, argsText: string): Suggestion => {\n const queryMethod = `${variant}By${queryName}`;\n\n return {\n queryName,\n queryMethod,\n variant,\n toString: () => `${queryMethod}(${argsText})`,\n };\n};\n\nconst roleSuggestion = (widget: Gtk.Widget, variant: Variant): Suggestion | undefined => {\n const role = widget.getAccessibleRole();\n\n if (role === Gtk.AccessibleRole.NONE || role === Gtk.AccessibleRole.GENERIC) {\n return undefined;\n }\n\n const roleText = `Gtk.AccessibleRole.${formatRole(role).toUpperCase()}`;\n const name = getWidgetAccessibleName(widget);\n\n if (name === null) {\n return makeSuggestion(\"Role\", variant, roleText);\n }\n\n return makeSuggestion(\"Role\", variant, `${roleText}, { name: '${name}' }`);\n};\n\nconst textSuggestion = (queryName: Method, variant: Variant, value: string | null): Suggestion | undefined => {\n if (value === null) {\n return undefined;\n }\n\n return makeSuggestion(queryName, variant, `'${value}'`);\n};\n\n/**\n * Computes the recommended query for reaching the given widget, preferring role, then label text,\n * placeholder text, text, display value, and name.\n * @param widget Widget to build a suggestion for.\n * @param variant Query variant the suggestion should use.\n * @param method Restrict the suggestion to a specific query family instead of choosing by priority.\n * @returns The best available suggestion, or undefined when no query applies.\n */\nconst getSuggestedQuery = (\n widget: Gtk.Widget,\n variant: Variant = \"get\",\n method?: Method,\n): Suggestion | undefined => {\n const builders: Record<Method, () => Suggestion | undefined> = {\n Role: () => roleSuggestion(widget, variant),\n LabelText: () =>\n textSuggestion(\"LabelText\", variant, getWidgetOwnLabel(widget) ?? getWidgetLabelledByText(widget)),\n PlaceholderText: () => textSuggestion(\"PlaceholderText\", variant, getWidgetPlaceholderText(widget)),\n Text: () => textSuggestion(\"Text\", variant, getWidgetLabelText(widget)),\n DisplayValue: () => textSuggestion(\"DisplayValue\", variant, getWidgetDisplayValue(widget)),\n Name: () => textSuggestion(\"Name\", variant, getWidgetName(widget)),\n };\n\n if (method) {\n return builders[method]();\n }\n\n const priority: Method[] = [\"Role\", \"LabelText\", \"PlaceholderText\", \"Text\", \"DisplayValue\", \"Name\"];\n\n for (const candidate of priority) {\n const suggestion = builders[candidate]();\n\n if (suggestion) {\n return suggestion;\n }\n }\n\n return undefined;\n};\n\nexport { getSuggestedQuery, type Variant, type Method, type Suggestion };\n"]}
|
package/dist/traversal.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
-
export declare const TOPLEVELS: unique symbol;
|
|
3
2
|
type QueryContainer = Gtk.Widget | Gtk.EventController | Gtk.LayoutManager | Gtk.ListItem | Gtk.ListHeader;
|
|
4
3
|
/**
|
|
5
4
|
* A scope that queries and traversal can run against: a widget, an event
|
|
6
5
|
* controller, a layout manager, a list item or header, an application, or the
|
|
7
6
|
* sentinel representing all current toplevel windows.
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
type Container = QueryContainer | Gtk.Application | typeof TOPLEVELS;
|
|
9
|
+
declare const TOPLEVELS: unique symbol;
|
|
10
|
+
declare const descendants: (widget: Gtk.Widget) => Generator<Gtk.Widget>;
|
|
11
|
+
declare const roots: (container: Container) => Generator<Gtk.Widget>;
|
|
12
|
+
declare const traverse: (container: Container) => Generator<Gtk.Widget>;
|
|
13
|
+
declare const findAll: (container: Container, isMatch: (node: Gtk.Widget) => boolean) => Gtk.Widget[];
|
|
14
|
+
export { TOPLEVELS, descendants, roots, traverse, findAll, type Container };
|
|
15
15
|
//# sourceMappingURL=traversal.d.ts.map
|
package/dist/traversal.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"traversal.d.ts","sourceRoot":"","sources":["../src/traversal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAEpC,
|
|
1
|
+
{"version":3,"file":"traversal.d.ts","sourceRoot":"","sources":["../src/traversal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAEpC,KAAK,cAAc,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,eAAe,GAAG,GAAG,CAAC,aAAa,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC;AAC3G;;;;GAIG;AACH,KAAK,SAAS,GAAG,cAAc,GAAG,GAAG,CAAC,WAAW,GAAG,OAAO,SAAS,CAAC;AAErE,QAAA,MAAM,SAAS,EAAE,OAAO,MAAiC,CAAC;AAc1D,QAAA,MAAM,WAAW,GAAc,QAAQ,GAAG,CAAC,MAAM,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAIvE,CAAC;AAsBF,QAAA,MAAM,KAAK,GAAc,WAAW,SAAS,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAYnE,CAAC;AAEF,QAAA,MAAM,QAAQ,GAAc,WAAW,SAAS,KAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAItE,CAAC;AAEF,QAAA,MAAM,OAAO,GAAI,WAAW,SAAS,EAAE,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,KAAK,OAAO,KAAG,GAAG,CAAC,MAAM,EAUzF,CAAC;AAEF,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,SAAS,EAAE,CAAC"}
|
package/dist/traversal.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
-
|
|
2
|
+
const TOPLEVELS = Symbol("gtkx.toplevels");
|
|
3
3
|
const isApplication = (container) => container instanceof Gtk.Application;
|
|
4
4
|
const traverseWidgetTree = function* (root) {
|
|
5
5
|
yield root;
|
|
@@ -9,43 +9,49 @@ const traverseWidgetTree = function* (root) {
|
|
|
9
9
|
child = child.getNextSibling();
|
|
10
10
|
}
|
|
11
11
|
};
|
|
12
|
-
|
|
12
|
+
const descendants = function* (widget) {
|
|
13
13
|
const tree = traverseWidgetTree(widget);
|
|
14
14
|
tree.next();
|
|
15
15
|
yield* tree;
|
|
16
16
|
};
|
|
17
17
|
const resolveRoot = (container) => {
|
|
18
|
-
if (container instanceof Gtk.Widget)
|
|
18
|
+
if (container instanceof Gtk.Widget) {
|
|
19
19
|
return container;
|
|
20
|
-
|
|
20
|
+
}
|
|
21
|
+
if (container instanceof Gtk.EventController) {
|
|
21
22
|
return container.getWidget();
|
|
22
|
-
|
|
23
|
+
}
|
|
24
|
+
if (container instanceof Gtk.LayoutManager) {
|
|
23
25
|
return container.getWidget();
|
|
24
|
-
|
|
26
|
+
}
|
|
27
|
+
if (container instanceof Gtk.ListItem || container instanceof Gtk.ListHeader) {
|
|
25
28
|
return container.getChild();
|
|
29
|
+
}
|
|
26
30
|
return null;
|
|
27
31
|
};
|
|
28
|
-
|
|
32
|
+
const roots = function* (container) {
|
|
29
33
|
if (container === TOPLEVELS || isApplication(container)) {
|
|
30
34
|
yield* Gtk.Window.listToplevels();
|
|
31
35
|
return;
|
|
32
36
|
}
|
|
33
37
|
const root = resolveRoot(container);
|
|
34
|
-
if (root)
|
|
38
|
+
if (root) {
|
|
35
39
|
yield root;
|
|
40
|
+
}
|
|
36
41
|
};
|
|
37
|
-
|
|
42
|
+
const traverse = function* (container) {
|
|
38
43
|
for (const root of roots(container)) {
|
|
39
44
|
yield* traverseWidgetTree(root);
|
|
40
45
|
}
|
|
41
46
|
};
|
|
42
|
-
|
|
47
|
+
const findAll = (container, isMatch) => {
|
|
43
48
|
const results = [];
|
|
44
49
|
for (const node of traverse(container)) {
|
|
45
|
-
if (
|
|
50
|
+
if (isMatch(node)) {
|
|
46
51
|
results.push(node);
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
54
|
return results;
|
|
50
55
|
};
|
|
56
|
+
export { TOPLEVELS, descendants, roots, traverse, findAll };
|
|
51
57
|
//# sourceMappingURL=traversal.js.map
|
package/dist/traversal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"traversal.js","sourceRoot":"","sources":["../src/traversal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"traversal.js","sourceRoot":"","sources":["../src/traversal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAUpC,MAAM,SAAS,GAAkB,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAE1D,MAAM,aAAa,GAAG,CAAC,SAAoB,EAAgC,EAAE,CAAC,SAAS,YAAY,GAAG,CAAC,WAAW,CAAC;AAEnH,MAAM,kBAAkB,GAAG,QAAQ,CAAC,EAAE,IAAgB;IAClD,MAAM,IAAI,CAAC;IACX,IAAI,KAAK,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;IAEjC,OAAO,KAAK,EAAE,CAAC;QACX,KAAK,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACjC,KAAK,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IACnC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,QAAQ,CAAC,EAAE,MAAkB;IAC7C,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,IAAI,EAAE,CAAC;IACZ,KAAK,CAAC,CAAC,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,SAAyB,EAAqB,EAAE;IACjE,IAAI,SAAS,YAAY,GAAG,CAAC,MAAM,EAAE,CAAC;QAClC,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,eAAe,EAAE,CAAC;QAC3C,OAAO,SAAS,CAAC,SAAS,EAAE,CAAC;IACjC,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,aAAa,EAAE,CAAC;QACzC,OAAO,SAAS,CAAC,SAAS,EAAE,CAAC;IACjC,CAAC;IAED,IAAI,SAAS,YAAY,GAAG,CAAC,QAAQ,IAAI,SAAS,YAAY,GAAG,CAAC,UAAU,EAAE,CAAC;QAC3E,OAAO,SAAS,CAAC,QAAQ,EAAE,CAAC;IAChC,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,SAAoB;IACzC,IAAI,SAAS,KAAK,SAAS,IAAI,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;QACtD,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;QAElC,OAAO;IACX,CAAC;IAED,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAEpC,IAAI,IAAI,EAAE,CAAC;QACP,MAAM,IAAI,CAAC;IACf,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,QAAQ,CAAC,EAAE,SAAoB;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QAClC,KAAK,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CAAC,SAAoB,EAAE,OAAsC,EAAgB,EAAE;IAC3F,MAAM,OAAO,GAAiB,EAAE,CAAC;IAEjC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC,CAAC;AAEF,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAkB,CAAC","sourcesContent":["import * as Gtk from \"@gtkx/gi/gtk\";\n\ntype QueryContainer = Gtk.Widget | Gtk.EventController | Gtk.LayoutManager | Gtk.ListItem | Gtk.ListHeader;\n/**\n * A scope that queries and traversal can run against: a widget, an event\n * controller, a layout manager, a list item or header, an application, or the\n * sentinel representing all current toplevel windows.\n */\ntype Container = QueryContainer | Gtk.Application | typeof TOPLEVELS;\n\nconst TOPLEVELS: unique symbol = Symbol(\"gtkx.toplevels\");\n\nconst isApplication = (container: Container): container is Gtk.Application => container instanceof Gtk.Application;\n\nconst traverseWidgetTree = function* (root: Gtk.Widget): Generator<Gtk.Widget> {\n yield root;\n let child = root.getFirstChild();\n\n while (child) {\n yield* traverseWidgetTree(child);\n child = child.getNextSibling();\n }\n};\n\nconst descendants = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {\n const tree = traverseWidgetTree(widget);\n tree.next();\n yield* tree;\n};\n\nconst resolveRoot = (container: QueryContainer): Gtk.Widget | null => {\n if (container instanceof Gtk.Widget) {\n return container;\n }\n\n if (container instanceof Gtk.EventController) {\n return container.getWidget();\n }\n\n if (container instanceof Gtk.LayoutManager) {\n return container.getWidget();\n }\n\n if (container instanceof Gtk.ListItem || container instanceof Gtk.ListHeader) {\n return container.getChild();\n }\n\n return null;\n};\n\nconst roots = function* (container: Container): Generator<Gtk.Widget> {\n if (container === TOPLEVELS || isApplication(container)) {\n yield* Gtk.Window.listToplevels();\n\n return;\n }\n\n const root = resolveRoot(container);\n\n if (root) {\n yield root;\n }\n};\n\nconst traverse = function* (container: Container): Generator<Gtk.Widget> {\n for (const root of roots(container)) {\n yield* traverseWidgetTree(root);\n }\n};\n\nconst findAll = (container: Container, isMatch: (node: Gtk.Widget) => boolean): Gtk.Widget[] => {\n const results: Gtk.Widget[] = [];\n\n for (const node of traverse(container)) {\n if (isMatch(node)) {\n results.push(node);\n }\n }\n\n return results;\n};\n\nexport { TOPLEVELS, descendants, roots, traverse, findAll, type Container };\n"]}
|