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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81e8cf766200bdb7f86334be97036e68f36df7e352618e823185976ebb07a629
4
- data.tar.gz: 1632047e8e7b668d0aee6fcf3b14ff16b01d5c23d4cca1f126315d6fe8c1606b
3
+ metadata.gz: 48cd9df54d8ad7bc1d41b8d4cfd961462ecff6e8056c4fd37e46abb56fa50f38
4
+ data.tar.gz: c77b9a6eb3a048806d17d4f5b2245dcf82dd1c5e818a247db48912e9b590d78d
5
5
  SHA512:
6
- metadata.gz: a0f08cd1cc35c05466f5f5b77f45f674635d390db8a68bf9a75e316e2cc70f681daf718211a8563b765ccbfe1b7689d38e28378311dc2ad9afa3bf57a946a172
7
- data.tar.gz: 5f6385932f9c350c3186dfe44a3c47bc0c169c86189feb90c8a54dc6ddfdb54f0b97489ffe812da557cc2e6828b384b28cd81ca286be977221586c4f0788fe33
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 filter |
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. Returns positions of all matches. |
394
- | `tui_find_elements` | Detect UI elements (buttons, checkboxes, dialogs, etc.) with optional role/text filters. |
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. Check exit status (or wait for exit)
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
- // 11. Clean up
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 (JSON test step types)"
328
- puts " tui-td help rspec (RSpec matchers)"
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. Optional "text" filter.
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. Optional text: filter.
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