@gtkx/testing 0.21.0 → 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/README.md +137 -34
- package/dist/act.d.ts +19 -0
- package/dist/act.d.ts.map +1 -0
- package/dist/act.js +56 -0
- package/dist/act.js.map +1 -0
- package/dist/bound-queries.d.ts +33 -0
- package/dist/bound-queries.d.ts.map +1 -0
- package/dist/bound-queries.js +2 -0
- package/dist/bound-queries.js.map +1 -0
- package/dist/build-queries.d.ts +16 -0
- package/dist/build-queries.d.ts.map +1 -0
- package/dist/build-queries.js +115 -0
- package/dist/build-queries.js.map +1 -0
- package/dist/cleanup-registry.d.ts +5 -0
- package/dist/cleanup-registry.d.ts.map +1 -0
- package/dist/cleanup-registry.js +21 -0
- package/dist/cleanup-registry.js.map +1 -0
- package/dist/config.d.ts +20 -52
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -46
- package/dist/config.js.map +1 -1
- package/dist/editable.d.ts +8 -0
- package/dist/editable.d.ts.map +1 -0
- package/dist/editable.js +26 -0
- package/dist/editable.js.map +1 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +135 -0
- package/dist/errors.js.map +1 -0
- package/dist/fire-event.d.ts +8 -25
- package/dist/fire-event.d.ts.map +1 -1
- package/dist/fire-event.js +11 -30
- package/dist/fire-event.js.map +1 -1
- package/dist/frame-sync.d.ts +4 -0
- package/dist/frame-sync.d.ts.map +1 -0
- package/dist/frame-sync.js +37 -0
- package/dist/frame-sync.js.map +1 -0
- package/dist/index.d.ts +17 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -7
- package/dist/index.js.map +1 -1
- package/dist/matchers.d.ts +59 -0
- package/dist/matchers.d.ts.map +1 -0
- package/dist/matchers.js +117 -0
- package/dist/matchers.js.map +1 -0
- package/dist/pretty-widget.d.ts +24 -26
- package/dist/pretty-widget.d.ts.map +1 -1
- package/dist/pretty-widget.js +111 -117
- package/dist/pretty-widget.js.map +1 -1
- package/dist/queries.d.ts +190 -180
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +291 -320
- package/dist/queries.js.map +1 -1
- package/dist/register-test-hooks.d.ts +2 -0
- package/dist/register-test-hooks.d.ts.map +1 -0
- package/dist/register-test-hooks.js +16 -0
- package/dist/register-test-hooks.js.map +1 -0
- package/dist/render-hook.d.ts +15 -35
- package/dist/render-hook.d.ts.map +1 -1
- package/dist/render-hook.js +9 -45
- package/dist/render-hook.js.map +1 -1
- package/dist/render.d.ts +13 -42
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +138 -121
- package/dist/render.js.map +1 -1
- package/dist/role-helpers.d.ts +19 -53
- package/dist/role-helpers.d.ts.map +1 -1
- package/dist/role-helpers.js +48 -63
- package/dist/role-helpers.js.map +1 -1
- package/dist/screen.d.ts +7 -63
- package/dist/screen.d.ts.map +1 -1
- package/dist/screen.js +24 -110
- package/dist/screen.js.map +1 -1
- package/dist/screenshot.d.ts +21 -23
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +98 -50
- package/dist/screenshot.js.map +1 -1
- package/dist/suggestions.d.ts +27 -0
- package/dist/suggestions.d.ts.map +1 -0
- package/dist/suggestions.js +61 -0
- package/dist/suggestions.js.map +1 -0
- package/dist/traversal.d.ts +12 -10
- package/dist/traversal.d.ts.map +1 -1
- package/dist/traversal.js +36 -15
- package/dist/traversal.js.map +1 -1
- package/dist/types.d.ts +115 -169
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/user-event/adjustment.d.ts +10 -0
- package/dist/user-event/adjustment.d.ts.map +1 -0
- package/dist/user-event/adjustment.js +35 -0
- package/dist/user-event/adjustment.js.map +1 -0
- package/dist/user-event/click.d.ts +8 -0
- package/dist/user-event/click.d.ts.map +1 -0
- package/dist/user-event/click.js +73 -0
- package/dist/user-event/click.js.map +1 -0
- package/dist/user-event/controller.d.ts +8 -0
- package/dist/user-event/controller.d.ts.map +1 -0
- package/dist/user-event/controller.js +31 -0
- package/dist/user-event/controller.js.map +1 -0
- package/dist/user-event/dispatch.d.ts +7 -0
- package/dist/user-event/dispatch.d.ts.map +1 -0
- package/dist/user-event/dispatch.js +11 -0
- package/dist/user-event/dispatch.js.map +1 -0
- package/dist/user-event/event-wrapper.d.ts +4 -0
- package/dist/user-event/event-wrapper.d.ts.map +1 -0
- package/dist/user-event/event-wrapper.js +59 -0
- package/dist/user-event/event-wrapper.js.map +1 -0
- package/dist/user-event/gesture.d.ts +35 -0
- package/dist/user-event/gesture.d.ts.map +1 -0
- package/dist/user-event/gesture.js +138 -0
- package/dist/user-event/gesture.js.map +1 -0
- package/dist/user-event/index.d.ts +51 -0
- package/dist/user-event/index.d.ts.map +1 -0
- package/dist/user-event/index.js +42 -0
- package/dist/user-event/index.js.map +1 -0
- package/dist/user-event/keyboard.d.ts +10 -0
- package/dist/user-event/keyboard.d.ts.map +1 -0
- package/dist/user-event/keyboard.js +204 -0
- package/dist/user-event/keyboard.js.map +1 -0
- package/dist/user-event/pointer.d.ts +10 -0
- package/dist/user-event/pointer.d.ts.map +1 -0
- package/dist/user-event/pointer.js +30 -0
- package/dist/user-event/pointer.js.map +1 -0
- package/dist/user-event/selection.d.ts +5 -0
- package/dist/user-event/selection.d.ts.map +1 -0
- package/dist/user-event/selection.js +105 -0
- package/dist/user-event/selection.js.map +1 -0
- package/dist/user-event/state.d.ts +7 -0
- package/dist/user-event/state.d.ts.map +1 -0
- package/dist/user-event/state.js +3 -0
- package/dist/user-event/state.js.map +1 -0
- package/dist/user-event/text.d.ts +18 -0
- package/dist/user-event/text.d.ts.map +1 -0
- package/dist/user-event/text.js +113 -0
- package/dist/user-event/text.js.map +1 -0
- package/dist/wait-for.d.ts +16 -36
- package/dist/wait-for.d.ts.map +1 -1
- package/dist/wait-for.js +74 -70
- package/dist/wait-for.js.map +1 -1
- package/dist/widget-accessible-properties.d.ts +34 -0
- package/dist/widget-accessible-properties.d.ts.map +1 -0
- package/dist/widget-accessible-properties.js +260 -0
- package/dist/widget-accessible-properties.js.map +1 -0
- package/dist/within.d.ts +10 -26
- package/dist/within.d.ts.map +1 -1
- package/dist/within.js +16 -25
- package/dist/within.js.map +1 -1
- package/package.json +23 -12
- package/src/act.ts +77 -0
- package/src/bound-queries.ts +37 -0
- package/src/build-queries.ts +226 -0
- package/src/cleanup-registry.ts +27 -0
- package/src/config.ts +35 -62
- package/src/editable.ts +37 -0
- package/src/errors.ts +191 -0
- package/src/fire-event.ts +13 -38
- package/src/frame-sync.ts +51 -0
- package/src/index.ts +58 -15
- package/src/matchers.ts +251 -0
- package/src/pretty-widget.ts +163 -130
- package/src/queries.ts +485 -396
- package/src/register-test-hooks.ts +19 -0
- package/src/render-hook.tsx +38 -45
- package/src/render.tsx +192 -155
- package/src/role-helpers.ts +65 -72
- package/src/screen.ts +26 -114
- package/src/screenshot.ts +128 -56
- package/src/suggestions.ts +106 -0
- package/src/traversal.ts +56 -22
- package/src/types.ts +144 -172
- package/src/user-event/adjustment.ts +62 -0
- package/src/user-event/click.ts +99 -0
- package/src/user-event/controller.ts +58 -0
- package/src/user-event/dispatch.ts +25 -0
- package/src/user-event/event-wrapper.ts +80 -0
- package/src/user-event/gesture.ts +239 -0
- package/src/user-event/index.ts +81 -0
- package/src/user-event/keyboard.ts +290 -0
- package/src/user-event/pointer.ts +46 -0
- package/src/user-event/selection.ts +159 -0
- package/src/user-event/state.ts +8 -0
- package/src/user-event/text.ts +172 -0
- package/src/wait-for.ts +117 -78
- package/src/widget-accessible-properties.ts +388 -0
- package/src/within.ts +29 -27
- package/dist/bind-queries.d.ts +0 -14
- package/dist/bind-queries.d.ts.map +0 -1
- package/dist/bind-queries.js +0 -29
- package/dist/bind-queries.js.map +0 -1
- package/dist/error-builder.d.ts +0 -28
- package/dist/error-builder.d.ts.map +0 -1
- package/dist/error-builder.js +0 -81
- package/dist/error-builder.js.map +0 -1
- package/dist/timing.d.ts +0 -18
- package/dist/timing.d.ts.map +0 -1
- package/dist/timing.js +0 -18
- package/dist/timing.js.map +0 -1
- package/dist/user-event.d.ts +0 -138
- package/dist/user-event.d.ts.map +0 -1
- package/dist/user-event.js +0 -411
- package/dist/user-event.js.map +0 -1
- package/dist/widget-text.d.ts +0 -69
- package/dist/widget-text.d.ts.map +0 -1
- package/dist/widget-text.js +0 -177
- package/dist/widget-text.js.map +0 -1
- package/dist/widget.d.ts +0 -3
- package/dist/widget.d.ts.map +0 -1
- package/dist/widget.js +0 -13
- package/dist/widget.js.map +0 -1
- package/src/bind-queries.ts +0 -52
- package/src/error-builder.ts +0 -102
- package/src/timing.ts +0 -17
- package/src/user-event.ts +0 -492
- package/src/widget-text.ts +0 -202
- package/src/widget.ts +0 -15
package/dist/within.d.ts
CHANGED
|
@@ -1,30 +1,14 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type {
|
|
1
|
+
import type { BoundQueries } from "./bound-queries.js";
|
|
2
|
+
import type { Container } from "./traversal.js";
|
|
3
|
+
import type { BoundCustomQueries, QueryMap } from "./types.js";
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
+
* Binds the built-in queries, and any custom queries, to a specific container
|
|
6
|
+
* so they search only within that scope.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
* the
|
|
8
|
-
*
|
|
9
|
-
* @param container - The widget to scope queries to
|
|
10
|
-
* @returns Object with query methods bound to the container
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```tsx
|
|
14
|
-
* import { render, within } from "@gtkx/testing";
|
|
15
|
-
*
|
|
16
|
-
* test("scoped queries", async () => {
|
|
17
|
-
* await render(<MyPage />);
|
|
18
|
-
*
|
|
19
|
-
* const sidebar = await screen.findByRole(Gtk.AccessibleRole.NAVIGATION);
|
|
20
|
-
* const sidebarQueries = within(sidebar);
|
|
21
|
-
*
|
|
22
|
-
* // Only searches within the sidebar
|
|
23
|
-
* const navButton = await sidebarQueries.findByRole(Gtk.AccessibleRole.BUTTON);
|
|
24
|
-
* });
|
|
25
|
-
* ```
|
|
26
|
-
*
|
|
27
|
-
* @see {@link screen} for global queries
|
|
8
|
+
* @param container The scope the returned queries operate on.
|
|
9
|
+
* @param queries Optional custom queries to bind alongside the built-in ones.
|
|
10
|
+
* @returns The queries bound to the container.
|
|
28
11
|
*/
|
|
29
|
-
|
|
12
|
+
declare const within: <Q extends QueryMap = Record<never, never>>(container: Container, queries?: Q) => BoundQueries & BoundCustomQueries<Q>;
|
|
13
|
+
export { within };
|
|
30
14
|
//# sourceMappingURL=within.d.ts.map
|
package/dist/within.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"within.d.ts","sourceRoot":"","sources":["../src/within.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,
|
|
1
|
+
{"version":3,"file":"within.d.ts","sourceRoot":"","sources":["../src/within.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAc/D;;;;;;;GAOG;AACH,QAAA,MAAM,MAAM,GAAI,CAAC,SAAS,QAAQ,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EACrD,WAAW,SAAS,EACpB,UAAU,CAAC,KACZ,YAAY,GAAG,kBAAkB,CAAC,CAAC,CAGpC,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,CAAC"}
|
package/dist/within.js
CHANGED
|
@@ -1,29 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { builtinQueries } from "./queries.js";
|
|
2
|
+
const bindQuery = (query, container) => (...args) => query(container, ...args);
|
|
3
|
+
const bindCustomQueries = (customQueries, container) => {
|
|
4
|
+
const entries = Object.entries(customQueries).map(([key, query]) => [key, bindQuery(query, container)]);
|
|
5
|
+
return Object.fromEntries(entries);
|
|
6
|
+
};
|
|
2
7
|
/**
|
|
3
|
-
*
|
|
8
|
+
* Binds the built-in queries, and any custom queries, to a specific container
|
|
9
|
+
* so they search only within that scope.
|
|
4
10
|
*
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
7
|
-
*
|
|
8
|
-
* @param container - The widget to scope queries to
|
|
9
|
-
* @returns Object with query methods bound to the container
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```tsx
|
|
13
|
-
* import { render, within } from "@gtkx/testing";
|
|
14
|
-
*
|
|
15
|
-
* test("scoped queries", async () => {
|
|
16
|
-
* await render(<MyPage />);
|
|
17
|
-
*
|
|
18
|
-
* const sidebar = await screen.findByRole(Gtk.AccessibleRole.NAVIGATION);
|
|
19
|
-
* const sidebarQueries = within(sidebar);
|
|
20
|
-
*
|
|
21
|
-
* // Only searches within the sidebar
|
|
22
|
-
* const navButton = await sidebarQueries.findByRole(Gtk.AccessibleRole.BUTTON);
|
|
23
|
-
* });
|
|
24
|
-
* ```
|
|
25
|
-
*
|
|
26
|
-
* @see {@link screen} for global queries
|
|
11
|
+
* @param container The scope the returned queries operate on.
|
|
12
|
+
* @param queries Optional custom queries to bind alongside the built-in ones.
|
|
13
|
+
* @returns The queries bound to the container.
|
|
27
14
|
*/
|
|
28
|
-
|
|
15
|
+
const within = (container, queries) => ({
|
|
16
|
+
...bindCustomQueries(builtinQueries, container),
|
|
17
|
+
...(queries ? bindCustomQueries(queries, container) : {}),
|
|
18
|
+
});
|
|
19
|
+
export { within };
|
|
29
20
|
//# sourceMappingURL=within.js.map
|
package/dist/within.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"within.js","sourceRoot":"","sources":["../src/within.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"within.js","sourceRoot":"","sources":["../src/within.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,SAAS,GACX,CAAiC,KAAsD,EAAE,SAAoB,EAAE,EAAE,CAC7G,CAAC,GAAG,IAAU,EAAU,EAAE,CACtB,KAAK,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,CAAC;AAEtC,MAAM,iBAAiB,GAAG,CAAqB,aAAgB,EAAE,SAAoB,EAAyB,EAAE;IAC5G,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,CAAU,CAAC,CAAC;IAEjH,OAAO,MAAM,CAAC,WAAW,CAAC,OAAO,CAA0B,CAAC;AAChE,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,GAAG,CACX,SAAoB,EACpB,OAAW,EACyB,EAAE,CAAC,CAAC;IACxC,GAAG,iBAAiB,CAAC,cAAc,EAAE,SAAS,CAAC;IAC/C,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,CAAE,EAA4B,CAAC;CACvF,CAAC,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,CAAC","sourcesContent":["import type { BoundQueries } from \"./bound-queries.js\";\nimport type { Container } from \"./traversal.js\";\nimport type { BoundCustomQueries, QueryMap } from \"./types.js\";\nimport { builtinQueries } from \"./queries.js\";\n\nconst bindQuery =\n <Args extends unknown[], Result>(query: (container: Container, ...args: Args) => Result, container: Container) =>\n (...args: Args): Result =>\n query(container, ...args);\n\nconst bindCustomQueries = <Q extends QueryMap>(customQueries: Q, container: Container): BoundCustomQueries<Q> => {\n const entries = Object.entries(customQueries).map(([key, query]) => [key, bindQuery(query, container)] as const);\n\n return Object.fromEntries(entries) as BoundCustomQueries<Q>;\n};\n\n/**\n * Binds the built-in queries, and any custom queries, to a specific container\n * so they search only within that scope.\n *\n * @param container The scope the returned queries operate on.\n * @param queries Optional custom queries to bind alongside the built-in ones.\n * @returns The queries bound to the container.\n */\nconst within = <Q extends QueryMap = Record<never, never>>(\n container: Container,\n queries?: Q,\n): BoundQueries & BoundCustomQueries<Q> => ({\n ...bindCustomQueries(builtinQueries, container),\n ...(queries ? bindCustomQueries(queries, container) : ({} as BoundCustomQueries<Q>)),\n});\n\nexport { within };\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/testing",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Testing
|
|
3
|
+
"version": "1.0.0-rc.2",
|
|
4
|
+
"description": "Testing Library-style queries, user events, and screenshots for GTK4 widget trees rendered by GTKX",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtkx",
|
|
7
7
|
"gtk",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
|
-
"url": "https://github.com/gtkx-org/gtkx.git",
|
|
22
|
+
"url": "git+https://github.com/gtkx-org/gtkx.git",
|
|
23
23
|
"directory": "packages/testing"
|
|
24
24
|
},
|
|
25
25
|
"license": "MPL-2.0",
|
|
@@ -32,23 +32,34 @@
|
|
|
32
32
|
"default": "./dist/index.js"
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
|
-
"sideEffects":
|
|
35
|
+
"sideEffects": [
|
|
36
|
+
"./src/register-test-hooks.ts",
|
|
37
|
+
"./dist/register-test-hooks.js"
|
|
38
|
+
],
|
|
36
39
|
"files": [
|
|
37
40
|
"dist",
|
|
38
41
|
"src"
|
|
39
42
|
],
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=24"
|
|
45
|
+
},
|
|
40
46
|
"dependencies": {
|
|
41
|
-
"@gtkx/
|
|
42
|
-
"@gtkx/
|
|
47
|
+
"@gtkx/react": "1.0.0-rc.2",
|
|
48
|
+
"@gtkx/runtime": "1.0.0-rc.2",
|
|
49
|
+
"@gtkx/utils": "1.0.0-rc.2"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@types/react": "^19.2",
|
|
53
|
+
"react": "^19.2"
|
|
43
54
|
},
|
|
44
55
|
"devDependencies": {
|
|
45
|
-
"@types/react
|
|
46
|
-
"
|
|
47
|
-
"
|
|
56
|
+
"@types/react": "^19.2.17",
|
|
57
|
+
"@vitest/expect": "^4.1.10",
|
|
58
|
+
"vitest": "^4.1.10",
|
|
59
|
+
"@gtkx/vitest": "1.0.0-rc.2",
|
|
60
|
+
"@gtkx/config": "1.0.0-rc.2"
|
|
48
61
|
},
|
|
49
62
|
"scripts": {
|
|
50
|
-
"
|
|
51
|
-
"test": "vitest run",
|
|
52
|
-
"typecheck": "tsc -b --emitDeclarationOnly"
|
|
63
|
+
"release": "tsx ../../scripts/release-package.ts"
|
|
53
64
|
}
|
|
54
65
|
}
|
package/src/act.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { act as reactAct } from "react";
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
var IS_REACT_ACT_ENVIRONMENT: boolean | undefined;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
type ActCallback<T> = () => T | PromiseLike<T>;
|
|
8
|
+
type ActImplementation = <T>(callback: ActCallback<T>) => PromiseLike<T>;
|
|
9
|
+
|
|
10
|
+
const actImplementation: ActImplementation = reactAct;
|
|
11
|
+
/**
|
|
12
|
+
* Runs a callback inside React's act environment, flushing pending state
|
|
13
|
+
* updates and effects, and resolves once the work has settled.
|
|
14
|
+
*
|
|
15
|
+
* @param callback Work to run within the act environment.
|
|
16
|
+
* @returns A promise that resolves with the callback's result after updates flush.
|
|
17
|
+
*/
|
|
18
|
+
const act: ActImplementation = withGlobalActEnvironment(actImplementation);
|
|
19
|
+
|
|
20
|
+
const getGlobalThis = (): typeof globalThis => {
|
|
21
|
+
if (typeof globalThis !== "undefined") {
|
|
22
|
+
return globalThis;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw new Error("unable to locate global object");
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const getIsReactActEnvironment = (): boolean | undefined => getGlobalThis().IS_REACT_ACT_ENVIRONMENT;
|
|
29
|
+
|
|
30
|
+
const setIsReactActEnvironment = (value: boolean | undefined): void => {
|
|
31
|
+
getGlobalThis().IS_REACT_ACT_ENVIRONMENT = value;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const isThenable = <T>(value: unknown): value is PromiseLike<T> =>
|
|
35
|
+
value !== null && typeof value === "object" && typeof (value as PromiseLike<T>).then === "function";
|
|
36
|
+
|
|
37
|
+
const restoreAfter = async <T>(result: PromiseLike<T>, previous: boolean | undefined): Promise<T> => {
|
|
38
|
+
try {
|
|
39
|
+
return await result;
|
|
40
|
+
} finally {
|
|
41
|
+
setIsReactActEnvironment(previous);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const runWithActEnvironment = <T>(isForced: boolean, fn: () => T | PromiseLike<T>): Promise<T> => {
|
|
46
|
+
const previousActEnvironment = getIsReactActEnvironment();
|
|
47
|
+
setIsReactActEnvironment(isForced);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const result: T | PromiseLike<T> = fn();
|
|
51
|
+
|
|
52
|
+
if (isThenable<T>(result)) {
|
|
53
|
+
return restoreAfter(result, previousActEnvironment);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setIsReactActEnvironment(previousActEnvironment);
|
|
57
|
+
|
|
58
|
+
return Promise.resolve(result);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
setIsReactActEnvironment(previousActEnvironment);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function withGlobalActEnvironment(actImplementation: ActImplementation): ActImplementation {
|
|
66
|
+
return <T>(callback: ActCallback<T>): PromiseLike<T> => {
|
|
67
|
+
const settled = runWithActEnvironment(true, () => actImplementation(() => callback()));
|
|
68
|
+
|
|
69
|
+
return Promise.resolve(settled);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const runInAct = async (callback: () => unknown): Promise<void> => {
|
|
74
|
+
await act(() => callback());
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export { act, getIsReactActEnvironment, setIsReactActEnvironment, runWithActEnvironment, runInAct };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import type { builtinQueries } from "./queries.js";
|
|
4
|
+
import type { Container } from "./traversal.js";
|
|
5
|
+
import type { BoundCustomQueries, DebugUtilities, QueryMap } from "./types.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The set of built-in queries (getBy, findBy, queryBy, and their All variants)
|
|
9
|
+
* bound to a specific container.
|
|
10
|
+
*/
|
|
11
|
+
type BoundQueries = BoundCustomQueries<typeof builtinQueries>;
|
|
12
|
+
/**
|
|
13
|
+
* The global screen object's shape: built-in queries scoped to the current
|
|
14
|
+
* toplevels, plus debug utilities.
|
|
15
|
+
*/
|
|
16
|
+
type Screen = BoundQueries & DebugUtilities;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The result of a render call: queries scoped to the rendered tree, debug
|
|
20
|
+
* utilities, and controls for updating or tearing down the render.
|
|
21
|
+
*
|
|
22
|
+
* @typeParam Q Custom queries provided at render time, merged with the built-in ones.
|
|
23
|
+
*/
|
|
24
|
+
type RenderResult<Q extends QueryMap = Record<never, never>> = BoundQueries &
|
|
25
|
+
BoundCustomQueries<Q> &
|
|
26
|
+
DebugUtilities & {
|
|
27
|
+
/** The top-level widget the element was rendered into. */
|
|
28
|
+
container: Gtk.Widget;
|
|
29
|
+
/** The root the queries are scoped to (defaults to all toplevels). */
|
|
30
|
+
baseElement: Container;
|
|
31
|
+
/** Unmounts the rendered tree and disposes its resources. */
|
|
32
|
+
unmount: () => Promise<void>;
|
|
33
|
+
/** Re-renders the tree with a new element, reusing the same root. */
|
|
34
|
+
rerender: (element: ReactNode) => Promise<void>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { type BoundQueries, type Screen, type RenderResult };
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import type { Container } from "./traversal.js";
|
|
3
|
+
import type { MatcherOptions, WaitForOptions } from "./types.js";
|
|
4
|
+
import { getConfig } from "./config.js";
|
|
5
|
+
import { runWithExpensiveErrorDiagnosticsDisabled, suggestionError } from "./errors.js";
|
|
6
|
+
import { getSuggestedQuery, type Method, type Variant } from "./suggestions.js";
|
|
7
|
+
import { waitFor } from "./wait-for.js";
|
|
8
|
+
|
|
9
|
+
type QueryAllBy<Args extends unknown[]> = (container: Container, ...args: Args) => Gtk.Widget[];
|
|
10
|
+
|
|
11
|
+
type MultipleErrorBuilder<Args extends unknown[]> = (
|
|
12
|
+
container: Container,
|
|
13
|
+
matches: Gtk.Widget[],
|
|
14
|
+
...args: Args
|
|
15
|
+
) => Error;
|
|
16
|
+
|
|
17
|
+
type MissingErrorBuilder<Args extends unknown[]> = (container: Container, ...args: Args) => Error;
|
|
18
|
+
|
|
19
|
+
type BuiltQueries<Args extends unknown[]> = {
|
|
20
|
+
queryBy: (container: Container, ...args: Args) => Gtk.Widget | null;
|
|
21
|
+
getAllBy: QueryAllBy<Args>;
|
|
22
|
+
getBy: (container: Container, ...args: Args) => Gtk.Widget;
|
|
23
|
+
findAllBy: (container: Container, ...args: Args) => Promise<Gtk.Widget[]>;
|
|
24
|
+
findBy: (container: Container, ...args: Args) => Promise<Gtk.Widget>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type FindQuery<Args extends unknown[]> = (container: Container, ...args: Args) => unknown;
|
|
28
|
+
type SingleQuery<Args extends unknown[]> = (container: Container, ...args: Args) => Gtk.Widget | null;
|
|
29
|
+
|
|
30
|
+
const extractWaitForOptions = (args: unknown[]): WaitForOptions => {
|
|
31
|
+
const last = args.at(-1);
|
|
32
|
+
|
|
33
|
+
if (last && typeof last === "object" && !(last instanceof RegExp)) {
|
|
34
|
+
const { timeout, interval, onTimeout } = last as WaitForOptions;
|
|
35
|
+
|
|
36
|
+
return { timeout, interval, onTimeout };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const extractSuggestOption = (args: unknown[]): boolean | undefined => {
|
|
43
|
+
const last = args.at(-1);
|
|
44
|
+
|
|
45
|
+
if (last && typeof last === "object" && !(last instanceof RegExp)) {
|
|
46
|
+
return (last as MatcherOptions).suggest;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return undefined;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const maybeThrowSuggestion = (options: {
|
|
53
|
+
container: Container;
|
|
54
|
+
match: Gtk.Widget;
|
|
55
|
+
queryName: Method;
|
|
56
|
+
variant: Variant;
|
|
57
|
+
suggest: boolean | undefined;
|
|
58
|
+
}): void => {
|
|
59
|
+
const { container, match, queryName, variant, suggest } = options;
|
|
60
|
+
const shouldSuggest = suggest ?? getConfig().throwSuggestions;
|
|
61
|
+
|
|
62
|
+
if (!shouldSuggest) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const suggestion = getSuggestedQuery(match, variant);
|
|
67
|
+
|
|
68
|
+
if (suggestion && suggestion.queryName !== queryName) {
|
|
69
|
+
throw suggestionError(suggestion.toString(), container);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const reRunForDiagnostics = <Args extends unknown[]>(
|
|
74
|
+
query: FindQuery<Args>,
|
|
75
|
+
container: Container,
|
|
76
|
+
args: Args,
|
|
77
|
+
fallback: Error,
|
|
78
|
+
): Error => {
|
|
79
|
+
try {
|
|
80
|
+
query(container, ...args);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error instanceof Error) {
|
|
83
|
+
return error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return fallback;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const findOptions = <Args extends unknown[]>(
|
|
91
|
+
query: FindQuery<Args>,
|
|
92
|
+
container: Container,
|
|
93
|
+
args: Args,
|
|
94
|
+
): WaitForOptions => {
|
|
95
|
+
const userOptions = extractWaitForOptions(args);
|
|
96
|
+
|
|
97
|
+
const onTimeout =
|
|
98
|
+
userOptions.onTimeout ?? ((fallback: Error) => reRunForDiagnostics(query, container, args, fallback));
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
stackTraceError: new Error("STACK_TRACE_MESSAGE"),
|
|
102
|
+
onTimeout,
|
|
103
|
+
timeout: userOptions.timeout,
|
|
104
|
+
interval: userOptions.interval,
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const singleFrom =
|
|
109
|
+
<Args extends unknown[]>(
|
|
110
|
+
allQuery: QueryAllBy<Args>,
|
|
111
|
+
getMultipleError: MultipleErrorBuilder<Args>,
|
|
112
|
+
): SingleQuery<Args> =>
|
|
113
|
+
(container, ...args) => {
|
|
114
|
+
const matches = allQuery(container, ...args);
|
|
115
|
+
|
|
116
|
+
if (matches.length > 1) {
|
|
117
|
+
throw getMultipleError(container, matches, ...args);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return matches[0] ?? null;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const allOrThrow =
|
|
124
|
+
<Args extends unknown[]>(
|
|
125
|
+
allQuery: QueryAllBy<Args>,
|
|
126
|
+
getMissingError: MissingErrorBuilder<Args>,
|
|
127
|
+
): QueryAllBy<Args> =>
|
|
128
|
+
(container, ...args) => {
|
|
129
|
+
const matches = allQuery(container, ...args);
|
|
130
|
+
|
|
131
|
+
if (matches.length === 0) {
|
|
132
|
+
throw getMissingError(container, ...args);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return matches;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const wrapSingleWithSuggestion =
|
|
139
|
+
<Args extends unknown[]>(query: SingleQuery<Args>, queryName: Method, variant: Variant): SingleQuery<Args> =>
|
|
140
|
+
(container, ...args) => {
|
|
141
|
+
const match = query(container, ...args);
|
|
142
|
+
|
|
143
|
+
if (match) {
|
|
144
|
+
maybeThrowSuggestion({ container, match, queryName, variant, suggest: extractSuggestOption(args) });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return match;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const wrapAllWithSuggestion =
|
|
151
|
+
<Args extends unknown[]>(query: QueryAllBy<Args>, queryName: Method, variant: Variant): QueryAllBy<Args> =>
|
|
152
|
+
(container, ...args) => {
|
|
153
|
+
const matches = query(container, ...args);
|
|
154
|
+
const [first] = matches;
|
|
155
|
+
|
|
156
|
+
if (first !== undefined) {
|
|
157
|
+
maybeThrowSuggestion({
|
|
158
|
+
container,
|
|
159
|
+
match: first,
|
|
160
|
+
queryName,
|
|
161
|
+
variant,
|
|
162
|
+
suggest: extractSuggestOption(args),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return matches;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const requireSingle =
|
|
170
|
+
<Args extends unknown[]>(query: SingleQuery<Args>, getMissingError: MissingErrorBuilder<Args>) =>
|
|
171
|
+
(container: Container, ...args: Args): Gtk.Widget => {
|
|
172
|
+
const match = query(container, ...args);
|
|
173
|
+
|
|
174
|
+
if (match === null) {
|
|
175
|
+
throw getMissingError(container, ...args);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return match;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const buildQueries = <Args extends unknown[]>(
|
|
182
|
+
queryName: Method,
|
|
183
|
+
queryAllBy: QueryAllBy<Args>,
|
|
184
|
+
getMultipleError: MultipleErrorBuilder<Args>,
|
|
185
|
+
getMissingError: MissingErrorBuilder<Args>,
|
|
186
|
+
): BuiltQueries<Args> => {
|
|
187
|
+
const getAllBy = allOrThrow(queryAllBy, getMissingError);
|
|
188
|
+
const getBy = requireSingle(singleFrom(getAllBy, getMultipleError), getMissingError);
|
|
189
|
+
|
|
190
|
+
const queryByWithSuggestion = wrapSingleWithSuggestion(
|
|
191
|
+
singleFrom(queryAllBy, getMultipleError),
|
|
192
|
+
queryName,
|
|
193
|
+
"query",
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const getAllByWithSuggestion = wrapAllWithSuggestion(getAllBy, queryName, "getAll");
|
|
197
|
+
|
|
198
|
+
const getByWithSuggestion: (container: Container, ...args: Args) => Gtk.Widget = (container, ...args) => {
|
|
199
|
+
const match = getBy(container, ...args);
|
|
200
|
+
maybeThrowSuggestion({ container, match, queryName, variant: "get", suggest: extractSuggestOption(args) });
|
|
201
|
+
|
|
202
|
+
return match;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const findAllBy = (container: Container, ...args: Args): Promise<Gtk.Widget[]> =>
|
|
206
|
+
waitFor(
|
|
207
|
+
() => runWithExpensiveErrorDiagnosticsDisabled(() => getAllByWithSuggestion(container, ...args)),
|
|
208
|
+
findOptions(getAllByWithSuggestion, container, args),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const findBy = (container: Container, ...args: Args): Promise<Gtk.Widget> =>
|
|
212
|
+
waitFor(
|
|
213
|
+
() => runWithExpensiveErrorDiagnosticsDisabled(() => getByWithSuggestion(container, ...args)),
|
|
214
|
+
findOptions(getByWithSuggestion, container, args),
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
queryBy: queryByWithSuggestion,
|
|
219
|
+
getAllBy: getAllByWithSuggestion,
|
|
220
|
+
getBy: getByWithSuggestion,
|
|
221
|
+
findAllBy,
|
|
222
|
+
findBy,
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export { buildQueries, type QueryAllBy, type BuiltQueries };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type CleanupFunction = () => void | Promise<void>;
|
|
2
|
+
|
|
3
|
+
const cleanupQueue: Set<CleanupFunction> = new Set();
|
|
4
|
+
|
|
5
|
+
const addToCleanupQueue = (fn: CleanupFunction): void => {
|
|
6
|
+
cleanupQueue.add(fn);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const runCleanup = async (): Promise<void> => {
|
|
10
|
+
const failures: unknown[] = [];
|
|
11
|
+
|
|
12
|
+
for (const fn of cleanupQueue) {
|
|
13
|
+
try {
|
|
14
|
+
await fn();
|
|
15
|
+
} catch (error) {
|
|
16
|
+
failures.push(error);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
cleanupQueue.clear();
|
|
21
|
+
|
|
22
|
+
if (failures.length > 0) {
|
|
23
|
+
throw new AggregateError(failures, "Cleanup failed");
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { addToCleanupQueue, runCleanup, type CleanupFunction };
|
package/src/config.ts
CHANGED
|
@@ -1,89 +1,62 @@
|
|
|
1
1
|
import type { Container } from "./traversal.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Global configuration for the testing harness, controlling query behavior,
|
|
5
|
+
* error formatting, and the timeouts used by async utilities and actionability
|
|
6
|
+
* checks.
|
|
5
7
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* Whether to show role suggestions in error messages when elements are not found.
|
|
9
|
-
* @default true
|
|
10
|
-
*/
|
|
11
|
-
showSuggestions: boolean;
|
|
8
|
+
type Config = {
|
|
9
|
+
throwSuggestions: boolean;
|
|
12
10
|
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
* Allows customizing how errors are constructed.
|
|
16
|
-
*/
|
|
17
|
-
getElementError: (message: string, container: Container) => Error;
|
|
11
|
+
/** Builds the error thrown when a query fails, given a message and optional container. */
|
|
12
|
+
getElementError: (message: string, container?: Container) => Error;
|
|
18
13
|
|
|
19
|
-
/**
|
|
20
|
-
* Default timeout in milliseconds for async utilities (waitFor, findBy* queries).
|
|
21
|
-
* @default 1000
|
|
22
|
-
*/
|
|
14
|
+
/** Default timeout in milliseconds for async utilities such as waitFor. */
|
|
23
15
|
asyncUtilTimeout: number;
|
|
24
|
-
};
|
|
25
16
|
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
/** Timeout in milliseconds for waiting on a widget to become actionable. */
|
|
18
|
+
actionabilityTimeout: number;
|
|
28
19
|
};
|
|
29
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Function form of a configuration update: receives the current config and
|
|
23
|
+
* returns the fields to override.
|
|
24
|
+
*/
|
|
25
|
+
type ConfigFn = (existingConfig: Config) => Partial<Config>;
|
|
26
|
+
|
|
30
27
|
const defaultConfig: Config = {
|
|
31
|
-
|
|
28
|
+
throwSuggestions: false,
|
|
32
29
|
getElementError: defaultGetElementError,
|
|
33
30
|
asyncUtilTimeout: 1000,
|
|
31
|
+
actionabilityTimeout: 500,
|
|
34
32
|
};
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
const currentConfig: Config = { ...defaultConfig };
|
|
35
|
+
|
|
36
|
+
function defaultGetElementError(message: string): Error {
|
|
37
|
+
return new GtkxElementError(message);
|
|
38
|
+
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
|
-
* Returns the current testing
|
|
40
|
-
*
|
|
41
|
-
* @returns The current configuration object
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```tsx
|
|
45
|
-
* import { getConfig } from "@gtkx/testing";
|
|
46
|
-
*
|
|
47
|
-
* const config = getConfig();
|
|
48
|
-
* console.log(config.showSuggestions);
|
|
49
|
-
* ```
|
|
41
|
+
* Returns the current global testing configuration.
|
|
50
42
|
*/
|
|
51
|
-
|
|
43
|
+
const getConfig = (): Config => {
|
|
52
44
|
return currentConfig;
|
|
53
45
|
};
|
|
54
46
|
|
|
55
47
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* Accepts either a partial configuration object or a function that receives
|
|
59
|
-
* the current configuration and returns updates.
|
|
48
|
+
* Merges the given overrides into the global testing configuration.
|
|
60
49
|
*
|
|
61
|
-
* @param newConfig
|
|
62
|
-
*
|
|
63
|
-
* @example
|
|
64
|
-
* ```tsx
|
|
65
|
-
* import { configure } from "@gtkx/testing";
|
|
66
|
-
*
|
|
67
|
-
* // Disable role suggestions
|
|
68
|
-
* configure({ showSuggestions: false });
|
|
69
|
-
*
|
|
70
|
-
* // Use updater function
|
|
71
|
-
* configure((current) => ({
|
|
72
|
-
* showSuggestions: !current.showSuggestions,
|
|
73
|
-
* }));
|
|
74
|
-
* ```
|
|
50
|
+
* @param newConfig Either a partial config object or a function that receives
|
|
51
|
+
* the current config and returns the fields to override.
|
|
75
52
|
*/
|
|
76
|
-
|
|
53
|
+
const configure = (newConfig: Partial<Config> | ConfigFn): void => {
|
|
77
54
|
const updates = typeof newConfig === "function" ? newConfig(currentConfig) : newConfig;
|
|
78
|
-
currentConfig
|
|
55
|
+
Object.assign(currentConfig, updates);
|
|
79
56
|
};
|
|
80
57
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
*/
|
|
87
|
-
export const resetConfig = (): void => {
|
|
88
|
-
currentConfig = { ...defaultConfig };
|
|
89
|
-
};
|
|
58
|
+
class GtkxElementError extends Error {
|
|
59
|
+
override name = "GtkxElementError";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { getConfig, configure, type Config, type ConfigFn };
|