Module: Charming::TestHelper

Defined in:
lib/charming/test_helper.rb

Overview

TestHelper provides controller- and component-level test ergonomics for Charming apps, in the spirit of Rails' ActionController::TestCase:

require "charming/test_helper"

RSpec.describe HomeController do
include Charming::TestHelper

let(:ctrl) { build_controller(HomeController) }

it "renders a greeting" do
  expect(ctrl.dispatch(:show)).to render_text("Welcome")
end

it "quits on q" do
  expect(press(ctrl_class: HomeController, key: "q")).to be_quit
end
end

Helpers:

  • build_controller(klass, app:, screen:, route:) — controller instance wired to an app
  • key_event("ctrl+p") — build a KeyEvent from a human-readable string
  • press(controller_or_class, "down") — dispatch a key press, returns the Response
  • press_sequence(klass, ["down", "down", "enter"], app:) — dispatch several presses

RSpec matchers (when RSpec is loaded):

  • expect(response).to render_text("...") / render_match(/.../)
  • expect(response).to be_quit / be_navigate (predicate matchers on Response)
  • expect(response).to navigate_to("/path")

Instance Method Summary collapse

Instance Method Details

#build_controller(controller_class, app: nil, screen: nil, route: nil, event: nil) ⇒ Object

Builds a controller instance with sensible test defaults: a fresh Application, an 80x24 screen, and no event.



38
39
40
41
42
# File 'lib/charming/test_helper.rb', line 38

def build_controller(controller_class, app: nil, screen: nil, route: nil, event: nil)
  app ||= Charming::Application.new
  screen ||= Charming::Screen.new(width: 80, height: 24)
  controller_class.new(application: app, event: event, screen: screen, route: route)
end

#key_event(description) ⇒ Object

Builds a KeyEvent from a human-readable string like "q", "down", "ctrl+p", or "shift+tab". Modifier order is irrelevant.



46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/charming/test_helper.rb', line 46

def key_event(description)
  parts = description.to_s.split("+")
  key = parts.pop
  modifiers = parts.map(&:downcase)
  char = (key.length == 1) ? key : nil
  Charming::Events::KeyEvent.new(
    key: key.to_sym,
    char: char,
    ctrl: modifiers.include?("ctrl") || modifiers.include?("control"),
    alt: modifiers.include?("alt"),
    shift: modifiers.include?("shift")
  )
end

#memory_backend(*keys, width: 80, height: 24) ⇒ Object

Builds a MemoryBackend pre-seeded with KeyEvents parsed from keys, ready to be passed to Charming::Runtime for integration-style tests.



76
77
78
79
# File 'lib/charming/test_helper.rb', line 76

def memory_backend(*keys, width: 80, height: 24)
  events = keys.map { |key| key.is_a?(String) ? key_event(key) : key }
  Charming::Internal::Terminal::MemoryBackend.new(events: events, width: width, height: height)
end

#press(controller_class, key, app:, screen: nil, route: nil) ⇒ Object

Dispatches a single key press against controller_class and returns the Response. Pass app: to share session state across presses.



62
63
64
65
# File 'lib/charming/test_helper.rb', line 62

def press(controller_class, key, app:, screen: nil, route: nil)
  controller = build_controller(controller_class, app: app, screen: screen, route: route, event: key_event(key))
  controller.dispatch_key
end

#press_sequence(controller_class, keys, app:, screen: nil, route: nil) ⇒ Object

Dispatches each key in keys in order against fresh controller instances sharing app's session (mirroring the runtime's controller-per-event model). Returns the last Response.



70
71
72
# File 'lib/charming/test_helper.rb', line 70

def press_sequence(controller_class, keys, app:, screen: nil, route: nil)
  keys.map { |key| press(controller_class, key, app: app, screen: screen, route: route) }.last
end