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,144 @@
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
+ class Note
10
+ attr_reader :path
11
+
12
+ @@app = nil
13
+ @@titles = {}
14
+
15
+ def self.open(*args)
16
+ @@app.new *args
17
+ end
18
+
19
+
20
+ DefaultOptions = {
21
+ :password => 'password',
22
+ :confirmation => true
23
+ }
24
+
25
+ def save_as(name, with_options = {})
26
+ options = DefaultOptions.merge with_options #(1)
27
+
28
+ @path = @@app.path_to(name) #(2)
29
+ File.delete @path if File.exist? @path
30
+
31
+ menu 'File', 'Save As...' #(3)
32
+
33
+ enter_filename @path #(4)
34
+ assign_password options
35
+ end
36
+
37
+
38
+
39
+
40
+ def exit!(with_options = {})
41
+ options = DefaultOptions.merge with_options
42
+
43
+ menu 'File', 'Exit'
44
+
45
+ @prompted[:to_confirm_exit] = dialog(@@titles[:exit]) do |d|
46
+ d.click(options[:save_as] ? '_Yes' : '_No')
47
+ end
48
+
49
+ if options[:save_as]
50
+ path = @@app.path_to options[:save_as]
51
+ enter_filename path
52
+ assign_password options
53
+ end
54
+ end
55
+
56
+
57
+
58
+ def change_password(with_options = {})
59
+ old_options = {
60
+ :password => with_options[:old_password]} #(5)
61
+
62
+ new_options = {
63
+ :password => with_options[:password],
64
+ :confirmation =>
65
+ with_options[:confirmation] ||
66
+ with_options[:password]} #(6)
67
+
68
+ menu 'File', 'Change Password...'
69
+
70
+ unlock_password old_options
71
+ assign_password new_options
72
+ end
73
+
74
+
75
+ def about
76
+ menu 'Help', @@titles[:about_menu]
77
+
78
+ @prompted[:with_help_text] = dialog(@@titles[:about]) do |d|
79
+ d.click '_OK'
80
+ end
81
+ end
82
+
83
+
84
+ [:undo, :cut, :copy, :paste, :find_next].each do |method|
85
+ item = method.to_s.split('_').collect {|m| m.capitalize}.join(' ')
86
+ define_method(method) {menu 'Edit', item, :wait} #(7)
87
+ end
88
+
89
+
90
+
91
+ def has_prompted?(kind)
92
+ @prompted[kind]
93
+ end
94
+
95
+
96
+ private
97
+
98
+
99
+ def enter_filename(path, approval = '_Save')
100
+ dialog(@@titles[:file]) do |d| #(8)
101
+ d.type_in path
102
+ d.click approval
103
+ end
104
+ end
105
+
106
+
107
+
108
+ def unlock_password(with_options = {})
109
+ options = DefaultOptions.merge with_options
110
+ options[:confirmation] = false #(9)
111
+
112
+ enter_password options
113
+ watch_for_error
114
+ end
115
+
116
+
117
+
118
+ def assign_password(with_options = {})
119
+ options = DefaultOptions.merge with_options
120
+
121
+ enter_password options
122
+ watch_for_error
123
+
124
+ if @prompted[:with_error]
125
+ enter_password :cancel_password => true #(10)
126
+ end
127
+ end
128
+
129
+ end
130
+
131
+
132
+
133
+ require 'fileutils'
134
+
135
+ class Note
136
+ def self.fixture(name)
137
+ source = @@app.path_to(name + 'Fixture')
138
+ target = @@app.path_to(name)
139
+
140
+ FileUtils.rm target if File.exist? target
141
+ FileUtils.copy source, target
142
+ end
143
+ end
144
+
@@ -0,0 +1,191 @@
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
+ require 'spec_helper'
10
+
11
+ describe 'The main window' do
12
+ it_should_behave_like 'a new document'
13
+
14
+ it 'launches with a welcome message' do
15
+ @note.text.should include('Welcome')
16
+ end
17
+
18
+ it 'exits without a prompt if nothing has changed' do
19
+ @note.exit!
20
+ @note.should_not have_prompted(:to_confirm_exit)
21
+ end
22
+
23
+ it 'prompts before exiting if the document has changed' do
24
+ @note.text = 'changed'
25
+ @note.exit!
26
+ @note.should have_prompted(:to_confirm_exit)
27
+ end
28
+
29
+ it 'offers information about the program' do
30
+ @note.about
31
+ @note.should have_prompted(:with_help_text)
32
+ end
33
+ end
34
+
35
+
36
+ describe 'Saving a document for the first time' do
37
+ it_should_behave_like 'a new document'
38
+
39
+ it 'requires a password' do
40
+ @note.save_as 'MyNote' #(1)
41
+ @note.should have_prompted(:for_password) #(2)
42
+ end
43
+ end
44
+
45
+
46
+
47
+ describe 'The password assignment prompt' do
48
+ it_should_behave_like 'a new document'
49
+
50
+ it 'ignores the new password if cancelled' do
51
+ @note.text = 'changed'
52
+ @note.save_as 'MyNote', :cancel_password => true
53
+ @note.exit!
54
+ @note.should have_prompted(:to_confirm_exit) #(3)
55
+ end
56
+
57
+ it 'ignores an unconfirmed password' do
58
+ @note.text = 'changed'
59
+ @note.save_as 'SavedNote', :confirmation => 'mismatch'
60
+ @note.should have_prompted(:with_error)
61
+ @note.exit!
62
+ @note.should have_prompted(:to_confirm_exit) #(4)
63
+ end
64
+ end
65
+
66
+
67
+
68
+ describe 'The password entry prompt' do
69
+ it_should_behave_like 'a saved document' #(5)
70
+
71
+ it 'ignores the password if cancelled' do
72
+ note = Note.open 'SavedNote', :cancel_password => true
73
+ note.should_not be_running
74
+ end
75
+
76
+ it 'exits with an error message for an invalid password' do
77
+ note = Note.open 'SavedNote', :password => 'invalid'
78
+ note.should_not be_running
79
+ note.should have_prompted(:with_error)
80
+ end
81
+ end
82
+
83
+
84
+
85
+ describe 'A previously saved document' do
86
+ it_should_behave_like 'a saved document'
87
+ it_should_behave_like 'a reopened document' #(6)
88
+
89
+ it 'preserves and encrypts the contents of the file' do
90
+ @note.text.should include('Welcome')
91
+ IO.read(@note.path).should_not include('Welcome') #(7)
92
+ end
93
+
94
+ it 'does not require a password on subsequent saves' do
95
+ @note.text = 'changed'
96
+ @note.exit! :save_as => 'MyNote' #(8)
97
+ @note.should_not have_prompted(:for_password)
98
+ end
99
+
100
+
101
+ it 'supports changing the password' do
102
+ @note.change_password \
103
+ :old_password => 'password',
104
+ :password => 'new'
105
+ @note.exit!
106
+
107
+ @note = Note.open 'SavedNote', :password => 'new'
108
+ @note.should_not have_prompted(:with_error)
109
+ @note.should be_running
110
+ end
111
+
112
+ end
113
+
114
+
115
+
116
+ describe 'The editor' do
117
+ it_should_behave_like 'a new document'
118
+
119
+ it 'supports multiple levels of undo' do
120
+ @note.text = 'abc'
121
+
122
+ @note.undo
123
+ @note.text.should == 'ab'
124
+
125
+ @note.undo
126
+ @note.text.should == 'a'
127
+ end
128
+
129
+ it 'supports copying and pasting text' do
130
+ @note.text = 'itchy'
131
+ @note.select_all
132
+ @note.copy
133
+ @note.text.should == 'itchy'
134
+
135
+ @note.text = 'scratchy'
136
+ @note.select_all
137
+ @note.paste
138
+ @note.text.should == 'itchy'
139
+ end
140
+
141
+
142
+ it 'supports cutting and pasting text' do
143
+ @note.text = 'pineapple'
144
+ @note.select_all
145
+ @note.cut
146
+ @note.text.should be_empty
147
+
148
+ @note.text = 'mango'
149
+ @note.select_all
150
+ @note.paste
151
+ @note.text.should == 'pineapple'
152
+ end
153
+
154
+ end
155
+
156
+
157
+
158
+ describe 'The Find window' do
159
+ it_should_behave_like 'a new document'
160
+ it_should_behave_like 'a searchable document'
161
+
162
+ it 'supports searching forward' do
163
+ @note.go_to :beginning
164
+ @note.find @term
165
+ @note.selection.begin.should == @first_match
166
+
167
+ @note.find_next
168
+ @note.selection.begin.should == @second_match
169
+ end
170
+
171
+ it 'supports searching backward' do
172
+ @note.go_to :end
173
+ @note.find @term, :direction => :back
174
+ @note.selection.begin.should == @reverse_match
175
+ end
176
+
177
+ it 'can restrict its search to whole words' do
178
+ pending 'on hold' do #(9)
179
+ @note.go_to :beginning
180
+ @note.find @term, :whole_word => true
181
+ @note.selection.begin.should == @word_match
182
+ end
183
+ end
184
+
185
+ it 'can restrict its search to exact case matches' do
186
+ @note.go_to :beginning
187
+ @note.find @term, :exact_case => true
188
+ @note.selection.begin.should == @case_match
189
+ end
190
+ end
191
+
@@ -0,0 +1,55 @@
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
+ describe 'a new document', :shared => true do #(1)
11
+ before do #(2)
12
+ @note = Note.open
13
+ end
14
+
15
+ after do #(3)
16
+ @note.exit! if @note.running? #(4)
17
+ end
18
+ end
19
+
20
+
21
+
22
+ describe 'a saved document', :shared => true do
23
+ before do
24
+ Note.fixture 'SavedNote' #(5)
25
+ end
26
+ end
27
+
28
+
29
+
30
+ describe 'a reopened document', :shared => true do
31
+ before do
32
+ @note = Note.open 'SavedNote'
33
+ end
34
+
35
+ after do
36
+ @note.exit! if @note.running?
37
+ end
38
+ end
39
+
40
+
41
+
42
+ describe 'a searchable document', :shared => true do
43
+ before do
44
+ @example = 'The longest island is Isabel Island.'
45
+ @term = 'Is'
46
+
47
+ @first_match = @example.index(/Is/i)
48
+ @second_match = @example.index(/Is/i, @first_match + 1)
49
+ @reverse_match = @example.rindex(/Is/i)
50
+ @word_match = @example.index(/Is\b/i)
51
+ @case_match = @example.index(/Is/)
52
+
53
+ @note.text = @example
54
+ end
55
+ end
@@ -0,0 +1,50 @@
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
+ require 'java'
10
+ require 'jemmy.jar'
11
+
12
+ include_class 'org.netbeans.jemmy.JemmyProperties'
13
+ include_class 'org.netbeans.jemmy.TestOut'
14
+
15
+ JemmyProperties.set_current_output TestOut.get_null_output
16
+
17
+ %w(Frame TextArea MenuBar Dialog Button TextField).each do |o|
18
+ include_class "org.netbeans.jemmy.operators.J#{o}Operator"
19
+ end
20
+
21
+
22
+ class JDialogOperator
23
+ def click(title)
24
+ b = JButtonOperator.new self, title.delete('_')
25
+ b.push
26
+ end
27
+
28
+ def type_in(message)
29
+ e = JTextFieldOperator.new self
30
+ e.type_text message
31
+ end
32
+ end
33
+
34
+
35
+
36
+ module SwingGui
37
+ def dialog(title, seconds=3)
38
+ JemmyProperties.set_current_timeout \
39
+ 'DialogWaiter.WaitDialogTimeout', seconds * 1000
40
+
41
+ begin
42
+ d = JDialogOperator.new title
43
+ yield d #(1)
44
+ d.wait_closed
45
+
46
+ true
47
+ rescue NativeException
48
+ end
49
+ end
50
+ end
@@ -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
+ require 'Win32API'
10
+
11
+ class String
12
+ def snake_case
13
+ gsub(/([a-z])([A-Z0-9])/, '\1_\2').downcase
14
+ end
15
+
16
+ def to_keys
17
+ unless size == 1
18
+ raise "conversion is for single characters only"
19
+ end
20
+
21
+ ascii = unpack('C')[0]
22
+
23
+ case self
24
+ when '0'..'9'
25
+ [ascii - ?0 + 0x30]
26
+ when 'A'..'Z'
27
+ [WindowsGui.const_get(:VK_SHIFT), ascii]
28
+ when 'a'..'z'
29
+ [ascii - ?a + ?A]
30
+ when ' '
31
+ [ascii]
32
+ when ','
33
+ [WindowsGui.const_get(:VK_OEM_COMMA)]
34
+ when '.'
35
+ [WindowsGui.const_get(:VK_OEM_PERIOD)]
36
+ when ':'
37
+ [:VK_SHIFT, :VK_OEM_1].map {|s| WindowsGui.const_get s}
38
+ when "\\"
39
+ [WindowsGui.const_get(:VK_OEM_102)]
40
+ else
41
+ raise "Can't convert unknown character #{self}"
42
+ end
43
+ end
44
+ end
45
+
46
+ module WindowsGui
47
+ def self.def_api(function, parameters, return_value, rename = nil)
48
+ api = Win32API.new 'user32', function, parameters, return_value
49
+ define_method(rename || function.snake_case) do |*args|
50
+ api.call *args
51
+ end
52
+ end
53
+
54
+
55
+ def self.load_symbols(header)
56
+ File.open(header) do |f|
57
+ f.grep(/#define\s+(ID\w+)\s+(\w+)/) do
58
+ name = $1
59
+ value = (0 == $2.to_i) ? $2.hex : $2.to_i #(1)
60
+ WindowsGui.const_set name, value #(2)
61
+ end
62
+ end
63
+ end
64
+
65
+
66
+
67
+ def_api 'FindWindow', ['P', 'P'], 'L'
68
+ def_api 'FindWindowEx', ['L', 'L', 'P', 'P'], 'L'
69
+ def_api 'SendMessage', ['L', 'L', 'L', 'P'], 'L', :send_with_buffer
70
+ def_api 'SendMessage', ['L', 'L', 'L', 'L'], 'L'
71
+ def_api 'PostMessage', ['L', 'L', 'L', 'L'], 'L'
72
+ def_api 'keybd_event', ['I', 'I', 'L', 'L'], 'V'
73
+ def_api 'GetDlgItem', ['L', 'L'], 'L'
74
+ def_api 'GetWindowRect', ['L', 'P'], 'I'
75
+ def_api 'SetCursorPos', ['L', 'L'], 'I'
76
+ def_api 'mouse_event', ['L', 'L', 'L', 'L', 'L'], 'V'
77
+ def_api 'IsWindow', ['L'], 'L'
78
+ def_api 'IsWindowVisible', ['L'], 'L'
79
+ def_api 'SetForegroundWindow', ['L'], 'L'
80
+
81
+
82
+
83
+ # Windows messages - general
84
+ WM_COMMAND = 0x0111
85
+ WM_SYSCOMMAND = 0x0112
86
+ SC_CLOSE = 0xF060
87
+
88
+ # Windows messages - text
89
+ WM_GETTEXT = 0x000D
90
+ EM_GETSEL = 0x00B0
91
+ EM_SETSEL = 0x00B1
92
+
93
+ # Commonly-used control IDs
94
+ IDOK = 1
95
+ IDCANCEL = 2
96
+ IDYES = 6
97
+ IDNO = 7
98
+
99
+ # Mouse and keyboard flags
100
+ MOUSEEVENTF_LEFTDOWN = 0x0002
101
+ MOUSEEVENTF_LEFTUP = 0x0004
102
+ KEYEVENTF_KEYDOWN = 0
103
+ KEYEVENTF_KEYUP = 2
104
+
105
+ # Modifier keys
106
+ VK_SHIFT = 0x10
107
+ VK_CONTROL = 0x11
108
+ VK_MENU = 0x12 # Alt
109
+
110
+ # Commonly-used keys
111
+ VK_BACK = 0x08
112
+ VK_TAB = 0x09
113
+ VK_RETURN = 0x0D
114
+ VK_ESCAPE = 0x1B
115
+ VK_OEM_1 = 0xBA # semicolon (US)
116
+ VK_OEM_102 = 0xE2 # backslash (US)
117
+ VK_OEM_PERIOD = 0xBE
118
+ VK_HOME = 0x24
119
+ VK_END = 0x23
120
+ VK_OEM_COMMA = 0xBC
121
+
122
+
123
+ def keystroke(*keys)
124
+ return if keys.empty?
125
+
126
+ keybd_event keys.first, 0, KEYEVENTF_KEYDOWN, 0
127
+ sleep 0.05
128
+ keystroke *keys[1..-1]
129
+ sleep 0.05
130
+ keybd_event keys.first, 0, KEYEVENTF_KEYUP, 0
131
+ end
132
+
133
+ def type_in(message)
134
+ message.scan(/./m) do |char|
135
+ keystroke(*char.to_keys)
136
+ end
137
+ end
138
+
139
+ def wait_for_window(title, seconds = 5)
140
+ timeout(seconds) do
141
+ sleep 0.2 while
142
+ (h = find_window nil, title) <= 0 ||
143
+ window_text(h) != title
144
+ h
145
+ end
146
+ end
147
+
148
+ class Window
149
+ include WindowsGui
150
+ extend WindowsGui
151
+
152
+ attr_reader :handle
153
+
154
+ def initialize(handle)
155
+ @handle = handle
156
+ end
157
+
158
+ def close
159
+ post_message @handle, WM_SYSCOMMAND, SC_CLOSE, 0
160
+ end
161
+
162
+ def wait_for_close
163
+ timeout(5) do
164
+ sleep 0.2 until 0 == is_window_visible(@handle)
165
+ end
166
+ end
167
+
168
+ def text(max_length = 2048)
169
+ buffer = '\0' * max_length
170
+ length = send_with_buffer @handle, WM_GETTEXT, buffer.length, buffer
171
+ length == 0 ? '' : buffer[0..length - 1]
172
+ end
173
+
174
+ def child(id)
175
+ result = case id
176
+ when String
177
+ by_title = find_window_ex @handle, 0, nil, id.gsub('_', '&') #(3)
178
+ by_class = find_window_ex @handle, 0, id, nil
179
+ by_title > 0 ? by_title : by_class
180
+ when Fixnum
181
+ get_dlg_item @handle, id
182
+ else
183
+ 0
184
+ end
185
+
186
+ raise "Control '#{id}' not found" if result == 0
187
+ Window.new result
188
+ end
189
+
190
+ def click(id)
191
+ h = child(id).handle
192
+
193
+ rectangle = [0, 0, 0, 0].pack 'LLLL'
194
+ get_window_rect h, rectangle
195
+ left, top, right, bottom = rectangle.unpack 'LLLL'
196
+
197
+ center = [(left + right) / 2, (top + bottom) / 2]
198
+ set_cursor_pos *center
199
+
200
+ mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
201
+ mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
202
+ end
203
+
204
+ def self.top_level(title, seconds=10, wnd_class = nil)
205
+ @handle = timeout(seconds) do
206
+ loop do
207
+ h = find_window wnd_class, title
208
+ break h if h > 0
209
+ sleep 0.3
210
+ end
211
+ end
212
+
213
+ Window.new @handle
214
+ end
215
+ end
216
+
217
+ DialogWndClass = '#32770'
218
+ def dialog(title, seconds=5)
219
+ close, dlg = begin
220
+ sleep 0.25
221
+ w = Window.top_level(title, seconds, DialogWndClass)
222
+ Window.set_foreground_window w.handle
223
+ sleep 0.25
224
+
225
+ [yield(w), w]
226
+ rescue TimeoutError
227
+ end
228
+
229
+ dlg.wait_for_close if dlg && close
230
+ return dlg
231
+ end
232
+ end
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+ #---
3
+ # Excerpted from "Scripted GUI Testing With Ruby",
4
+ # published by The Pragmatic Bookshelf.
5
+ # Copyrights apply to this code. It may not be used to create training material,
6
+ # courses, books, articles, and the like. Contact us if you are in doubt.
7
+ # We make no guarantees that this code is fit for any purpose.
8
+ # Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
9
+ #---
10
+ export PATH=/opt/jruby/bin:$PATH
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+ #---
3
+ # Excerpted from "Scripted GUI Testing With Ruby",
4
+ # published by The Pragmatic Bookshelf.
5
+ # Copyrights apply to this code. It may not be used to create training material,
6
+ # courses, books, articles, and the like. Contact us if you are in doubt.
7
+ # We make no guarantees that this code is fit for any purpose.
8
+ # Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
9
+ #---
10
+ /usr/bin/env jruby -J-Dapple.laf.useScreenMenuBar=true -J-Xdock:name=JunqueNote $*