Class: Dill::Widget

Inherits:
Object
  • Object
show all
Extended by:
Dill::Widgets::DSL, Forwardable
Includes:
CucumberMethods, Dill::WidgetParts::Container, Dill::WidgetParts::Struct
Defined in:
lib/dill/widgets/widget.rb,
lib/dill/widgets/widget/node_filter.rb

Direct Known Subclasses

Field, FieldGroup, List, ListItem, Table

Defined Under Namespace

Classes: MissingSelector, NodeFilter, Removed

Constant Summary

Constants included from Dill

VERSION

Instance Attribute Summary collapse

Widget macros collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Dill::Widgets::DSL

form, list, widget

Methods included from Dill::WidgetParts::Container

#has_widget?, #not_visible?, #visible?, #widget, #widgets

Methods included from Dill

#deprecate

Methods included from Constructors

#Decimal, #Integer, #Widget

Methods included from Dill::WidgetParts::Struct

included

Constructor Details

#initialize(root) ⇒ Widget

Returns a new instance of Widget.



197
198
199
# File 'lib/dill/widgets/widget.rb', line 197

def initialize(root)
  @root = root
end

Instance Attribute Details

#rootObject (readonly)

Returns the value of attribute root.



12
13
14
# File 'lib/dill/widgets/widget.rb', line 12

def root
  @root
end

Class Method Details

.action(name, selector = nil) ⇒ 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 (defaults to: nil)

    the selector for the widget that will be clicked



49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/dill/widgets/widget.rb', line 49

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

            widget wname, selector

            -> { widget(wname).click; self }
          else
            -> { click; self }
          end

  define_method name, &block
end

.filterObject



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

def self.filter
  @filter || superclass.filter
rescue NoMethodError
  raise MissingSelector, 'no selector defined'
end

.filter?Boolean

Returns:

  • (Boolean)


189
190
191
# File 'lib/dill/widgets/widget.rb', line 189

def self.filter?
  filter rescue false
end

.find_all_in(parent, *args) ⇒ Object



105
106
107
# File 'lib/dill/widgets/widget.rb', line 105

def self.find_all_in(parent, *args)
  filter.nodes(parent, *args).map { |e| new(e) }
end

.find_in(parent, *args) ⇒ 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



95
96
97
98
99
100
101
102
103
# File 'lib/dill/widgets/widget.rb', line 95

def self.find_in(parent, *args)
  new(filter.node(parent, *args))
rescue Capybara::Ambiguous => e
  raise AmbiguousWidget.new(e.message).
    tap { |x| x.set_backtrace e.backtrace }
rescue Capybara::ElementNotFound => e
  raise MissingWidget.new(e.message).
    tap { |x| x.set_backtrace e.backtrace }
end

.not_present_in?(parent, *args) ⇒ Boolean

Returns:

  • (Boolean)


119
120
121
# File 'lib/dill/widgets/widget.rb', line 119

def self.not_present_in?(parent, *args)
  filter.nodeless?(parent, *args)
end

.present_in?(parent, *args) ⇒ 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.



115
116
117
# File 'lib/dill/widgets/widget.rb', line 115

def self.present_in?(parent, *args)
  filter.node?(parent, *args)
end

.root(*selector, &block) ⇒ 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.visible?(: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


176
177
178
# File 'lib/dill/widgets/widget.rb', line 176

def self.root(*selector, &block)
  @filter = NodeFilter.new(block || selector)
end

.selectorObject



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

def self.selector
  filter.selector
end

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

Creates a delegator for one child widget message.

Since widgets are accessed through Dill::WidgetParts::Container#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.



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/dill/widgets/widget.rb', line 72

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

#class?(name) ⇒ Boolean

Determines if the widget has a specific class

Parameters:

  • name

    the name of the class

Returns:

  • (Boolean)

    true if the class is found, false otherwise



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

def class?(name)
  classes.include?(name)
end

#classesObject



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

def classes
  root['class'].split
end

#click(*args) ⇒ 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


228
229
230
231
232
233
234
# File 'lib/dill/widgets/widget.rb', line 228

def click(*args)
  if args.empty?
    root.click
  else
    widget(*args).click
  end
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.



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

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

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

#hover(*args) ⇒ Object

Hovers over 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 hover with no arguments to trigger a hover event on #container.

widget(:container).hover

This is the equivalent of doing the following using Capybara:

find('#container').hover

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

widget(:container).hover :link

This is the equivalent of doing the following using Capybara:

find('#container a').hover


263
264
265
266
267
268
269
# File 'lib/dill/widgets/widget.rb', line 263

def hover(*args)
  if args.empty?
    root.hover
  else
    widget(*args).hover
  end
end

#htmlObject



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

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

  Nokogiri::XML(xml, &:noblanks).to_xhtml.gsub("\n", "")
end

#idObject



285
286
287
# File 'lib/dill/widgets/widget.rb', line 285

def id
  root['id']
end

#textObject



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

def text
  StringValue.new(root.text.strip)
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.



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

def to_cell
  text
end

#to_sObject



324
325
326
# File 'lib/dill/widgets/widget.rb', line 324

def to_s
  text
end

#valueObject



328
329
330
# File 'lib/dill/widgets/widget.rb', line 328

def value
  text
end