From f706b123d0f6f1ce5e9a63d3fc1dd22436b75a8d Mon Sep 17 00:00:00 2001 From: Thomas Walpole Date: Thu, 8 Sep 2016 11:01:22 -0700 Subject: [PATCH] Document Built-in Selectors --- .../default/class/html/selectors.erb | 33 ++++++++ .../default/class/html/setup.rb | 17 ++++ .yard/yard_extensions.rb | 81 +++++++++++++++++++ .yardopts | 1 + Rakefile | 1 + lib/capybara/rspec/features.rb | 1 - lib/capybara/selector.rb | 40 ++++++++- lib/capybara/selector/selector.rb | 17 ++-- 8 files changed, 180 insertions(+), 11 deletions(-) create mode 100644 .yard/templates_custom/default/class/html/selectors.erb create mode 100644 .yard/templates_custom/default/class/html/setup.rb create mode 100644 .yard/yard_extensions.rb create mode 100644 .yardopts diff --git a/.yard/templates_custom/default/class/html/selectors.erb b/.yard/templates_custom/default/class/html/selectors.erb new file mode 100644 index 00000000..50037ff5 --- /dev/null +++ b/.yard/templates_custom/default/class/html/selectors.erb @@ -0,0 +1,33 @@ +
+

Built-in Selectors

+ +
diff --git a/.yard/templates_custom/default/class/html/setup.rb b/.yard/templates_custom/default/class/html/setup.rb new file mode 100644 index 00000000..91fbec56 --- /dev/null +++ b/.yard/templates_custom/default/class/html/setup.rb @@ -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 \ No newline at end of file diff --git a/.yard/yard_extensions.rb b/.yard/yard_extensions.rb new file mode 100644 index 00000000..64f76506 --- /dev/null +++ b/.yard/yard_extensions.rb @@ -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 diff --git a/.yardopts b/.yardopts new file mode 100644 index 00000000..6b4ed802 --- /dev/null +++ b/.yardopts @@ -0,0 +1 @@ +--load ./.yard/yard_extensions.rb diff --git a/Rakefile b/Rakefile index 51736c94..280cbc04 100644 --- a/Rakefile +++ b/Rakefile @@ -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| diff --git a/lib/capybara/rspec/features.rb b/lib/capybara/rspec/features.rb index 1b8ebbc4..8b405763 100644 --- a/lib/capybara/rspec/features.rb +++ b/lib/capybara/rspec/features.rb @@ -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 diff --git a/lib/capybara/selector.rb b/lib/capybara/selector.rb index 72b7e7e9..c6ed2927 100644 --- a/lib/capybara/selector.rb +++ b/lib/capybara/selector.rb @@ -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] :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] :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)] diff --git a/lib/capybara/selector/selector.rb b/lib/capybara/selector/selector.rb index b691ad3d..c3379183 100644 --- a/lib/capybara/selector/selector.rb +++ b/lib/capybara/selector/selector.rb @@ -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] 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] 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 : {}