ratatui_ruby 0.1.0 → 0.3.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/.builds/ruby-3.2.yml +52 -0
- data/.builds/ruby-3.3.yml +52 -0
- data/.builds/ruby-3.4.yml +52 -0
- data/.builds/ruby-4.0.0.yml +53 -0
- data/.pre-commit-config.yaml +9 -2
- data/AGENTS.md +53 -5
- data/CHANGELOG.md +51 -1
- data/README.md +38 -18
- data/REUSE.toml +5 -0
- data/Rakefile +3 -100
- data/{docs → doc}/contributors/index.md +2 -1
- data/doc/custom.css +8 -0
- data/doc/images/examples-calendar_demo.rb.png +0 -0
- data/doc/images/examples-chart_demo.rb.png +0 -0
- data/doc/images/examples-custom_widget.rb.png +0 -0
- data/doc/images/examples-list_styles.rb.png +0 -0
- data/doc/images/examples-popup_demo.rb.gif +0 -0
- data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
- data/doc/images/examples-scroll_text.rb.png +0 -0
- data/doc/images/examples-stock_ticker.rb.png +0 -0
- data/doc/images/examples-table_select.rb.png +0 -0
- data/{docs → doc}/index.md +1 -1
- data/{docs → doc}/quickstart.md +81 -11
- data/examples/analytics.rb +2 -1
- data/examples/calendar_demo.rb +55 -0
- data/examples/chart_demo.rb +84 -0
- data/examples/custom_widget.rb +43 -0
- data/examples/list_styles.rb +66 -0
- data/examples/login_form.rb +2 -1
- data/examples/popup_demo.rb +105 -0
- data/examples/quickstart_dsl.rb +30 -0
- data/examples/quickstart_lifecycle.rb +40 -0
- data/examples/readme_usage.rb +21 -0
- data/examples/scroll_text.rb +74 -0
- data/examples/stock_ticker.rb +13 -5
- data/examples/system_monitor.rb +2 -1
- data/examples/table_select.rb +70 -0
- data/examples/test_calendar_demo.rb +66 -0
- data/examples/test_list_styles.rb +61 -0
- data/examples/test_popup_demo.rb +62 -0
- data/examples/test_scroll_text.rb +130 -0
- data/examples/test_table_select.rb +37 -0
- data/ext/ratatui_ruby/.cargo/config.toml +5 -0
- data/ext/ratatui_ruby/Cargo.lock +260 -50
- data/ext/ratatui_ruby/Cargo.toml +5 -4
- data/ext/ratatui_ruby/extconf.rb +1 -1
- data/ext/ratatui_ruby/src/buffer.rs +54 -0
- data/ext/ratatui_ruby/src/events.rs +115 -107
- data/ext/ratatui_ruby/src/lib.rs +15 -6
- data/ext/ratatui_ruby/src/rendering.rs +18 -1
- data/ext/ratatui_ruby/src/style.rs +2 -1
- data/ext/ratatui_ruby/src/terminal.rs +27 -24
- data/ext/ratatui_ruby/src/widgets/calendar.rs +82 -0
- data/ext/ratatui_ruby/src/widgets/canvas.rs +1 -2
- data/ext/ratatui_ruby/src/widgets/center.rs +0 -2
- data/ext/ratatui_ruby/src/widgets/chart.rs +260 -0
- data/ext/ratatui_ruby/src/widgets/clear.rs +37 -0
- data/ext/ratatui_ruby/src/widgets/cursor.rs +1 -1
- data/ext/ratatui_ruby/src/widgets/layout.rs +2 -1
- data/ext/ratatui_ruby/src/widgets/list.rs +44 -5
- data/ext/ratatui_ruby/src/widgets/mod.rs +3 -1
- data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
- data/ext/ratatui_ruby/src/widgets/paragraph.rs +10 -0
- data/ext/ratatui_ruby/src/widgets/table.rs +25 -6
- data/ext/ratatui_ruby/src/widgets/tabs.rs +2 -1
- data/lib/ratatui_ruby/dsl.rb +64 -0
- data/lib/ratatui_ruby/schema/calendar.rb +26 -0
- data/lib/ratatui_ruby/schema/chart.rb +81 -0
- data/lib/ratatui_ruby/schema/clear.rb +83 -0
- data/lib/ratatui_ruby/schema/list.rb +8 -2
- data/lib/ratatui_ruby/schema/paragraph.rb +7 -4
- data/lib/ratatui_ruby/schema/rect.rb +24 -0
- data/lib/ratatui_ruby/schema/table.rb +8 -2
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby.rb +24 -2
- data/mise.toml +8 -0
- data/sig/ratatui_ruby/buffer.rbs +11 -0
- data/sig/ratatui_ruby/schema/calendar.rbs +13 -0
- data/sig/ratatui_ruby/schema/{line_chart.rbs → chart.rbs} +20 -1
- data/sig/ratatui_ruby/schema/list.rbs +4 -1
- data/sig/ratatui_ruby/schema/rect.rbs +14 -0
- data/tasks/bump/cargo_lockfile.rb +19 -0
- data/tasks/bump/changelog.rb +37 -0
- data/tasks/bump/comparison_links.rb +41 -0
- data/tasks/bump/header.rb +30 -0
- data/tasks/bump/history.rb +30 -0
- data/tasks/bump/manifest.rb +31 -0
- data/tasks/bump/ruby_gem.rb +35 -0
- data/tasks/bump/sem_ver.rb +34 -0
- data/tasks/bump/unreleased_section.rb +38 -0
- data/tasks/bump.rake +49 -0
- data/tasks/doc.rake +25 -0
- data/tasks/extension.rake +12 -0
- data/tasks/lint.rake +49 -0
- data/tasks/rdoc_config.rb +15 -0
- data/tasks/resources/build.yml.erb +65 -0
- data/tasks/resources/index.html.erb +38 -0
- data/tasks/resources/rubies.yml +7 -0
- data/tasks/sourcehut.rake +38 -0
- data/tasks/test.rake +31 -0
- data/tasks/website/index_page.rb +28 -0
- data/tasks/website/version.rb +117 -0
- data/tasks/website/version_menu.rb +68 -0
- data/tasks/website/versioned_documentation.rb +49 -0
- data/tasks/website/website.rb +53 -0
- data/tasks/website.rake +26 -0
- metadata +119 -28
- data/.build.yml +0 -34
- data/.ruby-version +0 -1
- data/CODE_OF_CONDUCT.md +0 -30
- data/CONTRIBUTING.md +0 -40
- data/docs/images/examples-stock_ticker.rb.png +0 -0
- data/ext/ratatui_ruby/src/widgets/linechart.rs +0 -154
- data/lib/ratatui_ruby/schema/line_chart.rb +0 -41
- /data/{docs → doc}/application_testing.md +0 -0
- /data/{docs → doc}/contributors/design/ruby_frontend.md +0 -0
- /data/{docs → doc}/contributors/design/rust_backend.md +0 -0
- /data/{docs → doc}/contributors/design.md +0 -0
- /data/{docs → doc}/images/examples-analytics.rb.png +0 -0
- /data/{docs → doc}/images/examples-box_demo.rb.png +0 -0
- /data/{docs → doc}/images/examples-dashboard.rb.png +0 -0
- /data/{docs → doc}/images/examples-login_form.rb.png +0 -0
- /data/{docs → doc}/images/examples-map_demo.rb.png +0 -0
- /data/{docs → doc}/images/examples-mouse_events.rb.png +0 -0
- /data/{docs → doc}/images/examples-scrollbar_demo.rb.png +0 -0
- /data/{docs → doc}/images/examples-system_monitor.rb.png +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
+
|
|
7
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
8
|
+
require "ratatui_ruby"
|
|
9
|
+
|
|
10
|
+
# Demo: Scrollable Paragraph
|
|
11
|
+
# Shows how to scroll through long text content using arrow keys
|
|
12
|
+
class ScrollTextDemo
|
|
13
|
+
def initialize
|
|
14
|
+
@scroll_x = 0
|
|
15
|
+
@scroll_y = 0
|
|
16
|
+
@lines = (1..100).map { |i| "Line #{i}: This is a long line of text that can be scrolled horizontally" }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def run
|
|
20
|
+
RatatuiRuby.init_terminal
|
|
21
|
+
begin
|
|
22
|
+
loop do
|
|
23
|
+
draw
|
|
24
|
+
break if handle_input == :quit
|
|
25
|
+
end
|
|
26
|
+
ensure
|
|
27
|
+
RatatuiRuby.restore_terminal
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def render
|
|
32
|
+
draw
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def handle_input
|
|
36
|
+
event = RatatuiRuby.poll_event
|
|
37
|
+
return nil unless event
|
|
38
|
+
|
|
39
|
+
if event[:type] == :key
|
|
40
|
+
case event[:code]
|
|
41
|
+
when "up"
|
|
42
|
+
@scroll_y = [@scroll_y - 1, 0].max
|
|
43
|
+
when "down"
|
|
44
|
+
@scroll_y = [@scroll_y + 1, @lines.length].min
|
|
45
|
+
when "left"
|
|
46
|
+
@scroll_x = [@scroll_x - 1, 0].max
|
|
47
|
+
when "right"
|
|
48
|
+
@scroll_x = [@scroll_x + 1, 100].min
|
|
49
|
+
when "q"
|
|
50
|
+
return :quit
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def draw
|
|
59
|
+
text = @lines.join("\n")
|
|
60
|
+
|
|
61
|
+
paragraph = RatatuiRuby::Paragraph.new(
|
|
62
|
+
text: text,
|
|
63
|
+
scroll: [@scroll_y, @scroll_x],
|
|
64
|
+
block: RatatuiRuby::Block.new(
|
|
65
|
+
title: "Scrollable Text (X: #{@scroll_x}, Y: #{@scroll_y}) - Arrow keys to scroll, 'q' to quit",
|
|
66
|
+
borders: [:all]
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
RatatuiRuby.draw(paragraph)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
ScrollTextDemo.new.run if __FILE__ == $PROGRAM_NAME
|
data/examples/stock_ticker.rb
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
4
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
7
|
+
require "ratatui_ruby"
|
|
7
8
|
|
|
8
9
|
# Simulate a stock ticker
|
|
9
10
|
class StockTickerApp
|
|
@@ -58,14 +59,21 @@ class StockTickerApp
|
|
|
58
59
|
style: RatatuiRuby::Style.new(fg: :cyan),
|
|
59
60
|
block: RatatuiRuby::Block.new(title: "Network Activity", borders: :all)
|
|
60
61
|
),
|
|
61
|
-
RatatuiRuby::
|
|
62
|
+
RatatuiRuby::Chart.new(
|
|
62
63
|
datasets: [
|
|
63
64
|
RatatuiRuby::Dataset.new(name: "RBY", data: @ruby_stock, color: :green),
|
|
64
65
|
RatatuiRuby::Dataset.new(name: "RST", data: @rust_stock, color: :red),
|
|
65
66
|
],
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
x_axis: RatatuiRuby::Axis.new(
|
|
68
|
+
title: "Time",
|
|
69
|
+
bounds: [((@counter > 100) ? @counter - 100.0 : 0.0), @counter.to_f],
|
|
70
|
+
labels: [@counter.to_s]
|
|
71
|
+
),
|
|
72
|
+
y_axis: RatatuiRuby::Axis.new(
|
|
73
|
+
title: "Price",
|
|
74
|
+
bounds: [0.0, 100.0],
|
|
75
|
+
labels: %w[0 50 100]
|
|
76
|
+
),
|
|
69
77
|
block: RatatuiRuby::Block.new(title: "Stock Ticker", borders: :all),
|
|
70
78
|
),
|
|
71
79
|
]
|
data/examples/system_monitor.rb
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
4
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
7
|
+
require "ratatui_ruby"
|
|
7
8
|
|
|
8
9
|
class SystemMonitorApp
|
|
9
10
|
def initialize
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
+
|
|
7
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
8
|
+
require "bundler/setup"
|
|
9
|
+
require "ratatui_ruby"
|
|
10
|
+
|
|
11
|
+
# Sample process data
|
|
12
|
+
PROCESSES = [
|
|
13
|
+
{ pid: 1234, name: "ruby", cpu: 15.2 },
|
|
14
|
+
{ pid: 5678, name: "postgres", cpu: 8.7 },
|
|
15
|
+
{ pid: 9012, name: "nginx", cpu: 3.1 },
|
|
16
|
+
{ pid: 3456, name: "redis", cpu: 12.4 },
|
|
17
|
+
{ pid: 7890, name: "sidekiq", cpu: 22.8 },
|
|
18
|
+
{ pid: 2345, name: "webpack", cpu: 45.3 },
|
|
19
|
+
{ pid: 6789, name: "node", cpu: 18.9 }
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
selected_index = 0
|
|
23
|
+
|
|
24
|
+
RatatuiRuby.init_terminal
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
loop do
|
|
28
|
+
# Create table rows from process data
|
|
29
|
+
rows = PROCESSES.map { |p| [p[:pid].to_s, p[:name], "#{p[:cpu]}%"] }
|
|
30
|
+
|
|
31
|
+
# Define column widths
|
|
32
|
+
widths = [
|
|
33
|
+
RatatuiRuby::Constraint.length(8),
|
|
34
|
+
RatatuiRuby::Constraint.length(15),
|
|
35
|
+
RatatuiRuby::Constraint.length(10)
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
# Create highlight style (yellow text)
|
|
39
|
+
highlight_style = RatatuiRuby::Style.new(fg: :yellow)
|
|
40
|
+
|
|
41
|
+
# Create table with selection
|
|
42
|
+
table = RatatuiRuby::Table.new(
|
|
43
|
+
header: ["PID", "Name", "CPU"],
|
|
44
|
+
rows: rows,
|
|
45
|
+
widths: widths,
|
|
46
|
+
selected_row: selected_index,
|
|
47
|
+
highlight_style: highlight_style,
|
|
48
|
+
highlight_symbol: "> ",
|
|
49
|
+
block: RatatuiRuby::Block.new(title: "Process Monitor (↑/↓ to select, q to quit)", borders: :all)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Draw the table
|
|
53
|
+
RatatuiRuby.draw(table)
|
|
54
|
+
|
|
55
|
+
# Handle events
|
|
56
|
+
event = RatatuiRuby.poll_event
|
|
57
|
+
next unless event
|
|
58
|
+
|
|
59
|
+
case event[:code]
|
|
60
|
+
when "q"
|
|
61
|
+
break
|
|
62
|
+
when "down", "j"
|
|
63
|
+
selected_index = (selected_index + 1) % PROCESSES.length
|
|
64
|
+
when "up", "k"
|
|
65
|
+
selected_index = (selected_index - 1) % PROCESSES.length
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
ensure
|
|
69
|
+
RatatuiRuby.restore_terminal
|
|
70
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
require "minitest/autorun"
|
|
7
|
+
require "ratatui_ruby"
|
|
8
|
+
|
|
9
|
+
module CalendarDemo
|
|
10
|
+
class TestCalendarDemo < Minitest::Test
|
|
11
|
+
def setup
|
|
12
|
+
RatatuiRuby.init_test_terminal(40, 20)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_demo_renders
|
|
16
|
+
# We can't easily test the loop in run, but we can test the rendering logic
|
|
17
|
+
now = Time.now
|
|
18
|
+
calendar = RatatuiRuby::Calendar.new(
|
|
19
|
+
year: now.year,
|
|
20
|
+
month: now.month,
|
|
21
|
+
header_style: RatatuiRuby::Style.new(fg: "yellow", modifiers: [:bold]),
|
|
22
|
+
block: RatatuiRuby::Block.new(title: " Calendar (q = quit) ", borders: [:all]),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Constrain the calendar to 24x10 characters
|
|
26
|
+
view = RatatuiRuby::Layout.new(
|
|
27
|
+
direction: :vertical,
|
|
28
|
+
constraints: [
|
|
29
|
+
RatatuiRuby::Constraint.length(10),
|
|
30
|
+
RatatuiRuby::Constraint.min(0),
|
|
31
|
+
],
|
|
32
|
+
children: [
|
|
33
|
+
RatatuiRuby::Layout.new(
|
|
34
|
+
direction: :horizontal,
|
|
35
|
+
constraints: [
|
|
36
|
+
RatatuiRuby::Constraint.length(24),
|
|
37
|
+
RatatuiRuby::Constraint.min(0),
|
|
38
|
+
],
|
|
39
|
+
children: [calendar, nil],
|
|
40
|
+
),
|
|
41
|
+
nil,
|
|
42
|
+
],
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
RatatuiRuby.draw(view)
|
|
46
|
+
content = RatatuiRuby.get_buffer_content
|
|
47
|
+
|
|
48
|
+
assert_match(/Calendar \(q = quit\)/, content)
|
|
49
|
+
assert_match(/#{now.year}/, content)
|
|
50
|
+
|
|
51
|
+
# Verify the size constraint: the terminal is 40x20
|
|
52
|
+
# But the layout should be constrained to 24x10.
|
|
53
|
+
lines = content.split("\n")
|
|
54
|
+
|
|
55
|
+
# First 10 rows: first 24 chars can have content, 24..39 should be empty
|
|
56
|
+
10.times do |i|
|
|
57
|
+
assert_equal " " * 16, lines[i][24..39], "Row #{i} should be empty after column 24"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Rows 10..19 should be completely empty
|
|
61
|
+
(10..19).each do |i|
|
|
62
|
+
assert_equal " " * 40, lines[i], "Row #{i} should be completely empty"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
7
|
+
require "ratatui_ruby"
|
|
8
|
+
require "ratatui_ruby/test_helper"
|
|
9
|
+
require "minitest/autorun"
|
|
10
|
+
require_relative "list_styles"
|
|
11
|
+
|
|
12
|
+
class Minitest::Test
|
|
13
|
+
include RatatuiRuby::TestHelper
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class TestListStyles < Minitest::Test
|
|
17
|
+
def setup
|
|
18
|
+
@app = ListStylesApp.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_render_initial_state
|
|
22
|
+
with_test_terminal(50, 20) do
|
|
23
|
+
@app.render
|
|
24
|
+
|
|
25
|
+
assert buffer_content.any? { |line| line.include?(">> Item 1") }
|
|
26
|
+
assert buffer_content.any? { |line| line.include?(" Item 2") }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_navigation_down
|
|
31
|
+
inject_event("key", { code: "down" })
|
|
32
|
+
@app.handle_input
|
|
33
|
+
|
|
34
|
+
with_test_terminal(50, 20) do
|
|
35
|
+
@app.render
|
|
36
|
+
assert buffer_content.any? { |line| line.include?(" Item 1") }
|
|
37
|
+
assert buffer_content.any? { |line| line.include?(">> Item 2") }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_navigation_up
|
|
42
|
+
# Move down to Item 2
|
|
43
|
+
inject_event("key", { code: "down" })
|
|
44
|
+
@app.handle_input
|
|
45
|
+
|
|
46
|
+
# Move up back to Item 1
|
|
47
|
+
inject_event("key", { code: "up" })
|
|
48
|
+
@app.handle_input
|
|
49
|
+
|
|
50
|
+
with_test_terminal(50, 20) do
|
|
51
|
+
@app.render
|
|
52
|
+
assert buffer_content.any? { |line| line.include?(">> Item 1") }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_quit
|
|
57
|
+
inject_event("key", { code: "q" })
|
|
58
|
+
status = @app.handle_input
|
|
59
|
+
assert_equal :quit, status
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
7
|
+
require "ratatui_ruby"
|
|
8
|
+
require "ratatui_ruby/test_helper"
|
|
9
|
+
require "minitest/autorun"
|
|
10
|
+
require_relative "popup_demo"
|
|
11
|
+
|
|
12
|
+
class Minitest::Test
|
|
13
|
+
include RatatuiRuby::TestHelper
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class TestPopupDemo < Minitest::Test
|
|
17
|
+
def setup
|
|
18
|
+
@app = PopupDemo.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_render_initial_state
|
|
22
|
+
with_test_terminal(60, 20) do
|
|
23
|
+
@app.render
|
|
24
|
+
|
|
25
|
+
# Should have background text
|
|
26
|
+
assert buffer_content.any? { |line| line.include?("BACKGROUND RED") }
|
|
27
|
+
|
|
28
|
+
# Should have popup with "Clear is DISABLED" message
|
|
29
|
+
assert buffer_content.any? { |line| line.include?("Clear is DISABLED") }
|
|
30
|
+
assert buffer_content.any? { |line| line.include?("Style Bleed: Popup is RED!") }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_toggle_clear
|
|
35
|
+
with_test_terminal(60, 20) do
|
|
36
|
+
# Initial state: Clear disabled
|
|
37
|
+
@app.render
|
|
38
|
+
assert buffer_content.any? { |line| line.include?("Clear is DISABLED") }
|
|
39
|
+
|
|
40
|
+
# Toggle Clear on
|
|
41
|
+
inject_event("key", { code: " " })
|
|
42
|
+
@app.handle_input
|
|
43
|
+
|
|
44
|
+
@app.render
|
|
45
|
+
assert buffer_content.any? { |line| line.include?("Clear is ENABLED") }
|
|
46
|
+
assert buffer_content.any? { |line| line.include?("Resets background to default") }
|
|
47
|
+
|
|
48
|
+
# Toggle Clear off
|
|
49
|
+
inject_event("key", { code: " " })
|
|
50
|
+
@app.handle_input
|
|
51
|
+
|
|
52
|
+
@app.render
|
|
53
|
+
assert buffer_content.any? { |line| line.include?("Clear is DISABLED") }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_quit
|
|
58
|
+
inject_event("key", { code: "q" })
|
|
59
|
+
status = @app.handle_input
|
|
60
|
+
assert_equal :quit, status
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
+
|
|
7
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
8
|
+
require "minitest/autorun"
|
|
9
|
+
require "ratatui_ruby/test_helper"
|
|
10
|
+
|
|
11
|
+
# Load the demo
|
|
12
|
+
require_relative "./scroll_text"
|
|
13
|
+
|
|
14
|
+
class TestScrollText < Minitest::Test
|
|
15
|
+
include RatatuiRuby::TestHelper
|
|
16
|
+
|
|
17
|
+
def setup
|
|
18
|
+
@demo = ScrollTextDemo.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_demo_initialization
|
|
22
|
+
assert_instance_of ScrollTextDemo, @demo
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_initial_rendering
|
|
26
|
+
with_test_terminal(60, 10) do
|
|
27
|
+
@demo.render
|
|
28
|
+
content = buffer_content
|
|
29
|
+
|
|
30
|
+
# Should show Line 1 at the top (inside the block border)
|
|
31
|
+
assert content[1].include?("Line 1")
|
|
32
|
+
# Should show title with scroll position 0, 0
|
|
33
|
+
assert content[0].include?("X: 0, Y: 0")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_scroll_down
|
|
38
|
+
inject_event("key", { code: "down" })
|
|
39
|
+
@demo.handle_input
|
|
40
|
+
|
|
41
|
+
with_test_terminal(60, 10) do
|
|
42
|
+
@demo.render
|
|
43
|
+
content = buffer_content
|
|
44
|
+
|
|
45
|
+
# After scrolling down once, Line 2 should be at the top
|
|
46
|
+
assert content[1].include?("Line 2"), "Expected Line 2 at top, got: #{content[1]}"
|
|
47
|
+
# Line 1 should not be visible (scrolled off)
|
|
48
|
+
refute content.any? { |line| line.include?("Line 1") }, "Line 1 should be scrolled off"
|
|
49
|
+
# Should show Y scroll position 1
|
|
50
|
+
assert content[0].include?("Y: 1")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_scroll_right
|
|
55
|
+
inject_event("key", { code: "right" })
|
|
56
|
+
@demo.handle_input
|
|
57
|
+
|
|
58
|
+
with_test_terminal(60, 10) do
|
|
59
|
+
@demo.render
|
|
60
|
+
content = buffer_content
|
|
61
|
+
|
|
62
|
+
# After scrolling right once, first character should be cut off
|
|
63
|
+
# "Line 1:" becomes "ine 1:"
|
|
64
|
+
assert content[1].include?("ine 1:"), "Expected horizontal scroll, got: #{content[1]}"
|
|
65
|
+
# Should show X scroll position 1
|
|
66
|
+
assert content[0].include?("X: 1")
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_scroll_left_at_edge
|
|
71
|
+
# Try to scroll left when already at x=0
|
|
72
|
+
inject_event("key", { code: "left" })
|
|
73
|
+
@demo.handle_input
|
|
74
|
+
|
|
75
|
+
with_test_terminal(60, 10) do
|
|
76
|
+
@demo.render
|
|
77
|
+
content = buffer_content
|
|
78
|
+
|
|
79
|
+
# Should still show full "Line 1:" (can't scroll left past 0)
|
|
80
|
+
assert content[1].include?("Line 1:")
|
|
81
|
+
# Should still show X scroll position 0
|
|
82
|
+
assert content[0].include?("X: 0")
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_scroll_up_at_top
|
|
87
|
+
# Try to scroll up when already at top (should stay at 0)
|
|
88
|
+
inject_event("key", { code: "up" })
|
|
89
|
+
@demo.handle_input
|
|
90
|
+
|
|
91
|
+
with_test_terminal(60, 10) do
|
|
92
|
+
@demo.render
|
|
93
|
+
content = buffer_content
|
|
94
|
+
|
|
95
|
+
# Should still show Line 1 at top (can't scroll above 0)
|
|
96
|
+
assert content[1].include?("Line 1")
|
|
97
|
+
# Should still show Y scroll position 0
|
|
98
|
+
assert content[0].include?("Y: 0")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_scroll_both_axes
|
|
103
|
+
# Scroll down 2 times and right 3 times
|
|
104
|
+
2.times do
|
|
105
|
+
inject_event("key", { code: "down" })
|
|
106
|
+
@demo.handle_input
|
|
107
|
+
end
|
|
108
|
+
3.times do
|
|
109
|
+
inject_event("key", { code: "right" })
|
|
110
|
+
@demo.handle_input
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
with_test_terminal(60, 10) do
|
|
114
|
+
@demo.render
|
|
115
|
+
content = buffer_content
|
|
116
|
+
|
|
117
|
+
# After scrolling down 2 and right 3, Line 3 should be at top with first 3 chars cut
|
|
118
|
+
# "Line 3:" becomes " 3:"
|
|
119
|
+
assert content[1].include?(" 3:"), "Expected Line 3 scrolled right by 3, got: #{content[1]}"
|
|
120
|
+
# Should show scroll position X: 3, Y: 2
|
|
121
|
+
assert content[0].include?("X: 3, Y: 2")
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def test_quit
|
|
126
|
+
inject_event("key", { code: "q" })
|
|
127
|
+
status = @demo.handle_input
|
|
128
|
+
assert_equal :quit, status
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
+
|
|
7
|
+
require "test_helper"
|
|
8
|
+
|
|
9
|
+
# Smoke test to ensure the table_select example can be loaded and instantiated
|
|
10
|
+
class TestTableSelect < Minitest::Test
|
|
11
|
+
def test_table_select_smoke
|
|
12
|
+
# Verify the example can create a table with selection
|
|
13
|
+
rows = [["1", "test", "10%"]]
|
|
14
|
+
widths = [
|
|
15
|
+
RatatuiRuby::Constraint.length(8),
|
|
16
|
+
RatatuiRuby::Constraint.length(15),
|
|
17
|
+
RatatuiRuby::Constraint.length(10)
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
table = RatatuiRuby::Table.new(
|
|
21
|
+
header: ["PID", "Name", "CPU"],
|
|
22
|
+
rows: rows,
|
|
23
|
+
widths: widths,
|
|
24
|
+
selected_row: 0,
|
|
25
|
+
highlight_style: RatatuiRuby::Style.new(fg: :yellow),
|
|
26
|
+
highlight_symbol: "> ",
|
|
27
|
+
block: RatatuiRuby::Block.new(title: "Test", borders: :all)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Verify it can be drawn
|
|
31
|
+
with_test_terminal(40, 10) do
|
|
32
|
+
RatatuiRuby.draw(table)
|
|
33
|
+
# Just verify it doesn't crash
|
|
34
|
+
assert true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -6,3 +6,8 @@ rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]
|
|
|
6
6
|
|
|
7
7
|
[target.x86_64-apple-darwin]
|
|
8
8
|
rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]
|
|
9
|
+
|
|
10
|
+
# Force dynamic linking on Alpine (musl) to fix "Dynamic loading not supported"
|
|
11
|
+
# errors when bindgen tries to load libclang.
|
|
12
|
+
[target.'cfg(target_env = "musl")']
|
|
13
|
+
rustflags = ["-C", "target-feature=-crt-static"]
|