lib/webrat/core/elements/field.rb
require "cgi"
require "digest/md5"
require "webrat/core_extensions/blank"
require "webrat/core_extensions/nil_to_query_string"
require "webrat/core/elements/element"
module Webrat
# Raised when Webrat is asked to manipulate a disabled form field
class DisabledFieldError < WebratError
end
class Field < Element #:nodoc:
attr_reader :value
def self.xpath_search
".//button|.//input|.//textarea|.//select"
end
def self.xpath_search_excluding_hidden
[".//button", ".//input[ @type != 'hidden']", ".//textarea", ".//select"]
end
def self.field_classes
@field_classes || []
end
def self.inherited(klass)
@field_classes ||= []
@field_classes << klass
# raise args.inspect
end
def self.load(session, element)
return nil if element.nil?
session.elements[element.path] ||= field_class(element).new(session, element)
end
def self.field_class(element)
case element.name
when "button" then ButtonField
when "select"
if element.attributes["multiple"].nil?
SelectField
else
MultipleSelectField
end
when "textarea" then TextareaField
else
case element["type"]
when "checkbox" then CheckboxField
when "hidden" then HiddenField
when "radio" then RadioField
when "password" then PasswordField
when "file" then FileField
when "reset" then ResetField
when "submit" then ButtonField
when "button" then ButtonField
when "image" then ButtonField
else TextField
end
end
end
def initialize(*args)
super
@value = default_value
end
def label_text
return nil if labels.empty?
labels.first.text
end
def id
@element["id"]
end
def disabled?
@element.attributes.has_key?("disabled") && @element["disabled"] != 'false'
end
def raise_error_if_disabled
return unless disabled?
raise DisabledFieldError.new("Cannot interact with disabled form element (#{self})")
end
def to_query_string
return nil if disabled?
query_string = case Webrat.configuration.mode
when :rails, :merb, :rack, :sinatra
build_query_string
when :mechanize
build_query_string(false)
end
query_string
end
def set(value)
@value = value
end
def unset
@value = default_value
end
protected
def form
Form.load(@session, form_element)
end
def form_element
parent = @element.parent
while parent.respond_to?(:parent)
return parent if parent.name == 'form'
parent = parent.parent
end
end
def name
@element["name"]
end
def build_query_string(escape_value=true)
if @value.is_a?(Array)
@value.collect {|value| "#{name}=#{ escape_value ? escape(value) : value }" }.join("&")
else
"#{name}=#{ escape_value ? escape(value) : value }"
end
end
def escape(value)
CGI.escape(value.to_s)
end
def escaped_value
CGI.escape(@value.to_s)
end
def labels
@labels ||= label_elements.map do |element|
Label.load(@session, element)
end
end
def label_elements
return @label_elements unless @label_elements.nil?
@label_elements = []
parent = @element.parent
while parent.respond_to?(:parent)
if parent.name == 'label'
@label_elements.push parent
break
end
parent = parent.parent
end
unless id.blank?
@label_elements += form.element.xpath(".//label[@for = '#{id}']")
end
@label_elements
end
def default_value
@element["value"]
end
end
class ButtonField < Field #:nodoc:
def self.xpath_search
[".//button", ".//input[@type = 'submit']", ".//input[@type = 'button']", ".//input[@type = 'image']"]
end
def to_query_string
return nil if @value.nil?
super
end
def default_value
nil
end
def click
raise_error_if_disabled
set(@element["value"]) unless @element["name"].blank?
form.submit
end
end
class HiddenField < Field #:nodoc:
def self.xpath_search
".//input[@type = 'hidden']"
end
def to_query_string
if collection_name?
super
else
checkbox_with_same_name = form.field_named(name, CheckboxField)
if checkbox_with_same_name.to_query_string.blank?
super
else
nil
end
end
end
protected
def collection_name?
name =~ /\[\]/
end
end
class CheckboxField < Field #:nodoc:
def self.xpath_search
".//input[@type = 'checkbox']"
end
def to_query_string
return nil if @value.nil?
super
end
def check
raise_error_if_disabled
set(@element["value"] || "on")
end
def checked?
@element["checked"] == "checked"
end
def uncheck
raise_error_if_disabled
set(nil)
end
protected
def default_value
if @element["checked"] == "checked"
@element["value"] || "on"
else
nil
end
end
end
class PasswordField < Field #:nodoc:
def self.xpath_search
".//input[@type = 'password']"
end
end
class RadioField < Field #:nodoc:
def self.xpath_search
".//input[@type = 'radio']"
end
def to_query_string
return nil if @value.nil?
super
end
def choose
raise_error_if_disabled
other_options.each do |option|
option.set(nil)
end
set(@element["value"] || "on")
end
def checked?
@element["checked"] == "checked"
end
protected
def other_options
form.fields.select { |f| f.name == name }
end
def default_value
if @element["checked"] == "checked"
@element["value"] || "on"
else
nil
end
end
end
class TextareaField < Field #:nodoc:
def self.xpath_search
".//textarea"
end
protected
def default_value
@element.inner_html
end
end
class FileField < Field #:nodoc:
def self.xpath_search
".//input[@type = 'file']"
end
attr_accessor :content_type
def set(value, content_type = nil)
@original_value = @value
@content_type ||= content_type
super(value)
end
def digest_value
@value ? Digest::MD5.hexdigest(self.object_id.to_s) : ""
end
def to_query_string
@value.nil? ? set("") : set(digest_value)
super
end
def test_uploaded_file
return "" if @original_value.blank?
case Webrat.configuration.mode
when :rails
if content_type
ActionController::TestUploadedFile.new(@original_value, content_type)
else
ActionController::TestUploadedFile.new(@original_value)
end
when :rack, :merb
Rack::Test::UploadedFile.new(@original_value, content_type)
end
end
end
class TextField < Field #:nodoc:
def self.xpath_search
[".//input[@type = 'text']", ".//input[not(@type)]"]
end
end
class ResetField < Field #:nodoc:
def self.xpath_search
[".//input[@type = 'reset']"]
end
end
class SelectField < Field #:nodoc:
def self.xpath_search
[".//select[not(@multiple)]"]
end
def options
@options ||= SelectOption.load_all(@session, @element)
end
def unset(value)
@value = nil
end
protected
def default_value
selected_options = @element.xpath(".//option[@selected = 'selected']")
selected_options = @element.xpath(".//option[position() = 1]") if selected_options.empty?
selected_options.map do |option|
return "" if option.nil?
option["value"] || option.inner_html
end.uniq.first
end
end
class MultipleSelectField < Field #:nodoc:
def self.xpath_search
[".//select[@multiple='multiple']"]
end
def options
@options ||= SelectOption.load_all(@session, @element)
end
def set(value)
@value << value
end
def unset(value)
@value.delete(value)
end
protected
# Overwrite SelectField definition because we don't want to select the first option
# (mutliples don't select the first option unlike their non multiple versions)
def default_value
selected_options = @element.xpath(".//option[@selected = 'selected']")
selected_options.map do |option|
return "" if option.nil?
option["value"] || option.inner_html
end.uniq
end
end
end