tui-td 0.2.10 → 0.2.11
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 +11 -0
- data/lib/tui_td/ansi_parser.rb +139 -131
- data/lib/tui_td/ansi_utils.rb +22 -20
- data/lib/tui_td/cairo_renderer.rb +5 -2
- data/lib/tui_td/cli.rb +12 -10
- data/lib/tui_td/driver.rb +43 -12
- data/lib/tui_td/html_renderer.rb +19 -17
- data/lib/tui_td/matchers.rb +21 -12
- data/lib/tui_td/mcp/server.rb +87 -84
- data/lib/tui_td/screenshot.rb +70 -52
- data/lib/tui_td/state.rb +11 -4
- data/lib/tui_td/test_runner.rb +25 -25
- data/lib/tui_td/unifont_glyphs.rb +2142 -2141
- data/lib/tui_td/version.rb +1 -1
- data/lib/tui_td.rb +7 -3
- metadata +40 -11
data/lib/tui_td/state.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
4
|
+
|
|
3
5
|
module TUITD
|
|
4
6
|
# Represents the parsed state of a terminal screen.
|
|
5
7
|
# Provides high-level query methods for AI consumption.
|
|
@@ -31,6 +33,7 @@ module TUITD
|
|
|
31
33
|
# Get text at a specific position
|
|
32
34
|
def text_at(row, col, length = @cols - col)
|
|
33
35
|
return "" if row >= @rows || col >= @cols
|
|
36
|
+
|
|
34
37
|
@grid[row][col, length].map { |c| c[:char] }.join
|
|
35
38
|
end
|
|
36
39
|
|
|
@@ -51,16 +54,19 @@ module TUITD
|
|
|
51
54
|
# Get the color at a specific cell
|
|
52
55
|
def foreground_at(row, col)
|
|
53
56
|
return nil if row >= @rows || col >= @cols
|
|
57
|
+
|
|
54
58
|
@grid[row][col][:fg]
|
|
55
59
|
end
|
|
56
60
|
|
|
57
61
|
def background_at(row, col)
|
|
58
62
|
return nil if row >= @rows || col >= @cols
|
|
63
|
+
|
|
59
64
|
@grid[row][col][:bg]
|
|
60
65
|
end
|
|
61
66
|
|
|
62
67
|
def style_at(row, col)
|
|
63
68
|
return nil if row >= @rows || col >= @cols
|
|
69
|
+
|
|
64
70
|
cell = @grid[row][col]
|
|
65
71
|
{ bold: cell[:bold], italic: cell[:italic], underline: cell[:underline] }
|
|
66
72
|
end
|
|
@@ -72,12 +78,12 @@ module TUITD
|
|
|
72
78
|
c = cursor_info[:col] || cursor_info["col"] || 0
|
|
73
79
|
styled_count = h.count { |hl| hl[:bold] || hl[:italic] || hl[:underline] || hl[:fg] || hl[:bg] }
|
|
74
80
|
|
|
75
|
-
summary =
|
|
76
|
-
summary << "#{styled_count} styled row#{styled_count == 1
|
|
81
|
+
summary = "Cursor at [#{r},#{c}]. "
|
|
82
|
+
summary << "#{styled_count} styled row#{"s" unless styled_count == 1}"
|
|
77
83
|
fgs = h.flat_map { |hl| hl[:fg] }.compact.uniq
|
|
78
84
|
bgs = h.flat_map { |hl| hl[:bg] }.compact.uniq
|
|
79
|
-
summary << ", colors: fg=#{fgs.sort.join(
|
|
80
|
-
summary << ", bg=#{bgs.sort.join(
|
|
85
|
+
summary << ", colors: fg=#{fgs.sort.join(",")}" unless fgs.empty?
|
|
86
|
+
summary << ", bg=#{bgs.sort.join(",")}" unless bgs.empty?
|
|
81
87
|
summary << "."
|
|
82
88
|
|
|
83
89
|
{
|
|
@@ -119,3 +125,4 @@ module TUITD
|
|
|
119
125
|
end
|
|
120
126
|
end
|
|
121
127
|
end
|
|
128
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
data/lib/tui_td/test_runner.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockLength
|
|
4
|
+
|
|
3
5
|
require "json"
|
|
4
6
|
|
|
5
7
|
module TUITD
|
|
@@ -39,10 +41,6 @@ module TUITD
|
|
|
39
41
|
@on_step = on_step
|
|
40
42
|
rescue JSON::ParserError => e
|
|
41
43
|
raise Error, "Invalid JSON: #{e.message}"
|
|
42
|
-
@plan[:steps] = @plan[:steps].map { |s| s.transform_keys(&:to_sym) }
|
|
43
|
-
@plan[:before_all] = @plan[:before_all]&.map { |s| s.transform_keys(&:to_sym) }
|
|
44
|
-
@plan[:after_all] = @plan[:after_all]&.map { |s| s.transform_keys(&:to_sym) }
|
|
45
|
-
@on_step = on_step
|
|
46
44
|
end
|
|
47
45
|
|
|
48
46
|
def run
|
|
@@ -55,7 +53,7 @@ module TUITD
|
|
|
55
53
|
hooks = [
|
|
56
54
|
{ label: :before_all, steps: @plan[:before_all] || [] },
|
|
57
55
|
{ label: :main, steps: @plan[:steps] },
|
|
58
|
-
{ label: :after_all, steps: @plan[:after_all] || [] }
|
|
56
|
+
{ label: :after_all, steps: @plan[:after_all] || [] },
|
|
59
57
|
]
|
|
60
58
|
|
|
61
59
|
all_results = []
|
|
@@ -120,7 +118,8 @@ module TUITD
|
|
|
120
118
|
if match
|
|
121
119
|
Result.new(step: action, passed: true, message: "Style at [#{row},#{col}] matches #{expected}")
|
|
122
120
|
else
|
|
123
|
-
Result.new(step: action, passed: false,
|
|
121
|
+
Result.new(step: action, passed: false,
|
|
122
|
+
message: "Style at [#{row},#{col}] is #{actual}, expected #{expected}",)
|
|
124
123
|
end
|
|
125
124
|
|
|
126
125
|
when "screenshot"
|
|
@@ -159,7 +158,6 @@ module TUITD
|
|
|
159
158
|
else
|
|
160
159
|
Result.new(step: action, passed: false, message: "Unknown action: #{action}")
|
|
161
160
|
end
|
|
162
|
-
|
|
163
161
|
rescue StandardError => e
|
|
164
162
|
r = Result.new(step: action, passed: false, message: "#{e.class}: #{e.message}")
|
|
165
163
|
end
|
|
@@ -167,23 +165,23 @@ module TUITD
|
|
|
167
165
|
all_results << r
|
|
168
166
|
all_passed &&= r.passed
|
|
169
167
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
@on_step.call(
|
|
178
|
-
index: all_results.size - 1,
|
|
179
|
-
total: total_steps,
|
|
180
|
-
action: action,
|
|
181
|
-
value: value,
|
|
182
|
-
result: r,
|
|
183
|
-
driver: driver,
|
|
184
|
-
state_data: state_data
|
|
185
|
-
)
|
|
168
|
+
next unless @on_step
|
|
169
|
+
|
|
170
|
+
state_data = nil
|
|
171
|
+
begin
|
|
172
|
+
state_data = driver.state_data if driver
|
|
173
|
+
rescue StandardError
|
|
174
|
+
# ignore — state retrieval is best-effort
|
|
186
175
|
end
|
|
176
|
+
@on_step.call(
|
|
177
|
+
index: all_results.size - 1,
|
|
178
|
+
total: total_steps,
|
|
179
|
+
action: action,
|
|
180
|
+
value: value,
|
|
181
|
+
result: r,
|
|
182
|
+
driver: driver,
|
|
183
|
+
state_data: state_data,
|
|
184
|
+
)
|
|
187
185
|
end
|
|
188
186
|
end
|
|
189
187
|
|
|
@@ -192,7 +190,7 @@ module TUITD
|
|
|
192
190
|
{
|
|
193
191
|
name: @plan[:name] || "(unnamed)",
|
|
194
192
|
passed: all_passed,
|
|
195
|
-
results: all_results.map(&:to_h)
|
|
193
|
+
results: all_results.map(&:to_h),
|
|
196
194
|
}
|
|
197
195
|
end
|
|
198
196
|
|
|
@@ -261,8 +259,10 @@ module TUITD
|
|
|
261
259
|
if actual == expected
|
|
262
260
|
Result.new(step: step.keys.first.to_s, passed: true, message: "#{label} at [#{row},#{col}] is #{expected}")
|
|
263
261
|
else
|
|
264
|
-
Result.new(step: step.keys.first.to_s, passed: false,
|
|
262
|
+
Result.new(step: step.keys.first.to_s, passed: false,
|
|
263
|
+
message: "#{label} at [#{row},#{col}] is #{actual}, expected #{expected}",)
|
|
265
264
|
end
|
|
266
265
|
end
|
|
267
266
|
end
|
|
268
267
|
end
|
|
268
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockLength
|