Class: Tuile::FakeScreen
- 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 =
Instance Attribute Summary collapse
- #prints ⇒ ::Array[String] readonly
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
-
#background_color=(color) ⇒ void
Plays the terminal answering the OSC 11 re-probe, so a spec can exercise app code that derives colors from Screen#background_color:.
- #clear ⇒ void
-
#click(x, y, button: :left) ⇒ void
Plays a whole click at a screen cell — the press, then the release that ends its grab:.
-
#coerce_point(point) ⇒ Point
@param
point. -
#detect_background ⇒ TerminalBackground::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.
-
#detect_color_depth ⇒ Symbol
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=dumbin CI, and inside tmux. -
#detect_locale ⇒ Locale
Pins the conventions instead of probing, so a spec asserting a painted date gets the same answer under
LC_TIME=en_DKas underLANG=C— and so no example pays for alocale(1)subprocess. -
#drag(*points, button: :left) ⇒ void
Plays a whole drag: the press at the first point, one move per point after it, and the release at the last.
-
#emit(str) ⇒ void
Captures the assembled repaint frame instead of writing to the test runner's TTY.
-
#initialize ⇒ FakeScreen
constructor
A new instance of FakeScreen.
-
#invalidated?(component) ⇒ Boolean
@param
component— the component to check. - #invalidated_clear ⇒ void
-
#move(x, y, button: nil) ⇒ void
Moves the pointer, firing the enter/exit hooks the new position implies — or, while a press is grabbed, one Component#handle_mouse_drag.
-
#paste(text) ⇒ Boolean
Pastes
textinto the focused component, as a real terminal would with bracketed paste on:. -
#press(x, y, button: :left) ⇒ void
Half a #click, for a spec about the grab — what is claimed, what the drag does, what the release lands on.
-
#print(*args) ⇒ void
Doesn't print anything: collects all strings in #prints.
-
#release(x, y) ⇒ void
The other half of #press.
-
#scroll(direction, x, y) ⇒ void
One wheel notch over a cell.
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
#initialize ⇒ FakeScreen
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.
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
97 98 99 |
# File 'lib/tuile/fake_screen.rb', line 97 def background_color=(color) handle_background_color(color) end |
#clear ⇒ void
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.
112 113 114 115 |
# File 'lib/tuile/fake_screen.rb', line 112 def click(x, y, button: :left) press(x, y, button: ) release(x, y) end |
#coerce_point(point) ⇒ Point
@param point
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_background ⇒ TerminalBackground::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_depth ⇒ Symbol
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.
198 |
# File 'lib/tuile/fake_screen.rb', line 198 def detect_color_depth = :truecolor |
#detect_locale ⇒ Locale
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.
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.
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: ) path.drop(1).each { move(_1.x, _1.y, 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
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.
79 |
# File 'lib/tuile/fake_screen.rb', line 79 def invalidated?(component) = @invalidated.include?(component) |
#invalidated_clear ⇒ void
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.
144 |
# File 'lib/tuile/fake_screen.rb', line 144 def move(x, y, button: nil) = handle_mouse(Mouse::MoveEvent.new(, 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.
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.
123 |
# File 'lib/tuile/fake_screen.rb', line 123 def press(x, y, button: :left) = handle_mouse(Mouse::DownEvent.new(, x, y)) |
#print(*args) ⇒ void
This method returns an undefined value.
Doesn't print anything: collects all strings in #prints.
@param args
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.
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.
136 |
# File 'lib/tuile/fake_screen.rb', line 136 def scroll(direction, x, y) = handle_mouse(Mouse::ScrollEvent.new(direction, x, y)) |