Class: Tuile::FakeScreen

Inherits:
Screen
  • Object
show all
Defined in:
lib/tuile/fake_screen.rb,
sig/tuile.rbs

Overview

Testing only — a screen which doesn't paint anything, so the TTY running the tests is not painted over. It runs no event loop, so Screen#check_locked admits the thread that called Screen.fake: a spec mutating the UI from a spawned thread raises, exactly as an app would.

Intended for unit-testing individual components: instantiate a component, mutate it, and assert against #prints or #invalidated?. It does not run an event loop, so it is not suitable for system-testing whole apps — for that, drive the real script through a PTY (see spec/examples/).

Call Screen.fake to initialize the fake screen easily. Typical usage:

before { Screen.fake }
after  { Screen.close }

it "paints its content" do
label = Component::Label.new.tap { |l| l.text = "hi" }
Screen.instance.content = Component::Window.new("Greeting").tap { |w| w.content = label }
Screen.instance.repaint
assert_includes Screen.instance.prints.join, "hi"
end

Constant Summary collapse

EDITING_KEYS =

Returns:

  • (::Array[String])

Instance Attribute Summary collapse

Attributes inherited from Screen

#background_color, #buffer, #color_depth, #color_scheme, #event_queue, #focused, #locale, #on_error, #on_focus_changed, #pane, #size, #theme, #theme_def

Instance Method Summary collapse

Methods inherited from Screen

#add_popup, #beep, #check_locked, close, #close, #content, #content=, #cursor_position, #cursor_sequence, #cycle_focus, #event_loop, fake, #fire_focus_hooks, #focus_next, #focus_previous, #grabbed, #handle_background_color, #handle_color_scheme, #handle_key?, #handle_mouse, #handle_paste, #has_popup?, #hidden?, #hovered, instance, instance?, #invalidate, #layout, #needs_full_repaint, #popups, #register_global_shortcut, #remove_popup, #repaint, #run_event_loop, #state, #unregister_global_shortcut

Constructor Details

#initializeFakeScreen

Returns a new instance of FakeScreen.



26
27
28
29
30
31
32
33
34
# File 'lib/tuile/fake_screen.rb', line 26

def initialize
  super
  @event_queue = FakeEventQueue.new
  @size = Size.new(160, 50)
  # super sized both to the test runner's TTY.
  @buffer.resize(@size)
  @pane.rect = Rect.new(0, 0, @size.width, @size.height)
  @prints = []
end

Instance Attribute Details

#prints::Array[String] (readonly)

@return — whatever #print / #emit produced so far. Component painting lands in Screen#buffer, not here — assert on Buffer#row_text / Buffer#row_ansi / Buffer#cell for content, and on prints for cursor and housekeeping escapes.

Returns:

  • (::Array[String])


40
41
42
# File 'lib/tuile/fake_screen.rb', line 40

def prints
  @prints
end

Instance Method Details

#background_color=(color) ⇒ void

This method returns an undefined value.

Plays the terminal answering the OSC 11 re-probe, so a spec can exercise app code that derives colors from Screen#background_color:

Screen.instance.background_color = Color.rgb(30, 30, 46)

Takes the same path a real reply does — a changed color fires Component#handle_theme_changed across the tree and invalidates it. There is no such writer on Screen: the value is a report from the terminal, not a setting.

@param color

Parameters:



97
98
99
# File 'lib/tuile/fake_screen.rb', line 97

def background_color=(color)
  handle_background_color(color)
end

#clearvoid

This method returns an undefined value.



43
44
45
# File 'lib/tuile/fake_screen.rb', line 43

def clear
  @prints.clear
end

#click(x, y, button: :left) ⇒ void

This method returns an undefined value.

Plays a whole click at a screen cell — the press, then the release that ends its grab:

screen.click(save_button.rect.left, save_button.rect.top)

Routed exactly as the terminal's own report would be (Mouse::Router), so it focuses, dismisses popups and bubbles.

@param x — 0-based column.

@param y — 0-based row.

@param button:left, :middle or :right.

Parameters:

  • x (Integer)
  • y (Integer)
  • button: (Symbol) (defaults to: :left)


112
113
114
115
# File 'lib/tuile/fake_screen.rb', line 112

def click(x, y, button: :left)
  press(x, y, button: button)
  release(x, y)
end

#coerce_point(point) ⇒ Point

@param point

Parameters:

  • point (Point, [Integer, Integer])

Returns:



174
175
176
177
178
179
180
181
182
183
# File 'lib/tuile/fake_screen.rb', line 174

def coerce_point(point)
  case point
  when Point then point
  when Array
    raise ArgumentError, "expected [x, y], got #{point.inspect}" unless point.size == 2

    Point.new(point[0], point[1])
  else raise ArgumentError, "expected a Point or [x, y], got #{point.inspect}"
  end
end

#detect_backgroundTerminalBackground::Result

No terminal probing in tests: skip TerminalBackground.detect (which would write an OSC 11 query to the test runner's TTY and steal its input) and pin the deterministic default. The color is nil — the case every app must handle anyway — until a spec assigns one through #background_color=.



191
# File 'lib/tuile/fake_screen.rb', line 191

def detect_background = TerminalBackground::Result.new(scheme: :dark, color: nil)

#detect_color_depthSymbol

Pins the depth rather than reading the test runner's environment, so a spec asserting flushed bytes gets the same answer on a truecolor terminal, under TERM=dumb in CI, and inside tmux. A spec exercising degradation builds its own Buffer with the depth it wants.

Returns:

  • (Symbol)


198
# File 'lib/tuile/fake_screen.rb', line 198

def detect_color_depth = :truecolor

#detect_localeLocale

Pins the conventions instead of probing, so a spec asserting a painted date gets the same answer under LC_TIME=en_DK as under LANG=C — and so no example pays for a locale(1) subprocess. A spec exercising detection assigns Screen#locale= or calls Locale.from_keywords with canned answers.

Returns:



206
# File 'lib/tuile/fake_screen.rb', line 206

def detect_locale = Locale::ISO

#drag(*points, button: :left) ⇒ void

This method returns an undefined value.

Plays a whole drag: the press at the first point, one move per point after it, and the release at the last.

screen.drag([2, 1], [3, 2], [4, 3])         # three reports, a diagonal
screen.drag(canvas.rect.top_left, [9, 9])   # the coarsest drag there is

Reports only the points given, as the wire does: at ~84 reports a second (R_mouse_reporting) a quick drag genuinely skips cells, so interpolating them would let a spec assert a continuity no terminal delivers. A one-point drag is a #click — use that.

@param points — two or more positions, as Points or [x, y] pairs.

@param button:left, :middle or :right.

Parameters:

  • points (::Array[(Point | [Integer, Integer])])
  • button: (Symbol) (defaults to: :left)


161
162
163
164
165
166
167
168
# File 'lib/tuile/fake_screen.rb', line 161

def drag(*points, button: :left)
  raise ArgumentError, "a drag needs at least two points, got #{points.size}" if points.size < 2

  path = points.map { coerce_point(_1) }
  press(path.first.x, path.first.y, button: button)
  path.drop(1).each { move(_1.x, _1.y, button: button) }
  release(path.last.x, path.last.y)
end

#emit(str) ⇒ void

This method returns an undefined value.

Captures the assembled repaint frame instead of writing to the test runner's TTY. Lands in #prints so cursor/sync escapes can be asserted; painted content is read from Screen#buffer.

@param str

Parameters:

  • str (String)


59
60
61
# File 'lib/tuile/fake_screen.rb', line 59

def emit(str)
  @prints << str
end

#invalidated?(component) ⇒ Boolean

@param component — the component to check.

Parameters:

Returns:

  • (Boolean)


79
# File 'lib/tuile/fake_screen.rb', line 79

def invalidated?(component) = @invalidated.include?(component)

#invalidated_clearvoid

This method returns an undefined value.



82
83
84
# File 'lib/tuile/fake_screen.rb', line 82

def invalidated_clear
  @invalidated.clear
end

#move(x, y, button: nil) ⇒ void

This method returns an undefined value.

Moves the pointer, firing the enter/exit hooks the new position implies — or, while a press is grabbed, one Component#handle_mouse_drag.

@param x — 0-based column.

@param y — 0-based row.

@param button — the button held while moving, if any.

Parameters:

  • x (Integer)
  • y (Integer)
  • button: (Symbol, nil) (defaults to: nil)


144
# File 'lib/tuile/fake_screen.rb', line 144

def move(x, y, button: nil) = handle_mouse(Mouse::MoveEvent.new(button, x, y))

#paste(text) ⇒ Boolean

Pastes text into the focused component, as a real terminal would with bracketed paste on:

area.focus
Screen.instance.paste("one\r\ntwo")
area.text   # => "one\ntwo" — one mutation, no ENTER anywhere

Goes through Keys.normalize_paste first, so a spec can hand it the CR-flavored line endings terminals actually deliver and still assert against \n.

@param text

@return — true if some component consumed it.

Parameters:

  • text (String)

Returns:

  • (Boolean)


75
# File 'lib/tuile/fake_screen.rb', line 75

def paste(text) = handle_paste(Keys.normalize_paste(text))

#press(x, y, button: :left) ⇒ void

This method returns an undefined value.

Half a #click, for a spec about the grab — what is claimed, what the drag does, what the release lands on.

@param x — 0-based column.

@param y — 0-based row.

@param button:left, :middle or :right.

Parameters:

  • x (Integer)
  • y (Integer)
  • button: (Symbol) (defaults to: :left)


123
# File 'lib/tuile/fake_screen.rb', line 123

def press(x, y, button: :left) = handle_mouse(Mouse::DownEvent.new(button, x, y))

This method returns an undefined value.

Doesn't print anything: collects all strings in #prints.

@param args

Parameters:

  • args (String)


50
51
52
# File 'lib/tuile/fake_screen.rb', line 50

def print(*args)
  @prints += args
end

#release(x, y) ⇒ void

This method returns an undefined value.

The other half of #press.

@param x — 0-based column.

@param y — 0-based row.

Parameters:

  • x (Integer)
  • y (Integer)


129
# File 'lib/tuile/fake_screen.rb', line 129

def release(x, y) = handle_mouse(Mouse::UpEvent.new(x, y))

#scroll(direction, x, y) ⇒ void

This method returns an undefined value.

One wheel notch over a cell.

@param direction:up, :down, :left or :right.

@param x — 0-based column.

@param y — 0-based row.

Parameters:

  • direction (Symbol)
  • x (Integer)
  • y (Integer)


136
# File 'lib/tuile/fake_screen.rb', line 136

def scroll(direction, x, y) = handle_mouse(Mouse::ScrollEvent.new(direction, x, y))