iCuke 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/.gitignore +13 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +69 -0
  4. data/Rakefile +80 -0
  5. data/VERSION +1 -0
  6. data/app/iCuke/.gitignore +1 -0
  7. data/app/iCuke/Classes/FlipsideView.h +13 -0
  8. data/app/iCuke/Classes/FlipsideView.m +32 -0
  9. data/app/iCuke/Classes/FlipsideViewController.h +25 -0
  10. data/app/iCuke/Classes/FlipsideViewController.m +54 -0
  11. data/app/iCuke/Classes/MainView.h +15 -0
  12. data/app/iCuke/Classes/MainView.m +32 -0
  13. data/app/iCuke/Classes/MainViewController.h +16 -0
  14. data/app/iCuke/Classes/MainViewController.m +86 -0
  15. data/app/iCuke/Classes/iCukeAppDelegate.h +20 -0
  16. data/app/iCuke/Classes/iCukeAppDelegate.m +33 -0
  17. data/app/iCuke/FlipsideView.xib +444 -0
  18. data/app/iCuke/MainView.xib +520 -0
  19. data/app/iCuke/MainWindow.xib +355 -0
  20. data/app/iCuke/SniffingView.h +20 -0
  21. data/app/iCuke/SniffingView.m +191 -0
  22. data/app/iCuke/iCuke-Info.plist +30 -0
  23. data/app/iCuke/iCuke.xcodeproj/project.pbxproj +313 -0
  24. data/app/iCuke/iCuke_Prefix.pch +14 -0
  25. data/app/iCuke/main.m +16 -0
  26. data/ext/iCuke/.gitignore +2 -0
  27. data/ext/iCuke/DefaultsResponse.h +5 -0
  28. data/ext/iCuke/DefaultsResponse.m +67 -0
  29. data/ext/iCuke/EventResponse.h +5 -0
  30. data/ext/iCuke/EventResponse.m +122 -0
  31. data/ext/iCuke/Rakefile +22 -0
  32. data/ext/iCuke/Recorder.h +15 -0
  33. data/ext/iCuke/Recorder.m +85 -0
  34. data/ext/iCuke/RecorderResponse.h +5 -0
  35. data/ext/iCuke/RecorderResponse.m +59 -0
  36. data/ext/iCuke/SynthesizeSingleton.h +68 -0
  37. data/ext/iCuke/ViewResponse.h +5 -0
  38. data/ext/iCuke/ViewResponse.m +84 -0
  39. data/ext/iCuke/Viewer.h +8 -0
  40. data/ext/iCuke/Viewer.m +153 -0
  41. data/ext/iCuke/iCukeHTTPResponseHandler.h +50 -0
  42. data/ext/iCuke/iCukeHTTPResponseHandler.m +381 -0
  43. data/ext/iCuke/iCukeHTTPServer.h +53 -0
  44. data/ext/iCuke/iCukeHTTPServer.m +365 -0
  45. data/ext/iCuke/iCukeServer.h +16 -0
  46. data/ext/iCuke/iCukeServer.m +46 -0
  47. data/ext/iCuke/json/JSON.h +50 -0
  48. data/ext/iCuke/json/NSObject+SBJSON.h +68 -0
  49. data/ext/iCuke/json/NSObject+SBJSON.m +53 -0
  50. data/ext/iCuke/json/NSString+SBJSON.h +58 -0
  51. data/ext/iCuke/json/NSString+SBJSON.m +55 -0
  52. data/ext/iCuke/json/SBJSON.h +75 -0
  53. data/ext/iCuke/json/SBJSON.m +212 -0
  54. data/ext/iCuke/json/SBJsonBase.h +86 -0
  55. data/ext/iCuke/json/SBJsonBase.m +78 -0
  56. data/ext/iCuke/json/SBJsonParser.h +87 -0
  57. data/ext/iCuke/json/SBJsonParser.m +475 -0
  58. data/ext/iCuke/json/SBJsonWriter.h +129 -0
  59. data/ext/iCuke/json/SBJsonWriter.m +228 -0
  60. data/features/icuke.feature +17 -0
  61. data/features/support/env.rb +3 -0
  62. data/iCuke.gemspec +113 -0
  63. data/lib/icuke/cucumber.rb +211 -0
  64. data/lib/icuke/simulate.rb +132 -0
  65. data/lib/icuke/simulator.rb +107 -0
  66. data/lib/icuke.rb +1 -0
  67. metadata +163 -0
@@ -0,0 +1,228 @@
1
+ /*
2
+ Copyright (C) 2009 Stig Brautaset. All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of the author nor the names of its contributors may be used
15
+ to endorse or promote products derived from this software without specific
16
+ prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+ */
29
+
30
+ #import "SBJsonWriter.h"
31
+
32
+ @interface SBJsonWriter ()
33
+
34
+ - (BOOL)appendValue:(id)fragment into:(NSMutableString*)json;
35
+ - (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json;
36
+ - (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json;
37
+ - (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json;
38
+
39
+ - (NSString*)indent;
40
+
41
+ @end
42
+
43
+ @implementation SBJsonWriter
44
+
45
+ @synthesize sortKeys;
46
+ @synthesize humanReadable;
47
+
48
+ /**
49
+ @deprecated This exists in order to provide fragment support in older APIs in one more version.
50
+ It should be removed in the next major version.
51
+ */
52
+ - (NSString*)stringWithFragment:(id)value {
53
+ [self clearErrorTrace];
54
+ depth = 0;
55
+ NSMutableString *json = [NSMutableString stringWithCapacity:128];
56
+
57
+ if ([self appendValue:value into:json])
58
+ return json;
59
+
60
+ return nil;
61
+ }
62
+
63
+
64
+ - (NSString*)stringWithObject:(id)value {
65
+
66
+ if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) {
67
+ return [self stringWithFragment:value];
68
+ }
69
+
70
+ [self clearErrorTrace];
71
+ [self addErrorWithCode:EFRAGMENT description:@"Not valid type for JSON"];
72
+ return nil;
73
+ }
74
+
75
+
76
+ - (NSString*)indent {
77
+ return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0];
78
+ }
79
+
80
+ - (BOOL)appendValue:(id)fragment into:(NSMutableString*)json {
81
+ if ([fragment isKindOfClass:[NSDictionary class]]) {
82
+ if (![self appendDictionary:fragment into:json])
83
+ return NO;
84
+
85
+ } else if ([fragment isKindOfClass:[NSArray class]]) {
86
+ if (![self appendArray:fragment into:json])
87
+ return NO;
88
+
89
+ } else if ([fragment isKindOfClass:[NSString class]]) {
90
+ if (![self appendString:fragment into:json])
91
+ return NO;
92
+
93
+ } else if ([fragment isKindOfClass:[NSNumber class]]) {
94
+ if ('c' == *[fragment objCType])
95
+ [json appendString:[fragment boolValue] ? @"true" : @"false"];
96
+ else
97
+ [json appendString:[fragment stringValue]];
98
+
99
+ } else if ([fragment isKindOfClass:[NSNull class]]) {
100
+ [json appendString:@"null"];
101
+ } else if ([fragment respondsToSelector:@selector(proxyForJson)]) {
102
+ [self appendValue:[fragment proxyForJson] into:json];
103
+
104
+ } else {
105
+ [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]];
106
+ return NO;
107
+ }
108
+ return YES;
109
+ }
110
+
111
+ - (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json {
112
+ if (maxDepth && ++depth > maxDepth) {
113
+ [self addErrorWithCode:EDEPTH description: @"Nested too deep"];
114
+ return NO;
115
+ }
116
+ [json appendString:@"["];
117
+
118
+ BOOL addComma = NO;
119
+ for (id value in fragment) {
120
+ if (addComma)
121
+ [json appendString:@","];
122
+ else
123
+ addComma = YES;
124
+
125
+ if ([self humanReadable])
126
+ [json appendString:[self indent]];
127
+
128
+ if (![self appendValue:value into:json]) {
129
+ return NO;
130
+ }
131
+ }
132
+
133
+ depth--;
134
+ if ([self humanReadable] && [fragment count])
135
+ [json appendString:[self indent]];
136
+ [json appendString:@"]"];
137
+ return YES;
138
+ }
139
+
140
+ - (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json {
141
+ if (maxDepth && ++depth > maxDepth) {
142
+ [self addErrorWithCode:EDEPTH description: @"Nested too deep"];
143
+ return NO;
144
+ }
145
+ [json appendString:@"{"];
146
+
147
+ NSString *colon = [self humanReadable] ? @" : " : @":";
148
+ BOOL addComma = NO;
149
+ NSArray *keys = [fragment allKeys];
150
+ if (self.sortKeys)
151
+ keys = [keys sortedArrayUsingSelector:@selector(compare:)];
152
+
153
+ for (id value in keys) {
154
+ if (addComma)
155
+ [json appendString:@","];
156
+ else
157
+ addComma = YES;
158
+
159
+ if ([self humanReadable])
160
+ [json appendString:[self indent]];
161
+
162
+ if (![value isKindOfClass:[NSString class]]) {
163
+ [self addErrorWithCode:EUNSUPPORTED description: @"JSON object key must be string"];
164
+ return NO;
165
+ }
166
+
167
+ if (![self appendString:value into:json])
168
+ return NO;
169
+
170
+ [json appendString:colon];
171
+ if (![self appendValue:[fragment objectForKey:value] into:json]) {
172
+ [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"Unsupported value for key %@ in object", value]];
173
+ return NO;
174
+ }
175
+ }
176
+
177
+ depth--;
178
+ if ([self humanReadable] && [fragment count])
179
+ [json appendString:[self indent]];
180
+ [json appendString:@"}"];
181
+ return YES;
182
+ }
183
+
184
+ - (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json {
185
+
186
+ static NSMutableCharacterSet *kEscapeChars;
187
+ if( ! kEscapeChars ) {
188
+ kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain];
189
+ [kEscapeChars addCharactersInString: @"\"\\"];
190
+ }
191
+
192
+ [json appendString:@"\""];
193
+
194
+ NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars];
195
+ if ( !esc.length ) {
196
+ // No special chars -- can just add the raw string:
197
+ [json appendString:fragment];
198
+
199
+ } else {
200
+ NSUInteger length = [fragment length];
201
+ for (NSUInteger i = 0; i < length; i++) {
202
+ unichar uc = [fragment characterAtIndex:i];
203
+ switch (uc) {
204
+ case '"': [json appendString:@"\\\""]; break;
205
+ case '\\': [json appendString:@"\\\\"]; break;
206
+ case '\t': [json appendString:@"\\t"]; break;
207
+ case '\n': [json appendString:@"\\n"]; break;
208
+ case '\r': [json appendString:@"\\r"]; break;
209
+ case '\b': [json appendString:@"\\b"]; break;
210
+ case '\f': [json appendString:@"\\f"]; break;
211
+ default:
212
+ if (uc < 0x20) {
213
+ [json appendFormat:@"\\u%04x", uc];
214
+ } else {
215
+ CFStringAppendCharacters((CFMutableStringRef)json, &uc, 1);
216
+ }
217
+ break;
218
+
219
+ }
220
+ }
221
+ }
222
+
223
+ [json appendString:@"\""];
224
+ return YES;
225
+ }
226
+
227
+
228
+ @end
@@ -0,0 +1,17 @@
1
+ Feature: Developer tests an application
2
+ In order to test my application
3
+ As a developer
4
+ I want to use some shiney cucumber steps
5
+
6
+ Background:
7
+ Given "iCuke" from "app/iCuke/iCuke.xcodeproj" is loaded in the simulator
8
+
9
+ Scenario: Press buttons and see stuff
10
+ Then I should see "iCuke"
11
+ When I tap "About"
12
+ Then I should see "About"
13
+ When I tap "Done"
14
+ Then I should see "iCuke"
15
+
16
+ Scenario: Type into a textfield
17
+ Then I type "fOo@Oo !# 54 x" in "Input"
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+
3
+ require 'icuke/cucumber'
data/iCuke.gemspec ADDED
@@ -0,0 +1,113 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{iCuke}
8
+ s.version = "0.4.5"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Rob Holland"]
12
+ s.date = %q{2010-04-21}
13
+ s.description = %q{Cucumber support for iPhone applications}
14
+ s.email = %q{rob@the-it-refinery.co.uk}
15
+ s.extensions = ["ext/iCuke/Rakefile"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE",
18
+ "README.rdoc"
19
+ ]
20
+ s.files = [
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "app/iCuke/.gitignore",
27
+ "app/iCuke/Classes/FlipsideView.h",
28
+ "app/iCuke/Classes/FlipsideView.m",
29
+ "app/iCuke/Classes/FlipsideViewController.h",
30
+ "app/iCuke/Classes/FlipsideViewController.m",
31
+ "app/iCuke/Classes/MainView.h",
32
+ "app/iCuke/Classes/MainView.m",
33
+ "app/iCuke/Classes/MainViewController.h",
34
+ "app/iCuke/Classes/MainViewController.m",
35
+ "app/iCuke/Classes/iCukeAppDelegate.h",
36
+ "app/iCuke/Classes/iCukeAppDelegate.m",
37
+ "app/iCuke/FlipsideView.xib",
38
+ "app/iCuke/MainView.xib",
39
+ "app/iCuke/MainWindow.xib",
40
+ "app/iCuke/SniffingView.h",
41
+ "app/iCuke/SniffingView.m",
42
+ "app/iCuke/iCuke-Info.plist",
43
+ "app/iCuke/iCuke.xcodeproj/project.pbxproj",
44
+ "app/iCuke/iCuke_Prefix.pch",
45
+ "app/iCuke/main.m",
46
+ "ext/iCuke/.gitignore",
47
+ "ext/iCuke/DefaultsResponse.h",
48
+ "ext/iCuke/DefaultsResponse.m",
49
+ "ext/iCuke/EventResponse.h",
50
+ "ext/iCuke/EventResponse.m",
51
+ "ext/iCuke/Rakefile",
52
+ "ext/iCuke/Recorder.h",
53
+ "ext/iCuke/Recorder.m",
54
+ "ext/iCuke/RecorderResponse.h",
55
+ "ext/iCuke/RecorderResponse.m",
56
+ "ext/iCuke/SynthesizeSingleton.h",
57
+ "ext/iCuke/ViewResponse.h",
58
+ "ext/iCuke/ViewResponse.m",
59
+ "ext/iCuke/Viewer.h",
60
+ "ext/iCuke/Viewer.m",
61
+ "ext/iCuke/iCukeHTTPResponseHandler.h",
62
+ "ext/iCuke/iCukeHTTPResponseHandler.m",
63
+ "ext/iCuke/iCukeHTTPServer.h",
64
+ "ext/iCuke/iCukeHTTPServer.m",
65
+ "ext/iCuke/iCukeServer.h",
66
+ "ext/iCuke/iCukeServer.m",
67
+ "ext/iCuke/json/JSON.h",
68
+ "ext/iCuke/json/NSObject+SBJSON.h",
69
+ "ext/iCuke/json/NSObject+SBJSON.m",
70
+ "ext/iCuke/json/NSString+SBJSON.h",
71
+ "ext/iCuke/json/NSString+SBJSON.m",
72
+ "ext/iCuke/json/SBJSON.h",
73
+ "ext/iCuke/json/SBJSON.m",
74
+ "ext/iCuke/json/SBJsonBase.h",
75
+ "ext/iCuke/json/SBJsonBase.m",
76
+ "ext/iCuke/json/SBJsonParser.h",
77
+ "ext/iCuke/json/SBJsonParser.m",
78
+ "ext/iCuke/json/SBJsonWriter.h",
79
+ "ext/iCuke/json/SBJsonWriter.m",
80
+ "features/icuke.feature",
81
+ "features/support/env.rb",
82
+ "iCuke.gemspec",
83
+ "lib/icuke.rb",
84
+ "lib/icuke/cucumber.rb",
85
+ "lib/icuke/simulate.rb",
86
+ "lib/icuke/simulator.rb"
87
+ ]
88
+ s.homepage = %q{http://github.com/unboxed/iCuke}
89
+ s.rdoc_options = ["--charset=UTF-8"]
90
+ s.require_paths = ["lib"]
91
+ s.rubygems_version = %q{1.3.6}
92
+ s.summary = %q{Cucumber support for iPhone applications}
93
+
94
+ if s.respond_to? :specification_version then
95
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
96
+ s.specification_version = 3
97
+
98
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
99
+ s.add_development_dependency(%q<cucumber>, [">= 0"])
100
+ s.add_runtime_dependency(%q<rb-appscript>, [">= 0"])
101
+ s.add_runtime_dependency(%q<httparty>, [">= 0"])
102
+ else
103
+ s.add_dependency(%q<cucumber>, [">= 0"])
104
+ s.add_dependency(%q<rb-appscript>, [">= 0"])
105
+ s.add_dependency(%q<httparty>, [">= 0"])
106
+ end
107
+ else
108
+ s.add_dependency(%q<cucumber>, [">= 0"])
109
+ s.add_dependency(%q<rb-appscript>, [">= 0"])
110
+ s.add_dependency(%q<httparty>, [">= 0"])
111
+ end
112
+ end
113
+
@@ -0,0 +1,211 @@
1
+ require 'nokogiri'
2
+
3
+ require 'icuke/simulator'
4
+ require 'icuke/simulate'
5
+
6
+ class ICukeWorld
7
+ include ICuke::Simulate::Gestures
8
+
9
+ attr_reader :response
10
+
11
+ def initialize
12
+ @simulator = ICuke::Simulator.new
13
+ end
14
+
15
+ def launch(application, options = {})
16
+ @simulator.launch(application, options)
17
+ end
18
+
19
+ def quit
20
+ @simulator.quit
21
+ end
22
+
23
+ def page
24
+ @xml ||= Nokogiri::XML::Document.parse(response).root
25
+ end
26
+
27
+ def response
28
+ @response ||= @simulator.view
29
+ end
30
+
31
+ def record
32
+ @simulator.record
33
+ end
34
+
35
+ def can_see?(text)
36
+ page.xpath(%Q{//*[contains(., "#{text}") or contains(@label, "#{text}") or contains(@value, "#{text}")]}).any?
37
+ end
38
+
39
+ def onscreen?(x, y)
40
+ return x >= 0 && y >= 0 && x < 320 && y < 480
41
+ end
42
+
43
+ def tap(label, options = {}, &block)
44
+ options = {
45
+ :pause => true
46
+ }.merge(options)
47
+
48
+ element =
49
+ page.xpath(
50
+ %Q{//*[#{trait(:button, :updates_frequently, :keyboard_key)} and @label="#{label}" and frame]},
51
+ %Q{//*[#{trait(:link)} and @value="#{label}" and frame]}
52
+ ).first
53
+
54
+ raise %Q{No element labelled "#{label}" found in: #{response}} unless element
55
+
56
+ # This seems brittle, revist how to fetch the frame without relying on it being the only child
57
+ frame = element.child
58
+
59
+ # Hit the element in the middle
60
+ x = frame['x'].to_f + (frame['width'].to_f / 2)
61
+ y = frame['y'].to_f + (frame['height'].to_f / 2)
62
+
63
+ raise %Q{Element "#{label}" is off screen in: #{response}} unless onscreen?(x, y)
64
+
65
+ @simulator.fire_event(Tap.new(x, y, options))
66
+
67
+ sleep(options[:pause] ? 1 : 0.2)
68
+
69
+ refresh
70
+
71
+ yield element if block_given?
72
+ end
73
+
74
+ def swipe(direction, options = {})
75
+ modifier = [:up, :left].include?(direction) ? -1 : 1
76
+
77
+ # Just swipe from the middle of an iPhone-dimensioned screen for now
78
+ x = 320 / 2
79
+ y = 480 / 2
80
+ x2 = x
81
+ y2 = y
82
+
83
+ if [:up, :down].include?(direction)
84
+ y2 = y + (100 * modifier)
85
+ else
86
+ x2 = x + (100 * modifier)
87
+ end
88
+
89
+ @simulator.fire_event(Swipe.new(x, y, x2, y2, options))
90
+
91
+ sleep(1)
92
+
93
+ refresh
94
+ end
95
+
96
+ def type(textfield, text, options = {})
97
+ tap(textfield, :hold_for => 0.75) do |field|
98
+ if field['value']
99
+ tap('Select All')
100
+ tap('Delete')
101
+ end
102
+ end
103
+
104
+ text.split('').each do |c|
105
+ begin
106
+ tap(c == ' ' ? 'space' : c, :pause => false)
107
+ rescue Exception => e
108
+ try_keyboards =
109
+ case c
110
+ when /[a-zA-Z]/
111
+ ['more, letters', 'shift']
112
+ when /[0-9]/
113
+ ['more, numbers']
114
+ else
115
+ ['more, numbers', 'more, symbols']
116
+ end
117
+ until try_keyboards.empty?
118
+ begin
119
+ tap(try_keyboards.shift, :pause => false)
120
+ retry
121
+ rescue
122
+ end
123
+ end
124
+ raise e
125
+ end
126
+ end
127
+
128
+ # From UIReturnKeyType
129
+ # Should probably sort these in rough order of likelyhood?
130
+ return_keys = ['return', 'go', 'google', 'join', 'next', 'route', 'search', 'send', 'yahoo', 'done', 'emergency call']
131
+ return_keys.each do |key|
132
+ begin
133
+ tap(key)
134
+ return
135
+ rescue
136
+ end
137
+ end
138
+ end
139
+
140
+ def scroll_to(text, options = {})
141
+ previous_response = response.dup
142
+ while page.xpath(%Q{//*[contains(., "#{text}") or contains(@label, "#{text}") or contains(@value, "#{text}")]}).empty? do
143
+ scroll(options[:direction])
144
+ raise %Q{Content "#{text}" not found in: #{response}} if response == previous_response
145
+ end
146
+ end
147
+
148
+ def scroll(direction)
149
+ swipe_directions = { :up => :down, :down => :up, :left => :right, :right => :left }
150
+ swipe(swipe_directions[direction])
151
+ end
152
+
153
+ def set_application_defaults(defaults)
154
+ @simulator.set_defaults(defaults)
155
+ end
156
+
157
+ private
158
+
159
+ def trait(*traits)
160
+ "(#{traits.map { |t| %Q{contains(@traits, "#{t}")} }.join(' or ')})"
161
+ end
162
+
163
+ def refresh
164
+ @response = nil
165
+ @xml = nil
166
+ end
167
+ end
168
+
169
+ World do
170
+ ICukeWorld.new
171
+ end
172
+
173
+ LIBICUKE = File.expand_path(File.dirname(__FILE__) + '/../../ext/iCuke/libicuke.dylib')
174
+
175
+ Given /^(?:"([^\"]*)" from )?"([^\"]*)" is loaded in the simulator(?: using sdk (.*))?$/ do |target, project, sdk|
176
+ launch File.expand_path(project),
177
+ :target => target,
178
+ :env => { 'DYLD_INSERT_LIBRARIES' => LIBICUKE }
179
+ end
180
+
181
+ Then /^I should see "([^\"]*)"$/ do |text|
182
+ raise %Q{Content "#{text}" not found in: #{response}} unless can_see?(text)
183
+ end
184
+
185
+ Then /^I should not see "([^\"]*)"$/ do |text|
186
+ raise %Q{Content "#{text}" was found but was not expected in: #{response}} if can_see?(text)
187
+ end
188
+
189
+ When /^I tap "([^\"]*)"$/ do |label|
190
+ tap(label)
191
+ end
192
+
193
+ When /^I type "([^\"]*)" in "([^\"]*)"$/ do |text, textfield|
194
+ type(textfield, text)
195
+ end
196
+
197
+ When /^I scroll (down|up|left|right)(?: to "([^\"]*)")?$/ do |direction, text|
198
+ if text
199
+ scroll_to(text, :direction => direction.to_sym)
200
+ else
201
+ scroll(direction.to_sym)
202
+ end
203
+ end
204
+
205
+ Then /^I put the phone into recording mode$/ do
206
+ record
207
+ end
208
+
209
+ Then /^show me the screen$/ do
210
+ puts response
211
+ end
@@ -0,0 +1,132 @@
1
+ require 'json'
2
+
3
+ module ICuke
4
+ module Simulate
5
+ module Events
6
+ class Event
7
+ attr_reader :hold_for, :event_time, :options
8
+
9
+ def initialize(options = {})
10
+ @options = options
11
+ @hold_for = options[:hold_for] || 0.2
12
+
13
+ @event_time = self.class.event_time
14
+
15
+ self.class.hold_for(hold_for)
16
+ end
17
+
18
+ def self.event_time
19
+ @time.to_i
20
+ end
21
+
22
+ def self.hold_for(seconds)
23
+ @time = @time.to_i + (seconds * 1_000_000_000)
24
+ end
25
+ end
26
+
27
+ class Touch < Event
28
+ GSEventHand = 3001
29
+ GSTouchDown = 1
30
+ GSTouchUp = 6
31
+ GSTouchMoved = 2
32
+
33
+ TYPE = {
34
+ :down => GSTouchDown,
35
+ :up => GSTouchUp,
36
+ :moved => GSTouchMoved
37
+ }.freeze
38
+
39
+ attr_accessor :type, :paths
40
+
41
+ def initialize(type, paths, options = {})
42
+ super(options)
43
+
44
+ @type = type
45
+ @paths = paths
46
+ end
47
+
48
+ def averageX
49
+ paths.inject(0) { |s,p| s + p[0] } / paths.size
50
+ end
51
+
52
+ def averageY
53
+ paths.inject(0) { |s,p| s + p[1] } / paths.size
54
+ end
55
+
56
+ def to_json(*a)
57
+ {
58
+ 'Type' => GSEventHand,
59
+ 'Time' => event_time,
60
+ 'WindowLocation' => { 'X' => averageX, 'Y' => averageY },
61
+ 'Data' => {
62
+ 'Delta' => { 'X' => paths.size, 'Y' => paths.size },
63
+ 'Type' => TYPE[type],
64
+ 'Paths' => paths.map { |p|
65
+ {
66
+ 'Location' => { 'X' => p[0], 'Y' => p[1] },
67
+ 'Size' => { 'X' => 1.0, 'Y' => 1.0 }
68
+ }
69
+ }
70
+ }
71
+ }.to_json(*a)
72
+ end
73
+ end
74
+ end
75
+
76
+ module Gestures
77
+ class Tap
78
+ attr_accessor :x, :y, :options
79
+
80
+ def initialize(x, y, options = {})
81
+ @options = options
82
+
83
+ @x = x
84
+ @y = y
85
+ end
86
+
87
+ def to_json(*a)
88
+ [
89
+ ICuke::Simulate::Events::Touch.new(:down, [[x, y]], options),
90
+ ICuke::Simulate::Events::Touch.new(:up, [[x, y]])
91
+ ].to_json(*a)
92
+ end
93
+ end
94
+
95
+ class Swipe
96
+ attr_accessor :x, :y, :x2, :y2, :options
97
+
98
+ def initialize(x, y, x2, y2, options)
99
+ @options = options
100
+
101
+ @x = x
102
+ @y = y
103
+ @x2 = x2
104
+ @y2 = y2
105
+ end
106
+
107
+ def to_json(*a)
108
+ events = [ICuke::Simulate::Events::Touch.new(:down, [[x, y]], options.merge(:hold_for => 0.015))]
109
+ each_point([x, y], [x2, y2], 25) do |ix, iy|
110
+ events << ICuke::Simulate::Events::Touch.new(:moved, [[ix, iy]], :hold_for => 0.015)
111
+ end
112
+ events << ICuke::Simulate::Events::Touch.new(:up, [[x2, y2]], :hold_for => 0.015)
113
+ events.to_json(*a)
114
+ end
115
+
116
+ private
117
+
118
+ def each_point(start, finish, max_step)
119
+ dx = finish[0] - start[0]
120
+ dy = finish[1] - start[1]
121
+ hypotenuse = Math.sqrt(dx*dx + dy*dy)
122
+ step = max_step / hypotenuse
123
+ steps = hypotenuse / max_step
124
+
125
+ 1.upto(steps) do |i|
126
+ yield start[0] + i * step * dx, start[1] + i * step * dy
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end