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