module RatatuiRuby::TestHelper
Helpers for testing RatatuiRuby applications.
This module provides methods to set up a test terminal, capture buffer content, and inject events, making it easier to write unit tests for your TUI apps.
Usage
require "ratatui_ruby/test_helper" class MyTest < Minitest::Test include RatatuiRuby::TestHelper def test_rendering with_test_terminal(80, 24) do # ... render your app ... assert_includes buffer_content, "Hello World" end end def test_key_handling inject_event(RatatuiRuby::Event::Key.new(code: "q")) result = @app.handle_input assert_equal :quit, result end end
Constants
- MockFrame
-
Mock frame for unit testing views.
Captures widgets passed to
render_widgetfor inspection. Does not render anything—purely captures the output.Examples
frame = MockFrame.new View::Log.new.call(state, tui, frame, area) widget = frame.rendered_widgets.first[:widget] assert_equal "Event Log", widget.block.title
- StubRect
-
Stub area for unit testing views.
Provides the minimal interface views expect (
width,height).Examples
area = StubRect.new(width: 60, height: 20)
Public Instance Methods
Source
# File lib/ratatui_ruby/test_helper.rb, line 243 def assert_cell_style(x, y, **expected_attributes) cell = get_cell(x, y) expected_attributes.each do |key, value| actual_value = cell.public_send(key) if value.nil? assert_nil actual_value, "Expected cell at (#{x}, #{y}) to have #{key}=nil, but got #{actual_value.inspect}" else assert_equal value, actual_value, "Expected cell at (#{x}, #{y}) to have #{key}=#{value.inspect}, but got #{actual_value.inspect}" end end end
Asserts that the cell at the given coordinates has the expected attributes.
assert_cell_style(0, 0, char: "H", fg: :red)
Source
# File lib/ratatui_ruby/test_helper.rb, line 345 def assert_screen_matches(expected, msg = nil) actual_lines = buffer_content if block_given? actual_lines = yield(actual_lines) end if expected.is_a?(String) # Snapshot file mode snapshot_path = expected update_snapshots = ENV["UPDATE_SNAPSHOTS"] == "1" || ENV["UPDATE_SNAPSHOTS"] == "true" if !File.exist?(snapshot_path) || update_snapshots FileUtils.mkdir_p(File.dirname(snapshot_path)) File.write(snapshot_path, "#{actual_lines.join("\n")}\n") if update_snapshots puts "Updated snapshot: #{snapshot_path}" else puts "Created snapshot: #{snapshot_path}" end end expected_lines = File.readlines(snapshot_path, chomp: true) else # Direct comparison mode expected_lines = expected end msg ||= "Screen content mismatch" assert_equal expected_lines.size, actual_lines.size, "#{msg}: Line count mismatch" expected_lines.each_with_index do |expected_line, i| actual_line = actual_lines[i] assert_equal expected_line, actual_line, "#{msg}: Line #{i + 1} mismatch.\nExpected: #{expected_line.inspect}\nActual: #{actual_line.inspect}" end end
Asserts that the current screen content matches the expected content.
Users need to verify that the entire TUI screen looks exactly as expected. Manually checking every cell or line is tedious and error-prone.
This helper compares the current buffer content against an expected string (file path) or array of strings. It supports automatic snapshot creation and updating via the UPDATE_SNAPSHOTS environment variable.
Use it to verify complex UI states, layouts, and renderings.
Usage
# Direct comparison assert_screen_matches(["Line 1", "Line 2"]) # File comparison assert_screen_matches("test/snapshots/login.txt") # With normalization (e.g., masking dynamic data) assert_screen_matches("test/snapshots/dashboard.txt") do |lines| lines.map { |l| l.gsub(/User ID: \d+/, "User ID: XXX") } end
- expected
-
String (file path) or Array<String> (content).
- msg
-
String optional failure message.
Source
# File lib/ratatui_ruby/test_helper.rb, line 309 def assert_snapshot(name, msg = nil, &) # Get the path of the test file calling this method caller_path = caller_locations(1, 1).first.path snapshot_dir = File.join(File.dirname(caller_path), "snapshots") snapshot_path = File.join(snapshot_dir, "#{name}.txt") assert_screen_matches(snapshot_path, msg, &) end
Asserts that the current screen content matches a stored snapshot.
This method simplifies snapshot testing by automatically resolving the snapshot path relative to the test file calling this method. It assumes a “snapshots” directory exists in the same directory as the test file.
# In test/test_login.rb assert_snapshot("login_screen") # Look for: test/snapshots/login_screen.txt # With normalization block assert_snapshot("clock") do |actual| actual.map { |l| l.gsub(/\d{2}:\d{2}/, "XX:XX") } end
- name
-
String name of the snapshot (without extension).
- msg
-
String optional failure message.
Source
# File lib/ratatui_ruby/test_helper.rb, line 78 def buffer_content RatatuiRuby.get_buffer_content.split("\n") end
Returns the current content of the terminal buffer as an array of strings. Each string represents a row in the terminal.
buffer_content # => ["Row 1 text", "Row 2 text", ...]
Source
# File lib/ratatui_ruby/test_helper.rb, line 87 def cursor_position x, y = RatatuiRuby.get_cursor_position { x:, y: } end
Returns the current cursor position as a hash with :x and :y keys.
cursor_position # => { x: 0, y: 0 }
Source
# File lib/ratatui_ruby/test_helper.rb, line 235 def get_cell(x, y) RatatuiRuby.get_cell_at(x, y) end
Returns the cell attributes at the given coordinates.
get_cell(0, 0) # => { "symbol" => "H", "fg" => :red, "bg" => nil }
Source
# File lib/ratatui_ruby/test_helper.rb, line 170 def inject_click(x:, y:, modifiers: []) inject_mouse(x:, y:, kind: :down, modifiers:, button: :left) end
Injects a mouse left click (down) event.
inject_click(x: 10, y: 5)
Source
# File lib/ratatui_ruby/test_helper.rb, line 186 def inject_drag(x:, y:, modifiers: [], button: :left) inject_mouse(x:, y:, kind: :drag, modifiers:, button:) end
Injects a mouse drag event.
inject_drag(x: 10, y: 5)
Source
# File lib/ratatui_ruby/test_helper.rb, line 120 def inject_event(event) unless @_ratatui_test_terminal_active raise "Events must be injected inside a `with_test_terminal` block. " \ "Calling this method outside the block causes a race condition where the event " \ "is flushed before the application starts." end case event when RatatuiRuby::Event::Key RatatuiRuby.inject_test_event("key", { code: event.code, modifiers: event.modifiers }) when RatatuiRuby::Event::Mouse RatatuiRuby.inject_test_event("mouse", { kind: event.kind, button: event.button, x: event.x, y: event.y, modifiers: event.modifiers, }) when RatatuiRuby::Event::Resize RatatuiRuby.inject_test_event("resize", { width: event.width, height: event.height }) when RatatuiRuby::Event::Paste RatatuiRuby.inject_test_event("paste", { content: event.content }) when RatatuiRuby::Event::FocusGained RatatuiRuby.inject_test_event("focus_gained", {}) when RatatuiRuby::Event::FocusLost RatatuiRuby.inject_test_event("focus_lost", {}) else raise ArgumentError, "Unknown event type: #{event.class}" end end
Injects an event into the event queue for testing.
Pass any RatatuiRuby::Event object. The event will be returned by the next call to RatatuiRuby.poll_event.
Raises a RuntimeError if called outside of a with_test_terminal block.
Examples
with_test_terminal do # Key events inject_event(RatatuiRuby::Event::Key.new(code: "q")) inject_event(RatatuiRuby::Event::Key.new(code: "s", modifiers: ["ctrl"])) # Mouse events inject_event(RatatuiRuby::Event::Mouse.new(kind: "down", button: "left", x: 10, y: 5)) # Resize events inject_event(RatatuiRuby::Event::Resize.new(width: 120, height: 40)) # Paste events inject_event(RatatuiRuby::Event::Paste.new(content: "Hello")) # Focus events inject_event(RatatuiRuby::Event::FocusGained.new) inject_event(RatatuiRuby::Event::FocusLost.new) end
Source
# File lib/ratatui_ruby/test_helper.rb, line 208 def inject_keys(*args) args.each do |arg| event = case arg when String RatatuiRuby::Event::Key.new(code: arg) when Symbol parts = arg.to_s.split("_") code = parts.pop modifiers = parts RatatuiRuby::Event::Key.new(code:, modifiers:) when Hash RatatuiRuby::Event::Key.new(**arg) when RatatuiRuby::Event::Key arg else raise ArgumentError, "Invalid key argument: #{arg.inspect}. Expected String, Symbol, Hash, or Key event." end inject_event(event) end end
Injects multiple Key events into the queue.
Supports multiple formats for convenience:
-
String: Converted to a Key event with that code.
-
Symbol: Parsed as modifier_code (e.g.,
:ctrl_c,:enter). -
Hash: Passed to Key.new constructor.
-
Key: Passed directly.
Examples
with_test_terminal do inject_keys("a", "b", "c") inject_keys(:enter, :esc) inject_keys(:ctrl_c, :alt_shift_left) inject_keys("j", { code: "k", modifiers: ["ctrl"] }) end
Source
# File lib/ratatui_ruby/test_helper.rb, line 155 def inject_mouse(x:, y:, kind: :down, modifiers: [], button: :left) event = RatatuiRuby::Event::Mouse.new( kind: kind.to_s, x:, y:, button: button.to_s, modifiers: ) inject_event(event) end
Injects a mouse event.
inject_mouse(x: 10, y: 5, kind: :down, button: :left)
Source
# File lib/ratatui_ruby/test_helper.rb, line 178 def inject_right_click(x:, y:, modifiers: []) inject_mouse(x:, y:, kind: :down, modifiers:, button: :right) end
Injects a mouse right click (down) event.
inject_right_click(x: 10, y: 5)
Source
# File lib/ratatui_ruby/test_helper.rb, line 48 def with_test_terminal(width = 80, height = 24, **opts) RatatuiRuby.init_test_terminal(width, height) # Flush any lingering events from previous tests while (event = RatatuiRuby.poll_event) && !event.none?; end RatatuiRuby.stub :init_terminal, nil do RatatuiRuby.stub :restore_terminal, nil do @_ratatui_test_terminal_active = true timeout = opts.fetch(:timeout, 2) if timeout Timeout.timeout(timeout) do yield end else yield end ensure @_ratatui_test_terminal_active = false end end ensure RatatuiRuby.restore_terminal end
Initializes a test terminal context with specified dimensions. Restores the original terminal state after the block executes.
width-
width of the test terminal (default: 80)
height-
height of the test terminal (default: 24)
timeout-
maximum execution time in seconds (default: 2). Pass nil to disable.
If a block is given, it is executed within the test terminal context.