tui-td 0.2.17 → 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: cadf6d0ad5e362e3704a7a87673e0ad7254573eba2f53575aea38fc45f949877
4
- data.tar.gz: fbfd589b84e911d6c46b16976b34dc46943019c52bd7519b04ffc6422a71b659
3
+ metadata.gz: 48cd9df54d8ad7bc1d41b8d4cfd961462ecff6e8056c4fd37e46abb56fa50f38
4
+ data.tar.gz: c77b9a6eb3a048806d17d4f5b2245dcf82dd1c5e818a247db48912e9b590d78d
5
5
  SHA512:
6
- metadata.gz: c0e8f58b7f193a556d2dee0e1f8c15f005b669fe3823d31763e57efd8c35d511f5e2e5dbb9ff03a61abe534030c1673cc1de437bcbe705b8c008c1ff9af8878a
7
- data.tar.gz: 520f31800ee26803d9ec56b9128b729f59a057246a11e2b70c1647f253b76bb95e06f99631fb4d6c23597415e8a48a92725e22c36b99faca932cb75d00c84d8c
6
+ metadata.gz: ce4bdb6d4397369717686756508ec43219340ed4a0cc0ec8a4851018a19dc19bbdec3050147238a221cd4b282c92d848df1d71a1377b4915bfb42282af400d95
7
+ data.tar.gz: 6053e73dd915f8839d92ef8b046578bae6fbf481b0fda21872851e7e9052816833ce62ab5d6a4d15a685ce517f161fe31e75d5a9309a0dacb3521a3df4e893af
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
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
+
3
32
  ## 0.2.16
4
33
 
5
34
  ### Added
data/README.md CHANGED
@@ -379,6 +379,56 @@ end
379
379
  | `have_progress_bar("50%")` | Assert a progress bar (`[####]`) is visible |
380
380
  | `have_exit_status(N)` | Assert the driver process exit status equals N |
381
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
+
382
432
  ## MCP Server — AI Integration
383
433
 
384
434
  Start the MCP server to let any MCP client control TUIs:
@@ -407,6 +457,8 @@ tui-td serve
407
457
  | `tui_element_actions` | Get click/type/press_key action hashes for a detected UI element. For AI-driven interaction. |
408
458
  | `tui_diff` | Compare current state against a previous snapshot. Returns cell-level differences. |
409
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. |
410
462
  | `tui_close` | Close the TUI and clean up. |
411
463
 
412
464
  ### MCP configuration
data/lib/tui_td/cli.rb CHANGED
@@ -120,6 +120,8 @@ module TUITD
120
120
  _help_test
121
121
  when "rspec"
122
122
  _help_rspec
123
+ when "minitest"
124
+ _help_minitest
123
125
  when nil
124
126
  _help_main
125
127
  else
@@ -373,8 +375,9 @@ module TUITD
373
375
  def _help_main
374
376
  puts(OptionParser.new { |o| o.banner = "Usage: tui-td <command> [options]" })
375
377
  puts
376
- puts "For more: tui-td help test (JSON test step types)"
377
- 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)"
378
381
  exit 0
379
382
  end
380
383
 
@@ -504,6 +507,15 @@ module TUITD
504
507
  {"assert_progress_bar": true} or {"assert_progress_bar": "50%"}
505
508
  Assert that a progress bar ([#### ]) is visible.
506
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.
518
+
507
519
  Example test file: examples/echo_test.json
508
520
  HELP
509
521
  exit 0
@@ -616,9 +628,19 @@ module TUITD
616
628
  Passes if a progress bar ([#### ]) is visible.
617
629
  Usage: expect(state).to have_progress_bar
618
630
 
619
- match_snapshot(snapshot, chars_only: false)
620
- Passes if the current state matches a previously saved snapshot.
621
- Use chars_only: true to ignore color/style changes.
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.
622
644
  Usage: pre = driver.snapshot; ... ; expect(driver).to match_snapshot(pre)
623
645
  Usage: expect(state).to match_snapshot(snap, chars_only: true)
624
646
 
@@ -631,6 +653,108 @@ module TUITD
631
653
  HELP
632
654
  exit 0
633
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
634
758
  end
635
759
  end
636
760
  # rubocop:enable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockLength
@@ -0,0 +1,263 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/ParameterLists
4
+
5
+ require "minitest"
6
+
7
+ module TUITD
8
+ module Minitest
9
+ # Assertions for TUI testing with Minitest.
10
+ #
11
+ # Include this module in your Minitest test class:
12
+ #
13
+ # require "tui_td/minitest/assertions"
14
+ #
15
+ # class MyTUITest < Minitest::Test
16
+ # include TUITD::Minitest::Assertions
17
+ #
18
+ # def test_login_screen
19
+ # driver = TUITD::Driver.new("my_tui", rows: 24, cols: 80)
20
+ # driver.start
21
+ # assert_text(driver, "Welcome")
22
+ # assert_button(driver, "OK")
23
+ # refute_text(driver, "Error")
24
+ # ensure
25
+ # driver&.close
26
+ # end
27
+ # end
28
+ #
29
+ # Auto-wait: When given a Driver, assertions wait up to 3 seconds.
30
+ # When given a State, assertions check immediately.
31
+ #
32
+ module Assertions
33
+ AUTO_WAIT_TIMEOUT = 3
34
+
35
+ private
36
+
37
+ def auto_wait(actual, timeout: AUTO_WAIT_TIMEOUT, &predicate)
38
+ if actual.respond_to?(:wait_for)
39
+ begin
40
+ actual.wait_for(timeout: timeout, &predicate)
41
+ true
42
+ rescue TUITD::TimeoutError
43
+ false
44
+ end
45
+ else
46
+ predicate.call(actual)
47
+ end
48
+ end
49
+
50
+ def state_from(actual)
51
+ if actual.respond_to?(:state_data)
52
+ TUITD::State.new(actual.state_data)
53
+ else
54
+ actual # State or raw hash — pass through
55
+ end
56
+ end
57
+
58
+ public
59
+
60
+ # --- Text / Regex / Color / Style ---
61
+
62
+ def assert_text(actual, expected)
63
+ result = auto_wait(actual) { |s| s.find_text(expected).any? }
64
+ assert(result, "Expected terminal to contain #{expected.inspect}")
65
+ end
66
+
67
+ def refute_text(actual, expected)
68
+ result = auto_wait(actual) { |s| s.find_text(expected).empty? }
69
+ assert(result, "Expected terminal NOT to contain #{expected.inspect}")
70
+ end
71
+
72
+ def assert_regex(actual, pattern)
73
+ regex = pattern.is_a?(Regexp) ? pattern : Regexp.new(pattern.to_s)
74
+ result = auto_wait(actual) { |s| s.find_text(regex).any? }
75
+ assert(result, "Expected terminal to match #{pattern.inspect}")
76
+ end
77
+
78
+ def refute_regex(actual, pattern)
79
+ regex = pattern.is_a?(Regexp) ? pattern : Regexp.new(pattern.to_s)
80
+ result = auto_wait(actual) { |s| s.find_text(regex).empty? }
81
+ assert(result, "Expected terminal NOT to match #{pattern.inspect}")
82
+ end
83
+
84
+ def assert_fg(actual, expected, row:, col:)
85
+ result = auto_wait(actual) { |s| s.foreground_at(row, col) == expected }
86
+ actual_fg = state_from(actual).foreground_at(row, col)
87
+ assert(result, "Expected FG at [#{row},#{col}] to be #{expected.inspect}, but was #{actual_fg.inspect}")
88
+ end
89
+
90
+ def assert_bg(actual, expected, row:, col:)
91
+ result = auto_wait(actual) { |s| s.background_at(row, col) == expected }
92
+ actual_bg = state_from(actual).background_at(row, col)
93
+ assert(result, "Expected BG at [#{row},#{col}] to be #{expected.inspect}, but was #{actual_bg.inspect}")
94
+ end
95
+
96
+ def assert_style(actual, row:, col:, **expected_styles)
97
+ result = auto_wait(actual) do |s|
98
+ style = s.style_at(row, col)
99
+ expected_styles.all? { |k, v| style[k] == v }
100
+ end
101
+ actual_style = state_from(actual).style_at(row, col)
102
+ assert(result,
103
+ "Expected style at [#{row},#{col}] to be #{expected_styles.inspect}, but was #{actual_style.inspect}",)
104
+ end
105
+
106
+ def assert_exit_status(actual, expected)
107
+ status = actual.exitstatus
108
+ assert(status == expected, "Expected exit status #{expected}, but was #{status}")
109
+ end
110
+
111
+ # --- Selector-based ---
112
+
113
+ def assert_button(actual, expected)
114
+ result = auto_wait(actual) { |s| TUITD::Selector.new(s).button(text: expected) }
115
+ assert(result, "Expected terminal to have a button #{expected.inspect}")
116
+ end
117
+
118
+ def refute_button(actual, expected)
119
+ result = auto_wait(actual) { |s| TUITD::Selector.new(s).button(text: expected).nil? }
120
+ assert(result, "Expected terminal NOT to have a button #{expected.inspect}")
121
+ end
122
+
123
+ def assert_dialog(actual)
124
+ result = auto_wait(actual) { |s| TUITD::Selector.new(s).dialogs.any? }
125
+ assert(result, "Expected terminal to have a dialog")
126
+ end
127
+
128
+ def refute_dialog(actual)
129
+ result = auto_wait(actual) { |s| TUITD::Selector.new(s).dialogs.empty? }
130
+ assert(result, "Expected terminal NOT to have a dialog")
131
+ end
132
+
133
+ def assert_checkbox(actual, expected, checked: nil, unchecked: nil)
134
+ checked = false if unchecked
135
+ result = auto_wait(actual) do |s|
136
+ filters = { text: expected }
137
+ filters[:checked] = checked unless checked.nil?
138
+ TUITD::Selector.new(s).checkbox(**filters)
139
+ end
140
+ msg = "Expected terminal to have checkbox #{expected.inspect}"
141
+ msg += " (checked)" if checked == true
142
+ msg += " (unchecked)" if checked == false
143
+ assert(result, msg)
144
+ end
145
+
146
+ def assert_role(actual, role, text: nil, checked: nil, disabled: nil)
147
+ result = auto_wait(actual) do |s|
148
+ filters = {}
149
+ filters[:text] = text if text
150
+ filters[:checked] = checked unless checked.nil?
151
+ filters[:disabled] = disabled unless disabled.nil?
152
+ TUITD::Selector.new(s).get_by_role(role, **filters).any?
153
+ end
154
+ msg = "Expected terminal to have role :#{role}"
155
+ msg += " with text #{text.inspect}" if text
156
+ assert(result, msg)
157
+ end
158
+
159
+ def assert_input(actual, expected = nil)
160
+ result = auto_wait(actual) do |s|
161
+ if expected
162
+ TUITD::Selector.new(s).input(text: expected)
163
+ else
164
+ TUITD::Selector.new(s).inputs.any?
165
+ end
166
+ end
167
+ msg = "Expected terminal to have an input field"
168
+ msg += " #{expected.inspect}" if expected
169
+ assert(result, msg)
170
+ end
171
+
172
+ def assert_label(actual, expected = nil)
173
+ result = auto_wait(actual) do |s|
174
+ if expected
175
+ TUITD::Selector.new(s).label(text: expected)
176
+ else
177
+ TUITD::Selector.new(s).labels.any?
178
+ end
179
+ end
180
+ msg = "Expected terminal to have a label"
181
+ msg += " #{expected.inspect}" if expected
182
+ assert(result, msg)
183
+ end
184
+
185
+ def assert_menu(actual, expected = nil)
186
+ result = auto_wait(actual) do |s|
187
+ if expected
188
+ TUITD::Selector.new(s).menu(text: expected)
189
+ else
190
+ TUITD::Selector.new(s).menus.any?
191
+ end
192
+ end
193
+ msg = "Expected terminal to have a menu"
194
+ msg += " #{expected.inspect}" if expected
195
+ assert(result, msg)
196
+ end
197
+
198
+ def assert_tab(actual, expected = nil)
199
+ result = auto_wait(actual) do |s|
200
+ if expected
201
+ TUITD::Selector.new(s).tab(text: expected)
202
+ else
203
+ TUITD::Selector.new(s).tabs.any?
204
+ end
205
+ end
206
+ msg = "Expected terminal to have a tab"
207
+ msg += " #{expected.inspect}" if expected
208
+ assert(result, msg)
209
+ end
210
+
211
+ def assert_statusbar(actual, expected = nil)
212
+ result = auto_wait(actual) do |s|
213
+ if expected
214
+ TUITD::Selector.new(s).statusbar(text: expected)
215
+ else
216
+ TUITD::Selector.new(s).statusbars.any?
217
+ end
218
+ end
219
+ msg = "Expected terminal to have a status bar"
220
+ msg += " #{expected.inspect}" if expected
221
+ assert(result, msg)
222
+ end
223
+
224
+ def assert_progress_bar(actual, expected = nil)
225
+ result = auto_wait(actual) do |s|
226
+ if expected
227
+ TUITD::Selector.new(s).progress_bar(text: expected)
228
+ else
229
+ TUITD::Selector.new(s).progress_bars.any?
230
+ end
231
+ end
232
+ msg = "Expected terminal to have a progress bar"
233
+ msg += " #{expected.inspect}" if expected
234
+ assert(result, msg)
235
+ end
236
+
237
+ # --- Snapshot ---
238
+
239
+ def assert_snapshot(actual, name, type: :text, wait: false, region: nil, ignore_rows: nil)
240
+ snap = TUITD::Snapshot.new(name.to_s, type: type)
241
+
242
+ state_data = if actual.respond_to?(:state_data)
243
+ actual.wait_for_stable if wait && actual.respond_to?(:wait_for_stable)
244
+ actual.state_data
245
+ elsif actual.respond_to?(:to_h)
246
+ actual.to_h
247
+ else
248
+ actual
249
+ end
250
+
251
+ if TUITD.configuration.update_snapshots? || !snap.exists?
252
+ snap.save(state_data)
253
+ pass
254
+ else
255
+ result = snap.compare(state_data, ignore_rows: ignore_rows, region: region)
256
+ msg = result.passed? ? nil : "Snapshot '#{name}' does not match.\n#{result.message}"
257
+ assert(result.passed?, msg)
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
263
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/ParameterLists
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TUITD
4
- VERSION = "0.2.17"
4
+ VERSION = "0.2.19"
5
5
  end
data/lib/tui_td.rb CHANGED
@@ -19,6 +19,7 @@ require_relative "tui_td/screenshot"
19
19
  require_relative "tui_td/html_renderer"
20
20
  require_relative "tui_td/test_runner"
21
21
  require_relative "tui_td/selector"
22
+ require_relative "tui_td/minitest/assertions"
22
23
  require_relative "tui_td/mcp/server"
23
24
  require_relative "tui_td/cli"
24
25
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tui-td
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.17
4
+ version: 0.2.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Haluk Durmus
@@ -151,7 +151,8 @@ dependencies:
151
151
  version: '1.50'
152
152
  description: tui-td drives terminal applications in a PTY, captures ANSI state as
153
153
  structured data, and provides PNG screenshots and HTML renders. Includes an MCP
154
- server for AI-driven testing, a JSON test runner, and RSpec matchers.
154
+ server for AI-driven testing, a JSON test runner, RSpec matchers, Minitest assertions,
155
+ semantic selectors, and named snapshot testing.
155
156
  email:
156
157
  - haluk_durmus@yahoo.de
157
158
  executables:
@@ -173,6 +174,7 @@ files:
173
174
  - lib/tui_td/html_renderer.rb
174
175
  - lib/tui_td/matchers.rb
175
176
  - lib/tui_td/mcp/server.rb
177
+ - lib/tui_td/minitest/assertions.rb
176
178
  - lib/tui_td/screenshot.rb
177
179
  - lib/tui_td/selector.rb
178
180
  - lib/tui_td/snapshot.rb
@@ -204,5 +206,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
206
  requirements: []
205
207
  rubygems_version: 4.0.6
206
208
  specification_version: 4
207
- summary: TUI testing framework — language-agnostic via JSON tests and MCP
209
+ summary: TUI testing framework — RSpec, Minitest, JSON, and MCP
208
210
  test_files: []