calabash 2.0.0.pre1 → 2.0.0.prelegacy2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -38
  3. data/lib/calabash.rb +18 -5
  4. data/lib/calabash/android.rb +3 -0
  5. data/lib/calabash/android/adb.rb +25 -1
  6. data/lib/calabash/android/application.rb +14 -3
  7. data/lib/calabash/android/build/builder.rb +15 -2
  8. data/lib/calabash/android/build/java_keystore.rb +10 -0
  9. data/lib/calabash/android/build/resigner.rb +4 -0
  10. data/lib/calabash/android/build/test_server.rb +2 -0
  11. data/lib/calabash/android/defaults.rb +1 -0
  12. data/lib/calabash/android/device.rb +42 -1
  13. data/lib/calabash/android/environment.rb +10 -0
  14. data/lib/calabash/android/interactions.rb +2 -0
  15. data/lib/calabash/android/legacy.rb +149 -0
  16. data/lib/calabash/android/lib/TestServer.apk +0 -0
  17. data/lib/calabash/android/life_cycle.rb +1 -0
  18. data/lib/calabash/android/physical_buttons.rb +8 -0
  19. data/lib/calabash/android/screenshot.rb +1 -0
  20. data/lib/calabash/android/scroll.rb +110 -0
  21. data/lib/calabash/android/server.rb +3 -1
  22. data/lib/calabash/android/text.rb +6 -0
  23. data/lib/calabash/application.rb +29 -0
  24. data/lib/calabash/cli/build.rb +15 -1
  25. data/lib/calabash/cli/console.rb +9 -5
  26. data/lib/calabash/cli/generate.rb +3 -0
  27. data/lib/calabash/cli/helpers.rb +7 -1
  28. data/lib/calabash/cli/resign.rb +1 -0
  29. data/lib/calabash/cli/run.rb +10 -6
  30. data/lib/calabash/cli/setup_keystore.rb +2 -0
  31. data/lib/calabash/color.rb +7 -0
  32. data/lib/calabash/defaults.rb +1 -0
  33. data/lib/calabash/device.rb +7 -0
  34. data/lib/calabash/environment.rb +1 -0
  35. data/lib/calabash/http/retriable_client.rb +2 -0
  36. data/lib/calabash/interactions.rb +1 -0
  37. data/lib/calabash/ios.rb +2 -0
  38. data/lib/calabash/ios/application.rb +8 -1
  39. data/lib/calabash/ios/conditions.rb +3 -0
  40. data/lib/calabash/ios/date_picker.rb +412 -0
  41. data/lib/calabash/ios/defaults.rb +1 -0
  42. data/lib/calabash/ios/device.rb +1 -0
  43. data/lib/calabash/ios/device/device_implementation.rb +16 -11
  44. data/lib/calabash/ios/device/ipad_1x_2x_mixin.rb +253 -0
  45. data/lib/calabash/ios/device/keyboard_mixin.rb +2 -0
  46. data/lib/calabash/ios/device/rotation_mixin.rb +1 -0
  47. data/lib/calabash/ios/device/routes/condition_route_mixin.rb +1 -0
  48. data/lib/calabash/ios/device/routes/map_route_mixin.rb +1 -0
  49. data/lib/calabash/ios/device/routes/response_parser.rb +1 -0
  50. data/lib/calabash/ios/device/routes/uia_route_mixin.rb +44 -6
  51. data/lib/calabash/ios/device/text_mixin.rb +2 -0
  52. data/lib/calabash/ios/device/uia_keyboard_mixin.rb +9 -0
  53. data/lib/calabash/ios/device/uia_mixin.rb +1 -0
  54. data/lib/calabash/ios/interactions.rb +30 -1
  55. data/lib/calabash/ios/runtime.rb +8 -0
  56. data/lib/calabash/ios/text.rb +5 -45
  57. data/lib/calabash/legacy.rb +7 -0
  58. data/lib/calabash/lib/skeleton/config/cucumber.yml +1 -3
  59. data/lib/calabash/lib/skeleton/features/support/env.rb +15 -1
  60. data/lib/calabash/life_cycle.rb +19 -2
  61. data/lib/calabash/location.rb +2 -0
  62. data/lib/calabash/page.rb +13 -0
  63. data/lib/calabash/patch.rb +1 -0
  64. data/lib/calabash/query_result.rb +4 -0
  65. data/lib/calabash/text.rb +53 -0
  66. data/lib/calabash/utility.rb +4 -4
  67. data/lib/calabash/version.rb +1 -1
  68. data/lib/calabash/wait.rb +4 -0
  69. metadata +119 -115
@@ -3,6 +3,7 @@ module Calabash
3
3
  module CLI
4
4
  # @!visibility private
5
5
  module Console
6
+ # @!visibility private
6
7
  def parse_console_arguments!
7
8
  application = @arguments.shift
8
9
 
@@ -22,12 +23,14 @@ module Calabash
22
23
  when '.apk'
23
24
  set_platform!(:android)
24
25
 
25
- # Create the test server if it does not exist
26
- test_server = Android::Build::TestServer.new(application_path)
26
+ unless Environment::TEST_SERVER_PATH
27
+ # Create the test server if it does not exist
28
+ test_server = Android::Build::TestServer.new(application_path)
27
29
 
28
- unless test_server.exists?
29
- Logger.info('Test server does not exist. Creating test server.')
30
- Calabash::Android::Build::Builder.new(application_path).build
30
+ unless test_server.exists?
31
+ Logger.info('Test server does not exist. Creating test server.')
32
+ Calabash::Android::Build::Builder.new(application_path).build
33
+ end
31
34
  end
32
35
 
33
36
  enter_console(application_path)
@@ -45,6 +48,7 @@ module Calabash
45
48
  end
46
49
  end
47
50
 
51
+ # @!visibility private
48
52
  def enter_console(application_path)
49
53
  irbrc_path = Environment::IRBRC
50
54
 
@@ -5,6 +5,7 @@ module Calabash
5
5
  module CLI
6
6
  # @!visibility private
7
7
  module Generate
8
+ # @!visibility private
8
9
  def parse_generate_arguments!
9
10
  if File.exist?('features')
10
11
  puts "A features directory already exists. Please remove this to continue."
@@ -70,10 +71,12 @@ module Calabash
70
71
  end
71
72
  end
72
73
 
74
+ # @!visibility private
73
75
  def file(file)
74
76
  File.join(Calabash::Environment::SKELETON_DIR_PATH, file)
75
77
  end
76
78
 
79
+ # @!visibility private
77
80
  def msg(title, &block)
78
81
  puts "\n" + "-"*10 + title + "-"*10
79
82
  block.call
@@ -3,6 +3,7 @@ module Calabash
3
3
  module CLI
4
4
  # @!visibility private
5
5
  module Helpers
6
+ # @!visibility private
6
7
  HELP = {
7
8
  help: 'help',
8
9
  generate: 'generate',
@@ -14,6 +15,7 @@ module Calabash
14
15
  build: 'build <apk>'
15
16
  }
16
17
 
18
+ # @!visibility private
17
19
  def key_for_command(command)
18
20
  HELP.each do |key, value|
19
21
  if value.split(' ').first == command
@@ -24,6 +26,7 @@ module Calabash
24
26
  nil
25
27
  end
26
28
 
29
+ # @!visibility private
27
30
  def print_usage_for(key, output=STDOUT)
28
31
  if key.nil? || HELP[key].nil?
29
32
  output.write <<EOF
@@ -37,6 +40,7 @@ EOF
37
40
  end
38
41
  end
39
42
 
43
+ # @!visibility private
40
44
  def print_usage(output=STDOUT)
41
45
  output.write <<EOF
42
46
  Usage: calabash [options] <command-name> [command specific options]
@@ -64,7 +68,7 @@ EOF
64
68
  #{HELP[:resign]}
65
69
  resigns the app with the currently configured keystore.
66
70
 
67
- #{HELP[:build]}
71
+ #{HELP[:build]} [-o <output_file>]
68
72
  builds the test server that will be used when testing the app.
69
73
 
70
74
  iOS specific commands
@@ -101,11 +105,13 @@ EOF
101
105
  EOF
102
106
  end
103
107
 
108
+ # @!visibility private
104
109
  def help
105
110
  file_name = File.join(File.dirname(__FILE__), '..', 'doc', 'calabash_help.txt')
106
111
  system("less \"#{file_name}\"")
107
112
  end
108
113
 
114
+ # @!visibility private
109
115
  def fail(reason, command=nil)
110
116
  STDERR.write("#{reason}\n")
111
117
 
@@ -3,6 +3,7 @@ module Calabash
3
3
  module CLI
4
4
  # @!visibility private
5
5
  module Resign
6
+ # @!visibility private
6
7
  def parse_resign_arguments!
7
8
  fail('Can only resign Android applications') unless @platform.nil? || @platform == :android
8
9
 
@@ -3,6 +3,7 @@ module Calabash
3
3
  module CLI
4
4
  # @!visibility private
5
5
  module Run
6
+ # @!visibility private
6
7
  def parse_run_arguments!
7
8
  first_argument = @arguments.first # Do not remove the entry from the arguments yet - it might be a cucumber arg
8
9
 
@@ -30,12 +31,14 @@ module Calabash
30
31
  when '.apk'
31
32
  set_platform!(:android)
32
33
 
33
- # Create the test server if it does not exist
34
- test_server = Android::Build::TestServer.new(application_path)
34
+ unless Environment::TEST_SERVER_PATH
35
+ # Create the test server if it does not exist
36
+ test_server = Android::Build::TestServer.new(application_path)
35
37
 
36
- unless test_server.exists?
37
- Logger.info('Test server does not exist. Creating test server.')
38
- Calabash::Android::Build::Builder.new(application_path).build
38
+ unless test_server.exists?
39
+ Logger.info('Test server does not exist. Creating test server.')
40
+ Calabash::Android::Build::Builder.new(application_path).build
41
+ end
39
42
  end
40
43
 
41
44
  run(application_path, @arguments)
@@ -54,6 +57,7 @@ module Calabash
54
57
  end
55
58
  end
56
59
 
60
+ # @!visibility private
57
61
  def run(application_path, cucumber_arguments)
58
62
  cucumber_environment = {}
59
63
  cucumber_environment['CAL_DEBUG'] = Environment::DEBUG ? '1' : '0'
@@ -88,7 +92,7 @@ module Calabash
88
92
  end
89
93
  end
90
94
 
91
- arguments = ['-S', 'cucumber', '-p', @platform.to_s, *cucumber_arguments]
95
+ arguments = ['-S', 'cucumber', *cucumber_arguments]
92
96
 
93
97
  Logger.debug("Starting Ruby with arguments: #{arguments.join(', ')} and environment #{cucumber_environment.to_s}")
94
98
 
@@ -6,6 +6,7 @@ module Calabash
6
6
  module CLI
7
7
  # @!visibility private
8
8
  module SetupKeystore
9
+ # @!visibility private
9
10
  def parse_setup_keystore_arguments!
10
11
  set_platform!(:android)
11
12
 
@@ -25,6 +26,7 @@ module Calabash
25
26
  puts "Saved your settings to '#{Android::Build::JavaKeystore::CALABASH_KEYSTORE_SETTINGS_FILENAME}'. You can edit the settings manually or run this setup script again"
26
27
  end
27
28
 
29
+ # @!visibility private
28
30
  def prompt(message, secure = false)
29
31
  puts message
30
32
 
@@ -1,30 +1,37 @@
1
1
  module Calabash
2
2
  # @!visibility private
3
3
  module Color
4
+ # @!visibility private
4
5
  def self.colorize(string, color)
5
6
  "\e[#{color}m#{string}\e[0m"
6
7
  end
7
8
 
9
+ # @!visibility private
8
10
  def self.red(string)
9
11
  colorize(string, 31)
10
12
  end
11
13
 
14
+ # @!visibility private
12
15
  def self.green(string)
13
16
  colorize(string, 32)
14
17
  end
15
18
 
19
+ # @!visibility private
16
20
  def self.yellow(string)
17
21
  colorize(string, 33)
18
22
  end
19
23
 
24
+ # @!visibility private
20
25
  def self.blue(string)
21
26
  colorize(string, 34)
22
27
  end
23
28
 
29
+ # @!visibility private
24
30
  def self.magenta(string)
25
31
  colorize(string, 35)
26
32
  end
27
33
 
34
+ # @!visibility private
28
35
  def self.cyan(string)
29
36
  colorize(string, 36)
30
37
  end
@@ -1,4 +1,5 @@
1
1
  module Calabash
2
+ # Runtime defaults.
2
3
  module Defaults
3
4
  # Get the default device. The device represents a physical device,
4
5
  # an emulator, or a simulator. Calabash will communicate with this
@@ -1,4 +1,6 @@
1
1
  module Calabash
2
+ # A model of the device under test. Can be a physical Android or iOS device,
3
+ # an Android emulator, or an iOS simulator.
2
4
  class Device
3
5
  include Utility
4
6
 
@@ -251,6 +253,11 @@ module Calabash
251
253
  abstract_method!
252
254
  end
253
255
 
256
+ # @!visibility private
257
+ def keyboard_visible?
258
+ abstract_method!
259
+ end
260
+
254
261
  private
255
262
 
256
263
  # @!visibility private
@@ -11,6 +11,7 @@ module Calabash
11
11
  ENV[name]
12
12
  end
13
13
 
14
+ # @!visibility private
14
15
  # Utility method to set the value of an environment variable.
15
16
  #
16
17
  # @param [String] name of the environment variable
@@ -57,10 +57,12 @@ module Calabash
57
57
  @on_error = {}
58
58
  end
59
59
 
60
+ # @!visibility private
60
61
  def on_error(type, &block)
61
62
  @on_error[type] = block
62
63
  end
63
64
 
65
+ # @!visibility private
64
66
  def change_server(new_server)
65
67
  @server = new_server
66
68
  end
@@ -1,4 +1,5 @@
1
1
  module Calabash
2
+ # Query, flash, backdoor, etc.
2
3
  module Interactions
3
4
  # Queries the view hierarchy to find all views matching `query`.
4
5
  # Optionally query takes a variable number of “invocation” arguments
data/lib/calabash/ios.rb CHANGED
@@ -31,6 +31,7 @@ module Calabash
31
31
  require 'calabash/ios/runtime'
32
32
  require 'calabash/ios/gestures'
33
33
  require 'calabash/ios/slider'
34
+ require 'calabash/ios/date_picker'
34
35
 
35
36
  include Calabash::IOS::Conditions
36
37
  include Calabash::IOS::Orientation
@@ -41,6 +42,7 @@ module Calabash
41
42
  include Calabash::IOS::Runtime
42
43
  include Calabash::IOS::Gestures
43
44
  include Calabash::IOS::Slider
45
+ include Calabash::IOS::DatePicker
44
46
 
45
47
  end
46
48
  end
@@ -27,7 +27,7 @@ module Calabash
27
27
  application_path = Environment::APP_PATH
28
28
 
29
29
  if application_path.nil?
30
- raise 'No application path is set'
30
+ raise 'No application path is set. Specify application with environment variable CAL_APP'
31
31
  end
32
32
 
33
33
  Application.new(application_path)
@@ -67,6 +67,13 @@ module Calabash
67
67
  @device_binary
68
68
  end
69
69
 
70
+ # Is this application an iOS application
71
+ #
72
+ # @return [Boolean] Always returns true
73
+ def ios_application?
74
+ true
75
+ end
76
+
70
77
  # Returns the sha1 of the directory or binary of this app's path.
71
78
  # @return [String] A checksum.
72
79
  def sha1
@@ -1,5 +1,8 @@
1
1
  module Calabash
2
2
  module IOS
3
+
4
+ # Methods for waiting for things to happen on iOS, like animations to
5
+ # complete.
3
6
  module Conditions
4
7
 
5
8
  # Waits for all elements to stop animating.
@@ -0,0 +1,412 @@
1
+ require 'date'
2
+
3
+ module Calabash
4
+ module IOS
5
+
6
+ # A collection of methods for interacting with UIDatePicker.
7
+ module DatePicker
8
+
9
+ # The API has been tested in various time zones and tested
10
+ # once while crossing the international date line (on a boat).
11
+ # With that said, the API makes some assumptions about locales
12
+ # and time zones. It is possible to customize the ruby date
13
+ # format and Objective-C date format to get the behavior you
14
+ # need. You will need to monkey patch the following methods:
15
+ #
16
+ # * date_picker_ruby_date_format
17
+ # * date_picker_objc_date_format
18
+ #
19
+ # Before going down this path, we recommend that you ask for
20
+ # advice on the Calabash support channels.
21
+
22
+ # @!visibility private
23
+ # Provided for monkey patching, but not part of the public API.
24
+ def date_picker_ruby_date_format
25
+ RUBY_DATE_AND_TIME_FMT
26
+ end
27
+
28
+ # @!visibility private
29
+ # Provided for monkey patching, but not part of the public API.
30
+ def date_picker_objc_date_format
31
+ OBJC_DATE_AND_TIME_FMT
32
+ end
33
+
34
+ # @!visibility private
35
+ # Returns the picker mode of the first UIDatePicker match by `query`.
36
+ #
37
+ # @see #time_mode?
38
+ # @see #date_mode?
39
+ # @see #date_and_time_mode?
40
+ # @see #countdown_mode?
41
+ #
42
+ # @param [String, Hash, Calabash::Query] query A query that can be used
43
+ # to find UIDatePickers.
44
+ # @return [String] Returns the picker mode which will be one of
45
+ # `{'0', '1', '2', '3'}`
46
+ # @raise [RuntimeError] If no picker can be found.
47
+ # @raise [RuntimeError] If an unknown mode is returned.
48
+ # @raise [RuntimeError] If first view matched by query does not responde
49
+ # to 'datePickerMode'.
50
+ def date_picker_mode(query)
51
+ Query.ensure_valid_query(query)
52
+
53
+ message = "Timed out waiting for picker with #{query}"
54
+ mode = nil
55
+
56
+ wait_for(message) do
57
+ result = query(query, :datePickerMode)
58
+ if result.empty?
59
+ false
60
+ else
61
+ mode = result.first
62
+ if [0, 1, 2, 3].include?(mode)
63
+ mode
64
+ else
65
+ if mode == '*****'
66
+ raise RuntimeError,
67
+ "Query #{query} matched a view that does not respond 'datePickerMode'"
68
+ else
69
+ raise RuntimeError,
70
+ "Query #{query} returned an unknown mode '#{mode}' for 'datePickerMode'"
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ # Is the date picker in time mode?
78
+ #
79
+ # @see #time_mode?
80
+ # @see #date_mode?
81
+ # @see #date_and_time_mode?
82
+ # @see #countdown_mode?
83
+ #
84
+ # @param [String, Hash, Calabash::Query] query A query that can be used
85
+ # to find UIDatePickers.
86
+ #
87
+ # @return [Boolean] True if the picker is in time mode.
88
+ #
89
+ # @raise [RuntimeError] If no picker can be found.
90
+ # @raise [RuntimeError] If an unknown mode is returned.
91
+ # @raise [RuntimeError] If first view matched by query does not responde
92
+ # to 'datePickerMode'.
93
+ def time_mode?(query)
94
+ date_picker_mode(query) == UI_DATE_PICKER_MODE_TIME
95
+ end
96
+
97
+ # Is the date picker in date mode?
98
+ #
99
+ # @see #time_mode?
100
+ # @see #date_mode?
101
+ # @see #date_and_time_mode?
102
+ # @see #countdown_mode?
103
+ #
104
+ # @param [String, Hash, Calabash::Query] query A query that can be used
105
+ # to find UIDatePickers.
106
+ #
107
+ # @return [Boolean] True if the picker is in date mode.
108
+ #
109
+ # @raise [RuntimeError] If no picker can be found.
110
+ # @raise [RuntimeError] If an unknown mode is returned.
111
+ # @raise [RuntimeError] If first view matched by query does not responde
112
+ # to 'datePickerMode'.
113
+ def date_mode?(query)
114
+ date_picker_mode(query) == UI_DATE_PICKER_MODE_DATE
115
+ end
116
+
117
+ # Is the date picker in date and time mode?
118
+ #
119
+ # @see #time_mode?
120
+ # @see #date_mode?
121
+ # @see #date_and_time_mode?
122
+ # @see #countdown_mode?
123
+ #
124
+ # @param [String, Hash, Calabash::Query] query A query that can be used
125
+ # to find UIDatePickers.
126
+ #
127
+ # @return [Boolean] True if the picker is in date and time mode.
128
+ #
129
+ # @raise [RuntimeError] If no picker can be found.
130
+ # @raise [RuntimeError] If an unknown mode is returned.
131
+ # @raise [RuntimeError] If first view matched by query does not responde
132
+ # to 'datePickerMode'.
133
+ def date_and_time_mode?(query)
134
+ date_picker_mode(query) == UI_DATE_PICKER_MODE_DATE_AND_TIME
135
+ end
136
+
137
+ # Is the date picker in countdown mode?
138
+ #
139
+ # @see #time_mode?
140
+ # @see #date_mode?
141
+ # @see #date_and_time_mode?
142
+ # @see #countdown_mode?
143
+ #
144
+ # @param [String, Hash, Calabash::Query] query A query that can be used
145
+ # to find UIDatePickers.
146
+ #
147
+ # @return [Boolean] True if the picker is in countdown mode.
148
+ #
149
+ # @raise [RuntimeError] If no picker can be found.
150
+ # @raise [RuntimeError] If an unknown mode is returned.
151
+ # @raise [RuntimeError] If first view matched by query does not responde
152
+ # to 'datePickerMode'.
153
+ def countdown_mode?(query)
154
+ date_picker_mode(query) == UI_DATE_PICKER_MODE_COUNT_DOWN_TIMER
155
+ end
156
+
157
+ # The maximum date for a picker. If there is no maximum date, this
158
+ # method returns nil.
159
+ #
160
+ # @note
161
+ # From the Apple docs:
162
+ # `The property is an NSDate object or nil (the default)`.
163
+ #
164
+ # @param [String, Hash, Calabash::Query] query A query that can be used
165
+ # to find UIDatePickers.
166
+ #
167
+ # @return [DateTime] The maximum date on the picker or nil if no maximum
168
+ # exists
169
+ #
170
+ # @raise [RuntimeError] If the picker is in countdown mode.
171
+ # @raise [RuntimeError] If no picker can be found.
172
+ # @raise [RuntimeError] If the date returned by the server cannot be
173
+ # converted to a DateTime object.
174
+ def maximum_date_time_from_picker(query)
175
+ Query.ensure_valid_query(query)
176
+
177
+ wait_for_view(query)
178
+
179
+ if countdown_mode?(query)
180
+ fail('Countdown pickers do not have a maximum date.')
181
+ end
182
+
183
+ result = query(query, :maximumDate)
184
+
185
+ if result.empty?
186
+ fail("Expected '#{query}' to return a visible UIDatePicker")
187
+ else
188
+ if result.first.nil?
189
+ nil
190
+ else
191
+ date_str = result.first
192
+ begin
193
+ date_time = DateTime.parse(date_str)
194
+ rescue TypeError, ArgumentError => _
195
+ raise RuntimeError,
196
+ "Could not convert string '#{date_str}' into a valid DateTime object"
197
+ end
198
+ date_time
199
+ end
200
+ end
201
+ end
202
+
203
+ # The minimum date for a picker. If there is no minimum date, this
204
+ # method returns nil.
205
+ #
206
+ # @note
207
+ # From the Apple docs:
208
+ # `The property is an NSDate object or nil (the default)`.
209
+ #
210
+ # @param [String, Hash, Calabash::Query] query A query that can be used
211
+ # to find UIDatePickers.
212
+ #
213
+ # @return [DateTime] The minimum date on the picker or nil if no minimum
214
+ # exists
215
+ #
216
+ # @raise [RuntimeError] If the picker is in countdown mode.
217
+ # @raise [RuntimeError] If no picker can be found.
218
+ # @raise [RuntimeError] If the date returned by the server cannot be
219
+ # converted to a DateTime object.
220
+ def minimum_date_time_from_picker(query)
221
+ Query.ensure_valid_query(query)
222
+
223
+ wait_for_view(query)
224
+
225
+ if countdown_mode?(query)
226
+ fail('Countdown pickers do not have a minimum date.')
227
+ end
228
+
229
+ result = query(query, :minimumDate)
230
+
231
+ if result.empty?
232
+ fail("Expected '#{query}' to return a visible UIDatePicker")
233
+ else
234
+ if result.first.nil?
235
+ nil
236
+ else
237
+ date_str = result.first
238
+ begin
239
+ date_time = DateTime.parse(date_str)
240
+ rescue TypeError, ArgumentError => _
241
+ raise RuntimeError,
242
+ "Could not convert string '#{date_str}' into a valid DateTime object"
243
+ end
244
+ date_time
245
+ end
246
+ end
247
+ end
248
+
249
+ # Returns the date and time from the picker as DateTime object.
250
+ #
251
+ # @param [String, Hash, Calabash::Query] query A query that can be used
252
+ # to find UIDatePickers.
253
+ #
254
+ # @return [DateTime] The date on the picker
255
+ #
256
+ # @raise [RuntimeError] If the picker is in countdown mode.
257
+ # @raise [RuntimeError] If no picker can be found.
258
+ # @raise [RuntimeError] If the date returned by the server cannot be
259
+ # converted to a DateTime object.
260
+ def date_time_from_picker(query)
261
+ Query.ensure_valid_query(query)
262
+
263
+ wait_for_view(query)
264
+
265
+ if countdown_mode?(query)
266
+ fail('This method is available for pickers in countdown mode.')
267
+ end
268
+
269
+ result = query(query, :date)
270
+
271
+ if result.empty?
272
+ fail("Expected '#{query}' to return a visible UIDatePicker")
273
+ else
274
+ if result.first.nil?
275
+ nil
276
+ else
277
+ date_str = result.first
278
+ date_time = DateTime.parse(date_str)
279
+ if date_time.nil?
280
+ raise RuntimeError,
281
+ "Could not convert string '#{date_str}' into a valid DateTime object"
282
+ end
283
+ date_time
284
+ end
285
+ end
286
+ end
287
+
288
+ # Sets the date and time on the _first_ UIDatePicker matched by
289
+ # `query`.
290
+ #
291
+ # This method is not valid for UIDatePickers in _countdown_ mode.
292
+ #
293
+ # @param [DateTime] date_time The date and time you want to change to.
294
+ #
295
+ # @raise [RuntimeError] If `query` does match exactly one picker.
296
+ # @raise [RuntimeError] If `query` matches a picker in countdown mode.
297
+ # @raise [RuntimeError] If the target date is greater than the picker's
298
+ # maximum date.
299
+ # @raise [RuntimeError] If the target date is less than the picker's
300
+ # minimum date
301
+ # @raise [ArgumentError] If the target date is not a DateTime instance.
302
+ def picker_set_date_time(date_time)
303
+ picker_set_date_time_in("UIDatePicker index:0", date_time)
304
+ end
305
+
306
+ # Sets the date and time on the _first_ UIDatePicker matched by
307
+ # `query`.
308
+ #
309
+ # This method is not valid for UIDatePickers in _countdown_ mode.
310
+ #
311
+ # An error will be raised if more than on view is matched by `query`.
312
+ #
313
+ # To avoid matching more than one UIPickerView or subclass:
314
+ # * Make the query more specific: "UIPickerView marked:'alarm'"
315
+ # * Use the index language feature: "UIPickerView index:0"
316
+ # * Query by custom class: "view:'MyPickerView'"
317
+ #
318
+ # @param [String, Hash, Calabash::Query] query A query that can be used
319
+ # to find UIDatePickers.
320
+ # @param [DateTime] date_time The date and time you want to change to.
321
+ #
322
+ # @raise [RuntimeError] If `query` does match exactly one picker.
323
+ # @raise [RuntimeError] If `query` matches a picker in countdown mode.
324
+ # @raise [RuntimeError] If the target date is greater than the picker's
325
+ # maximum date.
326
+ # @raise [RuntimeError] If the target date is less than the picker's
327
+ # minimum date
328
+ # @raise [ArgumentError] If the target date is not a DateTime instance.
329
+ def picker_set_date_time_in(query, date_time)
330
+ unless date_time.is_a?(DateTime)
331
+ raise ArgumentError,
332
+ "Date time argument '#{date_time}' must be a DateTime but found '#{date_time.class}'"
333
+ end
334
+
335
+ Query.ensure_valid_query(query)
336
+
337
+ message = "Timed out waiting for UIDatePicker with '#{query}'"
338
+
339
+ wait_for(message) do
340
+ result = query(query)
341
+ if result.length > 1
342
+ fail("Query '#{query}' matched more than on UIDatePicker")
343
+ else
344
+ !result.empty?
345
+ end
346
+ end
347
+
348
+ if countdown_mode?(query)
349
+ message =
350
+ [
351
+ "Query '#{query}' matched a picker in countdown mode.",
352
+ 'Setting the date or time on a countdown picker is not supported'
353
+ ].join("\n")
354
+ fail(message)
355
+ end
356
+
357
+ minimum_date = minimum_date_time_from_picker(query)
358
+ if !minimum_date.nil? && minimum_date > date_time
359
+ message = [
360
+ "Cannot set the date on the picker matched by '#{query}'",
361
+ "Picker minimum date: '#{minimum_date}'",
362
+ " Date to change to: '#{date_time}'",
363
+ "Target date comes before the minimum date."].join("\n")
364
+ fail(message)
365
+ end
366
+
367
+ maximum_date = maximum_date_time_from_picker(query)
368
+ if !maximum_date.nil? && maximum_date < date_time
369
+ message = [
370
+ "Cannot set the date on the picker matched by '#{query}'",
371
+ "Picker maximum date: '#{maximum_date}'",
372
+ " Date to change to: '#{date_time}'",
373
+ "Target date comes after the maximum date."].join("\n")
374
+ fail(message)
375
+ end
376
+
377
+ ruby_format = date_picker_ruby_date_format
378
+ objc_format = date_picker_objc_date_format
379
+ target_date_string = date_time.strftime(ruby_format).squeeze(' ').strip
380
+
381
+ Device.default.map_route(query,
382
+ :changeDatePickerDate,
383
+ target_date_string,
384
+ objc_format,
385
+ # notify targets
386
+ true,
387
+ # animate
388
+ true)
389
+ end
390
+
391
+ private
392
+
393
+ # @!visibility private
394
+ OBJC_DATE_AND_TIME_FMT = 'yyyy_MM_dd_HH_mm'
395
+
396
+ # @!visibility private
397
+ RUBY_DATE_AND_TIME_FMT = '%Y_%m_%d_%H_%M'
398
+
399
+ # UIDatePicker modes
400
+
401
+ # @!visibility private
402
+ UI_DATE_PICKER_MODE_TIME = 0
403
+ # @!visibility private
404
+ UI_DATE_PICKER_MODE_DATE = 1
405
+ # @!visibility private
406
+ UI_DATE_PICKER_MODE_DATE_AND_TIME = 2
407
+ # @!visibility private
408
+ UI_DATE_PICKER_MODE_COUNT_DOWN_TIMER = 3
409
+ end
410
+ end
411
+ end
412
+