tui-td 0.2.14 → 0.2.19
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +85 -0
- data/README.md +76 -6
- data/lib/tui_td/cli.rb +229 -4
- data/lib/tui_td/configuration.rb +33 -0
- data/lib/tui_td/driver.rb +13 -0
- data/lib/tui_td/matchers.rb +258 -12
- data/lib/tui_td/mcp/server.rb +262 -7
- data/lib/tui_td/minitest/assertions.rb +263 -0
- data/lib/tui_td/selector.rb +0 -23
- data/lib/tui_td/snapshot.rb +247 -0
- data/lib/tui_td/test_runner.rb +65 -6
- data/lib/tui_td/version.rb +1 -1
- data/lib/tui_td.rb +3 -0
- metadata +9 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48cd9df54d8ad7bc1d41b8d4cfd961462ecff6e8056c4fd37e46abb56fa50f38
|
|
4
|
+
data.tar.gz: c77b9a6eb3a048806d17d4f5b2245dcf82dd1c5e818a247db48912e9b590d78d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ce4bdb6d4397369717686756508ec43219340ed4a0cc0ec8a4851018a19dc19bbdec3050147238a221cd4b282c92d848df1d71a1377b4915bfb42282af400d95
|
|
7
|
+
data.tar.gz: 6053e73dd915f8839d92ef8b046578bae6fbf481b0fda21872851e7e9052816833ce62ab5d6a4d15a685ce517f161fe31e75d5a9309a0dacb3521a3df4e893af
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,90 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 0.2.17
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Named snapshot testing: `match_snapshot("name")` — first run creates golden master,
|
|
8
|
+
subsequent runs compare. Supports `type: :text` (chars_only), `:full`, `:png`, `:html`, `:all`.
|
|
9
|
+
- `ignore_rows:` parameter — skip specific rows during snapshot comparison
|
|
10
|
+
- `region:` parameter — restrict comparison to a row range (e.g., `region: 0..6`)
|
|
11
|
+
Combinable with `ignore_rows:` for fine-grained control.
|
|
12
|
+
- `UPDATE_SNAPSHOTS=1` — environment variable to auto-update all snapshots
|
|
13
|
+
- `TUITD.configure { |c| c.snapshot_dir = "..." }` — configurable snapshot directory
|
|
14
|
+
- `Driver#find_text(pattern, match:)` — convenience delegation to State#find_text
|
|
15
|
+
- `Driver#snapshot` — returns a State snapshot for in-memory comparison
|
|
16
|
+
- JSON test steps: `snapshot`, `assert_snapshot` with `type:` and `wait:` options
|
|
17
|
+
- MCP tools: `tui_save_snapshot`, `tui_assert_snapshot`, `tui_element_actions`,
|
|
18
|
+
`tui_annotate_element`, `tui_diff`
|
|
19
|
+
- Drive mode: `snapshot <name>`, `diff <name>` commands
|
|
20
|
+
- `TUITD::Snapshot` class — core abstraction for save/load/compare of named snapshots
|
|
21
|
+
- `TUITD::Configuration` class — global gem configuration
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- `match_snapshot` matcher: now accepts String names (named snapshots) in addition to
|
|
26
|
+
legacy State objects. New kwargs: `type:`, `wait:`, `ignore_rows:`, `region:`.
|
|
27
|
+
- `tui_find_text` MCP tool: added `match:` parameter (`partial`, `exact`, `regex`)
|
|
28
|
+
- `tui_find_elements` MCP tool: added `checked:`/`disabled:` filters, new roles
|
|
29
|
+
- `tui_diff` MCP tool: fixed deep key symbolization for snapshot hash compatibility
|
|
30
|
+
- Dependency: tans-parser `~> 0.1.3`
|
|
31
|
+
|
|
32
|
+
## 0.2.16
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- Driver#find_text: convenience delegation to State#find_text with match: modes
|
|
37
|
+
- Driver#snapshot: return a State snapshot for later diff comparison
|
|
38
|
+
- match_snapshot RSpec matcher: compare current state against saved snapshot,
|
|
39
|
+
supports chars_only: true to ignore color/style changes
|
|
40
|
+
- MCP tui_diff tool: compare current terminal state against a previous snapshot,
|
|
41
|
+
returns cell-level differences
|
|
42
|
+
- MCP tui_annotate_element tool: manually register UI element annotations that
|
|
43
|
+
are picked up by tui_find_elements
|
|
44
|
+
- Drive mode: snapshot and diff commands for interactive state comparison
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- Tighten tans-parser dependency to ~> 0.1.3 (includes State#diff,
|
|
49
|
+
State#annotate_role, improved dialog/statusbar detection)
|
|
50
|
+
|
|
51
|
+
## 0.2.15
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- Integration with tans-parser 0.1.2: new UI element roles (:input, :label, :menu, :tab)
|
|
56
|
+
- Filter kwargs on get_by_role: text:, checked:, disabled:
|
|
57
|
+
- Singular convenience methods: button(), checkbox(), input(), label(), menu(), tab(),
|
|
58
|
+
dialog(), statusbar(), progress_bar()
|
|
59
|
+
- Element action methods: click, type(text), press_key(key)
|
|
60
|
+
- Element predicates: checked?, disabled?
|
|
61
|
+
- Element bounds accessor
|
|
62
|
+
- disabled field on Element
|
|
63
|
+
- ScopedSelector via Selector#within(element, &block) with full query API
|
|
64
|
+
- State#find_text match modes: :partial (default), :exact, :regex
|
|
65
|
+
- RSpec matchers: have_input, have_label, have_menu, have_tab, have_statusbar,
|
|
66
|
+
have_progress_bar
|
|
67
|
+
- JSON test steps: assert_input, assert_label, assert_menu, assert_tab,
|
|
68
|
+
assert_statusbar, assert_progress_bar
|
|
69
|
+
- MCP tool: tui_element_actions — returns click/type/press_key action hashes
|
|
70
|
+
- Enhanced MCP tui_find_elements: checked/disabled filters, new roles, (disabled)/
|
|
71
|
+
(focused) output
|
|
72
|
+
- Enhanced MCP tui_find_text: match: parameter for exact/regex mode
|
|
73
|
+
- CLI drive mode: elements command now shows inputs, labels, menus, tabs
|
|
74
|
+
- Updated help texts (tui-td help test, tui-td help rspec) with new steps and matchers
|
|
75
|
+
|
|
76
|
+
### Changed
|
|
77
|
+
|
|
78
|
+
- RSpec matchers (have_button, have_checkbox, have_role) now use tans-parser filter
|
|
79
|
+
kwargs instead of manual .select post-filtering
|
|
80
|
+
- JSON test runner check_role helper uses tans-parser filter kwargs
|
|
81
|
+
- have_checkbox now supports .unchecked chain
|
|
82
|
+
|
|
83
|
+
### Removed
|
|
84
|
+
|
|
85
|
+
- Custom coordinate-based Selector#within (replaced by tans-parser's element-based
|
|
86
|
+
within with ScopedSelector)
|
|
87
|
+
|
|
3
88
|
## 0.2.14
|
|
4
89
|
|
|
5
90
|
### Fixed
|
data/README.md
CHANGED
|
@@ -286,7 +286,13 @@ tui-td test examples/echo_test.json
|
|
|
286
286
|
| `assert_button` | `"text"` | Assert a button with given text is visible (`[ OK ]`, `(Cancel)`, `<Submit>`) |
|
|
287
287
|
| `assert_dialog` | — | Assert a dialog (box-drawing region) is visible |
|
|
288
288
|
| `assert_checkbox` | `"label", "checked": true` | Assert a checkbox with given label (and optionally checked state) |
|
|
289
|
-
| `assert_role` | `":button", "text": "OK"` | Generic role assertion (`:button`, `:checkbox`, `:dialog`, `:statusbar`, `:progress`) |
|
|
289
|
+
| `assert_role` | `":button", "text": "OK"` | Generic role assertion (`:button`, `:checkbox`, `:dialog`, `:statusbar`, `:progress`, `:input`, `:label`, `:menu`, `:tab`) |
|
|
290
|
+
| `assert_input` | `"text"` (optional) | Assert an input field (`[____]`) is visible |
|
|
291
|
+
| `assert_label` | `"text"` | Assert a label (text ending with colon) is visible |
|
|
292
|
+
| `assert_menu` | `"text"` (optional) | Assert a menu bar or dropdown item is visible |
|
|
293
|
+
| `assert_tab` | `"text"` | Assert a tab (`[Tab1]`) is visible |
|
|
294
|
+
| `assert_statusbar` | — | Assert a status bar (bottom row with background) is visible |
|
|
295
|
+
| `assert_progress_bar` | `"text"` (optional) | Assert a progress bar (`[####]`) is visible |
|
|
290
296
|
| `close` | — | Close the TUI |
|
|
291
297
|
|
|
292
298
|
Example with `html` step for before/after snapshots:
|
|
@@ -364,9 +370,65 @@ end
|
|
|
364
370
|
| `have_button("OK")` | Assert a button with given text is visible |
|
|
365
371
|
| `have_dialog` | Assert a dialog (box-drawing region) is visible |
|
|
366
372
|
| `have_checkbox("Label").checked` | Assert a checkbox with given label (chain `.checked`) |
|
|
367
|
-
| `have_role(:button, text: "OK")` | Generic role assertion with optional text
|
|
373
|
+
| `have_role(:button, text: "OK", checked: true, disabled: false)` | Generic role assertion with optional text, checked, disabled filters |
|
|
374
|
+
| `have_input` | Assert an input field (`[____]`) is visible |
|
|
375
|
+
| `have_label("Name")` | Assert a label (text ending with colon) is visible |
|
|
376
|
+
| `have_menu` | Assert a menu bar or dropdown item is visible |
|
|
377
|
+
| `have_tab("File")` | Assert a tab is visible |
|
|
378
|
+
| `have_statusbar` | Assert a status bar (bottom row with background) is visible |
|
|
379
|
+
| `have_progress_bar("50%")` | Assert a progress bar (`[####]`) is visible |
|
|
368
380
|
| `have_exit_status(N)` | Assert the driver process exit status equals N |
|
|
369
381
|
|
|
382
|
+
## Snapshot Testing
|
|
383
|
+
|
|
384
|
+
Named, persisted snapshot testing à la Playwright/Jest. First run creates a golden master — subsequent runs compare.
|
|
385
|
+
|
|
386
|
+
### RSpec
|
|
387
|
+
|
|
388
|
+
```ruby
|
|
389
|
+
# Named snapshot (recommended)
|
|
390
|
+
expect(driver).to match_snapshot("login_screen")
|
|
391
|
+
expect(driver).to match_snapshot("login_screen", type: :all, wait: true)
|
|
392
|
+
|
|
393
|
+
# Partial screen comparison
|
|
394
|
+
expect(driver).to match_snapshot("banner", region: 0..6, chars_only: true)
|
|
395
|
+
|
|
396
|
+
# Skip volatile rows (e.g., prompt line)
|
|
397
|
+
expect(driver).to match_snapshot("main", ignore_rows: [5])
|
|
398
|
+
|
|
399
|
+
# In-memory comparison (legacy)
|
|
400
|
+
pre = driver.snapshot
|
|
401
|
+
expect(driver).to match_snapshot(pre, chars_only: true)
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Configuration
|
|
405
|
+
|
|
406
|
+
```ruby
|
|
407
|
+
TUITD.configure do |c|
|
|
408
|
+
c.snapshot_dir = "spec/snapshots" # default
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Update all snapshots
|
|
412
|
+
UPDATE_SNAPSHOTS=1 bundle exec rspec
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### JSON Test Steps
|
|
416
|
+
|
|
417
|
+
```json
|
|
418
|
+
{"snapshot": "login_screen", "type": "text"}
|
|
419
|
+
{"assert_snapshot": "login_screen", "type": "png", "wait": true}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Types
|
|
423
|
+
|
|
424
|
+
| Type | Comparison | File |
|
|
425
|
+
|------|-----------|------|
|
|
426
|
+
| `:text` | chars only (ignores colors/styles) | `.json` |
|
|
427
|
+
| `:full` | full cell comparison (chars + colors) | `.json` |
|
|
428
|
+
| `:png` | screenshot byte-identical | `.png` |
|
|
429
|
+
| `:html` | HTML render byte-identical | `.html` |
|
|
430
|
+
| `:all` | all three at once | `.json` + `.png` + `.html` |
|
|
431
|
+
|
|
370
432
|
## MCP Server — AI Integration
|
|
371
433
|
|
|
372
434
|
Start the MCP server to let any MCP client control TUIs:
|
|
@@ -390,8 +452,13 @@ tui-td serve
|
|
|
390
452
|
| `tui_html_render` | Render terminal state as a self-contained HTML document. Returns HTML inline or saves to file. |
|
|
391
453
|
| `tui_wait_for_exit` | Wait until the TUI process exits. Returns exit status. |
|
|
392
454
|
| `tui_exit_status` | Get the exit status code (nil if still running). |
|
|
393
|
-
| `tui_find_text` | Search for text or regex in terminal state.
|
|
394
|
-
| `tui_find_elements` | Detect UI elements (buttons, checkboxes, dialogs, etc.) with optional role
|
|
455
|
+
| `tui_find_text` | Search for text or regex in terminal state. Supports match modes: `partial` (default), `exact`, `regex`. |
|
|
456
|
+
| `tui_find_elements` | Detect UI elements (buttons, checkboxes, dialogs, inputs, labels, menus, tabs, etc.) with optional role, text, checked, and disabled filters. |
|
|
457
|
+
| `tui_element_actions` | Get click/type/press_key action hashes for a detected UI element. For AI-driven interaction. |
|
|
458
|
+
| `tui_diff` | Compare current state against a previous snapshot. Returns cell-level differences. |
|
|
459
|
+
| `tui_annotate_element` | Manually register a UI element annotation. Picked up by tui_find_elements. |
|
|
460
|
+
| `tui_save_snapshot` | Save the current terminal state as a named snapshot to disk. |
|
|
461
|
+
| `tui_assert_snapshot` | Assert current state matches a saved named snapshot. Creates on first run. |
|
|
395
462
|
| `tui_close` | Close the TUI and clean up. |
|
|
396
463
|
|
|
397
464
|
### MCP configuration
|
|
@@ -441,10 +508,13 @@ Add to your MCP client configuration:
|
|
|
441
508
|
// 9. Find UI elements by role
|
|
442
509
|
{"method": "tools/call", "params": {"name": "tui_find_elements", "arguments": {"role": "button"}}}
|
|
443
510
|
|
|
444
|
-
// 10.
|
|
511
|
+
// 10. Get actions for an element
|
|
512
|
+
{"method": "tools/call", "params": {"name": "tui_element_actions", "arguments": {"role": "button", "text": "OK"}}}
|
|
513
|
+
|
|
514
|
+
// 11. Check exit status (or wait for exit)
|
|
445
515
|
{"method": "tools/call", "params": {"name": "tui_exit_status", "arguments": {}}}
|
|
446
516
|
|
|
447
|
-
//
|
|
517
|
+
// 12. Clean up
|
|
448
518
|
{"method": "tools/call", "params": {"name": "tui_close", "arguments": {}}}
|
|
449
519
|
```
|
|
450
520
|
|
data/lib/tui_td/cli.rb
CHANGED
|
@@ -41,6 +41,10 @@ module TUITD
|
|
|
41
41
|
opts.separator " state Show terminal state as pretty JSON"
|
|
42
42
|
opts.separator " raw Show raw ANSI output"
|
|
43
43
|
opts.separator " elements Show detected UI elements (buttons, dialogs, etc.)"
|
|
44
|
+
opts.separator " snapshot <name> Save current state as named snapshot to disk"
|
|
45
|
+
opts.separator " snapshot Save current state in-memory (legacy)"
|
|
46
|
+
opts.separator " diff <name> Compare current state against named snapshot on disk"
|
|
47
|
+
opts.separator " diff Compare against in-memory snapshot (legacy)"
|
|
44
48
|
opts.separator " key <name> Send keystroke (enter, tab, escape, up, down, left, right,"
|
|
45
49
|
opts.separator " backspace, ctrl_c, ctrl_d)"
|
|
46
50
|
opts.separator " <text> Send text to the TUI"
|
|
@@ -116,6 +120,8 @@ module TUITD
|
|
|
116
120
|
_help_test
|
|
117
121
|
when "rspec"
|
|
118
122
|
_help_rspec
|
|
123
|
+
when "minitest"
|
|
124
|
+
_help_minitest
|
|
119
125
|
when nil
|
|
120
126
|
_help_main
|
|
121
127
|
else
|
|
@@ -202,8 +208,53 @@ module TUITD
|
|
|
202
208
|
puts "Buttons: #{selector.buttons.map { |e| "#{e.text}@[#{e.row},#{e.col}]" }.join(", ")}"
|
|
203
209
|
puts "Checkboxes: #{selector.checkboxes.map { |e| "#{e.text} (#{e.checked ? "✓" : "☐"})" }.join(", ")}"
|
|
204
210
|
puts "Dialogs: #{selector.dialogs.map { |e| "\"#{e.text}\" #{e.width}x#{e.height}" }.join(", ")}"
|
|
211
|
+
puts "Inputs: #{selector.inputs.map { |e| "[__]@[#{e.row},#{e.col}]" }.join(", ")}"
|
|
212
|
+
puts "Labels: #{selector.labels.map(&:text).join(", ")}"
|
|
213
|
+
puts "Menus: #{selector.menus.map(&:text).join(", ")}"
|
|
214
|
+
puts "Tabs: #{selector.tabs.map { |e| "#{e.text}#{" (focused)" if e.focused}" }.join(", ")}"
|
|
205
215
|
puts "Statusbars: #{selector.statusbars.map(&:text).join(", ")}"
|
|
206
216
|
puts "Progress: #{selector.progress_bars.map(&:text).join(", ")}"
|
|
217
|
+
elsif input.start_with?("snapshot ")
|
|
218
|
+
name = input.split(" ", 2).last.strip
|
|
219
|
+
unless name.empty?
|
|
220
|
+
snap = Snapshot.new(name)
|
|
221
|
+
snap.save(driver.state_data)
|
|
222
|
+
puts "Snapshot '#{name}' saved to #{snap.path}."
|
|
223
|
+
end
|
|
224
|
+
elsif input == "snapshot"
|
|
225
|
+
@last_snapshot = State.new(driver.state_data)
|
|
226
|
+
puts "In-memory snapshot saved."
|
|
227
|
+
elsif input.start_with?("diff ")
|
|
228
|
+
name = input.split(" ", 2).last.strip
|
|
229
|
+
unless name.empty?
|
|
230
|
+
snap = Snapshot.new(name)
|
|
231
|
+
unless snap.exists?
|
|
232
|
+
puts "No snapshot '#{name}' found at #{snap.path}."
|
|
233
|
+
next
|
|
234
|
+
end
|
|
235
|
+
result = snap.compare(driver.state_data)
|
|
236
|
+
if result.passed?
|
|
237
|
+
puts "No differences. Snapshot '#{name}' matches."
|
|
238
|
+
else
|
|
239
|
+
puts result.message
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
elsif input == "diff"
|
|
243
|
+
if @last_snapshot
|
|
244
|
+
current = State.new(driver.state_data)
|
|
245
|
+
diffs = current.diff(@last_snapshot)
|
|
246
|
+
if diffs.empty?
|
|
247
|
+
puts "No differences."
|
|
248
|
+
else
|
|
249
|
+
puts "#{diffs.size} difference(s):"
|
|
250
|
+
diffs.first(10).each do |d|
|
|
251
|
+
puts " [#{d[:row]},#{d[:col]}] #{d[:before][:char].inspect} -> #{d[:after][:char].inspect}"
|
|
252
|
+
end
|
|
253
|
+
puts " ..." if diffs.size > 10
|
|
254
|
+
end
|
|
255
|
+
else
|
|
256
|
+
puts "No snapshot saved. Use 'snapshot <name>' or 'snapshot' first."
|
|
257
|
+
end
|
|
207
258
|
elsif input.start_with?("key ")
|
|
208
259
|
driver.send_keys(input.split(" ", 2).last.to_sym)
|
|
209
260
|
else
|
|
@@ -324,8 +375,9 @@ module TUITD
|
|
|
324
375
|
def _help_main
|
|
325
376
|
puts(OptionParser.new { |o| o.banner = "Usage: tui-td <command> [options]" })
|
|
326
377
|
puts
|
|
327
|
-
puts "For more: tui-td help test
|
|
328
|
-
puts " tui-td help rspec
|
|
378
|
+
puts "For more: tui-td help test (JSON test step types)"
|
|
379
|
+
puts " tui-td help rspec (RSpec matchers)"
|
|
380
|
+
puts " tui-td help minitest (Minitest assertions)"
|
|
329
381
|
exit 0
|
|
330
382
|
end
|
|
331
383
|
|
|
@@ -433,7 +485,36 @@ module TUITD
|
|
|
433
485
|
|
|
434
486
|
{"assert_role": ":button", "text": "OK"}
|
|
435
487
|
Generic role assertion. Accepts :button, :checkbox, :dialog,
|
|
436
|
-
:statusbar, :progress
|
|
488
|
+
:statusbar, :progress, :input, :label, :menu, :tab.
|
|
489
|
+
Optional "text", "checked", and "disabled" filters.
|
|
490
|
+
|
|
491
|
+
{"assert_input": true} or {"assert_input": "text"}
|
|
492
|
+
Assert that an input field ([____]) is visible. Optional text
|
|
493
|
+
filter to match adjacent label.
|
|
494
|
+
|
|
495
|
+
{"assert_label": "Name"}
|
|
496
|
+
Assert that a label (text ending with colon) is visible.
|
|
497
|
+
|
|
498
|
+
{"assert_menu": true} or {"assert_menu": "File | Edit"}
|
|
499
|
+
Assert that a menu bar or dropdown item is visible.
|
|
500
|
+
|
|
501
|
+
{"assert_tab": "File"}
|
|
502
|
+
Assert that a tab ([Tab1]) is visible.
|
|
503
|
+
|
|
504
|
+
{"assert_statusbar": true}
|
|
505
|
+
Assert that a status bar (bottom row with background) is visible.
|
|
506
|
+
|
|
507
|
+
{"assert_progress_bar": true} or {"assert_progress_bar": "50%"}
|
|
508
|
+
Assert that a progress bar ([#### ]) is visible.
|
|
509
|
+
|
|
510
|
+
{"snapshot": "login_screen", "type": "text", "wait": true}
|
|
511
|
+
Save current terminal state as a named snapshot to disk.
|
|
512
|
+
type: "text" (default), "full", "png", "html", "all".
|
|
513
|
+
|
|
514
|
+
{"assert_snapshot": "login_screen", "type": "text", "wait": true}
|
|
515
|
+
Assert current state matches a saved named snapshot.
|
|
516
|
+
First run creates the golden master (passes automatically).
|
|
517
|
+
UPDATE_SNAPSHOTS=1 to force update all snapshots.
|
|
437
518
|
|
|
438
519
|
Example test file: examples/echo_test.json
|
|
439
520
|
HELP
|
|
@@ -518,9 +599,51 @@ module TUITD
|
|
|
518
599
|
|
|
519
600
|
have_role(:button, text: "OK")
|
|
520
601
|
Generic role matcher. Accepts :button, :checkbox, :dialog,
|
|
521
|
-
:statusbar, :progress
|
|
602
|
+
:statusbar, :progress, :input, :label, :menu, :tab.
|
|
603
|
+
Optional text:, checked:, disabled: filters.
|
|
522
604
|
Usage: expect(state).to have_role(:statusbar)
|
|
523
605
|
|
|
606
|
+
have_input
|
|
607
|
+
Passes if an input field ([____]) is visible.
|
|
608
|
+
Usage: expect(state).to have_input
|
|
609
|
+
Usage: expect(state).to have_input("Name")
|
|
610
|
+
|
|
611
|
+
have_label("Name")
|
|
612
|
+
Passes if a label (text ending with colon) is visible.
|
|
613
|
+
Usage: expect(state).to have_label("Name")
|
|
614
|
+
|
|
615
|
+
have_menu
|
|
616
|
+
Passes if a menu bar or dropdown item is visible.
|
|
617
|
+
Usage: expect(state).to have_menu
|
|
618
|
+
|
|
619
|
+
have_tab("File")
|
|
620
|
+
Passes if a tab is visible.
|
|
621
|
+
Usage: expect(state).to have_tab("File")
|
|
622
|
+
|
|
623
|
+
have_statusbar
|
|
624
|
+
Passes if a status bar (bottom row with background) is visible.
|
|
625
|
+
Usage: expect(state).to have_statusbar
|
|
626
|
+
|
|
627
|
+
have_progress_bar
|
|
628
|
+
Passes if a progress bar ([#### ]) is visible.
|
|
629
|
+
Usage: expect(state).to have_progress_bar
|
|
630
|
+
|
|
631
|
+
match_snapshot("<name>", type: :text, wait: false, region: 0..6, ignore_rows: [2])
|
|
632
|
+
Named, persisted snapshot testing. First run creates the snapshot
|
|
633
|
+
(passes automatically), subsequent runs compare.
|
|
634
|
+
Types: :text (chars_only, default), :full (chars+colors), :png, :html, :all.
|
|
635
|
+
region: restricts comparison to a row range (e.g., 0..6).
|
|
636
|
+
ignore_rows: skips specific rows during comparison.
|
|
637
|
+
UPDATE_SNAPSHOTS=1 to auto-update all snapshots.
|
|
638
|
+
Usage: expect(driver).to match_snapshot("login_screen")
|
|
639
|
+
Usage: expect(driver).to match_snapshot("banner", region: 0..6, chars_only: true)
|
|
640
|
+
Usage: expect(driver).to match_snapshot("main", ignore_rows: [5], wait: true)
|
|
641
|
+
|
|
642
|
+
match_snapshot(State, chars_only: false) (legacy in-memory)
|
|
643
|
+
Passes if the current state matches a previously saved snapshot object.
|
|
644
|
+
Usage: pre = driver.snapshot; ... ; expect(driver).to match_snapshot(pre)
|
|
645
|
+
Usage: expect(state).to match_snapshot(snap, chars_only: true)
|
|
646
|
+
|
|
524
647
|
Driver matchers (work on TUITD::Driver, not State)
|
|
525
648
|
--------------------------------------------------
|
|
526
649
|
|
|
@@ -530,6 +653,108 @@ module TUITD
|
|
|
530
653
|
HELP
|
|
531
654
|
exit 0
|
|
532
655
|
end
|
|
656
|
+
|
|
657
|
+
def _help_minitest
|
|
658
|
+
puts <<~HELP
|
|
659
|
+
Minitest Assertions
|
|
660
|
+
===================
|
|
661
|
+
|
|
662
|
+
Include the assertions module in your Minitest test class:
|
|
663
|
+
|
|
664
|
+
require "tui_td/minitest/assertions"
|
|
665
|
+
|
|
666
|
+
class MyTUITest < Minitest::Test
|
|
667
|
+
include TUITD::Minitest::Assertions
|
|
668
|
+
|
|
669
|
+
def setup
|
|
670
|
+
@driver = TUITD::Driver.new("my_tui", rows: 24, cols: 80)
|
|
671
|
+
@driver.start
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
def teardown
|
|
675
|
+
@driver&.close
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
Auto-wait: When given a Driver, assertions wait up to 3 seconds.
|
|
680
|
+
When given a State, assertions check immediately.
|
|
681
|
+
|
|
682
|
+
Assertions
|
|
683
|
+
----------
|
|
684
|
+
|
|
685
|
+
Text / Regex / Color / Style:
|
|
686
|
+
|
|
687
|
+
assert_text(driver, "Welcome")
|
|
688
|
+
Passes if text appears anywhere in the terminal.
|
|
689
|
+
Negate: refute_text(driver, "Error")
|
|
690
|
+
|
|
691
|
+
assert_regex(driver, /error|fail/)
|
|
692
|
+
Passes if regex pattern matches anywhere.
|
|
693
|
+
Negate: refute_regex(driver, /pattern/)
|
|
694
|
+
|
|
695
|
+
assert_fg(driver, "cyan", row: 0, col: 5)
|
|
696
|
+
Assert foreground color at position.
|
|
697
|
+
|
|
698
|
+
assert_bg(driver, "blue", row: 0, col: 0)
|
|
699
|
+
Assert background color at position.
|
|
700
|
+
|
|
701
|
+
assert_style(driver, row: 0, col: 0, bold: true, italic: false)
|
|
702
|
+
Assert style attributes at position.
|
|
703
|
+
|
|
704
|
+
assert_exit_status(driver, 0)
|
|
705
|
+
Assert the process exit status matches expected.
|
|
706
|
+
|
|
707
|
+
Selector assertions:
|
|
708
|
+
|
|
709
|
+
assert_button(driver, "OK")
|
|
710
|
+
Passes if a button with given text is visible.
|
|
711
|
+
Negate: refute_button(driver, "Cancel")
|
|
712
|
+
|
|
713
|
+
assert_dialog(driver)
|
|
714
|
+
Passes if a dialog (box-drawing region) is visible.
|
|
715
|
+
Negate: refute_dialog(driver)
|
|
716
|
+
|
|
717
|
+
assert_checkbox(driver, "Label", checked: true)
|
|
718
|
+
Passes if checkbox with given label (and optionally state) is visible.
|
|
719
|
+
Use checked: true, checked: false, or unchecked: true.
|
|
720
|
+
|
|
721
|
+
assert_role(driver, :button, text: "OK", checked: nil, disabled: nil)
|
|
722
|
+
Generic role assertion. Accepts :button, :checkbox, :dialog,
|
|
723
|
+
:statusbar, :progress, :input, :label, :menu, :tab.
|
|
724
|
+
|
|
725
|
+
assert_input(driver)
|
|
726
|
+
assert_input(driver, "Name")
|
|
727
|
+
Passes if an input field ([____]) is visible.
|
|
728
|
+
|
|
729
|
+
assert_label(driver, "Username")
|
|
730
|
+
Passes if a label (text ending with colon) is visible.
|
|
731
|
+
|
|
732
|
+
assert_menu(driver)
|
|
733
|
+
Passes if a menu bar or dropdown item is visible.
|
|
734
|
+
|
|
735
|
+
assert_tab(driver, "File")
|
|
736
|
+
Passes if a tab is visible.
|
|
737
|
+
|
|
738
|
+
assert_statusbar(driver)
|
|
739
|
+
Passes if a status bar (bottom row with background) is visible.
|
|
740
|
+
|
|
741
|
+
assert_progress_bar(driver, "50%")
|
|
742
|
+
Passes if a progress bar ([#### ]) is visible.
|
|
743
|
+
|
|
744
|
+
Snapshot:
|
|
745
|
+
|
|
746
|
+
assert_snapshot(driver, "login_screen", type: :text, wait: true)
|
|
747
|
+
Named snapshot testing. First run creates golden master,
|
|
748
|
+
subsequent runs compare.
|
|
749
|
+
|
|
750
|
+
assert_snapshot(driver, "banner", region: 0..6, chars_only: true)
|
|
751
|
+
Partial screen comparison with region:.
|
|
752
|
+
|
|
753
|
+
assert_snapshot(driver, "main", ignore_rows: [5])
|
|
754
|
+
Skip volatile rows with ignore_rows:.
|
|
755
|
+
HELP
|
|
756
|
+
exit 0
|
|
757
|
+
end
|
|
533
758
|
end
|
|
534
759
|
end
|
|
535
760
|
# rubocop:enable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockLength
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TUITD
|
|
4
|
+
# Global configuration for tui-td.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# TUITD.configure do |c|
|
|
8
|
+
# c.snapshot_dir = "spec/snapshots"
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
|
+
class Configuration
|
|
12
|
+
attr_accessor :snapshot_dir
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@snapshot_dir = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Check if UPDATE_SNAPSHOTS env var is set to update mode.
|
|
19
|
+
def update_snapshots?
|
|
20
|
+
%w[1 true].include?(ENV["UPDATE_SNAPSHOTS"].to_s)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
def configuration
|
|
26
|
+
@configuration ||= Configuration.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def configure
|
|
30
|
+
yield configuration
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/tui_td/driver.rb
CHANGED
|
@@ -211,6 +211,19 @@ module TUITD
|
|
|
211
211
|
Screenshot.new(@state).render(output_path)
|
|
212
212
|
end
|
|
213
213
|
|
|
214
|
+
# Search for text or regex pattern in the current terminal state.
|
|
215
|
+
# Delegates to TansParser::State#find_text.
|
|
216
|
+
# Supports match modes: :partial (default, substring), :exact, :regex.
|
|
217
|
+
def find_text(pattern, match: :partial)
|
|
218
|
+
TUITD::State.new(state_data).find_text(pattern, match: match)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Return a snapshot of the current terminal state as a TUITD::State object.
|
|
222
|
+
# Can be compared later with match_snapshot or State#diff.
|
|
223
|
+
def snapshot
|
|
224
|
+
TUITD::State.new(state_data)
|
|
225
|
+
end
|
|
226
|
+
|
|
214
227
|
# Close the driver and clean up
|
|
215
228
|
def close
|
|
216
229
|
_stop_reader_thread
|