Module: Tuile::Testing
- Defined in:
- lib/tuile/testing.rb,
sig/tuile.rbs
Overview
Finds a component in the tree, so a spec can drive the UI it built four layers down:
Testing.get(Component::Button, caption: "Save").handle_key?(Keys::ENTER)
Testing.get(id: :name).value = "Zaphod"
Testing.find(Component::Checkbox, in: pane, count: 3)
Testing.get demands exactly one match and raises with a Testing.dump of the tree it
searched; Testing.find returns every match and takes an optional count:. Both
search Screen's whole tree by default — popups included, since they live
under the same ScreenPane as the content — or the subtree given as in:.
Call these qualified, as above: find and get collide with names a
spec suite is likely to have already (Capybara's find), so there is no
Component#get and mixing this module in is not recommended. Tuile's own
specs sit inside module Tuile and so need no include.
A hidden component is never found: these simulate a user, and a spec that drove a hidden Component::Button would pass against a form nobody can operate. Both walk Component#walk_shown_tree; a failed lookup says how many hidden components would have matched, and Testing.dump shows them.
Testing.find(Component::TextField, count: 0) # the user can't reach it
refute field.visible? # it is hidden
There is deliberately no visible: filter handing one back to drive
(D_visibility).
For what a component shows, assert on Screen#buffer instead — this
locates and drives, it does not replace that channel. See book ch8 for the
worked usage and design/decisions.md D_component_lookup for the design.
Defined Under Namespace
Classes: LookupError
Class Method Summary collapse
-
.dump(scope, marked = [], excluded = []) ⇒ String
The searched tree, one component per row, indented by depth and with the
Tuile::namespaces stripped so a fifty-row dump stays readable (an app's own component classes keep their full name):. -
.find(klass = Component, in: nil, id: nil, caption: nil, count: nil, &predicate) ⇒ Object
Every component in the searched tree matching the spec, in pre-order.
-
.get(klass = Component, in: nil, id: nil, caption: nil, &predicate) ⇒ Object
The one component matching the spec — Testing.find with
count: 1, so it raises rather than returning nil, and raises on an ambiguous spec too.
Class Method Details
.dump(scope, marked = [], excluded = []) ⇒ String
The searched tree, one component per row, indented by depth and with
the Tuile:: namespaces stripped so a fifty-row dump stays readable
(an app's own component classes keep their full name):
#<ScreenPane rect=(0,0 160x50)>
#<Window rect=(0,0 40x10) caption="Settings">
#<Layout::Vertical rect=(1,1 38x8)>
→ #<Button id=:save rect=(1,1 38x1) caption="Save">
@param scope — root of the tree to dump.
@param marked — components to flag with a leading arrow — the matches, when a lookup found the wrong number of them.
@param excluded — components to flag with ⊘ — ones that matched the spec but were skipped for being hidden.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/tuile/testing.rb', line 110 def dump(scope, marked = [], excluded = []) base = scope.depth rows = [] # walk_tree, not walk_shown_tree: a reader looks here to find out where # their component went, so the ones the search skipped are the point. scope.walk_tree do |c| mark = if marked.any? { _1.equal?(c) } then "→" elsif excluded.any? { _1.equal?(c) } then "⊘" else " " end row = c.inspect.sub("#<Tuile::Component::", "#<").sub("#<Tuile::", "#<") rows << "#{mark} #{" " * (c.depth - base)}#{row}" end rows.join("\n") end |
.find(klass = Component, in: nil, id: nil, caption: nil, count: nil, &predicate) ⇒ Object
Every component in the searched tree matching the spec, in pre-order.
find(Component::Button) # every button on screen
find(Component::HasBadInput, in: form) # a mixin works too
find(Component::Label, caption: /^Total/) # Regexp: partial match
find(Component::Popup, count: 1..) # assert at least one
@param klass — matched with is_a?, so a mixin (Component::HasValue) finds every field that includes it.
@param in — root of the subtree to search, itself included. Defaults to Screen.instance.pane — the whole UI.
@param id — matched against Component#id.
@param caption — matched with === against Component::HasCaption#caption.to_s, so a String is exact and a Regexp is a partial match. Never matches a component without a caption.
@param count — how many matches are expected; any number when nil.
65 66 67 68 69 70 71 72 73 74 |
# File 'lib/tuile/testing.rb', line 65 def find(klass = Component, in: nil, id: nil, caption: nil, count: nil, &predicate) # `in` is a Ruby keyword, so the local it binds is unreachable by name. scope = binding.local_variable_get(:in) || Screen.instance.pane spec = ->(c) { matches_spec?(c, klass, id, caption, predicate) } matches = [] scope.walk_shown_tree { |c| matches << c if spec.call(c) } return matches if count.nil? || spec_match?(count, matches.size) raise LookupError, failure(klass, id, caption, predicate, count, matches, scope, spec) end |
.get(klass = Component, in: nil, id: nil, caption: nil, &predicate) ⇒ Object
90 91 92 93 |
# File 'lib/tuile/testing.rb', line 90 def get(klass = Component, in: nil, id: nil, caption: nil, &predicate) scope = binding.local_variable_get(:in) find(klass, in: scope, id:, caption:, count: 1, &predicate).first end |