ratatui_ruby 0.7.2 → 0.7.3
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/.builds/ruby-3.2.yml +1 -1
- data/.builds/ruby-3.3.yml +1 -1
- data/.builds/ruby-3.4.yml +1 -1
- data/.builds/ruby-4.0.0.yml +1 -1
- data/AGENTS.md +4 -3
- data/CHANGELOG.md +28 -0
- data/README.md +2 -2
- data/doc/concepts/application_testing.md +4 -2
- data/doc/contributors/developing_examples.md +7 -7
- data/doc/contributors/upstream_requests/tab_rects.md +173 -0
- data/doc/contributors/upstream_requests/title_rects.md +132 -0
- data/doc/contributors/v1.0.0_blockers.md +46 -739
- data/doc/troubleshooting/tui_output.md +76 -0
- data/examples/widget_barchart/README.md +1 -1
- data/examples/widget_block/README.md +1 -1
- data/examples/widget_overlay/README.md +1 -1
- data/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/ext/ratatui_ruby/src/lib.rs +2 -2
- data/ext/ratatui_ruby/src/rendering.rs +9 -0
- data/ext/ratatui_ruby/src/style.rs +22 -2
- data/ext/ratatui_ruby/src/text.rs +26 -0
- data/ext/ratatui_ruby/src/widgets/chart.rs +5 -0
- data/lib/ratatui_ruby/style/style.rb +1 -0
- data/lib/ratatui_ruby/test_helper/snapshot.rb +60 -21
- data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.ansi +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.txt +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.ansi +5 -0
- data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.txt +5 -0
- data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.ansi +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.txt +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/my_snapshot.txt +1 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.ansi +10 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.txt +10 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.ansi +10 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.txt +10 -0
- data/lib/ratatui_ruby/test_helper/terminal.rb +3 -0
- data/lib/ratatui_ruby/test_helper.rb +1 -1
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby/widgets/bar_chart.rb +3 -2
- data/lib/ratatui_ruby/widgets/block.rb +42 -0
- data/lib/ratatui_ruby/widgets/chart.rb +9 -4
- data/lib/ratatui_ruby/widgets/sparkline.rb +3 -2
- data/lib/ratatui_ruby.rb +128 -9
- metadata +26 -2
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Terminal Output During TUI Sessions
|
|
7
|
+
|
|
8
|
+
## The Problem
|
|
9
|
+
|
|
10
|
+
Writing to stdout or stderr during a TUI session **corrupts the display**.
|
|
11
|
+
|
|
12
|
+
When your application is running inside `RatatuiRuby.run`, the terminal is in "raw mode" and RatatuiRuby has taken control of the display buffer. Any output via `puts`, `warn`, `p`, `print`, or direct writes to `STDOUT`/`STDERR` will:
|
|
13
|
+
|
|
14
|
+
1. **Corrupt the screen layout** - Characters appear in random positions
|
|
15
|
+
2. **Mix with TUI output** - Text interleaves with your widgets unpredictably
|
|
16
|
+
3. **Trigger escape sequence errors** - Partial ANSI codes can break rendering
|
|
17
|
+
|
|
18
|
+
## Why This Happens
|
|
19
|
+
|
|
20
|
+
In raw mode:
|
|
21
|
+
- The terminal doesn't process newlines or carriage returns normally
|
|
22
|
+
- Output bypasses the TUI's controlled buffer
|
|
23
|
+
- Cursor position is undefined from the TUI's perspective
|
|
24
|
+
|
|
25
|
+
## Safe Patterns
|
|
26
|
+
|
|
27
|
+
### Defer output until after the TUI exits
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
messages = []
|
|
31
|
+
|
|
32
|
+
RatatuiRuby.run do |tui|
|
|
33
|
+
# Collect messages instead of printing them
|
|
34
|
+
messages << "Something happened"
|
|
35
|
+
|
|
36
|
+
# ... TUI logic ...
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Now safe to print
|
|
40
|
+
messages.each { |msg| puts msg }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Use debug logging to a file
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
DEBUG_LOG = File.open("/tmp/my_app_debug.log", "a")
|
|
47
|
+
|
|
48
|
+
RatatuiRuby.run do |tui|
|
|
49
|
+
DEBUG_LOG.puts "Debug: something happened"
|
|
50
|
+
DEBUG_LOG.flush
|
|
51
|
+
|
|
52
|
+
# ... TUI logic ...
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Display messages in the TUI itself
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
RatatuiRuby.run do |tui|
|
|
60
|
+
@status_message = "Something happened"
|
|
61
|
+
|
|
62
|
+
tui.draw do |frame|
|
|
63
|
+
# Show status in the UI
|
|
64
|
+
frame.render_widget(
|
|
65
|
+
tui.paragraph(text: @status_message),
|
|
66
|
+
status_area
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Library Behavior
|
|
73
|
+
|
|
74
|
+
RatatuiRuby automatically defers its own warnings (like experimental feature notices) during TUI sessions. They are queued and printed after `restore_terminal` is called.
|
|
75
|
+
|
|
76
|
+
You don't need to do anything special for library warnings—they're handled automatically.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
-->
|
|
5
5
|
# Block Example
|
|
6
6
|
|
|
7
|
-
[](app.rb)
|
|
8
8
|
|
|
9
9
|
This example demonstrates the versatile `Block` widget, which provides the visual container, borders, and titles for almost every other widget in `ratatui_ruby`.
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
-->
|
|
5
5
|
# Overlay Example
|
|
6
6
|
|
|
7
|
-
[](app.rb)
|
|
8
8
|
|
|
9
9
|
This example demonstrates the `Overlay` composition pattern for layering widgets with depth. Modals, notifications, and floating panels all require stacking widgets on top of each other.
|
|
10
10
|
|
data/ext/ratatui_ruby/Cargo.lock
CHANGED
data/ext/ratatui_ruby/Cargo.toml
CHANGED
data/ext/ratatui_ruby/src/lib.rs
CHANGED
|
@@ -121,7 +121,7 @@ fn init() -> Result<(), Error> {
|
|
|
121
121
|
let m = ruby.define_module("RatatuiRuby")?;
|
|
122
122
|
|
|
123
123
|
m.define_module_function("_init_terminal", function!(init_terminal, 2))?;
|
|
124
|
-
m.define_module_function("
|
|
124
|
+
m.define_module_function("_restore_terminal", function!(restore_terminal, 0))?;
|
|
125
125
|
m.define_module_function("_draw", function!(draw, -1))?;
|
|
126
126
|
|
|
127
127
|
// Register Frame class
|
|
@@ -147,7 +147,7 @@ fn init() -> Result<(), Error> {
|
|
|
147
147
|
|
|
148
148
|
// Test backend helpers
|
|
149
149
|
m.define_module_function(
|
|
150
|
-
"
|
|
150
|
+
"_init_test_terminal",
|
|
151
151
|
function!(terminal::init_test_terminal, 2),
|
|
152
152
|
)?;
|
|
153
153
|
m.define_module_function(
|
|
@@ -60,6 +60,15 @@ pub fn render_node(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Err
|
|
|
60
60
|
"RatatuiRuby::Widgets::RatatuiMascot" => {
|
|
61
61
|
widgets::ratatui_mascot::render_ratatui_mascot(frame, area, node)?;
|
|
62
62
|
}
|
|
63
|
+
// Text primitives can also be rendered directly as widgets
|
|
64
|
+
"RatatuiRuby::Text::Line" => {
|
|
65
|
+
let line = crate::text::parse_line(node)?;
|
|
66
|
+
frame.render_widget(line, area);
|
|
67
|
+
}
|
|
68
|
+
"RatatuiRuby::Text::Span" => {
|
|
69
|
+
let span = crate::text::parse_span(node)?;
|
|
70
|
+
frame.render_widget(span, area);
|
|
71
|
+
}
|
|
63
72
|
_ => {}
|
|
64
73
|
}
|
|
65
74
|
Ok(())
|
|
@@ -171,8 +171,28 @@ pub fn parse_border_set<'a>(
|
|
|
171
171
|
|
|
172
172
|
pub fn parse_bar_set<'a>(set_val: Value, bump: &'a Bump) -> Result<symbols::bar::Set<'a>, Error> {
|
|
173
173
|
let ruby = magnus::Ruby::get().unwrap();
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
|
|
175
|
+
// Check if set_val is a symbol shortcut
|
|
176
|
+
if let Ok(sym) = magnus::Symbol::from_value(set_val)
|
|
177
|
+
.ok_or::<Error>(Error::new(ruby.exception_type_error(), ""))
|
|
178
|
+
{
|
|
179
|
+
let sym_str = sym.name().unwrap_or_default();
|
|
180
|
+
return match sym_str.as_ref() {
|
|
181
|
+
"nine_levels" => Ok(symbols::bar::NINE_LEVELS),
|
|
182
|
+
"three_levels" => Ok(symbols::bar::THREE_LEVELS),
|
|
183
|
+
_ => Err(Error::new(
|
|
184
|
+
ruby.exception_arg_error(),
|
|
185
|
+
format!("unknown bar_set symbol: :{sym_str}"),
|
|
186
|
+
)),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let hash = magnus::RHash::from_value(set_val).ok_or_else(|| {
|
|
191
|
+
Error::new(
|
|
192
|
+
ruby.exception_type_error(),
|
|
193
|
+
"expected symbol or hash for bar_set",
|
|
194
|
+
)
|
|
195
|
+
})?;
|
|
176
196
|
|
|
177
197
|
let get_char = |key: &str| -> Result<Option<&'a str>, Error> {
|
|
178
198
|
let mut val: Value = hash
|
|
@@ -133,6 +133,8 @@ pub fn parse_line(value: Value) -> Result<Line<'static>, Error> {
|
|
|
133
133
|
let spans_val: Value = value.funcall("spans", ())?;
|
|
134
134
|
// v0.7.0: Extract style from the Ruby Line
|
|
135
135
|
let style_val: Value = value.funcall("style", ())?;
|
|
136
|
+
// Extract alignment from the Ruby Line
|
|
137
|
+
let alignment_val: Value = value.funcall("alignment", ())?;
|
|
136
138
|
|
|
137
139
|
if spans_val.is_nil() {
|
|
138
140
|
return Ok(Line::from(""));
|
|
@@ -177,6 +179,30 @@ pub fn parse_line(value: Value) -> Result<Line<'static>, Error> {
|
|
|
177
179
|
line = line.style(parse_style(style_val)?);
|
|
178
180
|
}
|
|
179
181
|
|
|
182
|
+
// Apply alignment if present
|
|
183
|
+
if !alignment_val.is_nil() {
|
|
184
|
+
if let Ok(alignment_sym) = magnus::Symbol::try_convert(alignment_val) {
|
|
185
|
+
let alignment_str = alignment_sym.name().map_err(|e| {
|
|
186
|
+
Error::new(
|
|
187
|
+
ruby.exception_type_error(),
|
|
188
|
+
format!("Invalid alignment symbol: {e}"),
|
|
189
|
+
)
|
|
190
|
+
})?;
|
|
191
|
+
let alignment = match alignment_str.as_ref() {
|
|
192
|
+
"left" => ratatui::layout::Alignment::Left,
|
|
193
|
+
"center" => ratatui::layout::Alignment::Center,
|
|
194
|
+
"right" => ratatui::layout::Alignment::Right,
|
|
195
|
+
_ => {
|
|
196
|
+
return Err(Error::new(
|
|
197
|
+
ruby.exception_arg_error(),
|
|
198
|
+
format!("Invalid alignment: {alignment_str}. Valid values: :left, :center, :right"),
|
|
199
|
+
));
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
line = line.alignment(alignment);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
180
206
|
Ok(line)
|
|
181
207
|
}
|
|
182
208
|
|
|
@@ -75,6 +75,7 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
75
75
|
"braille" => symbols::Marker::Braille,
|
|
76
76
|
"block" => symbols::Marker::Block,
|
|
77
77
|
"bar" => symbols::Marker::Bar,
|
|
78
|
+
"half_block" => symbols::Marker::HalfBlock,
|
|
78
79
|
_ => symbols::Marker::Dot,
|
|
79
80
|
};
|
|
80
81
|
|
|
@@ -115,8 +116,12 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
115
116
|
let pos_sym: Symbol = legend_position_val.funcall("to_sym", ())?;
|
|
116
117
|
let pos = match pos_sym.to_string().as_str() {
|
|
117
118
|
"top_left" => LegendPosition::TopLeft,
|
|
119
|
+
"top" => LegendPosition::Top,
|
|
118
120
|
"bottom_left" => LegendPosition::BottomLeft,
|
|
121
|
+
"bottom" => LegendPosition::Bottom,
|
|
119
122
|
"bottom_right" => LegendPosition::BottomRight,
|
|
123
|
+
"left" => LegendPosition::Left,
|
|
124
|
+
"right" => LegendPosition::Right,
|
|
120
125
|
_ => LegendPosition::TopRight,
|
|
121
126
|
};
|
|
122
127
|
chart = chart.legend_position(Some(pos));
|
|
@@ -39,6 +39,7 @@ module RatatuiRuby
|
|
|
39
39
|
# * <tt>:dark_gray</tt>, <tt>:light_red</tt>, <tt>:light_green</tt>,
|
|
40
40
|
# <tt>:light_yellow</tt>, <tt>:light_blue</tt>, <tt>:light_magenta</tt>,
|
|
41
41
|
# <tt>:light_cyan</tt>, <tt>:white</tt>
|
|
42
|
+
# * <tt>:reset</tt> — Restores the terminal's default color.
|
|
42
43
|
#
|
|
43
44
|
# ==== String
|
|
44
45
|
# Represents a specific RGB color using a Hex code (<tt>"#RRGGBB"</tt>).
|
|
@@ -15,9 +15,10 @@ module RatatuiRuby
|
|
|
15
15
|
# Verifying every character of a TUI screen by hand is tedious. Snapshots let you
|
|
16
16
|
# capture the screen once and compare against it in future runs.
|
|
17
17
|
#
|
|
18
|
-
# This mixin provides <tt>
|
|
19
|
-
# <tt>assert_rich_snapshot</tt> for styled ANSI output
|
|
20
|
-
#
|
|
18
|
+
# This mixin provides <tt>assert_plain_snapshot</tt> for plain text,
|
|
19
|
+
# <tt>assert_rich_snapshot</tt> for styled ANSI output, and
|
|
20
|
+
# <tt>assert_snapshots</tt> (plural) for both. All auto-create snapshot
|
|
21
|
+
# files on first run.
|
|
21
22
|
#
|
|
22
23
|
# Use it to verify complex layouts, styles, and interactions without manual assertions.
|
|
23
24
|
#
|
|
@@ -62,38 +63,46 @@ module RatatuiRuby
|
|
|
62
63
|
#
|
|
63
64
|
# Mask dynamic content (timestamps, IDs) with a normalization block:
|
|
64
65
|
#
|
|
65
|
-
#
|
|
66
|
+
# assert_snapshots("dashboard") do |lines|
|
|
66
67
|
# lines.map { |l| l.gsub(/\d{4}-\d{2}-\d{2}/, "YYYY-MM-DD") }
|
|
67
68
|
# end
|
|
68
69
|
#
|
|
69
70
|
module Snapshot
|
|
70
71
|
##
|
|
71
|
-
# Asserts that the current screen content matches a stored snapshot.
|
|
72
|
+
# Asserts that the current screen content matches a stored plain text snapshot.
|
|
72
73
|
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
74
|
+
# Plain text snapshots capture layout but miss styling bugs: wrong colors, missing bold,
|
|
75
|
+
# invisible text on a matching background. *Prefer <tt>assert_snapshots</tt>* (plural) to catch
|
|
76
|
+
# styling regressions.
|
|
76
77
|
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
78
|
+
# Plain text snapshots are human-readable when viewed in any editor or diff tool. They
|
|
79
|
+
# pair well with rich snapshots for documentation. Use <tt>assert_snapshots</tt> to generate both.
|
|
80
|
+
#
|
|
81
|
+
# assert_plain_snapshot("login_screen")
|
|
82
|
+
# # Compares against: test/snapshots/login_screen.txt
|
|
80
83
|
#
|
|
81
84
|
# # With normalization block
|
|
82
|
-
#
|
|
85
|
+
# assert_plain_snapshot("clock") do |actual|
|
|
83
86
|
# actual.map { |l| l.gsub(/\d{2}:\d{2}/, "XX:XX") }
|
|
84
87
|
# end
|
|
85
88
|
#
|
|
86
89
|
# [name] String name of the snapshot (without extension).
|
|
87
90
|
# [msg] String optional failure message.
|
|
88
|
-
def
|
|
91
|
+
def assert_plain_snapshot(name, msg = nil, snapshot_dir: nil, &)
|
|
89
92
|
# Get the path of the test file calling this method
|
|
90
|
-
|
|
91
|
-
snapshot_dir = File.join(File.dirname(caller_path), "snapshots")
|
|
93
|
+
snapshot_dir ||= File.join(File.dirname(caller_locations(1, 1).first.path), "snapshots")
|
|
92
94
|
snapshot_path = File.join(snapshot_dir, "#{name}.txt")
|
|
93
95
|
|
|
94
96
|
assert_screen_matches(snapshot_path, msg, &)
|
|
95
97
|
end
|
|
96
98
|
|
|
99
|
+
##
|
|
100
|
+
# @deprecated Use {#assert_plain_snapshot} instead.
|
|
101
|
+
def assert_snapshot(name, msg = nil, &)
|
|
102
|
+
warn "assert_snapshot is deprecated; use assert_plain_snapshot instead", uplevel: 1
|
|
103
|
+
assert_plain_snapshot(name, msg, &)
|
|
104
|
+
end
|
|
105
|
+
|
|
97
106
|
##
|
|
98
107
|
# Asserts that the current screen content matches the expected content.
|
|
99
108
|
#
|
|
@@ -186,12 +195,17 @@ module RatatuiRuby
|
|
|
186
195
|
end
|
|
187
196
|
|
|
188
197
|
##
|
|
189
|
-
# Asserts that the current screen content (including colors
|
|
198
|
+
# Asserts that the current screen content (including colors and styles) matches a stored ANSI snapshot.
|
|
199
|
+
#
|
|
200
|
+
# TUIs communicate meaning through colors and styles. Rich snapshots capture everything:
|
|
201
|
+
# wrong colors, missing bold, invisible text on a matching background. *Prefer <tt>assert_snapshots</tt>*
|
|
202
|
+
# (plural) to also generate human-readable plain text files for documentation.
|
|
190
203
|
#
|
|
191
|
-
#
|
|
192
|
-
#
|
|
204
|
+
# The <tt>.ansi</tt> snapshot files contain ANSI escape codes. You can <tt>cat</tt> them in a terminal
|
|
205
|
+
# to see exactly what the screen looked like.
|
|
193
206
|
#
|
|
194
207
|
# assert_rich_snapshot("login_screen")
|
|
208
|
+
# # Compares against: test/snapshots/login_screen.ansi
|
|
195
209
|
#
|
|
196
210
|
# # With normalization
|
|
197
211
|
# assert_rich_snapshot("log_view") do |lines|
|
|
@@ -200,9 +214,8 @@ module RatatuiRuby
|
|
|
200
214
|
#
|
|
201
215
|
# [name] String snapshot name.
|
|
202
216
|
# [msg] String optional failure message.
|
|
203
|
-
def assert_rich_snapshot(name, msg = nil)
|
|
204
|
-
|
|
205
|
-
snapshot_dir = File.join(File.dirname(caller_path), "snapshots")
|
|
217
|
+
def assert_rich_snapshot(name, msg = nil, snapshot_dir: nil)
|
|
218
|
+
snapshot_dir ||= File.join(File.dirname(caller_locations(1, 1).first.path), "snapshots")
|
|
206
219
|
snapshot_path = File.join(snapshot_dir, "#{name}.ansi")
|
|
207
220
|
|
|
208
221
|
actual_content = _render_buffer_with_ansi
|
|
@@ -254,6 +267,32 @@ module RatatuiRuby
|
|
|
254
267
|
end
|
|
255
268
|
end
|
|
256
269
|
|
|
270
|
+
##
|
|
271
|
+
# Asserts both plain text and rich (ANSI-styled) snapshots match.
|
|
272
|
+
#
|
|
273
|
+
# This is the recommended snapshot assertion. It calls both <tt>assert_plain_snapshot</tt> and
|
|
274
|
+
# <tt>assert_rich_snapshot</tt> with the same name, generating <tt>.txt</tt> and <tt>.ansi</tt> files.
|
|
275
|
+
#
|
|
276
|
+
# Rich snapshots catch styling bugs that plain text misses. Plain text snapshots are
|
|
277
|
+
# human-readable in any editor or diff tool, making them valuable for documentation and
|
|
278
|
+
# code review. Together, they provide comprehensive coverage and discoverability.
|
|
279
|
+
#
|
|
280
|
+
# assert_snapshots("login_screen")
|
|
281
|
+
# # Creates/compares: snapshots/login_screen.txt AND snapshots/login_screen.ansi
|
|
282
|
+
#
|
|
283
|
+
# # With normalization (masks dynamic content like timestamps)
|
|
284
|
+
# assert_snapshots("dashboard") do |lines|
|
|
285
|
+
# lines.map { |l| l.gsub(/\d{2}:\d{2}:\d{2}/, "HH:MM:SS") }
|
|
286
|
+
# end
|
|
287
|
+
#
|
|
288
|
+
# [name] String snapshot name (without extension).
|
|
289
|
+
# [msg] String optional failure message.
|
|
290
|
+
def assert_snapshots(name, msg = nil, &)
|
|
291
|
+
snapshot_dir = File.join(File.dirname(caller_locations(1, 1).first.path), "snapshots")
|
|
292
|
+
assert_plain_snapshot(name, msg, snapshot_dir:, &)
|
|
293
|
+
assert_rich_snapshot(name, msg, snapshot_dir:, &)
|
|
294
|
+
end
|
|
295
|
+
|
|
257
296
|
##
|
|
258
297
|
# Returns the current buffer content as an ANSI-encoded string.
|
|
259
298
|
#
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[0m┌Aligned Chart─────────────────────────────────────────────────────────────────┐[0m
|
|
2
|
+
[0m│10│Value ┌──────┐│[0m
|
|
3
|
+
[0m│ │ │[32mTestDS││[0m
|
|
4
|
+
[0m│ │ [32m••└──────┘│[0m
|
|
5
|
+
[0m│ │ [32m•••• │[0m
|
|
6
|
+
[0m│ │ [32m•••• │[0m
|
|
7
|
+
[0m│ │ [32m•••• │[0m
|
|
8
|
+
[0m│ │ [32m•••• │[0m
|
|
9
|
+
[0m│ │ [32m•••• │[0m
|
|
10
|
+
[0m│ │ [32m•••• │[0m
|
|
11
|
+
[0m│ │ [32m••• │[0m
|
|
12
|
+
[0m│ 5│ [32m•••• │[0m
|
|
13
|
+
[0m│ │ [32m•••• │[0m
|
|
14
|
+
[0m│ │ [32m•••• │[0m
|
|
15
|
+
[0m│ │ [32m•••• │[0m
|
|
16
|
+
[0m│ │ [32m•••• │[0m
|
|
17
|
+
[0m│ │ [32m•••• │[0m
|
|
18
|
+
[0m│ │ [32m•••• │[0m
|
|
19
|
+
[0m│ │ [32m•••• │[0m
|
|
20
|
+
[0m│ │ [32m•••• │[0m
|
|
21
|
+
[0m│ 0│[32m•• Time│[0m
|
|
22
|
+
[0m│ └───────────────────────────────────────────────────────────────────────────│[0m
|
|
23
|
+
[0m│ 0 5 10│[0m
|
|
24
|
+
[0m└──────────────────────────────────────────────────────────────────────────────┘[0m
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
┌Aligned Chart─────────────────────────────────────────────────────────────────┐
|
|
2
|
+
│10│Value ┌──────┐│
|
|
3
|
+
│ │ │TestDS││
|
|
4
|
+
│ │ ••└──────┘│
|
|
5
|
+
│ │ •••• │
|
|
6
|
+
│ │ •••• │
|
|
7
|
+
│ │ •••• │
|
|
8
|
+
│ │ •••• │
|
|
9
|
+
│ │ •••• │
|
|
10
|
+
│ │ •••• │
|
|
11
|
+
│ │ ••• │
|
|
12
|
+
│ 5│ •••• │
|
|
13
|
+
│ │ •••• │
|
|
14
|
+
│ │ •••• │
|
|
15
|
+
│ │ •••• │
|
|
16
|
+
│ │ •••• │
|
|
17
|
+
│ │ •••• │
|
|
18
|
+
│ │ •••• │
|
|
19
|
+
│ │ •••• │
|
|
20
|
+
│ │ •••• │
|
|
21
|
+
│ 0│•• Time│
|
|
22
|
+
│ └───────────────────────────────────────────────────────────────────────────│
|
|
23
|
+
│ 0 5 10│
|
|
24
|
+
└──────────────────────────────────────────────────────────────────────────────┘
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[0m┌Test Chart────────────────────────────────────────────────────────────────────┐[0m
|
|
2
|
+
[0m│10│Value ┌──────┐│[0m
|
|
3
|
+
[0m│ │ │[31mTestDS││[0m
|
|
4
|
+
[0m│ │ [31m••└──────┘│[0m
|
|
5
|
+
[0m│ │ [31m•••• │[0m
|
|
6
|
+
[0m│ │ [31m•••• │[0m
|
|
7
|
+
[0m│ │ [31m•••• │[0m
|
|
8
|
+
[0m│ │ [31m•••• │[0m
|
|
9
|
+
[0m│ │ [31m•••• │[0m
|
|
10
|
+
[0m│ │ [31m•••• │[0m
|
|
11
|
+
[0m│ │ [31m••• │[0m
|
|
12
|
+
[0m│ │ [31m•••• │[0m
|
|
13
|
+
[0m│ │ [31m•••• │[0m
|
|
14
|
+
[0m│ │ [31m•••• │[0m
|
|
15
|
+
[0m│ │ [31m•••• │[0m
|
|
16
|
+
[0m│ │ [31m•••• │[0m
|
|
17
|
+
[0m│ │ [31m•••• │[0m
|
|
18
|
+
[0m│ │ [31m•••• │[0m
|
|
19
|
+
[0m│ │ [31m•••• │[0m
|
|
20
|
+
[0m│ │ [31m•••• │[0m
|
|
21
|
+
[0m│0 │[31m•• Time│[0m
|
|
22
|
+
[0m│ └───────────────────────────────────────────────────────────────────────────│[0m
|
|
23
|
+
[0m│ 0 10│[0m
|
|
24
|
+
[0m└──────────────────────────────────────────────────────────────────────────────┘[0m
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
┌Test Chart────────────────────────────────────────────────────────────────────┐
|
|
2
|
+
│10│Value ┌──────┐│
|
|
3
|
+
│ │ │TestDS││
|
|
4
|
+
│ │ ••└──────┘│
|
|
5
|
+
│ │ •••• │
|
|
6
|
+
│ │ •••• │
|
|
7
|
+
│ │ •••• │
|
|
8
|
+
│ │ •••• │
|
|
9
|
+
│ │ •••• │
|
|
10
|
+
│ │ •••• │
|
|
11
|
+
│ │ ••• │
|
|
12
|
+
│ │ •••• │
|
|
13
|
+
│ │ •••• │
|
|
14
|
+
│ │ •••• │
|
|
15
|
+
│ │ •••• │
|
|
16
|
+
│ │ •••• │
|
|
17
|
+
│ │ •••• │
|
|
18
|
+
│ │ •••• │
|
|
19
|
+
│ │ •••• │
|
|
20
|
+
│ │ •••• │
|
|
21
|
+
│0 │•• Time│
|
|
22
|
+
│ └───────────────────────────────────────────────────────────────────────────│
|
|
23
|
+
│ 0 10│
|
|
24
|
+
└──────────────────────────────────────────────────────────────────────────────┘
|