Class: Dill::Widget

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
WidgetContainer
Defined in:
lib/dill/widget.rb

Defined Under Namespace

Classes: Removed

Instance Attribute Summary collapse

Widget macros collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from WidgetContainer

#has_widget?, #widget

Constructor Details

#initialize(root) ⇒ Widget

Returns a new instance of Widget.



263
264
265
# File 'lib/dill/widget.rb', line 263

def initialize(root)
  self.root = root
end

Instance Attribute Details

#rootObject

Returns the root node (a Capybara::Node::Element) of the current widget.



261
262
263
# File 'lib/dill/widget.rb', line 261

def root
  @root
end

Class Method Details

.action(name, selector) ⇒ Object

Defines a new action.

This is a shortcut to help defining a widget and a method that clicks on that widget. You can then send a widget instance the message given by name.

You can access the underlying widget by appending “_widget” to the action name.

Examples:

# Consider the widget will encapsulate the following HTML
#
# <div id="profile">
#  <a href="/profiles/1/edit" rel="edit">Edit</a>
# </div>
class PirateProfile < Dill::Widget
  root "#profile"

  # Declare the action
  action :edit, '[rel = edit]'
end

pirate_profile = widget(:pirate_profile)

# Access the action widget
action_widget = pirate_profile.widget(:edit_widget)
action_widget = pirate_profile.edit_widget

# Click the link
pirate_profile.edit

Parameters:

  • name

    the name of the action

  • selector

    the selector for the widget that will be clicked



44
45
46
47
48
49
50
51
52
53
54
# File 'lib/dill/widget.rb', line 44

def self.action(name, selector)
  wname = :"#{name}_widget"

  widget wname, selector

  define_method name do
    widget(wname).click

    self
  end
end

.find_in(node) ⇒ Object

Finds a single instance of the current widget in node.

Parameters:

  • node

    the node we want to search in

Returns:

  • a new instance of the current widget class.

Raises:

  • (Capybara::ElementNotFoundError)

    if the widget can’t be found



184
185
186
# File 'lib/dill/widget.rb', line 184

def self.find_in(node)
  new(node.find(*selector))
end

.present_in?(parent_node) ⇒ Boolean

Determines if an instance of this widget class exists in parent_node.

Parameters:

  • parent_node (Capybara::Node)

    the node we want to search in

Returns:

  • (Boolean)

    true if a widget instance is found, false otherwise.



194
195
196
# File 'lib/dill/widget.rb', line 194

def self.present_in?(parent_node)
  parent_node.has_selector?(*selector)
end

.root(*selector) ⇒ Object

Sets this widget’s default selector.

You can pass more than one argument to it, or a single Array. Any valid Capybara selector accepted by Capybara::Node::Finders#find will work.

Examples

Most of the time, your selectors will be Strings:

class MyWidget < Dill::Widget
  root '.selector'
end

This will match any element with a class of “selector”. For example:

Pick me!

Composite selectors

If you’re using CSS as the query language, it’s useful to be able to use text: ‘Some text’ to zero in on a specific node:

class MySpecificWidget < Dill::Widget
  root '.selector', text: 'Pick me!'
end

This is especially useful, e.g., when you want to create a widget to match a specific error or notification:

class NoFreeSpace < Dill::Widget
  root '.error', text: 'No free space left!'
end

So, given the following HTML:

<body>
  <div class="error">No free space left!</div>

  <!-- ... -->
</body>

You can test for the error’s present using the following code:

document.has_widget?(:no_free_space) #=> true

Note: When you want to match text, consider using I18n.t instead of hard-coding the text, so that your tests don’t break when the text changes.

Finally, you may want to override the query language:

class MyWidgetUsesXPath < Dill::Widget
  root :xpath, '//some/node'
end


251
252
253
# File 'lib/dill/widget.rb', line 251

def self.root(*selector)
  @selector = selector.flatten
end

.selectorObject

Returns the selector specified with root.



256
257
258
# File 'lib/dill/widget.rb', line 256

def self.selector
  @selector
end

.widget(name, selector, type = Widget) ⇒ Object .widget(name, type) ⇒ Object

Declares a new child widget.

Child widgets are accessible inside the container widget using the Dill::WidgetContainer#widget message, or by sending a message name. They are automatically scoped to the parent widget’s root node.

Examples:

Defining a widget

# Given the following HTML:
#
# <div id="root">
#   <span id="child">Child</span>
# </div>
class Container < Dill::Widget
  root '#root'

  widget :my_widget, '#child'
end

container = widget(:container)

# accessing using #widget
my_widget = container.widget(:my_widget)

# accessing using #my_widget
my_widget = container.my_widget

Overloads:

  • .widget(name, selector, type = Widget) ⇒ Object

    The most common form, it allows you to pass in a selector as well as a type for the child widget. The selector will override type‘s root selector, if type has one defined.

    Parameters:

    • name

      the child widget’s name.

    • selector

      the child widget’s selector. You can pass either a String or, if you want to use a composite selector, an Array.

    • type (defaults to: Widget)

      the child widget’s parent class.

  • .widget(name, type) ⇒ Object

    This form allows you to omit selector from the arguments. It will reuse type‘s root selector.

    Parameters:

    • name

      the child widget’s name.

    • type

      the child widget’s parent class.

    Raises:

    • ArgumentError if type has no root selector defined.

Yields:

  • A block allowing you to further customize the widget behavior.

Raises:

  • (ArgumentError)

See Also:



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/dill/widget.rb', line 106

def self.widget(name, *rest, &block)
  raise ArgumentError, "`#{name}' is a reserved name" \
    if WidgetContainer.instance_methods.include?(name.to_sym)

  case rest.first
  when Class
    arg_count = rest.size + 1
    raise ArgumentError, "wrong number of arguments (#{arg_count} for 2)" \
      unless arg_count == 2

    type = rest.first
    raise TypeError, "can't convert `#{type}' to Widget" \
      unless type.methods.include?(:selector)
    raise ArgumentError, "missing root selector for `#{type}'" \
      unless type.selector

    selector = type.selector
  when String, Array
    arg_count = rest.size + 1

    case arg_count
    when 0, 1
      raise ArgumentError, "wrong number of arguments (#{arg_count} for 2)"
    when 2
      selector, type = [*rest, Widget]
    when 3
      selector, type = rest

      raise TypeError, "can't convert `#{type}' to Widget" \
        unless Class === type
    else
      raise ArgumentError, "wrong number of arguments (#{arg_count} for 3)"
    end
  else
    raise ArgumentError, "unknown method signature: #{rest.inspect}"
  end

  child = WidgetClass.new(selector, type, &block)

  const_set(Dill::WidgetName.new(name).to_sym, child)

  define_method name do
    widget(name)
  end
end

.widget_delegator(name, widget_message, method_name = nil) ⇒ Object

Creates a delegator for one child widget message.

Since widgets are accessed through Dill::WidgetContainer#widget, we can’t use Forwardable to delegate messages to widgets.

Parameters:

  • name

    the name of the receiver child widget

  • widget_message

    the name of the message to be sent to the child widget

  • method_name (defaults to: nil)

    the name of the delegator. If nil the method will have the same name as the message it will send.



161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/dill/widget.rb', line 161

def self.widget_delegator(name, widget_message, method_name = nil)
  method_name = method_name || widget_message

  class_eval <<-RUBY
    def #{method_name}(*args)
      if args.size == 1
        widget(:#{name}).#{widget_message} args.first
      else
        widget(:#{name}).#{widget_message} *args
      end
    end
  RUBY
end

Instance Method Details

#!=(value) ⇒ Object

Compares the current widget with value, waiting for the comparison to return false.



319
320
321
# File 'lib/dill/widget.rb', line 319

def !=(value)
  test { cast_to_type_of(value) != value }
end

#!~(regexp) ⇒ Object

Calls !~ on this widget’s text content.



313
314
315
# File 'lib/dill/widget.rb', line 313

def !~(regexp)
  test { to_s !~ regexp }
end

#<(value) ⇒ Object

Returns true if this widget’s representation is less than value.

Waits for the result to be true for the time defined in ‘Capybara.default_wait_time`.



271
272
273
# File 'lib/dill/widget.rb', line 271

def <(value)
  test { cast_to_type_of(value) < value }
end

#<=(value) ⇒ Object

Returns true if this widget’s representation is less than or equal to value.

Waits for the result to be true for the time defined in ‘Capybara.default_wait_time`.



280
281
282
# File 'lib/dill/widget.rb', line 280

def <=(value)
  test { cast_to_type_of(value) <= value }
end

#==(value) ⇒ Object

Compares the current widget with value, waiting for the comparison to return true.



303
304
305
# File 'lib/dill/widget.rb', line 303

def ==(value)
  test { cast_to_type_of(value) == value }
end

#=~(regexp) ⇒ Object

Calls =~ on this widget’s text content.



308
309
310
# File 'lib/dill/widget.rb', line 308

def =~(regexp)
  test { to_s =~ regexp }
end

#>(value) ⇒ Object

Returns true if this widget’s representation is greater than value.

Waits for the result to be true for the time defined in ‘Capybara.default_wait_time`.



288
289
290
# File 'lib/dill/widget.rb', line 288

def >(value)
  test { cast_to_type_of(value) > value }
end

#>=(value) ⇒ Object

Returns true if this widget’s representation is greater than or equal to value.

Waits for the result to be true for the time defined in ‘Capybara.default_wait_time`.



297
298
299
# File 'lib/dill/widget.rb', line 297

def >=(value)
  test { cast_to_type_of(value) >= value }
end

#click(name = nil) ⇒ Object

Clicks the current widget, or the child widget given by name.

Usage

Given the following widget definition:

class Container < Dill::Widget
  root '#container'

  widget :link, 'a'
end

Send click with no arguments to trigger a click event on #container.

widget(:container).click

This is the equivalent of doing the following using Capybara:

find('#container').click

Send click :link to trigger a click event on a:

widget(:container).click :link

This is the equivalent of doing the following using Capybara:

find('#container a').click


350
351
352
353
354
355
356
# File 'lib/dill/widget.rb', line 350

def click(name = nil)
  if name
    widget(name).click
  else
    root.click
  end
end

#diff(diffable, wait_time = Capybara.default_wait_time) ⇒ Object

Compares this widget with the given diffable, as long as it responds to the same protocol as Cucumber::Ast::Table#diff!.

Waits wait_time seconds for the comparison to be successful, otherwise raises Cucumber::Ast::Table::Different on failure.

This is especially useful when you’re not sure if the widget is in the proper state to be compared with the table.

Example

Then(/^some step that takes in a cucumber table$/) do |table|
  widget(:my_widget).diff table
end


372
373
374
375
376
377
378
379
380
381
# File 'lib/dill/widget.rb', line 372

def diff(diffable, wait_time = Capybara.default_wait_time)
  # #diff! raises an exception if the comparison fails, or returns nil if it
  # doesn't. We don't need to worry about failure, because that will be
  # propagated, but we need to return +true+ when it succeeds, to end the
  # comparison.
  #
  # We use WidgetCheckpoint instead of #test because we want the
  # succeed-or-raise behavior.
  WidgetCheckpoint.wait_for(wait_time) { diffable.diff!(to_table) || true }
end

#has_action?(name) ⇒ Boolean

Determines if the widget underlying an action exists.

Parameters:

  • name

    the name of the action

Returns:

  • (Boolean)

    true if the action widget is found, false otherwise.

Raises:

  • Missing if an action with name can’t be found.



391
392
393
394
395
# File 'lib/dill/widget.rb', line 391

def has_action?(name)
  raise Missing, "couldn't find `#{name}' action" unless respond_to?(name)

  has_widget?(:"#{name}_widget")
end

#inspectObject



397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/dill/widget.rb', line 397

def inspect
  inspection = "<!-- #{self.class.name}: -->\n"
  root       = self.root

  begin
    xml = Nokogiri::HTML(page.body).at(root.path).to_xml

    inspection << Nokogiri::XML(xml, &:noblanks).to_xhtml
  rescue Capybara::NotSupportedByDriverError
    inspection << "<#{root.tag_name}>\n#{to_s}"
  end
end

#match(pattern, position = 0) {|the| ... } ⇒ MatchData

Calls match on this widget’s text content.

If a block is given, passes the resulting match data to the block.

Parameters:

  • pattern

    the pattern to match

  • position (defaults to: 0)

    where to begin the search

Yield Parameters:

  • the (MatchData)

    match data from running match on the text.

Returns:

  • (MatchData)

    the match data from running match on the text.



459
460
461
# File 'lib/dill/widget.rb', line 459

def match(pattern, position = 0, &block)
  test { to_s.match(pattern, position, &block) }
end

#reload(wait_time = Capybara.default_wait_time) { ... } ⇒ Object

Reloads the widget, waiting for its contents to change (by default), or until wait_time expires.

Call this method to make sure a widget has enough time to update itself.

You can pass a block to this method to control what it means for the widget to be reloaded.

*Note: does not account for multiple changes to the widget yet.*

Parameters:

  • wait_time (Numeric) (defaults to: Capybara.default_wait_time)

    how long we should wait for changes, in seconds.

Yields:

  • A block that determines what it means for a widget to be reloaded.

Yield Returns:

  • (Boolean)

    true if the widget is considered to be reloaded, false otherwise.

Returns:

  • the current widget

See Also:



432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# File 'lib/dill/widget.rb', line 432

def reload(wait_time = Capybara.default_wait_time, &condition)
  unless test
    old_root = root
    test     = ->{ old_root != root }
  end

  test wait_time, &condition

  begin
    root.inspect
  rescue
    raise Removed, "widget was removed"
  end

  self
end

#textObject Also known as: to_s



463
464
465
# File 'lib/dill/widget.rb', line 463

def text
  NodeText.new(root)
end

#to_cellObject

Converts this widget into a string representation suitable to be displayed in a Cucumber table cell. By default calls #text.

This method will be called by methods that build tables or rows (usually #to_table or #to_row) so, in general, you won’t call it directly, but feel free to override it when needed.

Returns a String.



475
476
477
# File 'lib/dill/widget.rb', line 475

def to_cell
  to_s
end

#to_fObject



483
484
485
# File 'lib/dill/widget.rb', line 483

def to_f
  to_s.to_f
end

#to_iObject



479
480
481
# File 'lib/dill/widget.rb', line 479

def to_i
  to_s.to_i
end