tui-td 0.1.2 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -6
- data/README.md +32 -6
- data/lib/tui_td/ansi_parser.rb +41 -6
- data/lib/tui_td/cli.rb +206 -9
- data/lib/tui_td/test_runner.rb +20 -1
- data/lib/tui_td/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b8ebaa8989019a8cd573f682a538ca76a21a17daee810f29a1f178f8f35f0008
|
|
4
|
+
data.tar.gz: 52fecfc7d59143648f450e71612ac7b7b6839e1b697d03056cb80e0a0dd067ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9563af163f07bbaae4ea0427120785cc814c444289f7a6bc75b527d9a2e2a5a29785b99f40fa7c31c807c10708693f7f1a5cb5d9c39fea40b82a31e8d8c56964
|
|
7
|
+
data.tar.gz: 6912a0399394405e8678e33b13b1c95a7575ce5ef7f7731c23a514d2f55c720724c1f4d17bcb73be654a58461bade34d03adfcfd7e615509d1244c53ac8a8673
|
data/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,45 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.2.0
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
5
|
+
- Live debug modes for `tui-td test`: `-v` (verbose), `-l` (live screen-refresh), `-s` (step-by-step pause)
|
|
6
|
+
- Fix: skip DEC private mode sequences (`\e[?1049h` alternate screen, `\e[?25h` cursor visibility, etc.)
|
|
7
|
+
- TestRunner `on_step` callback API for programmatic step-by-step observation
|
|
8
|
+
- Vim interaction test example: type, yank, paste, substitute
|
|
9
|
+
- Removed aruba dev dependency from Gemfile
|
|
10
|
+
|
|
11
|
+
## 0.1.3
|
|
12
|
+
|
|
13
|
+
- UTF-8 multi-byte character support in ANSI parser (`_utf8_char_at`)
|
|
14
|
+
- `--help` is now a complete CLI reference: Examples section, interactive drive commands
|
|
15
|
+
- `tui-td help test` — JSON test step reference with CLI and Ruby code workflow
|
|
16
|
+
- `tui-td help rspec` — RSpec matchers reference with Driver/State setup workflow
|
|
17
|
+
- `--version` flag
|
|
18
|
+
|
|
19
|
+
## 0.1.2
|
|
20
|
+
|
|
21
|
+
- Background reader thread — continuous PTY reads prevent buffer overflow
|
|
22
|
+
- Thread-safe output buffer with Mutex protection
|
|
23
|
+
- DSR (Device Status Report) support — respond to `\e[6n` cursor position requests
|
|
24
|
+
- Replace `IO.select`/`readpartial` with `read_nonblock`
|
|
25
|
+
|
|
26
|
+
## 0.1.1
|
|
27
|
+
|
|
28
|
+
- Add `--chdir` / `-C` CLI flag for running commands in a specific directory
|
|
29
|
+
- Default `capture` output to text instead of full JSON grid
|
|
30
|
+
- Use `permute!` for CLI option parsing so global flags work after the command
|
|
31
|
+
- Fix: skip ISO 2022 charset sequences (`ESC(B`) instead of leaking `(B` into output
|
|
32
|
+
- Add `raw` field to state JSON (original ANSI with all escape sequences preserved)
|
|
33
|
+
- Publish to rubygems.org
|
|
34
|
+
|
|
35
|
+
## 0.1.0
|
|
36
|
+
|
|
37
|
+
- PTY-based TUI driver with `start`, `send`, `send_keys`, `wait_for_text`, `wait_for_stable`
|
|
38
|
+
- ANSI parser: SGR colors (16, 256, TrueColor), cursor movement, erase, scroll
|
|
39
|
+
- Structured state output: `{size, cursor, rows, raw}`
|
|
40
|
+
- CLI: `capture`, `run`, `drive`, `test`, `serve`
|
|
41
|
+
- Pure Ruby PNG screenshots via `chunky_png` with embedded Spleen 8×16 font
|
|
42
|
+
- HTML renderer with run-length encoding
|
|
43
|
+
- JSON test runner with 12 step types
|
|
44
|
+
- RSpec matchers: `have_text`, `have_fg`, `have_bg`, `have_style`
|
|
45
|
+
- MCP server (JSON-RPC over stdio) for AI-driven TUI testing
|
data/README.md
CHANGED
|
@@ -98,24 +98,50 @@ Usage: tui-td <command> [options]
|
|
|
98
98
|
|
|
99
99
|
Commands:
|
|
100
100
|
serve Start MCP server (JSON-RPC over stdio)
|
|
101
|
-
test <file> Run JSON test file
|
|
102
|
-
run <command> Run a TUI app and show live output
|
|
103
|
-
drive <command> Drive a TUI with structured state output
|
|
104
101
|
capture <command> Run once, capture and display state
|
|
102
|
+
drive <command> Drive a TUI interactively
|
|
103
|
+
run <command> Run a TUI app and show live output
|
|
104
|
+
test <file.json> Run JSON test file
|
|
105
|
+
help [topic] Show this help, or help test / help rspec
|
|
106
|
+
|
|
107
|
+
Examples:
|
|
108
|
+
tui-td capture "ls -la"
|
|
109
|
+
tui-td --screenshot out.png capture "htop" --timeout 5
|
|
110
|
+
tui-td --html out.html capture "glow README.md"
|
|
111
|
+
tui-td -C /my/project capture "make test"
|
|
112
|
+
tui-td drive "vim file.txt" --rows 24 --cols 80
|
|
113
|
+
tui-td test examples/echo_test.json
|
|
114
|
+
tui-td -vl test examples/vim_hello_world.json
|
|
115
|
+
tui-td serve
|
|
116
|
+
|
|
117
|
+
Interactive commands (drive mode):
|
|
118
|
+
state Show terminal state as pretty JSON
|
|
119
|
+
raw Show raw ANSI output
|
|
120
|
+
key <name> Send keystroke (enter, tab, escape, up, down, left, right,
|
|
121
|
+
backspace, ctrl_c, ctrl_d)
|
|
122
|
+
<text> Send text to the TUI
|
|
123
|
+
exit Quit drive mode
|
|
105
124
|
|
|
106
125
|
Global options:
|
|
107
126
|
-r, --rows N Terminal rows (default: 40)
|
|
108
127
|
-c, --cols N Terminal cols (default: 120)
|
|
109
|
-
-C, --chdir PATH Working directory for the command
|
|
110
128
|
-t, --timeout N Timeout in seconds (default: 30)
|
|
129
|
+
-C, --chdir PATH Working directory for the command
|
|
111
130
|
--screenshot PATH Save PNG screenshot
|
|
112
131
|
--html PATH Save HTML render for browser viewing
|
|
113
132
|
--json Output state as compact JSON (includes raw ANSI)
|
|
114
133
|
--pretty Output state as pretty JSON
|
|
115
|
-
--text Output state as plain text table
|
|
116
|
-
-
|
|
134
|
+
--text Output state as plain text table
|
|
135
|
+
-v, --verbose Show each test step as it runs
|
|
136
|
+
-l, --live Show terminal state after each test step (screen-refresh)
|
|
137
|
+
-s, --step Pause after each test step for confirmation
|
|
138
|
+
--version Show version
|
|
139
|
+
-h, --help Show complete reference
|
|
117
140
|
```
|
|
118
141
|
|
|
142
|
+
`tui-td --help` serves as the full CLI reference. `tui-td help test` shows all JSON test
|
|
143
|
+
step types and `tui-td help rspec` shows all RSpec matchers — no need to consult the docs.
|
|
144
|
+
|
|
119
145
|
## Ruby API
|
|
120
146
|
|
|
121
147
|
### Driver — Start, send, capture
|
data/lib/tui_td/ansi_parser.rb
CHANGED
|
@@ -103,7 +103,7 @@ module TUITD
|
|
|
103
103
|
if processed[i] == "\e" && processed[i + 1] == "["
|
|
104
104
|
# Find end of CSI sequence
|
|
105
105
|
j = i + 2
|
|
106
|
-
j += 1 while j < processed.length && !processed[j].match?(/[A-HJ-KP-SX@`
|
|
106
|
+
j += 1 while j < processed.length && !processed[j].match?(/[A-HJ-KP-SX@`fhlmnR]/)
|
|
107
107
|
seq = processed[i..j]
|
|
108
108
|
|
|
109
109
|
dsr = _apply_csi(seq, cursor, attrs, grid, rows, cols)
|
|
@@ -137,16 +137,16 @@ module TUITD
|
|
|
137
137
|
else
|
|
138
138
|
i += 1
|
|
139
139
|
end
|
|
140
|
-
elsif processed
|
|
141
|
-
# Printable character
|
|
140
|
+
elsif (char, char_len = _utf8_char_at(processed, i))
|
|
141
|
+
# Printable character (including multi-byte UTF-8)
|
|
142
142
|
if cursor[:row] < rows && cursor[:col] < cols
|
|
143
143
|
cell = grid[cursor[:row]][cursor[:col]]
|
|
144
|
-
cell[:char] =
|
|
144
|
+
cell[:char] = char
|
|
145
145
|
cell.merge!(attrs)
|
|
146
146
|
cursor[:col] += 1
|
|
147
147
|
cursor[:col] = cols - 1 if cursor[:col] >= cols
|
|
148
148
|
end
|
|
149
|
-
i +=
|
|
149
|
+
i += char_len
|
|
150
150
|
else
|
|
151
151
|
i += 1
|
|
152
152
|
end
|
|
@@ -214,7 +214,7 @@ module TUITD
|
|
|
214
214
|
def self._apply_csi(seq, cursor, attrs, grid, rows, cols)
|
|
215
215
|
# Strip leading escape char if present
|
|
216
216
|
cleaned = seq.sub(/^\e/, "")
|
|
217
|
-
match = cleaned.match(/^\[([\d;]*)([A-HJ-KP-SX@`
|
|
217
|
+
match = cleaned.match(/^\[([\d;]*)([A-HJ-KP-SX@`fhlmnR])$/)
|
|
218
218
|
return unless match
|
|
219
219
|
|
|
220
220
|
params = match[1].split(";").map(&:to_i)
|
|
@@ -270,6 +270,8 @@ module TUITD
|
|
|
270
270
|
next unless cursor[:row] < rows && cursor[:col] + i < cols
|
|
271
271
|
grid[cursor[:row]][cursor[:col] + i][:char] = " "
|
|
272
272
|
end
|
|
273
|
+
when "h", "l" # DEC private mode set/reset — skip (alternate screen, cursor show/hide, etc.)
|
|
274
|
+
nil
|
|
273
275
|
when "n" # DSR — Device Status Report request
|
|
274
276
|
# \e[6n = request cursor position → caller must respond with \e[row;colR
|
|
275
277
|
return params[0] == 6
|
|
@@ -409,5 +411,38 @@ module TUITD
|
|
|
409
411
|
nil
|
|
410
412
|
end
|
|
411
413
|
end
|
|
414
|
+
|
|
415
|
+
# Extract a single UTF-8 character at position i in a binary string.
|
|
416
|
+
# Returns [char_string, byte_length] or nil if the byte is not printable/valid.
|
|
417
|
+
def self._utf8_char_at(str, i)
|
|
418
|
+
byte = str.getbyte(i)
|
|
419
|
+
return nil unless byte
|
|
420
|
+
|
|
421
|
+
if byte < 0x80
|
|
422
|
+
# Single-byte ASCII
|
|
423
|
+
return nil unless byte >= 0x20 # only printable, skip control chars
|
|
424
|
+
return [byte.chr, 1]
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Multi-byte UTF-8
|
|
428
|
+
len = if byte & 0xE0 == 0xC0
|
|
429
|
+
2
|
|
430
|
+
elsif byte & 0xF0 == 0xE0
|
|
431
|
+
3
|
|
432
|
+
elsif byte & 0xF8 == 0xF0
|
|
433
|
+
4
|
|
434
|
+
else
|
|
435
|
+
return nil # continuation byte or invalid — let main loop advance
|
|
436
|
+
end
|
|
437
|
+
return nil if i + len > str.bytesize
|
|
438
|
+
|
|
439
|
+
bytes = str.byteslice(i, len)
|
|
440
|
+
char = bytes.dup.force_encoding("UTF-8")
|
|
441
|
+
return nil unless char.valid_encoding?
|
|
442
|
+
|
|
443
|
+
[char, len]
|
|
444
|
+
rescue StandardError
|
|
445
|
+
nil
|
|
446
|
+
end
|
|
412
447
|
end
|
|
413
448
|
end
|
data/lib/tui_td/cli.rb
CHANGED
|
@@ -18,12 +18,30 @@ module TUITD
|
|
|
18
18
|
opts.banner = "Usage: tui-td <command> [options]"
|
|
19
19
|
opts.separator ""
|
|
20
20
|
opts.separator "Commands:"
|
|
21
|
-
opts.separator " serve
|
|
22
|
-
opts.separator " run <command> Run a TUI app and show live output"
|
|
23
|
-
opts.separator " drive <command> Drive a TUI with structured state output"
|
|
21
|
+
opts.separator " serve Start MCP server (JSON-RPC over stdio)"
|
|
24
22
|
opts.separator " capture <command> Run once, capture and display state"
|
|
25
|
-
opts.separator "
|
|
26
|
-
opts.separator "
|
|
23
|
+
opts.separator " drive <command> Drive a TUI interactively"
|
|
24
|
+
opts.separator " run <command> Run a TUI app and show live output"
|
|
25
|
+
opts.separator " test <file.json> Run JSON test file"
|
|
26
|
+
opts.separator " help [topic] Show this help, or help test / help rspec"
|
|
27
|
+
opts.separator ""
|
|
28
|
+
opts.separator "Examples:"
|
|
29
|
+
opts.separator " tui-td capture \"ls -la\""
|
|
30
|
+
opts.separator " tui-td --screenshot out.png capture \"htop\" --timeout 5"
|
|
31
|
+
opts.separator " tui-td --html out.html capture \"glow README.md\""
|
|
32
|
+
opts.separator " tui-td -C /my/project capture \"make test\""
|
|
33
|
+
opts.separator " tui-td drive \"vim file.txt\" --rows 24 --cols 80"
|
|
34
|
+
opts.separator " tui-td test examples/echo_test.json"
|
|
35
|
+
opts.separator " tui-td -vl test examples/vim_hello_world.json"
|
|
36
|
+
opts.separator " tui-td serve"
|
|
37
|
+
opts.separator ""
|
|
38
|
+
opts.separator "Interactive commands (drive mode):"
|
|
39
|
+
opts.separator " state Show terminal state as pretty JSON"
|
|
40
|
+
opts.separator " raw Show raw ANSI output"
|
|
41
|
+
opts.separator " key <name> Send keystroke (enter, tab, escape, up, down, left, right,"
|
|
42
|
+
opts.separator " backspace, ctrl_c, ctrl_d)"
|
|
43
|
+
opts.separator " <text> Send text to the TUI"
|
|
44
|
+
opts.separator " exit Quit drive mode"
|
|
27
45
|
opts.separator ""
|
|
28
46
|
opts.separator "Global options:"
|
|
29
47
|
|
|
@@ -54,6 +72,19 @@ module TUITD
|
|
|
54
72
|
opts.on("--text", "Output state as plain text table") do |_|
|
|
55
73
|
global_opts[:format] = :text
|
|
56
74
|
end
|
|
75
|
+
opts.on("-v", "--verbose", "Show each test step as it runs") do |_|
|
|
76
|
+
global_opts[:verbose] = true
|
|
77
|
+
end
|
|
78
|
+
opts.on("-l", "--live", "Show terminal state after each step (screen-refresh)") do |_|
|
|
79
|
+
global_opts[:live] = true
|
|
80
|
+
end
|
|
81
|
+
opts.on("-s", "--step", "Pause after each test step for confirmation") do |_|
|
|
82
|
+
global_opts[:step_mode] = true
|
|
83
|
+
end
|
|
84
|
+
opts.on("--version", "Show version") do
|
|
85
|
+
puts "tui-td #{TUITD::VERSION}"
|
|
86
|
+
exit 0
|
|
87
|
+
end
|
|
57
88
|
opts.on("-h", "--help", "Show help") do
|
|
58
89
|
puts opts
|
|
59
90
|
exit 0
|
|
@@ -74,9 +105,20 @@ module TUITD
|
|
|
74
105
|
cmd_capture(command_opts, global_opts)
|
|
75
106
|
when "test"
|
|
76
107
|
cmd_test(command_opts, global_opts)
|
|
77
|
-
when
|
|
78
|
-
|
|
79
|
-
|
|
108
|
+
when "help"
|
|
109
|
+
topic = argv.shift
|
|
110
|
+
case topic
|
|
111
|
+
when "test"
|
|
112
|
+
_help_test
|
|
113
|
+
when "rspec"
|
|
114
|
+
_help_rspec
|
|
115
|
+
when nil
|
|
116
|
+
_help_main
|
|
117
|
+
else
|
|
118
|
+
abort "Unknown help topic: #{topic.inspect}\nTry: tui-td help test, tui-td help rspec"
|
|
119
|
+
end
|
|
120
|
+
when nil
|
|
121
|
+
_help_main
|
|
80
122
|
else
|
|
81
123
|
abort "Unknown command: #{command.inspect}\nUse tui-td --help for usage"
|
|
82
124
|
end
|
|
@@ -196,9 +238,35 @@ module TUITD
|
|
|
196
238
|
path = args.first
|
|
197
239
|
abort "File not found: #{path}" unless File.exist?(path)
|
|
198
240
|
|
|
241
|
+
verbose = globals[:verbose]
|
|
242
|
+
live = globals[:live]
|
|
243
|
+
step_mode = globals[:step_mode]
|
|
244
|
+
|
|
245
|
+
on_step = if verbose || live || step_mode
|
|
246
|
+
lambda do |info|
|
|
247
|
+
if live && info[:driver]
|
|
248
|
+
info[:driver].wait_for_stable(stable_ms: 200)
|
|
249
|
+
end
|
|
250
|
+
if verbose
|
|
251
|
+
status = info[:result].passed ? "PASS" : "FAIL"
|
|
252
|
+
puts "[#{info[:index] + 1}/#{info[:total]}] #{info[:action]}: #{info[:result].message}"
|
|
253
|
+
puts " → #{status}"
|
|
254
|
+
end
|
|
255
|
+
if live && info[:driver]
|
|
256
|
+
print "\e[2J\e[H" # clear screen, home cursor
|
|
257
|
+
_render_text(info[:driver].state_data)
|
|
258
|
+
end
|
|
259
|
+
if step_mode
|
|
260
|
+
print "\n[Enter=weiter, q=abbruch] "
|
|
261
|
+
input = $stdin.gets
|
|
262
|
+
exit 1 if input&.chomp == "q"
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
199
267
|
require "json"
|
|
200
268
|
plan = JSON.parse(File.read(path), symbolize_names: true)
|
|
201
|
-
runner = TestRunner.new(plan)
|
|
269
|
+
runner = TestRunner.new(plan, on_step: on_step)
|
|
202
270
|
result = runner.run
|
|
203
271
|
|
|
204
272
|
puts
|
|
@@ -228,5 +296,134 @@ module TUITD
|
|
|
228
296
|
puts line.empty? ? "~" : line
|
|
229
297
|
end
|
|
230
298
|
end
|
|
299
|
+
|
|
300
|
+
def _help_main
|
|
301
|
+
puts OptionParser.new { |o| o.banner = "Usage: tui-td <command> [options]" }
|
|
302
|
+
puts
|
|
303
|
+
puts "For more: tui-td help test (JSON test step types)"
|
|
304
|
+
puts " tui-td help rspec (RSpec matchers)"
|
|
305
|
+
exit 0
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def _help_test
|
|
309
|
+
puts <<~HELP
|
|
310
|
+
JSON Test Format
|
|
311
|
+
================
|
|
312
|
+
|
|
313
|
+
Run from CLI:
|
|
314
|
+
|
|
315
|
+
tui-td test examples/echo_test.json
|
|
316
|
+
tui-td -v test examples/echo_test.json (verbose: show each step)
|
|
317
|
+
tui-td -vl test examples/echo_test.json (verbose + live terminal view)
|
|
318
|
+
tui-td -vs test examples/echo_test.json (verbose + pause after each step)
|
|
319
|
+
tui-td -vls test examples/vim_hello_world.json (all three: watch vim edit live)
|
|
320
|
+
|
|
321
|
+
Or from Ruby code:
|
|
322
|
+
|
|
323
|
+
require "tui_td/test_runner"
|
|
324
|
+
runner = TUITD::TestRunner.new(name: "my test", steps: [...])
|
|
325
|
+
result = runner.run # => { passed: true, results: [...] }
|
|
326
|
+
|
|
327
|
+
A test is a Hash or JSON string: {"name": "...", "steps": [...]}
|
|
328
|
+
|
|
329
|
+
Top-level keys: name, steps, rows (default 40), cols (default 120),
|
|
330
|
+
timeout (default 30), chdir
|
|
331
|
+
|
|
332
|
+
Each step is an object with a single action key:
|
|
333
|
+
|
|
334
|
+
{"start": "<command>"}
|
|
335
|
+
Start a TUI process in a PTY.
|
|
336
|
+
|
|
337
|
+
{"send": "<text>"}
|
|
338
|
+
Send text to the TUI. Use "\\n" for Enter.
|
|
339
|
+
|
|
340
|
+
{"send_key": "<name>"}
|
|
341
|
+
Send a keystroke. Names: enter, tab, escape, up, down,
|
|
342
|
+
left, right, backspace, ctrl_c, ctrl_d.
|
|
343
|
+
|
|
344
|
+
{"wait_for_text": "<substring>"}
|
|
345
|
+
Wait until the given text appears in the output.
|
|
346
|
+
|
|
347
|
+
{"wait_for_stable": true}
|
|
348
|
+
Wait until the output stops changing (default 300ms quiet).
|
|
349
|
+
|
|
350
|
+
{"assert_text": "<substring>"}
|
|
351
|
+
Fail if the text is not found in the current state.
|
|
352
|
+
|
|
353
|
+
{"assert_fg": [row, col], "is": "<color>"}
|
|
354
|
+
Assert foreground color at cell. Colors: "default",
|
|
355
|
+
named ANSI (red, green, blue, cyan, ...), "bright_*",
|
|
356
|
+
"color<N>" (256-color), "#rrggbb" (TrueColor).
|
|
357
|
+
|
|
358
|
+
{"assert_bg": [row, col], "is": "<color>"}
|
|
359
|
+
Assert background color at cell. Same color format.
|
|
360
|
+
|
|
361
|
+
{"assert_style": [row, col], "bold": true, "italic": false, ...}
|
|
362
|
+
Assert style attributes at cell. Checks only the keys provided.
|
|
363
|
+
|
|
364
|
+
{"screenshot": "<path>"}
|
|
365
|
+
Save a PNG screenshot. Path defaults to /tmp/tui_td_<ts>.png.
|
|
366
|
+
|
|
367
|
+
{"html": "<path>"}
|
|
368
|
+
Save an HTML render. Path defaults to /tmp/tui_td_<ts>.html.
|
|
369
|
+
|
|
370
|
+
{"close": true}
|
|
371
|
+
Close the driver session.
|
|
372
|
+
|
|
373
|
+
Example test file: examples/echo_test.json
|
|
374
|
+
HELP
|
|
375
|
+
exit 0
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def _help_rspec
|
|
379
|
+
puts <<~HELP
|
|
380
|
+
RSpec Matchers
|
|
381
|
+
==============
|
|
382
|
+
|
|
383
|
+
Matchers work on TUITD::State objects, not raw output.
|
|
384
|
+
Get a State from the Driver:
|
|
385
|
+
|
|
386
|
+
require "tui_td"
|
|
387
|
+
require "tui_td/matchers"
|
|
388
|
+
|
|
389
|
+
driver = TUITD::Driver.new("my_tui", rows: 24, cols: 80)
|
|
390
|
+
driver.start
|
|
391
|
+
state = TUITD::State.new(driver.state_data)
|
|
392
|
+
|
|
393
|
+
expect(state).to have_text("Hello")
|
|
394
|
+
expect(state).to have_fg("red").at(0, 5)
|
|
395
|
+
|
|
396
|
+
driver.close
|
|
397
|
+
|
|
398
|
+
Or build a State manually for unit tests:
|
|
399
|
+
|
|
400
|
+
state = TUITD::State.new(
|
|
401
|
+
size: { rows: 5, cols: 20 },
|
|
402
|
+
cursor: { row: 0, col: 0 },
|
|
403
|
+
rows: [[{ char: "H", fg: "default", bg: "default",
|
|
404
|
+
bold: false, italic: false, underline: false }]]
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
Matchers
|
|
408
|
+
--------
|
|
409
|
+
|
|
410
|
+
have_text(expected)
|
|
411
|
+
Passes if expected text appears anywhere in the terminal state.
|
|
412
|
+
Usage: expect(state).to have_text("Hello")
|
|
413
|
+
|
|
414
|
+
have_fg(expected).at(row, col)
|
|
415
|
+
Assert foreground color at [row, col] matches expected.
|
|
416
|
+
Usage: expect(state).to have_fg("red").at(0, 5)
|
|
417
|
+
|
|
418
|
+
have_bg(expected).at(row, col)
|
|
419
|
+
Assert background color at [row, col] matches expected.
|
|
420
|
+
Usage: expect(state).to have_bg("default").at(0, 0)
|
|
421
|
+
|
|
422
|
+
have_style.at(row, col).with(bold: true, italic: false, ...)
|
|
423
|
+
Assert style attributes at [row, col] match the given hash.
|
|
424
|
+
Usage: expect(state).to have_style.at(0, 0).with(bold: true)
|
|
425
|
+
HELP
|
|
426
|
+
exit 0
|
|
427
|
+
end
|
|
231
428
|
end
|
|
232
429
|
end
|
data/lib/tui_td/test_runner.rb
CHANGED
|
@@ -26,10 +26,11 @@ module TUITD
|
|
|
26
26
|
class TestRunner
|
|
27
27
|
Result = Struct.new(:step, :passed, :message, keyword_init: true)
|
|
28
28
|
|
|
29
|
-
def initialize(source)
|
|
29
|
+
def initialize(source, on_step: nil)
|
|
30
30
|
raw = source.is_a?(String) ? JSON.parse(source) : source
|
|
31
31
|
@plan = raw.transform_keys(&:to_sym)
|
|
32
32
|
@plan[:steps] = @plan[:steps].map { |s| s.transform_keys(&:to_sym) }
|
|
33
|
+
@on_step = on_step
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
def run
|
|
@@ -150,6 +151,24 @@ module TUITD
|
|
|
150
151
|
|
|
151
152
|
results << r
|
|
152
153
|
all_passed &&= r.passed
|
|
154
|
+
|
|
155
|
+
if @on_step
|
|
156
|
+
state_data = nil
|
|
157
|
+
begin
|
|
158
|
+
state_data = driver.state_data if driver
|
|
159
|
+
rescue StandardError
|
|
160
|
+
# ignore — state retrieval is best-effort
|
|
161
|
+
end
|
|
162
|
+
@on_step.call(
|
|
163
|
+
index: results.size - 1,
|
|
164
|
+
total: @plan[:steps].size,
|
|
165
|
+
action: action,
|
|
166
|
+
value: value,
|
|
167
|
+
result: r,
|
|
168
|
+
driver: driver,
|
|
169
|
+
state_data: state_data
|
|
170
|
+
)
|
|
171
|
+
end
|
|
153
172
|
end
|
|
154
173
|
|
|
155
174
|
driver&.close
|
data/lib/tui_td/version.rb
CHANGED