diff --git a/.gitignore b/.gitignore index 8893ac4..e51eeaf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ email.txt log .project .loadpath +*.swp diff --git a/History.txt b/History.txt index 70dd07b..da96e37 100644 --- a/History.txt +++ b/History.txt @@ -1,5 +1,9 @@ == Trunk +* Major enhancements + + * Added selects_time, selects_date, and selects_datetime to API. (Ben Mabey, Ticket #36) + * Minor enhancements * Allow clicking links by id and id regexp (gaffo) diff --git a/lib/webrat/core/field.rb b/lib/webrat/core/field.rb index 7aeae82..b6236cb 100644 --- a/lib/webrat/core/field.rb +++ b/lib/webrat/core/field.rb @@ -34,8 +34,16 @@ module Webrat labels.first.text end + def id + @element["id"] + end + def matches_id?(id) - @element["id"] == id.to_s + if id.is_a?(Regexp) + @element["id"] =~ id + else + @element["id"] == id.to_s + end end def matches_name?(name) @@ -83,10 +91,6 @@ module Webrat protected - def id - @element["id"] - end - def name @element["name"] end diff --git a/lib/webrat/core/form.rb b/lib/webrat/core/form.rb index b07247f..c2eb168 100644 --- a/lib/webrat/core/form.rb +++ b/lib/webrat/core/form.rb @@ -44,6 +44,10 @@ module Webrat end end + def labels + @labels ||= element.search("label").map { |element| Label.new(nil, element) } + end + def submit @session.request_page(form_action, form_method, params) end @@ -65,6 +69,10 @@ module Webrat end matching_fields.min { |a, b| a.label_text.length <=> b.label_text.length } end + + def label_matching(label_text) + labels.detect { |label| label.matches_text?(label_text) } + end protected @@ -127,4 +135,4 @@ module Webrat end end -end \ No newline at end of file +end diff --git a/lib/webrat/core/label.rb b/lib/webrat/core/label.rb index 7107f10..5557108 100644 --- a/lib/webrat/core/label.rb +++ b/lib/webrat/core/label.rb @@ -17,6 +17,10 @@ module Webrat str.squeeze!(" ") str end + + def for_id + @element['for'] + end end -end \ No newline at end of file +end diff --git a/lib/webrat/core/locators.rb b/lib/webrat/core/locators.rb index 0a7c108..72cff9b 100644 --- a/lib/webrat/core/locators.rb +++ b/lib/webrat/core/locators.rb @@ -87,6 +87,15 @@ module Webrat flunk("Could not find link with text or title or id #{text_or_title_or_id.inspect}") end end + + def find_field_id_for_label(label_text) + label = forms.detect_mapped { |form| form.label_matching(label_text) } + if label + label.for_id + else + flunk("Could not find the label with text #{label_text}") + end + end end -end \ No newline at end of file +end diff --git a/lib/webrat/core/scope.rb b/lib/webrat/core/scope.rb index 187e81a..cfdb931 100644 --- a/lib/webrat/core/scope.rb +++ b/lib/webrat/core/scope.rb @@ -92,6 +92,114 @@ module Webrat alias_method :select, :selects + DATE_TIME_SUFFIXES = {:rails => {:year => '1i', :month => '2i', :day => '3i', + :hour => '4i', :minute => '5i'}, + :full_words => {:year => 'year', :month => 'month', :day => 'day', + :hour => 'hour', :minute => 'minute'} + } + + # Verifies that date elements (year, month, day) exist on the current page + # with the specified values. You can optionally restrict the search to a specific + # date's elements by assigning options[:from] the value of the date's + # label. Selects all the date elements with date provided. The date provided may + # be a string or a Date/Time object. + # + # By default Rail's convention is used for detecting the date elements, but this + # may be overriden by assinging a options[:suffix_convention] or + # options[:suffixes]. In all cases all elements are assumed to have a + # shared prefix. For example, a birthday date on a form might have the following: + # 'birthday_Year', 'birthday_Month', 'birthday_Day'. You may also specify the + # prefix by assigning options[:id_prefix]. + # + # Examples: + # selects_date "January 23, 2004" + # selects_date "April 26, 1982", :from => "Birthday" + # selects_date Date.parse("December 25, 2000"), :from => "Event" + # selects_date "April 26, 1982", :suffix_convention => :full_words + # selects_date "April 26, 1982", :id_prefix => 'birthday', + # :suffixes => {:year => 'Year', :month => 'Mon', :day => 'Day'} + def selects_date(date_to_select, options ={}) + date = date_to_select.is_a?(Date) || date_to_select.is_a?(Time) ? + date_to_select : Date.parse(date_to_select) + + suffixes = extract_date_time_suffixes(options) + + id_prefix = locate_id_prefix(options) do + year_field = find_field_with_id(/(.*?)_#{suffixes[:year]}$/) + flunk("No date fields were found") unless year_field && year_field.id =~ /(.*?)_1i/ + $1 + end + + selects date.year, :from => "#{id_prefix}_#{suffixes[:year]}" + selects date.strftime('%B'), :from => "#{id_prefix}_#{suffixes[:month]}" + selects date.day, :from => "#{id_prefix}_#{suffixes[:day]}" + end + + alias_method :select_date, :selects_date + + # Verifies that time elements (hour, minute) exist on the current page + # with the specified values. You can optionally restrict the search to a specific + # time's elements by assigning options[:from] the value of the time's + # label. Selects all the time elements with date provided. The time provided may + # be a string or a Time object. + # + # By default Rail's convention is used for detecting the time elements, but this + # may be overriden by assinging a options[:suffix_convention] or + # options[:suffixes]. In all cases all elements are assumed to have a + # shared prefix. For example, an appointment time on a form might have the + # following: 'appt_time_hour', 'appt_time_min'. You may also specify the + # prefix by assigning options[:id_prefix]. + # + # Note: Just like Rails' time_select helper this assumes the form is using + # 24 hour select boxes, and not 12 hours with AM/PM. + # + # Examples: + # selects_time "9:30" + # selects_date "3:30PM", :from => "Party Time" + # selects_date Time.parse("10:00PM"), :from => "Event" + # selects_date "8:30", :suffix_convention => :full_words + # selects_date "10:30AM", :id_prefix => 'meeting', + # :suffixes => {:hour => 'Hour', :min => 'Min'} + def selects_time(time_to_select, options ={}) + time = time_to_select.is_a?(Time) ? time_to_select : Time.parse(time_to_select) + + suffixes = extract_date_time_suffixes(options) + + id_prefix = locate_id_prefix(options) do + hour_field = find_field_with_id(/(.*?)_#{suffixes[:hour]}$/) + flunk("No time fields were found") unless hour_field && hour_field.id =~ /(.*?)_4i/ + $1 + end + + selects time.hour.to_s.rjust(2,'0'), :from => "#{id_prefix}_#{suffixes[:hour]}" + selects time.min.to_s.rjust(2,'0'), :from => "#{id_prefix}_#{suffixes[:minute]}" + end + + + alias_method :select_time, :selects_time + + # Verifies and selects all the date and time elements on the current page. + # See #selects_time and #selects_date for more details and available options. + # + # Examples: + # selects_datetime "January 23, 2004 10:30AM" + # selects_datetime "April 26, 1982 7:00PM", :from => "Birthday" + # selects_datetime Time.parse("December 25, 2000 15:30"), :from => "Event" + # selects_datetime "April 26, 1982 5:50PM", :suffix_convention => :full_words + # selects_datetime "April 26, 1982 5:50PM", :id_prefix => 'birthday', + # :suffixes => {:year => 'Year', :month => 'Mon', :day => 'Day', + # :hour => 'Hour', :minute => 'Min'} + def selects_datetime(time_to_select, options ={}) + time = time_to_select.is_a?(Time) ? time_to_select : Time.parse(time_to_select) + + options[:id_prefix] ||= (options[:from] ? find_field_with_id(options[:from]) : nil) + + selects_date time, options + selects_time time, options + end + + alias_method :select_datetime, :selects_datetime + # Verifies that an input file field exists on the current page and sets # its value to the given +file+, so that the file will be uploaded # along with the form. An optional content_type may be given. @@ -191,6 +299,17 @@ module Webrat end end + def locate_id_prefix(options, &location_strategy) #:nodoc: + return options[:id_prefix] if options[:id_prefix] + id_prefix = options[:from] ? find_field_id_for_label(options[:from]) : yield + end + + def extract_date_time_suffixes(options) #:nodoc: + options[:suffixes] || + DATE_TIME_SUFFIXES[options[:suffix_convention]] || + DATE_TIME_SUFFIXES[:rails] + end + def areas #:nodoc: Webrat::XML.css_search(dom, "area").map do |element| Area.new(@session, element) @@ -212,4 +331,4 @@ module Webrat end end -end \ No newline at end of file +end diff --git a/lib/webrat/core/session.rb b/lib/webrat/core/session.rb index c1fec8e..47fa949 100644 --- a/lib/webrat/core/session.rb +++ b/lib/webrat/core/session.rb @@ -184,6 +184,9 @@ module Webrat def_delegators :current_scope, :uncheck, :unchecks def_delegators :current_scope, :choose, :chooses def_delegators :current_scope, :select, :selects + def_delegators :current_scope, :select_datetime, :selects_datetime + def_delegators :current_scope, :select_date, :selects_date + def_delegators :current_scope, :select_time, :selects_time def_delegators :current_scope, :attach_file, :attaches_file def_delegators :current_scope, :click_area, :clicks_area def_delegators :current_scope, :click_link, :clicks_link diff --git a/spec/api/selects_date_spec.rb b/spec/api/selects_date_spec.rb new file mode 100644 index 0000000..f7b3f01 --- /dev/null +++ b/spec/api/selects_date_spec.rb @@ -0,0 +1,130 @@ +require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") + +describe "selects_date" do + before do + @session = Webrat::TestSession.new + end + + it "should send the values for each individual date component" do + @session.response_body = <<-EOS +
+ EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"date(1i)" => '2003', "date(2i)" => "12", "date(3i)" => "25"}) + @session.selects_date "December 25, 2003", :from => "Date" + @session.click_button + end + + it "should select the date components with the suffix convention provided" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"year" => '2003', "month" => "12", "day" => "25"}) + @session.selects_date "December 25, 2003 9:30", :from => "Date", :suffix_convention => :full_words + @session.click_button + end + + it "should select the date components with the suffixes provided" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"y" => '2003', "mo" => "12", "d" => "25"}) + @session.selects_date "December 25, 2003 9:30", :from => "Date", + :suffixes => {:year => 'y', :month => 'mo', :day => 'd'} + @session.click_button + end + + + it "should accept a date object" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"date(1i)" => '2003', "date(2i)" => "12", "date(3i)" => "25"}) + @session.selects_date Date.parse("December 25, 2003"), :from => "date" + @session.click_button + end + + it "should work when no label is specified" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"date(1i)" => '2003', "date(2i)" => "12", "date(3i)" => "25"}) + @session.selects_date "December 25, 2003" + @session.click_button + end + + it "should fail if the specified label is not found" do + @session.response_body = <<-EOS + + EOS + + lambda { @session.selects_date "December 25, 2003", :from => "date" }.should raise_error + end + +end diff --git a/spec/api/selects_datetime_spec.rb b/spec/api/selects_datetime_spec.rb new file mode 100644 index 0000000..3ac1146 --- /dev/null +++ b/spec/api/selects_datetime_spec.rb @@ -0,0 +1,159 @@ +require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") + +describe "selects_datetime" do + before do + @session = Webrat::TestSession.new + end + + it "should send the values for each individual date and time components" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"time(1i)" => '2003', "time(2i)" => "12", "time(3i)" => "25", "time(4i)" => "09", "time(5i)" => "30"}) + @session.selects_datetime "December 25, 2003 9:30", :from => "Time" + @session.click_button + end + + it "should select the date and time components with the suffix convention provided" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"year" => '2003', "month" => "12", "day" => "25", "hour" => "09", "minute" => "30"}) + @session.selects_datetime "December 25, 2003 9:30", :from => "Time", :suffix_convention => :full_words + @session.click_button + end + + it "should select the date and time components with the suffixes provided" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"y" => '2003', "mo" => "12", "d" => "25", "h" => "09", "mi" => "30"}) + @session.selects_datetime "December 25, 2003 9:30", :from => "Time", + :suffixes => {:year => 'y', :month => 'mo', :day => 'd', :hour => 'h', :minute => 'mi'} + @session.click_button + end + + it "should accept a time object" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"time(1i)" => '2003', "time(2i)" => "12", "time(3i)" => "25", "time(4i)" => "09", "time(5i)" => "30"}) + @session.select_datetime Time.parse("December 25, 2003 9:30"), :from => "Time" + @session.click_button + end + + it "should work when no label is specified" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"time(1i)" => '2003', "time(2i)" => "12", "time(3i)" => "25", "time(4i)" => "09", "time(5i)" => "30"}) + @session.selects_datetime "December 25, 2003 9:30" + @session.click_button + end + + it "should fail if the specified label is not found" do + @session.response_body = <<-EOS + + EOS + + lambda { @session.selects_datetime "December 25, 2003 9:30", :from => "Time" }.should raise_error + end + +end diff --git a/spec/api/selects_time_spec.rb b/spec/api/selects_time_spec.rb new file mode 100644 index 0000000..ea1bf71 --- /dev/null +++ b/spec/api/selects_time_spec.rb @@ -0,0 +1,114 @@ +require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") + +describe "select_time" do + before do + @session = Webrat::TestSession.new + end + + it "should send the values for each individual time component" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"time(4i)" => "09", "time(5i)" => "30"}) + @session.selects_time "9:30AM", :from => "Time" + @session.click_button + end + + it "should select time components with the suffix convention provided" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"hour" => "09", "minute" => "30"}) + @session.selects_time "9:30", :from => "Time", :suffix_convention => :full_words + @session.click_button + end + + it "should select the time components with the suffixes provided" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"h" => "09", "mi" => "30"}) + @session.selects_time "9:30", :from => "Time", + :suffixes => {:hour => 'h', :minute => 'mi'} + @session.click_button + end + + it "should accept a time object" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"time(4i)" => "09", "time(5i)" => "30"}) + @session.select_time Time.parse("9:30AM"), :from => "Time" + @session.click_button + end + + it "should work when no label is specified" do + @session.response_body = <<-EOS + + EOS + @session.should_receive(:post).with("/appointments", + "appointment" => {"time(4i)" => "09", "time(5i)" => "30"}) + @session.select_time "9:30" + @session.click_button + end + + it "should fail if the specified label is not found" do + @session.response_body = <<-EOS + + EOS + + lambda { @session.select_time "9:30", :from => "Time" }.should raise_error + end + +end