require 'date' module Calabash module Cucumber # A collection of methods for interacting with UIDatePicker module DatePicker include Calabash::Cucumber::Core # equivalent formats for ruby and objc # we convert DateTime object to a string # and pass the format that objc can use # to convert the string into NSDate # @!visibility private RUBY_DATE_AND_TIME_FMT = '%Y_%m_%d_%H_%M' # @!visibility private OBJC_DATE_AND_TIME_FMT = 'yyyy_MM_dd_HH_mm' # picker modes # @!visibility private UI_DATE_PICKER_MODE_TIME = 0 # @!visibility private UI_DATE_PICKER_MODE_DATE = 1 # @!visibility private UI_DATE_PICKER_MODE_DATE_AND_TIME = 2 # @!visibility private UI_DATE_PICKER_MODE_COUNT_DOWN_TIMER = 3 # @!visibility private # Returns the picker mode of the UIDatePicker. # # @see #time_mode? # @see #date_mode? # @see #date_and_time_mode? # @see #countdown_mode? # # @param [String] picker_id The accessibility label or id of the picker # you are looking for. If nil, then the first UIDatePicker that is # found will be queried. # @return [String] Returns the picker mode which will be one of # `{'0', '1', '2', '3'}` # @raise [RuntimeError] if no picker can be found def date_picker_mode(picker_id=nil) query_str = should_see_date_picker picker_id res = query(query_str, :datePickerMode) if res.empty? screenshot_and_raise "should be able to get mode from picker with query '#{query_str}'" end res.first end # Is the date picker in time mode? # # @see #time_mode? # @see #date_mode? # @see #date_and_time_mode? # @see #countdown_mode? # # @param [String] picker_id The accessibility label or id of the picker # you are looking for. If nil, then the first UIDatePicker that is # found will be queried. # @return [Boolean] true if the picker is in time mode # @raise [RuntimeError] if no picker can be found def time_mode?(picker_id=nil) date_picker_mode(picker_id) == UI_DATE_PICKER_MODE_TIME end # Is the date picker in date mode? # # @see #time_mode? # @see #date_mode? # @see #date_and_time_mode? # @see #countdown_mode? # # @param [String] picker_id The accessibility label or id of the picker # you are looking for. If nil, then the first UIDatePicker that is # found will be queried. # @return [Boolean] true if the picker is in date mode # @raise [RuntimeError] if no picker can be found def date_mode?(picker_id=nil) date_picker_mode(picker_id) == UI_DATE_PICKER_MODE_DATE end # Is the date picker in date and time mode? # # @see #time_mode? # @see #date_mode? # @see #date_and_time_mode? # @see #countdown_mode? # # @param [String] picker_id The accessibility label or id of the picker # you are looking for. If nil, then the first UIDatePicker that is # found will be queried. # @return [Boolean] true if the picker is in date and time mode # @raise [RuntimeError] if no picker can be found def date_and_time_mode?(picker_id=nil) date_picker_mode(picker_id) == UI_DATE_PICKER_MODE_DATE_AND_TIME end # Is the date picker in countdown mode? # # @see #time_mode? # @see #date_mode? # @see #date_and_time_mode? # @see #countdown_mode? # # @param [String] picker_id The accessibility label or id of the picker # you are looking for. If nil, then the first UIDatePicker that is # found will be queried. # @return [Boolean] true if the picker is in countdown mode # @raise [RuntimeError] if no picker can be found def countdown_mode?(picker_id=nil) date_picker_mode(picker_id) == UI_DATE_PICKER_MODE_COUNT_DOWN_TIMER end # @!visibility private # Returns a query string for a date picker. # # @param [String] picker_id The accessibility label or id of the picker # you are looking for. If nil, a query that looks for any UIDatePicker # will be constructed. # @return [String] a query-ready string for finding a UIDatePicker def query_string_for_picker (picker_id = nil) picker_id.nil? ? 'datePicker' : "datePicker marked:'#{picker_id}'" end # Asserts that a date picker is visible. # # @param [String] picker_id The accessibility label or id of the picker # you are looking for. If nil, any visible date picker will satisfy the # assertion. # @raise [RuntimeError] if a matching date picker is not visible def should_see_date_picker (picker_id=nil) query_str = query_string_for_picker picker_id if query(query_str).empty? screenshot_and_raise "should see picker with query '#{query_str}'" end query_str end # The maximum date for a picker. If there is no maximum date, this # method returns nil. # # @note # From the Apple docs: # `The property is an NSDate object or nil (the default)`. # # @param [String] picker_id The accessibility label or id of the picker # you are looking for. If nil, then the first UIDatePicker that is # found will be queried. # @return [DateTime] the maximum date on the picker or nil if no maximum # exists # @raise [RuntimeError] if the picker is in countdown mode # @raise [RuntimeError] if the picker cannot be found def maximum_date_time_from_picker (picker_id = nil) if countdown_mode? picker_id screenshot_and_raise 'method is not available for pickers that are not in date or date time mode' end query_str = should_see_date_picker picker_id res = query(query_str, :maximumDate) if res.empty? screenshot_and_raise "should be able to get max date from picker with query '#{query_str}'" end return nil if res.first.nil? DateTime.parse(res.first) end # The minimum date for a picker. If there is no minimum date, this # method returns nil. # # @note # From the Apple docs: # `The property is an NSDate object or nil (the default)`. # # @param [String] picker_id The accessibility label or id of the picker # you are looking for. If nil, then the first UIDatePicker that is # found will be queried. # @return [DateTime] the minimum date on the picker or nil if no minimum # exists # @raise [RuntimeError] if the picker is in countdown mode # @raise [RuntimeError] if the picker cannot be found def minimum_date_time_from_picker (picker_id = nil) if countdown_mode? picker_id screenshot_and_raise 'method is not available for pickers that are not in date or date time mode' end query_str = should_see_date_picker picker_id res = query(query_str, :minimumDate) if res.empty? screenshot_and_raise "should be able to get min date from picker with query '#{query_str}'" end return nil if res.first.nil? DateTime.parse(res.first) end # Returns the date and time from the picker. # # @param [String] picker_id The accessibility label or id of the picker # you are looking for. If nil, then the first UIDatePicker that is # found will be queried. # @return [DateTime] the date on the picker # @raise [RuntimeError] if the picker is in countdown mode # @raise [RuntimeError] if the picker cannot be found def date_time_from_picker (picker_id=nil) if countdown_mode? picker_id screenshot_and_raise 'method is not available for pickers that are not in date or date time mode' end query_str = query_string_for_picker picker_id res = query(query_str, :date) if res.empty? screenshot_and_raise "should be able to get date from picker with query '#{query_str}'" end DateTime.parse(res.first) end # @!visibility private # @todo replace with a `merge` inside the calling function # -1 @jmoody for ruby noob-ness def args_for_change_date_on_picker(options) args = [] if options.has_key?(:notify_targets) args << options[:notify_targets] ? 1 : 0 else args << 1 end if options.has_key?(:animate) args << options[:animate] ? 1 : 0 else args << 1 end args end # Sets the date and time on picker. # # @note # When `:notify_targets => true` this operation iterates through the # target/action pairs on the objc `UIDatePicker` instance and calls # `performSelector: object:`. This has the effect of # generating `UIEvents`. # # @param [DateTime] target_dt the date and time you want to change to # @param [Hash] options controls the behavior of this method # @option options [Boolean] :animate (true) animate the date change # @option options [String] :picker_id (nil) The accessibility id or label # of the date picker you are looking for. If nil, the first UIDatePicker # that is found will have the date and time applied. # @option options [Boolean] :notify_target (true) If true, all the date # picker's target/action pairs will be called. This is necessary to # generate UIEvents. If false, no UIEvents will be generated and it is # likely that your UI will not update correctly. # # @raise [RuntimeError] if no date picker can be found # @raise [RuntimeError] if the target date is greater than the picker's # maximum date # @raise [RuntimeError] if the target date is less than the picker's # minimum date # @raise [RuntimeError] if the target date is not a DateTime instance # @todo replace `args_for_change_date_on_picker` with hash table `merge` def picker_set_date_time (target_dt, options = {:animate => true, :picker_id => nil, :notify_targets => true}) unless target_dt.is_a?(DateTime) raise "target_dt must be a DateTime but found '#{target_dt.class}'" end picker_id = options == nil ? nil : options[:picker_id] if time_mode?(picker_id) == UI_DATE_PICKER_MODE_COUNT_DOWN_TIMER pending('picker is in count down mode which is not yet supported') end target_str = target_dt.strftime(RUBY_DATE_AND_TIME_FMT).squeeze(' ').strip fmt_str = OBJC_DATE_AND_TIME_FMT args = args_for_change_date_on_picker options query_str = query_string_for_picker picker_id views_touched = Map.map(query_str, :changeDatePickerDate, target_str, fmt_str, *args) msg = "could not change date on picker to '#{target_dt}' using query '#{query_str}' with options '#{options}'" Map.assert_map_results(views_touched,msg) views_touched end end end end