Document Built-in Selectors

This commit is contained in:
Thomas Walpole 2016-09-08 11:01:22 -07:00
parent 17479d7cd0
commit f706b123d0
8 changed files with 180 additions and 11 deletions

View File

@ -0,0 +1,33 @@
<div id="selectors">
<h2>Built-in Selectors</h2>
<ul>
<% @selectors.each do |name, selector| %>
<li>
<h3>:<%= name %></h3>
<div class="tags">
<p class="inline">Locator:</p>
<% if locator=selector.tag('locator') %>
<p class="inline"><%= locator.text %></p>
<% end %>
<% if selector.has_tag?('filter') %>
<p>Filters:</p>
<ul class="param">
<% selector.tags('filter').each do |filter| %>
<li>
<span class="name"><%= filter.name %></span>
<% if filter.types %>
<span class="type">(<tt><%= filter.types.join(', ') %></tt>)</span>
<% end %>
<% if filter.text %>
<div class="inline"><p><%= filter.text %></p></div>
<% end %>
</li>
<% end %>
</ul>
<% end %>
</div
</li>
<% end %>
</ul>
</div>

View File

@ -0,0 +1,17 @@
def init
super
sections.place(:builtins).before(:subclasses)
end
def builtins
return if object.path != "Capybara::Selector" # only show built-in selectors for Selector class
@selectors = Registry.all(:selector)
return if @selectors.nil? || @selectors.empty?
@selectors = @selectors.map do |selector|
[selector.name, selector]
end
erb(:selectors)
end

81
.yard/yard_extensions.rb Normal file
View File

@ -0,0 +1,81 @@
YARD::Templates::Engine.register_template_path Pathname.new('./.yard/templates_custom')
YARD::Tags::Library.define_tag "Locator", :locator
YARD::Tags::Library.define_tag "Filter", :filter, :with_types_and_name
class SelectorObject < YARD::CodeObjects::Base
def path
"__Capybara" + sep + super
end
end
class AddSelectorHandler < YARD::Handlers::Ruby::Base
handles method_call(:add_selector)
namespace_only
process do
name = statement.parameters.first.jump(:tstring_content, :ident).source
# object = YARD::CodeObjects::MethodObject.new(namespace, name.to_sym)
# object = SelectorObject.new(YARD::Registry.resolve(P("Capybara"), "#add_selector", false, true), name.to_sym)
object = SelectorObject.new(namespace, name)
register(object)
parse_block(statement.last.last, :owner => object)
# modify the object
object.dynamic = true
# add custom metadata to the object
object['custom_field'] = 'Generated by add_selector'
end
end
class AddExpressionFilterHandler < YARD::Handlers::Ruby::Base
handles method_call(:xpath)
handles method_call(:css)
process do
return unless owner.is_a?(SelectorObject)
return if statement.parameters.empty?
# names = statement.parameters.children.map { |p| p.jump(:tstring_content, :ident).source.sub(/^:/, '') }
names = statement.parameters.children.map &:source
current_names = owner.tags(:filter).map(&:name)
(names-current_names).each do |name|
owner.add_tag(YARD::Tags::Tag.new(:filter, nil, nil, name))
end
end
end
class AddFilterHandler < YARD::Handlers::Ruby::Base
handles method_call(:filter)
process do
return unless owner.is_a?(SelectorObject)
return if statement.parameters.empty?
name = statement.parameters.first.source
type = if statement.parameters[1] && statement.parameters[1].source == ':boolean'
'Boolean'
else
nil
end
if owner.tags(:filter).none? {|tag| tag.name == name }
filter_tag = YARD::Tags::Tag.new(:filter, nil, type, name)
owner.add_tag(filter_tag)
end
end
end
class FilterSetHandler < YARD::Handlers::Ruby::Base
handles method_call(:filter_set)
process do
return unless owner.is_a?(SelectorObject)
return if statement.parameters.empty? || !statement.parameters[1]
names = statement.parameters[1].flatten.map { |name| ":#{name}" }
names.each do |name|
if owner.tags(:filter).none? {|tag| tag.name == name }
filter_tag = YARD::Tags::Tag.new(:filter, nil, nil, name)
owner.add_tag(filter_tag)
end
end
end
end

1
.yardopts Normal file
View File

@ -0,0 +1 @@
--load ./.yard/yard_extensions.rb

View File

@ -2,6 +2,7 @@ require 'rubygems'
require 'rspec/core/rake_task'
require 'cucumber/rake/task'
require 'yard'
require_relative './.yard/yard_extensions'
desc "Run all examples"
RSpec::Core::RakeTask.new(:spec) do |t|

View File

@ -39,7 +39,6 @@ else
end
end
def self.feature(*args, &block)
options = if args.last.is_a?(Hash) then args.pop else {} end
options[:capybara_feature] = true

View File

@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'capybara/selector/selector'
Capybara::Selector::FilterSet.add(:_field) do
filter(:checked, :boolean) { |node, value| not(value ^ node.checked?) }
filter(:unchecked, :boolean) { |node, value| (value ^ node.checked?) }
@ -19,18 +18,40 @@ Capybara::Selector::FilterSet.add(:_field) do
end
end
#
# @locator An XPath expression
#
Capybara.add_selector(:xpath) do
xpath { |xpath| xpath }
end
#
# @locator A CSS selector
#
Capybara.add_selector(:css) do
css { |css| css }
end
#
# @locator The id of the element to match
#
Capybara.add_selector(:id) do
xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
end
#
# @locator Matches against the id, name, or placeholder
# @filter [String] :id Matches the id attribute
# @filter [String] :name Matches the name attribute
# @filter [String] :placeholder Matches the placeholder attribute
# @filter [String] :type Matches the type attribute of the field or element type for 'textarea' and 'select'
# @filter [Boolean] :readonly
# @filter [String] :with Matches the current value of the field
# @filter [String, Array<String>] :class Matches the class(es) provided
# @filter [Boolean] :checked Match checked fields?
# @filter [Boolean] :unchecked Match unchecked fields?
# @filter [Boolean] :disabled Match disabled field?
# @filter [Boolean] :multiple Match fields that accept multiple values
Capybara.add_selector(:field) do
xpath(:id, :name, :placeholder, :type, :class) do |locator, options|
xpath = XPath.descendant(:input, :textarea, :select)[~XPath.attr(:type).one_of('submit', 'image', 'hidden')]
@ -61,6 +82,13 @@ Capybara.add_selector(:field) do
end
end
#
# @locator Matches id or contents of wrapped legend
#
# @filter [String] :id Matches id attribute
# @filter [String] :legend Matches contents of wrapped legend
# @filter [String, Array<String>] :class Matches the class(es) provided
#
Capybara.add_selector(:fieldset) do
xpath(:id, :legend, :class) do |locator, options|
xpath = XPath.descendant(:fieldset)
@ -71,7 +99,15 @@ Capybara.add_selector(:fieldset) do
xpath
end
end
#
# @locator Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
#
# @filter [String] :id Matches the id attribute
# @filter [String] :title Matches the title attribute
# @filter [String] :alt Matches the alt attribute of a contained img element
# @filter [String] :class Matches the class(es) provided
# @filter [String, Regexp] :href Matches the normalized href of the link
#
Capybara.add_selector(:link) do
xpath(:id, :title, :alt, :class) do |locator, options={}|
xpath = XPath.descendant(:a)[XPath.attr(:href)]

View File

@ -157,14 +157,15 @@ module Capybara
##
#
# Define a non-expression filter for use with this selector
# @overload filter(name, *types, options={}, &block)
# @param [Symbol] name The filter name
# @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
# @param [Hash] options ({}) Options of the filter
# @option options [Array<>] :valid_values Valid values for this filter
# @option options :default The default value of the filter (if any)
# @option options :skip_if Value of the filter that will cause it to be skipped
# Define a non-expression filter for use with this selector
#
# @overload filter(name, *types, options={}, &block)
# @param [Symbol] name The filter name
# @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
# @param [Hash] options ({}) Options of the filter
# @option options [Array<>] :valid_values Valid values for this filter
# @option options :default The default value of the filter (if any)
# @option options :skip_if Value of the filter that will cause it to be skipped
#
def filter(name, *types_and_options, &block)
options = types_and_options.last.is_a?(Hash) ? types_and_options.pop.dup : {}