win_gui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +43 -0
  5. data/Rakefile +58 -0
  6. data/VERSION +1 -0
  7. data/book_code/early_success/bundle.rb +34 -0
  8. data/book_code/early_success/english.txt +1 -0
  9. data/book_code/early_success/jruby_basics.rb +47 -0
  10. data/book_code/early_success/windows_basics.rb +97 -0
  11. data/book_code/guessing/locknote.rb +379 -0
  12. data/book_code/guessing/monkeyshines.rb +14 -0
  13. data/book_code/guessing/note.rb +120 -0
  14. data/book_code/guessing/note_spec.rb +175 -0
  15. data/book_code/guessing/replay.rb +21 -0
  16. data/book_code/guessing/seed.rb +9 -0
  17. data/book_code/guessing/spec_helper.rb +69 -0
  18. data/book_code/guessing/windows_gui.rb +247 -0
  19. data/book_code/home_stretch/junquenote.rb +151 -0
  20. data/book_code/home_stretch/locknote.rb +180 -0
  21. data/book_code/home_stretch/note.rb +144 -0
  22. data/book_code/home_stretch/note_spec.rb +191 -0
  23. data/book_code/home_stretch/spec_helper.rb +55 -0
  24. data/book_code/home_stretch/swing_gui.rb +50 -0
  25. data/book_code/home_stretch/windows_gui.rb +232 -0
  26. data/book_code/junquenote/exports.sh +10 -0
  27. data/book_code/junquenote/jruby_mac.sh +10 -0
  28. data/book_code/junquenote/junquenote_app.rb +262 -0
  29. data/book_code/novite/Rakefile +10 -0
  30. data/book_code/novite/app/controllers/application.rb +18 -0
  31. data/book_code/novite/app/controllers/guests_controller.rb +28 -0
  32. data/book_code/novite/app/controllers/parties_controller.rb +77 -0
  33. data/book_code/novite/app/helpers/application_helper.rb +11 -0
  34. data/book_code/novite/app/helpers/guests_helper.rb +10 -0
  35. data/book_code/novite/app/helpers/parties_helper.rb +10 -0
  36. data/book_code/novite/app/models/guest.rb +11 -0
  37. data/book_code/novite/app/models/party.rb +32 -0
  38. data/book_code/novite/app/models/party_mailer.rb +19 -0
  39. data/book_code/novite/app/views/layouts/application.rhtml +44 -0
  40. data/book_code/novite/app/views/parties/new.html.erb +42 -0
  41. data/book_code/novite/app/views/parties/show.html.erb +43 -0
  42. data/book_code/novite/app/views/party_mailer/invite.erb +17 -0
  43. data/book_code/novite/config/boot.rb +117 -0
  44. data/book_code/novite/config/database.yml +19 -0
  45. data/book_code/novite/config/environment.rb +67 -0
  46. data/book_code/novite/config/environments/development.rb +29 -0
  47. data/book_code/novite/config/environments/production.rb +27 -0
  48. data/book_code/novite/config/environments/test.rb +30 -0
  49. data/book_code/novite/config/initializers/inflections.rb +18 -0
  50. data/book_code/novite/config/initializers/mime_types.rb +13 -0
  51. data/book_code/novite/config/routes.rb +47 -0
  52. data/book_code/novite/db/migrate/001_create_parties.rb +26 -0
  53. data/book_code/novite/db/migrate/002_create_guests.rb +23 -0
  54. data/book_code/novite/db/schema.rb +41 -0
  55. data/book_code/novite/log/empty.txt +0 -0
  56. data/book_code/novite/public/.htaccess +40 -0
  57. data/book_code/novite/public/404.html +38 -0
  58. data/book_code/novite/public/422.html +38 -0
  59. data/book_code/novite/public/500.html +38 -0
  60. data/book_code/novite/public/dispatch.cgi +10 -0
  61. data/book_code/novite/public/dispatch.fcgi +24 -0
  62. data/book_code/novite/public/dispatch.rb +18 -0
  63. data/book_code/novite/public/favicon.ico +0 -0
  64. data/book_code/novite/public/images/rails.png +0 -0
  65. data/book_code/novite/public/index.html +285 -0
  66. data/book_code/novite/public/javascripts/application.js +10 -0
  67. data/book_code/novite/public/javascripts/controls.js +971 -0
  68. data/book_code/novite/public/javascripts/dragdrop.js +980 -0
  69. data/book_code/novite/public/javascripts/effects.js +1128 -0
  70. data/book_code/novite/public/javascripts/prototype.js +4233 -0
  71. data/book_code/novite/public/robots.txt +5 -0
  72. data/book_code/novite/script/about +3 -0
  73. data/book_code/novite/script/console +3 -0
  74. data/book_code/novite/script/destroy +3 -0
  75. data/book_code/novite/script/generate +3 -0
  76. data/book_code/novite/script/performance/benchmarker +3 -0
  77. data/book_code/novite/script/performance/profiler +3 -0
  78. data/book_code/novite/script/performance/request +3 -0
  79. data/book_code/novite/script/plugin +3 -0
  80. data/book_code/novite/script/process/inspector +3 -0
  81. data/book_code/novite/script/process/reaper +3 -0
  82. data/book_code/novite/script/process/spawner +3 -0
  83. data/book_code/novite/script/runner +3 -0
  84. data/book_code/novite/script/server +3 -0
  85. data/book_code/novite/test/test_helper.rb +46 -0
  86. data/book_code/one_more_thing/applescript.rb +68 -0
  87. data/book_code/one_more_thing/note_spec.rb +50 -0
  88. data/book_code/one_more_thing/spec_helper.rb +17 -0
  89. data/book_code/one_more_thing/textedit-pure.rb +28 -0
  90. data/book_code/one_more_thing/textedit.applescript +26 -0
  91. data/book_code/one_more_thing/textedit.rb +32 -0
  92. data/book_code/one_more_thing/textnote.rb +87 -0
  93. data/book_code/simplify/junquenote.rb +48 -0
  94. data/book_code/simplify/locknote.rb +46 -0
  95. data/book_code/simplify/note.rb +35 -0
  96. data/book_code/simplify/note_spec.rb +28 -0
  97. data/book_code/simplify/swing_gui.rb +45 -0
  98. data/book_code/simplify/windows_gui.rb +232 -0
  99. data/book_code/simplify/windows_gui_spec.rb +35 -0
  100. data/book_code/story/invite.story +19 -0
  101. data/book_code/story/journal.txt +29 -0
  102. data/book_code/story/novite_stories.rb +156 -0
  103. data/book_code/story/party.rb +149 -0
  104. data/book_code/story/password.rb +61 -0
  105. data/book_code/story/password.story +26 -0
  106. data/book_code/story/rsvp.story +29 -0
  107. data/book_code/tables/TestTime.html +93 -0
  108. data/book_code/tables/TestTimeSample.html +63 -0
  109. data/book_code/tables/calculate_time.rb +39 -0
  110. data/book_code/tables/calculator.rb +108 -0
  111. data/book_code/tables/calculator_actions.rb +27 -0
  112. data/book_code/tables/calculator_spec.rb +47 -0
  113. data/book_code/tables/fit.rb +32 -0
  114. data/book_code/tables/matrix.rb +109 -0
  115. data/book_code/tables/pseudocode.rb +17 -0
  116. data/book_code/tubes/book_selenium.rb +67 -0
  117. data/book_code/tubes/book_watir.rb +60 -0
  118. data/book_code/tubes/dragdrop.html +81 -0
  119. data/book_code/tubes/html_capture.rb +33 -0
  120. data/book_code/tubes/joke_list.rb +67 -0
  121. data/book_code/tubes/list_spec.rb +41 -0
  122. data/book_code/tubes/search_spec.rb +32 -0
  123. data/book_code/tubes/selenium_example.rb +66 -0
  124. data/book_code/tubes/selenium_link.rb +23 -0
  125. data/book_code/tubes/web_server.rb +14 -0
  126. data/book_code/windows/wgui.rb +29 -0
  127. data/book_code/windows/wobj.rb +25 -0
  128. data/book_code/windows/wsh.rb +25 -0
  129. data/book_code/with_rspec/empty_spec.rb +13 -0
  130. data/book_code/with_rspec/junquenote.rb +60 -0
  131. data/book_code/with_rspec/locknote.rb +129 -0
  132. data/book_code/with_rspec/note_spec.rb +32 -0
  133. data/book_code/with_rspec/should_examples.rb +18 -0
  134. data/exp/exp.rb +6 -0
  135. data/exp/exp_encodings.rb +40 -0
  136. data/exp/exp_enum_windows.rb +60 -0
  137. data/exp/exp_quik.rb +38 -0
  138. data/exp/exp_wsh.rb +115 -0
  139. data/exp/old/windows_basics.rb +80 -0
  140. data/exp/old/wnote.rb +80 -0
  141. data/exp/old/wnote_spec.rb +20 -0
  142. data/features/step_definitions/win_gui_steps.rb +0 -0
  143. data/features/support/env.rb +4 -0
  144. data/features/win_gui.feature +9 -0
  145. data/lib/note/java/jemmy.jar +0 -0
  146. data/lib/note/java/jnote.rb +48 -0
  147. data/lib/note/java/jruby_basics.rb +37 -0
  148. data/lib/note/java/junquenote_app.rb +262 -0
  149. data/lib/note/java/note_spec.rb +20 -0
  150. data/lib/note/win/locknote.rb +19 -0
  151. data/lib/note.rb +15 -0
  152. data/lib/win_gui/constants.rb +66 -0
  153. data/lib/win_gui/string_extensions.rb +24 -0
  154. data/lib/win_gui/win_gui.rb +274 -0
  155. data/lib/win_gui/window.rb +70 -0
  156. data/lib/win_gui.rb +3 -0
  157. data/spec/note/win/locknote_spec.rb +7 -0
  158. data/spec/spec.opts +2 -0
  159. data/spec/spec_helper.rb +100 -0
  160. data/spec/test_apps/locknote/LockNote.exe +0 -0
  161. data/spec/win_gui/string_extensions_spec.rb +61 -0
  162. data/spec/win_gui/win_gui_spec.rb +733 -0
  163. data/spec/win_gui/window_spec.rb +124 -0
  164. metadata +251 -0
@@ -0,0 +1,232 @@
1
+ #---
2
+ # Excerpted from "Scripted GUI Testing With Ruby",
3
+ # published by The Pragmatic Bookshelf.
4
+ # Copyrights apply to this code. It may not be used to create training material,
5
+ # courses, books, articles, and the like. Contact us if you are in doubt.
6
+ # We make no guarantees that this code is fit for any purpose.
7
+ # Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
8
+ #---
9
+
10
+ class String
11
+ def snake_case
12
+ gsub(/([a-z])([A-Z0-9])/, '\1_\2').downcase #(1)
13
+ end
14
+ end
15
+
16
+
17
+
18
+
19
+ require 'Win32API'
20
+
21
+ module WindowsGui
22
+ def self.def_api(function, parameters, return_value) #(2)
23
+ api = Win32API.new 'user32', function, parameters, return_value
24
+
25
+ define_method(function.snake_case) do |*args| #(3)
26
+ api.call *args #(4)
27
+ end
28
+ end
29
+ end
30
+
31
+
32
+
33
+
34
+ module WindowsGui
35
+ def_api 'FindWindow', ['P', 'P'], 'L'
36
+ def_api 'keybd_event', ['I', 'I', 'L', 'L'], 'V'
37
+
38
+ # rest of API definitions here...
39
+
40
+ WM_GETTEXT = 0x000D
41
+ WM_SYSCOMMAND = 0x0112
42
+
43
+ # rest of constant definitions here...
44
+ end
45
+
46
+
47
+
48
+ module WindowsGui
49
+ def_api 'FindWindow', ['P', 'P'], 'L'
50
+ def_api 'FindWindowEx', ['L', 'L', 'P', 'P'], 'L'
51
+ def_api 'PostMessage', ['L', 'L', 'L', 'L'], 'L'
52
+ def_api 'SendMessage', ['L', 'L', 'L', 'P'], 'L'
53
+ def_api 'keybd_event', ['I', 'I', 'L', 'L'], 'V'
54
+ def_api 'GetDlgItem', ['L', 'L'], 'L'
55
+ def_api 'GetWindowRect', ['L', 'P'], 'I'
56
+ def_api 'SetCursorPos', ['L', 'L'], 'I'
57
+ def_api 'mouse_event', ['L', 'L', 'L', 'L', 'L'], 'V'
58
+ def_api 'IsWindowVisible', ['L'], 'L'
59
+
60
+ SC_CLOSE = 0xF060
61
+
62
+ IDNO = 7
63
+
64
+ MOUSEEVENTF_LEFTDOWN = 0x0002
65
+ MOUSEEVENTF_LEFTUP = 0x0004
66
+
67
+ KEYEVENTF_KEYDOWN = 0
68
+ KEYEVENTF_KEYUP = 2
69
+ end
70
+
71
+
72
+
73
+ module WindowsGui
74
+ VK_SHIFT = 0x10
75
+ VK_CONTROL = 0x11
76
+ VK_BACK = 0x08
77
+ end
78
+
79
+
80
+
81
+
82
+ class String
83
+ def to_keys
84
+ unless size == 1 #(5)
85
+ raise "conversion is for single characters only"
86
+ end
87
+
88
+ ascii = unpack('C')[0] #(6)
89
+
90
+ case self #(7)
91
+ when '0'..'9'
92
+ [ascii - ?0 + 0x30]
93
+ when 'A'..'Z'
94
+ [WindowsGui.const_get(:VK_SHIFT), ascii]
95
+ when 'a'..'z'
96
+ [ascii - ?a + ?A]
97
+ when ' '
98
+ [ascii]
99
+ else
100
+ raise "Can't convert unknown character #{self}"
101
+ end
102
+ end
103
+ end
104
+
105
+
106
+
107
+
108
+ module WindowsGui
109
+ def keystroke(*keys)
110
+ return if keys.empty?
111
+
112
+ keybd_event keys.first, 0, KEYEVENTF_KEYDOWN, 0
113
+ sleep 0.05
114
+ keystroke *keys[1..-1]
115
+ sleep 0.05
116
+ keybd_event keys.first, 0, KEYEVENTF_KEYUP, 0
117
+ end
118
+ end
119
+
120
+
121
+
122
+
123
+ module WindowsGui
124
+ def type_in(message)
125
+ message.scan(/./m) do |char|
126
+ keystroke(*char.to_keys)
127
+ end
128
+ end
129
+ end
130
+
131
+
132
+
133
+
134
+ module WindowsGui
135
+ class Window
136
+ include WindowsGui #(8)
137
+
138
+ attr_reader :handle
139
+
140
+ def initialize(handle)
141
+ @handle = handle
142
+ end
143
+
144
+ def close
145
+ post_message @handle, WM_SYSCOMMAND, SC_CLOSE, 0
146
+ end
147
+
148
+ def wait_for_close #(9)
149
+ timeout(5) do
150
+ sleep 0.2 until 0 == is_window_visible(@handle)
151
+ end
152
+ end
153
+
154
+ def text
155
+ buffer = '\0' * 2048
156
+ length = send_message @handle, WM_GETTEXT, buffer.length, buffer
157
+ length == 0 ? '' : buffer[0..length - 1]
158
+ end
159
+ end
160
+ end
161
+
162
+
163
+
164
+
165
+ class WindowsGui::Window
166
+ extend WindowsGui #(10)
167
+
168
+ def self.top_level(title, seconds=3)
169
+ @handle = timeout(seconds) do
170
+ sleep 0.2 while (h = find_window nil, title) <= 0; h
171
+ end
172
+
173
+ Window.new @handle
174
+ end
175
+ end
176
+
177
+
178
+
179
+
180
+ class WindowsGui::Window
181
+ def child(id)
182
+ result = case id
183
+ when String
184
+ by_title = find_window_ex @handle, 0, nil, id.gsub('_', '&') #(11)
185
+ by_class = find_window_ex @handle, 0, id, nil
186
+ by_title > 0 ? by_title : by_class
187
+ when Fixnum
188
+ get_dlg_item @handle, id
189
+ else
190
+ 0
191
+ end
192
+
193
+ raise "Control '#{id}' not found" if result == 0
194
+ Window.new result
195
+ end
196
+ end
197
+
198
+
199
+
200
+
201
+ class WindowsGui::Window
202
+ def click(id)
203
+ h = child(id).handle
204
+
205
+ rectangle = [0, 0, 0, 0].pack 'LLLL'
206
+ get_window_rect h, rectangle
207
+ left, top, right, bottom = rectangle.unpack 'LLLL'
208
+
209
+ center = [(left + right) / 2, (top + bottom) / 2]
210
+ set_cursor_pos *center
211
+
212
+ mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
213
+ mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
214
+ end
215
+ end
216
+
217
+
218
+
219
+
220
+ module WindowsGui
221
+ def dialog(title, seconds=3)
222
+ d = begin
223
+ w = Window.top_level(title, seconds)
224
+ yield(w) ? w : nil #(12)
225
+ rescue TimeoutError
226
+ end
227
+
228
+ d.wait_for_close if d
229
+ return d
230
+ end
231
+ end
232
+
@@ -0,0 +1,35 @@
1
+ #---
2
+ # Excerpted from "Scripted GUI Testing With Ruby",
3
+ # published by The Pragmatic Bookshelf.
4
+ # Copyrights apply to this code. It may not be used to create training material,
5
+ # courses, books, articles, and the like. Contact us if you are in doubt.
6
+ # We make no guarantees that this code is fit for any purpose.
7
+ # Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
8
+ #---
9
+
10
+ require 'windows_gui'
11
+
12
+ describe WindowsGui do #(1)
13
+ include WindowsGui
14
+
15
+ it 'wraps a Windows call with a method' do
16
+ find_window(nil, nil).should_not == 0 #(2)
17
+ end
18
+
19
+ it 'enforces the argument count' do
20
+ lambda {find_window}.should raise_error #(3)
21
+ end
22
+ end
23
+
24
+
25
+
26
+
27
+ describe String, '#snake_case' do
28
+ it 'transforms CamelCase strings' do
29
+ 'GetCharWidth32'.snake_case.should == 'get_char_width_32'
30
+ end
31
+
32
+ it 'leaves snake_case strings intact' do
33
+ 'keybd_event'.snake_case.should == 'keybd_event'
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ Story: minimalist invites
2
+ As a host with lots to do
3
+ I want to plan a party with a minimum of mouse clicks
4
+ So that I can get on with the rest of my tasks
5
+
6
+ Scenario: manual invites
7
+ Given a party called "Celebration"
8
+ And a description of "There's a party goin' on"
9
+ And a location of "Right here"
10
+ And a starting time of September 29, 2010 at 12:30 PM
11
+ And an ending time of September 29, 2010 at 12:35 PM
12
+
13
+ When I view the invitation
14
+ Then I should see the Web address to send to my friends
15
+ And the name should be "Celebration"
16
+ And the description should be "There's a party goin' on"
17
+ And the location should be "Right here"
18
+ And the party should begin on September 29, 2010 at 12:30 PM
19
+ And the party should end on September 29, 2010 at 12:35 PM
@@ -0,0 +1,29 @@
1
+ Make sure you install the Selenium gem, with a capital S.
2
+
3
+ First story.
4
+
5
+ Add browser code.
6
+
7
+ ./script/generate rspec_scaffold Party name:string description:text location:text begins_at:datetime ends_at:datetime
8
+
9
+ rake db:migrate
10
+
11
+ ./script/generate migration AddLinkToParty link:string
12
+
13
+ Edit view.
14
+
15
+ RESTful with to_param
16
+
17
+ Make sure date/time updates!!!!
18
+
19
+ Scenario: email invites
20
+ Given a blank invitation
21
+ When I plan a party called "Celebration"
22
+ And I enter a description of "Good times, come on"
23
+ And I enter a location of "Right here in Jersey City, NJ"
24
+ And I enter a starting time of "Sep. 29, 1980, 8 PM"
25
+ And I enter an ending time of "Sep. 29, 1980, 10 PM"
26
+ And I enter a recipient address of "example@example.com"
27
+ Then I should see an email preview
28
+ When I confirm the email
29
+ Then I should see a confirmation
@@ -0,0 +1,156 @@
1
+ #---
2
+ # Excerpted from "Scripted GUI Testing With Ruby",
3
+ # published by The Pragmatic Bookshelf.
4
+ # Copyrights apply to this code. It may not be used to create training material,
5
+ # courses, books, articles, and the like. Contact us if you are in doubt.
6
+ # We make no guarantees that this code is fit for any purpose.
7
+ # Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
8
+ #---
9
+
10
+ require 'rubygems'
11
+ require 'spec/story'
12
+ require 'chronic'
13
+ require 'party'
14
+
15
+ class Listener
16
+ attr_reader :browser
17
+
18
+ def run_started(num_scenarios)
19
+ @browser = Selenium::SeleniumDriver.new \
20
+ 'localhost', 4444, '*firefox', 'http://localhost:3000', 10000
21
+ @browser.start
22
+ end
23
+
24
+ def run_ended
25
+ @browser.stop
26
+ end
27
+
28
+ def method_missing(name, *args, &block)
29
+ # We don't care about the rest of the Story Runner events.
30
+ end
31
+ end
32
+
33
+ listener = Listener.new
34
+ Spec::Story::Runner.register_listener(listener)
35
+
36
+
37
+
38
+
39
+ steps_for :planning do
40
+ Given 'a party called "$name"' do |name|
41
+ @party = Party.new(listener.browser)
42
+ @party.name = name
43
+ end
44
+
45
+ Given 'a description of "$desc"' do |desc|
46
+ @party.description = desc
47
+ end
48
+
49
+ Given 'a location of "$loc"' do |loc|
50
+ @party.location = loc
51
+ end
52
+
53
+ Given /an? $event time of $sometime/ do |event, sometime| #(1)
54
+ clean = sometime.gsub ',', ' '
55
+ date_time = Chronic.parse clean, :now => Time.now - 86400 #(2)
56
+
57
+ if event == 'starting'
58
+ @party.begins_at = date_time
59
+ else
60
+ @party.ends_at = date_time
61
+ end
62
+ end
63
+
64
+ When 'I view the invitation' do
65
+ @party.save_and_view
66
+ end
67
+ end
68
+
69
+
70
+
71
+
72
+ steps_for :reviewing do
73
+ Then 'the $setting should be "$value"' do |setting, value|
74
+ @party.send(setting).should == value
75
+ end
76
+
77
+ Then 'the party should $event on $date_time' do |event, date_time|
78
+ actual_time =
79
+ (event == 'begin') ?
80
+ @party.begins_at :
81
+ @party.ends_at
82
+
83
+ clean = date_time.gsub ',', ' '
84
+ expected_time = Chronic.parse clean, :now => Time.now - 86400
85
+
86
+ actual_time.should == expected_time
87
+ end
88
+
89
+ Then 'I should see the Web address to send to my friends' do
90
+ @party.link.should match(%r{^http://})
91
+ end
92
+ end
93
+
94
+
95
+
96
+
97
+ steps_for :rsvp do
98
+ Then 'I should see the party details' do
99
+ @party.should have_name
100
+ @party.should have_description
101
+ @party.should have_location
102
+ @party.should have_times
103
+ end
104
+
105
+ When /I answer that "$guest" will( not)? attend/ do |guest, answer|
106
+ attending = !answer.include?('not')
107
+ @party.rsvp guest, attending
108
+ end
109
+
110
+ Then 'I should see "$guest" in the list of $type' do |guest, type|
111
+ want_attending = (type == 'partygoers')
112
+ @party.responses(want_attending).should include(guest)
113
+ end
114
+ end
115
+
116
+
117
+
118
+
119
+ steps_for :email do
120
+ Given 'a guest list of "$list"' do |list|
121
+ @party.recipients = list
122
+ end
123
+
124
+ Then 'I should see that e-mail was sent to "$list"' do |list|
125
+ @party.notice.include?(list).should be_true
126
+ end
127
+
128
+ When 'I view the e-mail that was sent to "$address"' do |address|
129
+ @email = @party.email_to address
130
+ end
131
+
132
+ Then 'I should see "Yes/No" links' do
133
+ @email.should match(%r{Yes - http://})
134
+ @email.should match(%r{No - http://})
135
+ end
136
+
137
+ When 'I follow the "$answer" link' do |answer|
138
+ link = %r{#{answer} - (http://.+)}.match(@email)[1]
139
+ @party.rsvp_at link
140
+ end
141
+ end
142
+
143
+
144
+
145
+
146
+ with_steps_for :planning, :reviewing do
147
+ run 'invite.story'
148
+ end
149
+
150
+
151
+
152
+
153
+ with_steps_for :planning, :reviewing, :rsvp, :email do
154
+ run 'rsvp.story'
155
+ end
156
+
@@ -0,0 +1,149 @@
1
+ #---
2
+ # Excerpted from "Scripted GUI Testing With Ruby",
3
+ # published by The Pragmatic Bookshelf.
4
+ # Copyrights apply to this code. It may not be used to create training material,
5
+ # courses, books, articles, and the like. Contact us if you are in doubt.
6
+ # We make no guarantees that this code is fit for any purpose.
7
+ # Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
8
+ #---
9
+
10
+ require 'rubygems'
11
+ require 'selenium'
12
+ require 'time'
13
+
14
+ class Party
15
+ def initialize(browser)
16
+ @browser = browser
17
+ @browser.open '/parties/new'
18
+ end
19
+
20
+ def self.def_setting(setting, type = :read_write)
21
+ if type == :readable || type == :read_write
22
+ define_method(setting) do
23
+ @browser.get_text("id=party_#{setting}")
24
+ end
25
+
26
+ define_method("has_#{setting}?") do
27
+ send(setting) rescue nil
28
+ end
29
+ end
30
+
31
+ if type == :writable || type == :read_write
32
+ define_method("#{setting}=") do |value|
33
+ @browser.type "id=party_#{setting}", value
34
+ end
35
+ end
36
+ end
37
+
38
+ def_setting :name
39
+ def_setting :description
40
+ def_setting :location
41
+ def_setting :link, :readable
42
+ def_setting :notice, :readable
43
+ def_setting :recipients, :writable
44
+ end
45
+
46
+
47
+
48
+
49
+ class Party
50
+ def begins_at=(time); set_time(:begin, time) end
51
+ def ends_at= (time); set_time(:end, time) end
52
+
53
+ def set_time(event, time)
54
+ ['%Y', '%B', '%d', '%H', '%M'].each_with_index do |part, index|
55
+ element = "id=party_#{event}s_at_#{index + 1}i"
56
+ value = time.strftime part
57
+
58
+ @browser.select element, value
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+
65
+
66
+ class Party
67
+ def begins_at; get_times.first end
68
+ def ends_at; get_times.last end
69
+
70
+ def get_times
71
+ begins_on = @browser.get_text 'party_begins_on'
72
+ begins_at = @browser.get_text 'party_begins_at'
73
+ ends_at = @browser.get_text 'party_ends_at'
74
+
75
+ begins = Time.parse(begins_on + ' ' + begins_at)
76
+ ends = Time.parse(begins_on + ' ' + ends_at)
77
+ ends += 86400 if ends < begins
78
+
79
+ [begins, ends]
80
+ end
81
+
82
+ def has_times?
83
+ get_times rescue nil
84
+ end
85
+ end
86
+
87
+
88
+
89
+
90
+ class Party
91
+ def save_and_view
92
+ @browser.click 'id=party_submit'
93
+ @browser.wait_for_page_to_load 5000
94
+ @saved = true
95
+ end
96
+ end
97
+
98
+
99
+
100
+
101
+ class Party
102
+ def rsvp(name, attending)
103
+ @browser.type 'guest_name', name
104
+ @browser.click 'guest_attending' unless attending
105
+ @browser.click 'rsvp'
106
+ @browser.wait_for_page_to_load 5000
107
+ end
108
+ end
109
+
110
+
111
+
112
+
113
+ class Party
114
+ RsvpItem = '//ul[@id="guests"]/li'
115
+
116
+ def responses(want_attending)
117
+ num_guests = @browser.get_xpath_count(RsvpItem).to_i
118
+ return [] unless num_guests >= 1
119
+
120
+ all = (1..num_guests).map do |i|
121
+ name = @browser.get_text \
122
+ "#{RsvpItem}[#{i}]/span[@class='rsvp_name']"
123
+ rsvp = @browser.get_text \
124
+ "#{RsvpItem}[#{i}]/span[@class='rsvp_attending']"
125
+ [name, rsvp]
126
+ end
127
+
128
+ matching = all.select do |name, rsvp|
129
+ is_attending = !rsvp.include?('not')
130
+ !(want_attending ^ is_attending)
131
+ end
132
+
133
+ matching.map {|name, rsvp| name}
134
+ end
135
+ end
136
+
137
+
138
+
139
+
140
+ class Party
141
+ def email_to(address)
142
+ @browser.open link + '.txt?email=' + address
143
+ @browser.get_body_text
144
+ end
145
+
146
+ def rsvp_at(rsvp_link)
147
+ @browser.open rsvp_link
148
+ end
149
+ end
@@ -0,0 +1,61 @@
1
+ #---
2
+ # Excerpted from "Scripted GUI Testing With Ruby",
3
+ # published by The Pragmatic Bookshelf.
4
+ # Copyrights apply to this code. It may not be used to create training material,
5
+ # courses, books, articles, and the like. Contact us if you are in doubt.
6
+ # We make no guarantees that this code is fit for any purpose.
7
+ # Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
8
+ #---
9
+
10
+ require 'rubygems'
11
+ require 'spec/story'
12
+
13
+ steps_for :app_state do #(1)
14
+ Given 'a new document' do
15
+ @note = Note.open
16
+ end
17
+
18
+ When 'I exit the app' do
19
+ @note.exit!
20
+ end
21
+
22
+ Then 'the app should be running' do
23
+ @note.should be_running
24
+ end
25
+ end
26
+
27
+
28
+
29
+
30
+ steps_for :documents do
31
+ When 'I type "$something"' do |something|
32
+ @note.text = something
33
+ end
34
+
35
+ When 'I save the document as "$name" with password "$password"' do #(2)
36
+ |name, password|
37
+ @note.save_as name, :password => password
38
+ end
39
+
40
+ When 'I open the document "$name" with password "$password"' do
41
+ |name, password|
42
+ @note = Note.open name, :password => password
43
+ end
44
+
45
+ When 'I change the password from "$old" to "$password"' do
46
+ |old, password|
47
+ @note.change_password :old_password => old, :password => password
48
+ end
49
+
50
+ Then 'the text should be "$something"' do |something|
51
+ @note.text.should == something
52
+ end
53
+ end
54
+
55
+
56
+
57
+
58
+ with_steps_for :app_state, :documents do
59
+ run 'password.story'
60
+ end
61
+
@@ -0,0 +1,26 @@
1
+ START: password_story
2
+ Story: Password protection
3
+
4
+ As a security-conscious person
5
+ I want to encrypt each document with a password
6
+ So that only I (and the NSA) can read it
7
+ END: password_story
8
+
9
+ START: password_scenario
10
+ Scenario: Changing the password
11
+ Given a new document
12
+ When I type "this is my document"
13
+ And I save the document as "Secrets" with password "unguessable"
14
+ And I exit the app
15
+ And I open the document "Secrets" with password "unguessable"
16
+ Then the app should be running
17
+ And the text should be "this is my document"
18
+
19
+ When I change the password from "unguessable" to "uncrackable"
20
+ And I exit the app
21
+ And I open the document "Secrets" with password "uncrackable"
22
+ Then the app should be running
23
+ And the text should be "this is my document"
24
+ END: password_scenario
25
+
26
+ When I exit the app