Class: Dill::Widget

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

Direct Known Subclasses

AutoTable::Row, BaseTable, Field, FieldGroup, List, ListItem

Defined Under Namespace

Classes: MissingSelector, NodeFilter, Removed

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_no_widget?, #has_widget?, #widget, #widgets

Methods included from Dill::WidgetParts::Struct

included

Constructor Details

#initialize(node) ⇒ Widget

Returns a new instance of Widget.



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

def initialize(node)
  self.query = node.respond_to?(:call) ? node : -> { node }
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



46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/dill/widgets/widget.rb', line 46

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



170
171
172
173
174
# File 'lib/dill/widgets/widget.rb', line 170

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

.filter?Boolean

Returns:

  • (Boolean)


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

def self.filter?
  filter rescue false
end

.find_all_in(parent, *args) ⇒ Object



96
97
98
# File 'lib/dill/widgets/widget.rb', line 96

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



92
93
94
# File 'lib/dill/widgets/widget.rb', line 92

def self.find_in(parent, *args)
  new(filter.query(parent, *args))
end

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



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

def self.present_in?(parent)
  find_in(parent).present?
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.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


163
164
165
# File 'lib/dill/widgets/widget.rb', line 163

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

.selectorObject



180
181
182
# File 'lib/dill/widgets/widget.rb', line 180

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.



69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/dill/widgets/widget.rb', line 69

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

#absent?Boolean

Alias for #gone?

Returns:

  • (Boolean)


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

def absent?
  gone?
end

#classesObject



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

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


220
221
222
223
224
225
226
# File 'lib/dill/widgets/widget.rb', line 220

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

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

Compares this widget with the given Cucumber table.

Example

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


235
236
237
# File 'lib/dill/widgets/widget.rb', line 235

def diff(table, wait_time = Capybara.default_wait_time)
  table.diff!(to_table) || true
end

#gone?Boolean

Returns true if the widget is not visible, or has been removed from the DOM.

Returns:

  • (Boolean)


241
242
243
# File 'lib/dill/widgets/widget.rb', line 241

def gone?
  ! root rescue 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.



253
254
255
256
257
# File 'lib/dill/widgets/widget.rb', line 253

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

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

#idObject



259
260
261
# File 'lib/dill/widgets/widget.rb', line 259

def id
  root['id']
end

#inspectObject



267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/dill/widgets/widget.rb', line 267

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

  begin
    root = self.root
    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}"
  rescue Dill::MissingWidget, *page.driver.invalid_element_errors
    "#<DETACHED>"
  end
end

#present?Boolean

Returns true if widget is visible.

Returns:

  • (Boolean)


283
284
285
# File 'lib/dill/widgets/widget.rb', line 283

def present?
  !! root rescue false
end

#rootObject



287
288
289
290
291
292
293
# File 'lib/dill/widgets/widget.rb', line 287

def root
  query.()
rescue Capybara::Ambiguous => e
  raise wrap_exception(e, AmbiguousWidget)
rescue Capybara::ElementNotFound => e
  raise wrap_exception(e, MissingWidget)
end

#textObject



295
296
297
# File 'lib/dill/widgets/widget.rb', line 295

def text
  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.



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

def to_cell
  text
end

#to_sObject



311
312
313
# File 'lib/dill/widgets/widget.rb', line 311

def to_s
  text
end

#valueObject



315
316
317
# File 'lib/dill/widgets/widget.rb', line 315

def value
  text
end