@textui/testing 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -14
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -2
- package/package.json +10 -10
- package/src/index.ts +55 -2
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @textui/testing
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@textui/testing) [](https://github.com/softov/textui/blob/main/LICENSE)
|
|
4
|
+
|
|
5
|
+
A headless harness for TextUI applications. No terminal, no timers, no snapshots of a screen nobody looked at.
|
|
5
6
|
|
|
6
7
|
```bash
|
|
7
8
|
npm install --save-dev @textui/testing
|
|
@@ -20,9 +21,7 @@ expect(t.text()).toContain('Saved');
|
|
|
20
21
|
|
|
21
22
|
## Query by what it is, not where it is
|
|
22
23
|
|
|
23
|
-
Every component declares a semantic role, so a test asks the way a person would
|
|
24
|
-
- the Save button, the row labelled `api` - rather than by index into a tree
|
|
25
|
-
that changes the moment the layout does.
|
|
24
|
+
Every component declares a semantic role, so a test asks the way a person would - the Save button, the row labelled `api` - rather than by index into a tree that changes the moment the layout does.
|
|
26
25
|
|
|
27
26
|
| | |
|
|
28
27
|
|---|---|
|
|
@@ -32,21 +31,17 @@ that changes the moment the layout does.
|
|
|
32
31
|
| `text()`, `line(y)`, `lines()` | What is actually on the screen |
|
|
33
32
|
| `tree()` | The instance tree, for when the pixels are not the question |
|
|
34
33
|
|
|
35
|
-
An `Element` is a description - role, label, text, rect, focus, props - not a
|
|
36
|
-
handle. You act on the application with keys, the way a person does.
|
|
34
|
+
An `Element` is a description - role, label, text, rect, focus, props - not a handle. You act on the application with keys, the way a person does.
|
|
37
35
|
|
|
38
36
|
`get*` throws with the near misses listed; `query*` returns null.
|
|
39
37
|
|
|
40
38
|
## Drive it, then let it settle
|
|
41
39
|
|
|
42
|
-
`press('ctrl+s')`, `pressAll('down', 'down', 'enter')`, `type('hello')`, and
|
|
43
|
-
`resize(w, h)`. Effects and re-renders are asynchronous, so `await t.settle()`
|
|
44
|
-
is what separates "I sent the key" from "the screen caught up".
|
|
40
|
+
`press('ctrl+s')`, `pressAll('down', 'down', 'enter')`, `type('hello')`, and `resize(w, h)`. Effects and re-renders are asynchronous, so `await t.settle()` is what separates "I sent the key" from "the screen caught up".
|
|
45
41
|
|
|
46
42
|
## Test at two sizes
|
|
47
43
|
|
|
48
|
-
A single fixed size skips every breakpoint and hides the layout break you were
|
|
49
|
-
trying to catch. `resize` is cheap, and a loop over two sizes costs one line:
|
|
44
|
+
A single fixed size skips every breakpoint and hides the layout break you were trying to catch. `resize` is cheap, and a loop over two sizes costs one line:
|
|
50
45
|
|
|
51
46
|
```ts
|
|
52
47
|
for (const size of [{ width: 100, height: 30 }, { width: 60, height: 18 }]) {
|
|
@@ -56,9 +51,16 @@ for (const size of [{ width: 100, height: 30 }, { width: 60, height: 18 }]) {
|
|
|
56
51
|
|
|
57
52
|
## Runtime
|
|
58
53
|
|
|
59
|
-
Depends on `@textui/core` and `@textui/terminal
|
|
60
|
-
on the virtual terminal, so there is no tty to have. Node 22+ and Bun.
|
|
54
|
+
Depends on [`@textui/core`](https://www.npmjs.com/package/@textui/core) and [`@textui/terminal`](https://www.npmjs.com/package/@textui/terminal). No `node:` imports - it runs on the virtual terminal, so there is no tty to have. Node 22+ and Bun.
|
|
61
55
|
|
|
62
56
|
## Documentation
|
|
63
57
|
|
|
64
58
|
<https://softov.github.io/textui/>
|
|
59
|
+
|
|
60
|
+
<!-- family -->
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
Part of **[TextUI](https://github.com/softov/textui)** - [documentation](https://softov.github.io/textui/) - [getting started](https://softov.github.io/textui/getting-started.html)
|
|
65
|
+
|
|
66
|
+
[`@textui/kit`](https://www.npmjs.com/package/@textui/kit) one install · [`@textui/core`](https://www.npmjs.com/package/@textui/core) the runtime · [`@textui/widgets`](https://www.npmjs.com/package/@textui/widgets) the catalog · [`@textui/terminal`](https://www.npmjs.com/package/@textui/terminal) adapters and input · **`@textui/testing`** the harness · [`@textui/cli`](https://www.npmjs.com/package/@textui/cli) the CLI
|
package/dist/index.d.ts
CHANGED
|
@@ -105,6 +105,24 @@ export interface Harness {
|
|
|
105
105
|
click(x: number, y: number, button?: 'left' | 'middle' | 'right'): void;
|
|
106
106
|
/** Click the centre of an element. */
|
|
107
107
|
clickOn(element: Element): void;
|
|
108
|
+
/**
|
|
109
|
+
* Click the same cell `times` in a row, close enough together to count as
|
|
110
|
+
* one gesture.
|
|
111
|
+
*
|
|
112
|
+
* `click` deliberately does not: it steps the clock past the repeat window
|
|
113
|
+
* each time, so two of them are two clicks and a test that means a double
|
|
114
|
+
* click has to say so.
|
|
115
|
+
*/
|
|
116
|
+
clickRepeat(x: number, y: number, times: number): void;
|
|
117
|
+
/**
|
|
118
|
+
* Press at one point, drag through any points between, and release at the
|
|
119
|
+
* last one - the whole gesture, because that is the only part of it a
|
|
120
|
+
* handler can be wrong about.
|
|
121
|
+
*
|
|
122
|
+
* The drag goes to whoever took the press, wherever the points lead, so
|
|
123
|
+
* dragging off the element under test is a case worth writing.
|
|
124
|
+
*/
|
|
125
|
+
drag(from: [number, number], ...to: [number, number][]): void;
|
|
108
126
|
moveMouse(x: number, y: number): void;
|
|
109
127
|
wheel(x: number, y: number, delta: number): void;
|
|
110
128
|
focusTerminal(focused: boolean): void;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -69,6 +69,14 @@ async function mount(options) {
|
|
|
69
69
|
}
|
|
70
70
|
function createHarness(app, terminal, errors) {
|
|
71
71
|
const flush = () => app.flush();
|
|
72
|
+
/**
|
|
73
|
+
* The clock mouse events are stamped from.
|
|
74
|
+
*
|
|
75
|
+
* A real terminal stamps them on arrival; here they are synthesised, so the
|
|
76
|
+
* harness owns the time - which is what lets "a double click" and "two
|
|
77
|
+
* clicks" be two different tests rather than a race.
|
|
78
|
+
*/
|
|
79
|
+
let mouseClock = 0;
|
|
72
80
|
const collect = (predicate) => {
|
|
73
81
|
const tree = app.inspect();
|
|
74
82
|
if (!tree)
|
|
@@ -172,8 +180,22 @@ function createHarness(app, terminal, errors) {
|
|
|
172
180
|
flush();
|
|
173
181
|
},
|
|
174
182
|
click(x, y, button = 'left') {
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
// Past any repeat window, so consecutive clicks in a test are what they
|
|
184
|
+
// look like: separate ones.
|
|
185
|
+
mouseClock += 1000;
|
|
186
|
+
app.handleInput({ type: 'mouse', action: 'down', button, x, y, ctrl: false, alt: false, shift: false, at: mouseClock, handled: false });
|
|
187
|
+
app.handleInput({ type: 'mouse', action: 'up', button, x, y, ctrl: false, alt: false, shift: false, at: mouseClock, handled: false });
|
|
188
|
+
flush();
|
|
189
|
+
},
|
|
190
|
+
clickRepeat(x, y, times) {
|
|
191
|
+
mouseClock += 1000;
|
|
192
|
+
for (let i = 0; i < times; i++) {
|
|
193
|
+
// Inside the window, and on the same cell: the two things that make
|
|
194
|
+
// presses one gesture rather than several.
|
|
195
|
+
mouseClock += 60;
|
|
196
|
+
app.handleInput({ type: 'mouse', action: 'down', button: 'left', x, y, ctrl: false, alt: false, shift: false, at: mouseClock, handled: false });
|
|
197
|
+
app.handleInput({ type: 'mouse', action: 'up', button: 'left', x, y, ctrl: false, alt: false, shift: false, at: mouseClock, handled: false });
|
|
198
|
+
}
|
|
177
199
|
flush();
|
|
178
200
|
},
|
|
179
201
|
clickOn(element) {
|
|
@@ -182,6 +204,19 @@ function createHarness(app, terminal, errors) {
|
|
|
182
204
|
throw new Error(`[textui/testing] <${element.component}> has no bounds to click`);
|
|
183
205
|
this.click(rect.x + Math.floor(rect.width / 2), rect.y + Math.floor(rect.height / 2));
|
|
184
206
|
},
|
|
207
|
+
drag(from, ...to) {
|
|
208
|
+
mouseClock += 1000;
|
|
209
|
+
const send = (action, [x, y]) => {
|
|
210
|
+
mouseClock += 20;
|
|
211
|
+
app.handleInput({ type: 'mouse', action, button: 'left', x, y, ctrl: false, alt: false, shift: false, at: mouseClock, handled: false });
|
|
212
|
+
};
|
|
213
|
+
send('down', from);
|
|
214
|
+
const last = to[to.length - 1] ?? from;
|
|
215
|
+
for (const point of to)
|
|
216
|
+
send('drag', point);
|
|
217
|
+
send('up', last);
|
|
218
|
+
flush();
|
|
219
|
+
},
|
|
185
220
|
moveMouse(x, y) {
|
|
186
221
|
app.handleInput({ type: 'mouse', action: 'move', button: 'none', x, y, ctrl: false, alt: false, shift: false, handled: false });
|
|
187
222
|
flush();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@textui/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Headless testing harness for TextUI - semantic queries, input, resizing, time",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"terminal",
|
|
@@ -39,17 +39,17 @@
|
|
|
39
39
|
"import": "./dist/index.js"
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"@textui/widgets": "^0.1.0",
|
|
44
|
-
"@textui/terminal": "^0.1.0",
|
|
45
|
-
"@textui/core": "^0.1.0"
|
|
46
|
-
},
|
|
47
|
-
"publishConfig": {
|
|
48
|
-
"access": "public"
|
|
49
|
-
},
|
|
50
42
|
"scripts": {
|
|
51
43
|
"build": "tsc -p tsconfig.json",
|
|
52
44
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json",
|
|
53
45
|
"test": "vitest run"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@textui/core": "^0.2.0",
|
|
49
|
+
"@textui/terminal": "^0.2.0",
|
|
50
|
+
"@textui/widgets": "^0.2.0"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
54
|
}
|
|
55
|
-
}
|
|
55
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -119,6 +119,24 @@ export interface Harness {
|
|
|
119
119
|
click(x: number, y: number, button?: 'left' | 'middle' | 'right'): void;
|
|
120
120
|
/** Click the centre of an element. */
|
|
121
121
|
clickOn(element: Element): void;
|
|
122
|
+
/**
|
|
123
|
+
* Click the same cell `times` in a row, close enough together to count as
|
|
124
|
+
* one gesture.
|
|
125
|
+
*
|
|
126
|
+
* `click` deliberately does not: it steps the clock past the repeat window
|
|
127
|
+
* each time, so two of them are two clicks and a test that means a double
|
|
128
|
+
* click has to say so.
|
|
129
|
+
*/
|
|
130
|
+
clickRepeat(x: number, y: number, times: number): void;
|
|
131
|
+
/**
|
|
132
|
+
* Press at one point, drag through any points between, and release at the
|
|
133
|
+
* last one - the whole gesture, because that is the only part of it a
|
|
134
|
+
* handler can be wrong about.
|
|
135
|
+
*
|
|
136
|
+
* The drag goes to whoever took the press, wherever the points lead, so
|
|
137
|
+
* dragging off the element under test is a case worth writing.
|
|
138
|
+
*/
|
|
139
|
+
drag(from: [number, number], ...to: [number, number][]): void;
|
|
122
140
|
moveMouse(x: number, y: number): void;
|
|
123
141
|
wheel(x: number, y: number, delta: number): void;
|
|
124
142
|
focusTerminal(focused: boolean): void;
|
|
@@ -241,6 +259,15 @@ function createHarness(
|
|
|
241
259
|
): Harness {
|
|
242
260
|
const flush = (): void => app.flush();
|
|
243
261
|
|
|
262
|
+
/**
|
|
263
|
+
* The clock mouse events are stamped from.
|
|
264
|
+
*
|
|
265
|
+
* A real terminal stamps them on arrival; here they are synthesised, so the
|
|
266
|
+
* harness owns the time - which is what lets "a double click" and "two
|
|
267
|
+
* clicks" be two different tests rather than a race.
|
|
268
|
+
*/
|
|
269
|
+
let mouseClock = 0;
|
|
270
|
+
|
|
244
271
|
const collect = (predicate: (node: InspectorNode) => boolean): Element[] => {
|
|
245
272
|
const tree = app.inspect();
|
|
246
273
|
if (!tree) return [];
|
|
@@ -354,8 +381,22 @@ function createHarness(
|
|
|
354
381
|
flush();
|
|
355
382
|
},
|
|
356
383
|
click(x, y, button = 'left') {
|
|
357
|
-
|
|
358
|
-
|
|
384
|
+
// Past any repeat window, so consecutive clicks in a test are what they
|
|
385
|
+
// look like: separate ones.
|
|
386
|
+
mouseClock += 1000;
|
|
387
|
+
app.handleInput({ type: 'mouse', action: 'down', button, x, y, ctrl: false, alt: false, shift: false, at: mouseClock, handled: false });
|
|
388
|
+
app.handleInput({ type: 'mouse', action: 'up', button, x, y, ctrl: false, alt: false, shift: false, at: mouseClock, handled: false });
|
|
389
|
+
flush();
|
|
390
|
+
},
|
|
391
|
+
clickRepeat(x, y, times) {
|
|
392
|
+
mouseClock += 1000;
|
|
393
|
+
for (let i = 0; i < times; i++) {
|
|
394
|
+
// Inside the window, and on the same cell: the two things that make
|
|
395
|
+
// presses one gesture rather than several.
|
|
396
|
+
mouseClock += 60;
|
|
397
|
+
app.handleInput({ type: 'mouse', action: 'down', button: 'left', x, y, ctrl: false, alt: false, shift: false, at: mouseClock, handled: false });
|
|
398
|
+
app.handleInput({ type: 'mouse', action: 'up', button: 'left', x, y, ctrl: false, alt: false, shift: false, at: mouseClock, handled: false });
|
|
399
|
+
}
|
|
359
400
|
flush();
|
|
360
401
|
},
|
|
361
402
|
clickOn(element) {
|
|
@@ -363,6 +404,18 @@ function createHarness(
|
|
|
363
404
|
if (!rect) throw new Error(`[textui/testing] <${element.component}> has no bounds to click`);
|
|
364
405
|
this.click(rect.x + Math.floor(rect.width / 2), rect.y + Math.floor(rect.height / 2));
|
|
365
406
|
},
|
|
407
|
+
drag(from, ...to) {
|
|
408
|
+
mouseClock += 1000;
|
|
409
|
+
const send = (action: 'down' | 'drag' | 'up', [x, y]: [number, number]): void => {
|
|
410
|
+
mouseClock += 20;
|
|
411
|
+
app.handleInput({ type: 'mouse', action, button: 'left', x, y, ctrl: false, alt: false, shift: false, at: mouseClock, handled: false });
|
|
412
|
+
};
|
|
413
|
+
send('down', from);
|
|
414
|
+
const last = to[to.length - 1] ?? from;
|
|
415
|
+
for (const point of to) send('drag', point);
|
|
416
|
+
send('up', last);
|
|
417
|
+
flush();
|
|
418
|
+
},
|
|
366
419
|
moveMouse(x, y) {
|
|
367
420
|
app.handleInput({ type: 'mouse', action: 'move', button: 'none', x, y, ctrl: false, alt: false, shift: false, handled: false });
|
|
368
421
|
flush();
|