Class: Dill::Widget

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

Defined Under Namespace

Classes: Reload, 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(settings = {}) ⇒ Widget

Returns a new instance of Widget.



215
216
217
# File 'lib/dill/widget.rb', line 215

def initialize(settings = {})
  self.root = settings.fetch(:root)
end

Instance Attribute Details

#rootObject

Returns The root node of the current widget.

Returns:

  • The root node of the current widget



211
212
213
# File 'lib/dill/widget.rb', line 211

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, options = {}) ⇒ 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, options = {})
  new(options.merge(root: 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.

Parameters:

  • selector (String)

    a CSS or XPath query



201
202
203
# File 'lib/dill/widget.rb', line 201

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

.selectorObject

Returns The selector specified with root.

Returns:

  • The selector specified with root.



206
207
208
# File 'lib/dill/widget.rb', line 206

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.

    • 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:



105
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 105

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
    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 = Class.new(type) { root selector }
  child.class_eval(&block) if block_given?

  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 "    def \#{method_name}(*args)\n      if args.size == 1\n        widget(:\#{name}).\#{widget_message} args.first\n      else\n        widget(:\#{name}).\#{widget_message} *args\n      end\n    end\n  RUBY\nend\n"

Instance Method Details

#!=(value) ⇒ Object

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



227
228
229
# File 'lib/dill/widget.rb', line 227

def !=(value)
  checkpoint.wait_until(false) { cast_to(value) != value }
end

#==(value) ⇒ Object

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



221
222
223
# File 'lib/dill/widget.rb', line 221

def ==(value)
  checkpoint.wait_until(false) { cast_to(value) == value }
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.



243
244
245
246
247
# File 'lib/dill/widget.rb', line 243

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

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

#inspectObject



249
250
251
252
253
254
255
256
257
258
# File 'lib/dill/widget.rb', line 249

def inspect
  root = self.root

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

  "<!-- #{self.class.name}: -->\n" <<
   Nokogiri::XML(xml, &:noblanks).to_xhtml
rescue Capybara::NotSupportedByDriverError
  root.inspect
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:



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/dill/widget.rb', line 284

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

  checkpoint(wait_time).wait_until(false, &test)

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

  self
end

#to_fObject



305
306
307
# File 'lib/dill/widget.rb', line 305

def to_f
  to_s.to_f
end

#to_iObject



301
302
303
# File 'lib/dill/widget.rb', line 301

def to_i
  to_s.to_i
end

#to_sObject



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

def to_s
  node_text(root)
end