win_gui 0.2.16 → 0.2.17
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +4 -0
- data/VERSION +1 -1
- data/lib/win_gui/app.rb +1 -1
- data/lib/win_gui/window.rb +1 -1
- data/lib/win_gui.rb +3 -2
- data/spec/extension_spec.rb +49 -52
- data/spec/spec_helper.rb +65 -67
- data/spec/win_gui/app_spec.rb +88 -91
- data/spec/win_gui/convenience_spec.rb +53 -56
- data/spec/win_gui/window_spec.rb +213 -216
- data/tasks/spec.rake +3 -7
- metadata +5 -31
- data/spec/spec.opts +0 -2
data/HISTORY
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.17
|
data/lib/win_gui/app.rb
CHANGED
data/lib/win_gui/window.rb
CHANGED
data/lib/win_gui.rb
CHANGED
data/spec/extension_spec.rb
CHANGED
@@ -1,68 +1,65 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
|
2
|
+
require "spec_helper.rb"
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
('0'..'9').each {|char| char.to_key.should == char.unpack('C')}
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'transforms uppercase letters into [shift, equivalent key code]' do
|
13
|
-
('A'..'Z').each {|char| char.to_key.should == [0x10, *char.unpack('C')]}
|
14
|
-
end
|
4
|
+
describe String do
|
5
|
+
describe '#to_key' do
|
6
|
+
it 'transforms number char into [equivalent key code]' do
|
7
|
+
('0'..'9').each { |char| char.to_key.should == char.unpack('C') }
|
8
|
+
end
|
15
9
|
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
it 'transforms uppercase letters into [shift, equivalent key code]' do
|
11
|
+
('A'..'Z').each { |char| char.to_key.should == [0x10, *char.unpack('C')] }
|
12
|
+
end
|
19
13
|
|
20
|
-
|
21
|
-
|
22
|
-
|
14
|
+
it 'transforms lowercase letters into [(upcase) key code]' do
|
15
|
+
('a'..'z').each { |char| char.to_key.should == char.upcase.unpack('C') }
|
16
|
+
end
|
23
17
|
|
24
|
-
|
25
|
-
|
26
|
-
|
18
|
+
it 'transforms space into [equivalent key code]' do
|
19
|
+
' '.to_key.should == " ".unpack('C')
|
20
|
+
end
|
27
21
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
':'.to_key.should == [VK_SHIFT, VK_OEM_1]
|
32
|
-
';'.to_key.should == [VK_OEM_1]
|
33
|
-
"\\".to_key.should == [VK_OEM_102]
|
34
|
-
end
|
22
|
+
it 'transforms \n into [VK_RETURN]' do
|
23
|
+
"\n".to_key.should == [VK_RETURN]
|
24
|
+
end
|
35
25
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
26
|
+
it 'transforms .,:;\\ into [equivalent key code]' do
|
27
|
+
','.to_key.should == [VK_OEM_COMMA]
|
28
|
+
'.'.to_key.should == [VK_OEM_PERIOD]
|
29
|
+
':'.to_key.should == [VK_SHIFT, VK_OEM_1]
|
30
|
+
';'.to_key.should == [VK_OEM_1]
|
31
|
+
"\\".to_key.should == [VK_OEM_102]
|
32
|
+
end
|
42
33
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
34
|
+
it 'raises error if char is not implemented punctuation' do
|
35
|
+
('!'..'+').each { |char| lambda { char.to_key }.should raise_error ERROR_CONVERSION }
|
36
|
+
(']'..'`').each { |char| lambda { char.to_key }.should raise_error ERROR_CONVERSION }
|
37
|
+
('{'..'~').each { |char| lambda { char.to_key }.should raise_error ERROR_CONVERSION }
|
38
|
+
['-', '/', '['].each { |char| lambda { char.to_key }.should raise_error ERROR_CONVERSION }
|
39
|
+
end
|
47
40
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
41
|
+
it 'raises error if char is non-printable or non-ascii' do
|
42
|
+
lambda { 1.chr.to_key }.should raise_error ERROR_CONVERSION
|
43
|
+
lambda { 230.chr.to_key }.should raise_error ERROR_CONVERSION
|
52
44
|
end
|
53
45
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
win_string_thought_utf8 = win_string.force_encoding('utf-8')
|
60
|
-
win_string_thought_dos = win_string.force_encoding('cp866')
|
46
|
+
it 'raises error if string is multi-char' do
|
47
|
+
lambda { 'hello'.to_key }.should raise_error ERROR_CONVERSION
|
48
|
+
lambda { '23'.to_key }.should raise_error ERROR_CONVERSION
|
49
|
+
end
|
50
|
+
end
|
61
51
|
|
62
|
-
|
63
|
-
|
64
|
-
|
52
|
+
describe '#to_print' do
|
53
|
+
it 'converts String from (implied) WinCyrillic (CP1251) to default output encoding' do
|
54
|
+
string = "Широкая электрификация южных губерний даст мощный толчок подъёму сельского хозяйства"
|
55
|
+
win_string = string.encode('cp1251')
|
56
|
+
print_string = win_string.encode(Encoding.default_external, :undef => :replace)
|
57
|
+
win_string_thought_utf8 = win_string.force_encoding('utf-8')
|
58
|
+
win_string_thought_dos = win_string.force_encoding('cp866')
|
65
59
|
|
60
|
+
win_string_thought_utf8.to_print.should == print_string
|
61
|
+
win_string_thought_dos.to_print.should == print_string
|
66
62
|
end
|
63
|
+
|
67
64
|
end
|
68
65
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
require '
|
2
|
-
require 'spec/autorun'
|
1
|
+
require 'rspec'
|
3
2
|
require 'win_gui'
|
4
3
|
|
5
4
|
# Customize RSpec with my own extensions
|
@@ -14,86 +13,85 @@ module SpecMacros
|
|
14
13
|
# reads description line from source file and drops external brackets (like its{}, use{}
|
15
14
|
def description_from(file, line)
|
16
15
|
File.open(file) do |f|
|
17
|
-
f.lines.to_a[line-1].gsub(
|
16
|
+
f.lines.to_a[line-1].gsub(/(spec.*?{)|(use.*?{)|}/, '').strip
|
18
17
|
end
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
22
|
-
|
21
|
+
RSpec.configure { |config|
|
22
|
+
config.include Win::Gui # This is a namespace from win gem.
|
23
|
+
config.include WinGui # This is our own main namespace. TODO: looks confusing... better names?
|
24
|
+
config.extend SpecMacros }
|
23
25
|
|
24
|
-
module WinGuiTest
|
25
|
-
include Win::Gui # This is a namespace from win gem.
|
26
|
-
include WinGui # This is our own main namespace. TODO: looks confusing... better names?
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
27
|
+
# Test related Constants:
|
28
|
+
TIMEOUT = 0.001
|
29
|
+
KEY_DELAY = 0.001
|
30
|
+
SLEEP_DELAY = 0.01
|
31
|
+
APP_PATH = File.join(File.dirname(__FILE__), "../misc/locknote/LockNote.exe")
|
32
|
+
DIALOG_TITLE = "Save As"
|
33
|
+
WIN_TITLE = 'LockNote - Steganos LockNote'
|
34
|
+
WIN_CLASS = 'ATL:00434098'
|
35
|
+
TEXTAREA_CLASS = 'ATL:00434310'
|
36
|
+
STATUSBAR_CLASS = 'msctls_statusbar32'
|
37
|
+
IMPOSSIBLE = 'Impossible'
|
38
|
+
ERROR_CONVERSION = /Can.t convert/
|
40
39
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
# Helper methods:
|
41
|
+
def use
|
42
|
+
lambda { yield }.should_not raise_error
|
43
|
+
end
|
45
44
|
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
def any_handle
|
46
|
+
find_window(nil, nil)
|
47
|
+
end
|
49
48
|
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
def not_a_handle
|
50
|
+
123
|
51
|
+
end
|
53
52
|
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
def any_block
|
54
|
+
lambda { |*args| args }
|
55
|
+
end
|
57
56
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
57
|
+
def launch_test_app
|
58
|
+
#system APP_START
|
59
|
+
@test_app = App.launch(path: APP_PATH, title: WIN_TITLE, timeout: 1)
|
60
|
+
end
|
62
61
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
62
|
+
def close_test_app
|
63
|
+
while @test_app && @test_app.main_window.window?
|
64
|
+
@test_app.close
|
65
|
+
# Dealing with closing confirmation modal dialog
|
66
|
+
if dialog = dialog(title: "Steganos Locknote", timeout: SLEEP_DELAY)
|
67
|
+
dialog.set_foreground_window
|
68
|
+
keystroke("N")
|
71
69
|
end
|
72
|
-
@test_app = nil
|
73
70
|
end
|
71
|
+
@test_app = nil
|
72
|
+
end
|
74
73
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
74
|
+
# Creates test app object and yields it back to the block
|
75
|
+
def test_app
|
76
|
+
yield launch_test_app
|
77
|
+
close_test_app
|
78
|
+
end
|
80
79
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
end
|
80
|
+
def with_dialog(type=:close)
|
81
|
+
case type
|
82
|
+
when :close
|
83
|
+
keystroke('A')
|
84
|
+
@app.close
|
85
|
+
title, key = "Steganos Locknote", "N"
|
86
|
+
when :save
|
87
|
+
keystroke(VK_ALT, 'F', 'A')
|
88
|
+
title, key = "Save As", VK_ESCAPE
|
89
|
+
end
|
90
|
+
sleep 0.01 until dialog = Window.top_level(title: title)
|
91
|
+
yield dialog
|
92
|
+
while dialog.window?
|
93
|
+
dialog.set_foreground_window
|
94
|
+
keystroke(key)
|
95
|
+
sleep 0.01
|
98
96
|
end
|
99
97
|
end
|
data/spec/win_gui/app_spec.rb
CHANGED
@@ -1,120 +1,117 @@
|
|
1
|
-
|
1
|
+
require "spec_helper.rb"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
app.exit(timeout=10) if app
|
9
|
-
end
|
10
|
-
|
11
|
-
context 'initializing' do
|
12
|
-
context '::new' do
|
13
|
-
before(:each) { launch_test_app }
|
14
|
-
after(:each) { close_test_app }
|
15
|
-
|
16
|
-
it 'wraps new App around existing Window' do
|
17
|
-
window = Window.top_level(title: WIN_TITLE)
|
18
|
-
@app = App.new(window)
|
19
|
-
@app.should be_an App
|
20
|
-
@app.main_window.should == window
|
21
|
-
end
|
3
|
+
describe WinGui::App do
|
4
|
+
after(:each) do # Reliably closes launched app window (without calling close_test_app)
|
5
|
+
app = App.find(title: WIN_TITLE)
|
6
|
+
app.exit(timeout=10) if app
|
7
|
+
end
|
22
8
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@app.main_window.handle.should == window.handle
|
28
|
-
end
|
9
|
+
context 'initializing' do
|
10
|
+
context '::new' do
|
11
|
+
before(:each) { launch_test_app }
|
12
|
+
after(:each) { close_test_app }
|
29
13
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
14
|
+
it 'wraps new App around existing Window' do
|
15
|
+
window = Window.top_level(title: WIN_TITLE)
|
16
|
+
@app = App.new(window)
|
17
|
+
@app.should be_an App
|
18
|
+
@app.main_window.should == window
|
36
19
|
end
|
37
20
|
|
38
|
-
|
39
|
-
|
40
|
-
|
21
|
+
it 'wraps new App around active window handle (of top-level Window)' do
|
22
|
+
window = Window.top_level(title: WIN_TITLE)
|
23
|
+
@app = App.new(window.handle)
|
24
|
+
@app.should be_an App
|
25
|
+
@app.main_window.handle.should == window.handle
|
26
|
+
end
|
41
27
|
|
42
|
-
|
43
|
-
|
44
|
-
|
28
|
+
it 'raises error trying to create App with wrong init args' do
|
29
|
+
expect { App.new() }.to raise_error ArgumentError, /wrong number of arguments/
|
30
|
+
[[nil], 1.2, {title: WIN_TITLE}].each do |args|
|
31
|
+
expect { App.new(*args) }.to raise_error WinGui::Errors::InitError
|
45
32
|
end
|
33
|
+
end
|
34
|
+
end
|
46
35
|
|
47
|
-
|
48
|
-
|
49
|
-
|
36
|
+
context '::find' do
|
37
|
+
before(:each) { launch_test_app }
|
38
|
+
after(:each) { close_test_app }
|
50
39
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
40
|
+
it 'finds already launched App given valid Window info' do
|
41
|
+
use { @app = App.find(title: WIN_TITLE) }
|
42
|
+
@app.should be_an App
|
55
43
|
end
|
56
44
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
@app.should be_an App
|
61
|
-
end
|
45
|
+
it 'returns nil if asked to find App with invalid Window info' do
|
46
|
+
App.find(title: IMPOSSIBLE).should == nil
|
47
|
+
end
|
62
48
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
49
|
+
it 'raises error only if asked to find App with invalid Window info and :raise option is set' do
|
50
|
+
expect { App.find(title: IMPOSSIBLE, raise: WinGui::Errors::InitError) }.
|
51
|
+
to raise_error WinGui::Errors::InitError
|
52
|
+
end
|
53
|
+
end
|
67
54
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
55
|
+
context '::launch' do
|
56
|
+
it 'launches new App given valid path and Window info' do
|
57
|
+
use { @app = App.launch(path: APP_PATH, title: WIN_TITLE) }
|
58
|
+
@app.should be_an App
|
59
|
+
end
|
72
60
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
61
|
+
it 'raises error if asked to launch App with invalid path' do
|
62
|
+
expect { App.launch(path: IMPOSSIBLE, title: WIN_TITLE) }.
|
63
|
+
to raise_error WinGui::Errors::InitError, /Unable to launch "Impossible"/
|
64
|
+
end
|
77
65
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
end
|
66
|
+
it 'raises error if asked to launch App with invalid Window info' do
|
67
|
+
expect { App.launch(path: APP_PATH, title: IMPOSSIBLE) }.
|
68
|
+
to raise_error WinGui::Errors::InitError, /Unable to launch App with .*?:title=>"Impossible"/
|
82
69
|
end
|
83
70
|
|
84
|
-
|
85
|
-
|
86
|
-
|
71
|
+
it 'changes to given valid directory before launch' do
|
72
|
+
use { @app = App.launch(dir: APP_PATH.sub(/LockNote.exe/, ''), path: 'Locknote.exe', title: WIN_TITLE) }
|
73
|
+
@app.should be_an App
|
74
|
+
end
|
87
75
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
76
|
+
it 'raises error if given invalid directory to change' do
|
77
|
+
expect { @app = App.launch(dir: APP_PATH, path: APP_PATH, title: WIN_TITLE) }.
|
78
|
+
to raise_error WinGui::Errors::InitError, /Unable to change to ".*LockNote.exe"/
|
92
79
|
end
|
93
80
|
end
|
94
81
|
|
95
|
-
context '
|
82
|
+
context 'properties:' do
|
96
83
|
before(:each) { @app = App.launch(path: APP_PATH, title: WIN_TITLE) }
|
84
|
+
after(:each) { @app.close }
|
97
85
|
|
98
|
-
it '
|
99
|
-
@app.
|
100
|
-
|
101
|
-
@app.main_window.visible?.should == false
|
102
|
-
@app.main_window.window?.should == false
|
86
|
+
it 'main_window' do
|
87
|
+
@app.main_window.should be_a Window
|
88
|
+
@app.main_window.title.should == WIN_TITLE
|
103
89
|
end
|
90
|
+
end
|
91
|
+
end
|
104
92
|
|
105
|
-
|
106
|
-
|
107
|
-
sleep SLEEP_DELAY # needed to ensure window had enough time to close down
|
108
|
-
@app.main_window.visible?.should == false
|
109
|
-
@app.main_window.window?.should == false
|
110
|
-
end
|
93
|
+
context 'manipulating' do
|
94
|
+
before(:each) { @app = App.launch(path: APP_PATH, title: WIN_TITLE) }
|
111
95
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
96
|
+
it 'exits App gracefully' do
|
97
|
+
@app.exit
|
98
|
+
sleep SLEEP_DELAY # needed to ensure window had enough time to close down
|
99
|
+
@app.main_window.visible?.should == false
|
100
|
+
@app.main_window.window?.should == false
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'closes App gracefully' do
|
104
|
+
@app.close
|
105
|
+
sleep SLEEP_DELAY # needed to ensure window had enough time to close down
|
106
|
+
@app.main_window.visible?.should == false
|
107
|
+
@app.main_window.window?.should == false
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'exits App with timeout' do
|
111
|
+
@app.exit(1)
|
112
|
+
# No sleep SLEEP_DELAY needed!
|
113
|
+
@app.main_window.visible?.should == false
|
114
|
+
@app.main_window.window?.should == false
|
118
115
|
end
|
119
116
|
end
|
120
117
|
end
|
@@ -1,75 +1,72 @@
|
|
1
|
-
|
1
|
+
require "spec_helper.rb"
|
2
2
|
|
3
|
-
|
3
|
+
describe WinGui, 'Convenience methods' do
|
4
|
+
before(:each) { @win = launch_test_app.main_window }
|
5
|
+
after(:each) { close_test_app }
|
4
6
|
|
5
|
-
describe '
|
6
|
-
|
7
|
-
|
7
|
+
describe '#dialog' do
|
8
|
+
it 'returns top-level dialog window with given title if no block attached' do
|
9
|
+
with_dialog(:save) do
|
10
|
+
dialog_window = dialog(title: DIALOG_TITLE, timeout: 0.1)
|
11
|
+
dialog_window.should_not == nil
|
12
|
+
dialog_window.should be_a Window
|
13
|
+
dialog_window.text.should == DIALOG_TITLE
|
14
|
+
end
|
15
|
+
end
|
8
16
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
dialog_window = dialog(title: DIALOG_TITLE, timeout: 0.1)
|
17
|
+
it 'yields found dialog window to block if block is attached' do
|
18
|
+
with_dialog(:save) do
|
19
|
+
dialog(title: DIALOG_TITLE) do |dialog_window|
|
13
20
|
dialog_window.should_not == nil
|
14
21
|
dialog_window.should be_a Window
|
15
22
|
dialog_window.text.should == DIALOG_TITLE
|
16
23
|
end
|
17
24
|
end
|
25
|
+
end
|
18
26
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
dialog_window.should_not == nil
|
23
|
-
dialog_window.should be_a Window
|
24
|
-
dialog_window.text.should == DIALOG_TITLE
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'returns nil if there is no dialog with given title' do
|
30
|
-
with_dialog(:save) do
|
31
|
-
dialog(title: IMPOSSIBLE, timeout: 0.1).should == nil
|
32
|
-
end
|
27
|
+
it 'returns nil if there is no dialog with given title' do
|
28
|
+
with_dialog(:save) do
|
29
|
+
dialog(title: IMPOSSIBLE, timeout: 0.1).should == nil
|
33
30
|
end
|
31
|
+
end
|
34
32
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
33
|
+
it 'yields nil to attached block if no dialog found' do
|
34
|
+
with_dialog(:save) do
|
35
|
+
dialog(title: IMPOSSIBLE, timeout: 0.1) do |dialog_window|
|
36
|
+
dialog_window.should == nil
|
40
37
|
end
|
41
38
|
end
|
39
|
+
end
|
42
40
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
41
|
+
it 'considers all arguments optional' do
|
42
|
+
with_dialog(:save) do
|
43
|
+
use { dialog_window = dialog() }
|
47
44
|
end
|
48
|
-
end
|
45
|
+
end
|
46
|
+
end # describe dialog
|
49
47
|
|
50
|
-
|
51
|
-
|
52
|
-
|
48
|
+
describe 'convenience input methods on top of Windows API' do
|
49
|
+
describe '#keystroke' do
|
50
|
+
spec { use { keystroke(vkey = 30, char = 'Z') } }
|
53
51
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
52
|
+
it 'emulates combinations of keys pressed (Ctrl+Alt+P+M, etc)' do
|
53
|
+
keystroke(VK_CONTROL, 'A')
|
54
|
+
keystroke(VK_SPACE)
|
55
|
+
textarea = @win.child(class: TEXTAREA_CLASS)
|
56
|
+
textarea.text.should.should == ' '
|
57
|
+
keystroke('1', '2', 'A', 'B'.ord)
|
58
|
+
textarea.text.should.should == ' 12ab'
|
59
|
+
end
|
60
|
+
end # describe '#keystroke'
|
63
61
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
62
|
+
describe '#type_in' do
|
63
|
+
it 'types text message into the window holding the focus' do
|
64
|
+
text = '1234 abcdefg'
|
65
|
+
type_in(text)
|
66
|
+
textarea = @win.child(class: TEXTAREA_CLASS)
|
67
|
+
textarea.text.should =~ Regexp.new(text)
|
68
|
+
end
|
69
|
+
end # describe '#type_in'
|
72
70
|
|
73
|
-
|
74
|
-
|
75
|
-
end
|
71
|
+
end # Input methods
|
72
|
+
end # Convenience methods
|
data/spec/win_gui/window_spec.rb
CHANGED
@@ -1,275 +1,272 @@
|
|
1
|
-
|
1
|
+
require "spec_helper.rb"
|
2
2
|
|
3
|
-
|
3
|
+
describe WinGui::Window do
|
4
|
+
before(:each) { @win = launch_test_app.main_window }
|
5
|
+
after(:each) { close_test_app }
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
context 'initializing' do
|
8
|
+
it 'can be wrapped around any existing window' do
|
9
|
+
any_handle = find_window(nil, nil)
|
10
|
+
use { Window.new any_handle }
|
11
|
+
end
|
12
|
+
end
|
8
13
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
context 'manipulating' do
|
15
|
+
it 'closes when asked nicely' do
|
16
|
+
@win.close
|
17
|
+
sleep SLEEP_DELAY # needed to ensure window had enough time to close down
|
18
|
+
find_window(nil, WIN_TITLE).should == nil
|
14
19
|
end
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
it 'waits for window to disappear (NB: this happens before handle is released!)' do
|
22
|
+
start = Time.now
|
23
|
+
@win.close
|
24
|
+
@win.wait_for_close
|
25
|
+
(Time.now - start).should be <= CLOSE_TIMEOUT
|
26
|
+
window_visible?(@win.handle).should be false
|
27
|
+
window?(@win.handle).should be false
|
28
|
+
end
|
29
|
+
end
|
22
30
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
+
context 'handle-related WinGui functions as instance methods' do
|
32
|
+
it 'calls all WinGui functions as instance methods (with handle as implicit first argument)' do
|
33
|
+
@win.window?.should == true
|
34
|
+
@win.visible?.should == true
|
35
|
+
@win.foreground?.should == true
|
36
|
+
@win.maximized?.should == false
|
37
|
+
@win.minimized?.should == false
|
38
|
+
@win.child?(any_handle).should == false
|
39
|
+
|
40
|
+
@win.window_rect.should be_an Array
|
41
|
+
@win.window_thread_process_id.should be_an Array
|
42
|
+
@win.enum_child_windows.should be_an Array
|
31
43
|
end
|
44
|
+
end
|
32
45
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
@win.maximized?.should == false
|
39
|
-
@win.minimized?.should == false
|
40
|
-
@win.child?(any_handle).should == false
|
41
|
-
|
42
|
-
@win.window_rect.should be_an Array
|
43
|
-
@win.window_thread_process_id.should be_an Array
|
44
|
-
@win.enum_child_windows.should be_an Array
|
45
|
-
end
|
46
|
-
end
|
46
|
+
context 'derived properties' do
|
47
|
+
it 'has handle property equal to underlying window handle' do
|
48
|
+
any = Window.new any_handle
|
49
|
+
any.handle.should == any_handle
|
50
|
+
end
|
47
51
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
52
|
+
it 'has class_name and text/title properties (derived from WinGui function calls)' do
|
53
|
+
@win.class_name.should == WIN_CLASS
|
54
|
+
# text propery accessed by sending WM_GETTEXT directly to window (convenience method in WinGui)
|
55
|
+
@win.text.should == WIN_TITLE
|
56
|
+
# window_text propery accessed via GetWindowText
|
57
|
+
@win.window_text.should == WIN_TITLE
|
58
|
+
# title property is just an alias for window_text
|
59
|
+
@win.title.should == WIN_TITLE
|
60
|
+
end
|
53
61
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
@win.window_text.should == WIN_TITLE
|
60
|
-
# title property is just an alias for window_text
|
61
|
-
@win.title.should == WIN_TITLE
|
62
|
-
end
|
62
|
+
it 'has thread and process properties derived from get_window_thread_process_id' do
|
63
|
+
thread = @win.thread
|
64
|
+
process = @win.process
|
65
|
+
[thread, process].should == get_window_thread_process_id(@win.handle)
|
66
|
+
end
|
63
67
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
68
|
+
it 'has id property that only makes sense for controls' do
|
69
|
+
use { @win.id }
|
70
|
+
end
|
71
|
+
end
|
69
72
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
+
describe '::top_level' do
|
74
|
+
it 'finds top-level window by title and wraps it in a Window object' do
|
75
|
+
window = Window.top_level(title: WIN_TITLE, timeout: 1)
|
76
|
+
window.handle.should == @win.handle
|
73
77
|
end
|
74
78
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
end
|
79
|
+
it 'finds top-level window by class and wraps it in a Window object' do
|
80
|
+
window = Window.top_level(class: WIN_CLASS, timeout: 1)
|
81
|
+
window.handle.should == @win.handle
|
82
|
+
end
|
80
83
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
84
|
+
it 'finds ANY top-level window without args and wraps it in a Window object' do
|
85
|
+
use { @window = Window.top_level() }
|
86
|
+
@window.should be_a Window
|
87
|
+
end
|
85
88
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
89
|
+
it 'returns nil immediately if top-level window with given title not found' do
|
90
|
+
start = Time.now
|
91
|
+
Window.top_level(title: IMPOSSIBLE).should == nil
|
92
|
+
(Time.now - start).should be_within(0.03).of 0
|
93
|
+
end
|
90
94
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
95
|
+
it 'returns nil after timeout if top-level window with given title not found' do
|
96
|
+
start = Time.now
|
97
|
+
Window.top_level(title: IMPOSSIBLE, timeout: 0.3).should == nil
|
98
|
+
(Time.now - start).should be_within(0.03).of 0.3
|
99
|
+
end
|
96
100
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
(Time.now - start).should be_close 0.3, 0.03
|
101
|
-
end
|
101
|
+
it 'raises exception if asked to' do
|
102
|
+
expect { Window.top_level(title: IMPOSSIBLE, raise: "Horror!") }.to raise_error "Horror!"
|
103
|
+
end
|
102
104
|
|
103
|
-
|
104
|
-
|
105
|
-
|
105
|
+
it 'uses .find as alias for .top_level' do
|
106
|
+
use { @window = Window.find() }
|
107
|
+
@window.should be_a Window
|
108
|
+
end
|
109
|
+
end # describe .top_level
|
106
110
|
|
107
|
-
|
108
|
-
|
109
|
-
@window.should be_a Window
|
110
|
-
end
|
111
|
-
end # describe .top_level
|
111
|
+
describe '#child' do
|
112
|
+
spec { use { @child = @win.child(title: "Title", class: "Class", id: 0) } }
|
112
113
|
|
113
|
-
|
114
|
-
|
114
|
+
it 'returns nil immediately if specific child not found' do
|
115
|
+
start = Time.now
|
116
|
+
@win.child(title: IMPOSSIBLE).should == nil
|
117
|
+
(Time.now - start).should be_within(0.03).of 0
|
118
|
+
end
|
115
119
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
120
|
+
it 'returns nil after timeout if specific child not found' do
|
121
|
+
start = Time.now
|
122
|
+
@win.child(title: IMPOSSIBLE, timeout: 0.5).should == nil
|
123
|
+
(Time.now - start).should be_within(0.03).of 0.5
|
124
|
+
end
|
121
125
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
126
|
+
it 'finds ANY child window without args' do
|
127
|
+
use { @child = @win.child() }
|
128
|
+
@child.should_not == nil
|
129
|
+
@win.child?(@child.handle).should == true
|
130
|
+
end
|
127
131
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
132
|
+
it 'finds child window by class and returns it as a Window object (no timeout)' do
|
133
|
+
child = @win.child(class: TEXTAREA_CLASS)
|
134
|
+
child.should_not == nil
|
135
|
+
@win.child?(child.handle).should == true
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'finds child window by class and returns it as a Window object (with timeout)' do
|
139
|
+
child = @win.child(class: TEXTAREA_CLASS, timeout: 0.5)
|
140
|
+
child.should_not == nil
|
141
|
+
|
142
|
+
@win.child?(child.handle).should == true
|
143
|
+
child = @win.child(class: STATUSBAR_CLASS, timeout: 0.5)
|
144
|
+
child.should_not == nil
|
145
|
+
@win.child?(child.handle).should == true
|
146
|
+
end
|
133
147
|
|
134
|
-
|
135
|
-
|
148
|
+
it 'finds child with specific text and returns it as a Window object' do
|
149
|
+
with_dialog(:save) do |dialog|
|
150
|
+
child = dialog.child(title: "Cancel")
|
136
151
|
child.should_not == nil
|
137
|
-
|
152
|
+
dialog.child?(child.handle).should == true
|
153
|
+
child.get_dlg_ctrl_id.should == IDCANCEL
|
154
|
+
|
155
|
+
child = dialog.child(title: "&Save")
|
156
|
+
child.should_not == nil
|
157
|
+
dialog.child?(child.handle).should == true
|
158
|
+
child.get_dlg_ctrl_id.should == IDOK
|
138
159
|
end
|
160
|
+
end
|
139
161
|
|
140
|
-
|
141
|
-
|
162
|
+
it 'finds child control with a given ID and returns it as a Window object' do
|
163
|
+
with_dialog(:save) do |dialog|
|
164
|
+
child = dialog.child(id: IDCANCEL)
|
142
165
|
child.should_not == nil
|
166
|
+
dialog.child?(child.handle).should == true
|
167
|
+
child.text.should == "Cancel"
|
168
|
+
end
|
169
|
+
end
|
143
170
|
|
144
|
-
|
145
|
-
|
171
|
+
context 'indirect child' do
|
172
|
+
it 'returns nil if specified child not found' do
|
173
|
+
@win.child(title: IMPOSSIBLE, indirect: true).should == nil
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'finds ANY child window without other args' do
|
177
|
+
use { @child = @win.child(indirect: true) }
|
178
|
+
@child.should_not == nil
|
179
|
+
@win.child?(@child.handle).should == true
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'finds child window by class' do
|
183
|
+
child = @win.child(class: TEXTAREA_CLASS, indirect: true)
|
146
184
|
child.should_not == nil
|
147
185
|
@win.child?(child.handle).should == true
|
148
186
|
end
|
149
187
|
|
150
|
-
it 'finds child with specific text
|
188
|
+
it 'finds child with specific text' do
|
151
189
|
with_dialog(:save) do |dialog|
|
152
|
-
child = dialog.child(
|
190
|
+
child = dialog.child(title: "Cancel", indirect: true)
|
153
191
|
child.should_not == nil
|
154
192
|
dialog.child?(child.handle).should == true
|
155
|
-
child.
|
193
|
+
child.id.should == IDCANCEL
|
156
194
|
|
157
|
-
child = dialog.child(
|
195
|
+
child = dialog.child(title: "&Save", indirect: true)
|
158
196
|
child.should_not == nil
|
159
197
|
dialog.child?(child.handle).should == true
|
160
|
-
child.
|
198
|
+
child.id.should == IDOK
|
161
199
|
end
|
162
200
|
end
|
163
201
|
|
164
|
-
it 'finds child control with a given ID
|
202
|
+
it 'finds child control with a given ID ' do
|
165
203
|
with_dialog(:save) do |dialog|
|
166
|
-
child = dialog.child(
|
204
|
+
child = dialog.child(id: IDCANCEL, indirect: true)
|
167
205
|
child.should_not == nil
|
168
206
|
dialog.child?(child.handle).should == true
|
169
207
|
child.text.should == "Cancel"
|
170
208
|
end
|
171
209
|
end
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
child.id.should == IDCANCEL
|
196
|
-
|
197
|
-
child = dialog.child( title: "&Save", indirect: true)
|
198
|
-
child.should_not == nil
|
199
|
-
dialog.child?(child.handle).should == true
|
200
|
-
child.id.should == IDOK
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
it 'finds child control with a given ID ' do
|
205
|
-
with_dialog(:save) do |dialog|
|
206
|
-
child = dialog.child( id: IDCANCEL, indirect: true)
|
207
|
-
child.should_not == nil
|
208
|
-
dialog.child?(child.handle).should == true
|
209
|
-
child.text.should == "Cancel"
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end # context indirect
|
213
|
-
end # describe child
|
214
|
-
|
215
|
-
describe '#children' do
|
216
|
-
spec { use { children = @win.children }}
|
217
|
-
|
218
|
-
it 'returns an array of Windows that are descendants (not only DIRECT children) of a given Window' do
|
219
|
-
children = @win.children
|
220
|
-
children.should be_a_kind_of Array
|
221
|
-
children.should_not be_empty
|
222
|
-
children.should have(2).elements
|
223
|
-
children.each{|child| child?(@win.handle, child.handle).should == true }
|
224
|
-
children.last.class_name.should == TEXTAREA_CLASS
|
210
|
+
end # context indirect
|
211
|
+
end # describe child
|
212
|
+
|
213
|
+
describe '#children' do
|
214
|
+
spec { use { children = @win.children } }
|
215
|
+
|
216
|
+
it 'returns an array of Windows that are descendants (not only DIRECT children) of a given Window' do
|
217
|
+
children = @win.children
|
218
|
+
children.should be_a_kind_of Array
|
219
|
+
children.should_not be_empty
|
220
|
+
children.should have(2).elements
|
221
|
+
children.each { |child| child?(@win.handle, child.handle).should == true }
|
222
|
+
children.last.class_name.should == TEXTAREA_CLASS
|
223
|
+
end
|
224
|
+
end # describe #children
|
225
|
+
|
226
|
+
describe '#click' do
|
227
|
+
it 'emulates left click of the control identified by id, returns click coords' do
|
228
|
+
with_dialog(:save) do |dialog|
|
229
|
+
point = dialog.click(id: IDCANCEL)
|
230
|
+
point.should be_an Array
|
231
|
+
sleep 0.3
|
232
|
+
dialog.window?.should == false
|
225
233
|
end
|
226
|
-
end
|
234
|
+
end
|
227
235
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
dialog.window?.should == false
|
235
|
-
end
|
236
|
+
it 'emulates left click of the control identified by title, returns click coords' do
|
237
|
+
with_dialog(:save) do |dialog|
|
238
|
+
point = dialog.click(title: "Cancel")
|
239
|
+
point.should be_an Array
|
240
|
+
sleep 0.3
|
241
|
+
dialog.window?.should == false
|
236
242
|
end
|
243
|
+
end
|
237
244
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
it 'emulates right click of the control identified by id, returns click coords' do
|
248
|
-
with_dialog(:save) do |dialog|
|
249
|
-
point = dialog.click(id: IDCANCEL, mouse_button: :right)
|
250
|
-
point.should be_an Array
|
251
|
-
sleep 0.3
|
252
|
-
dialog.window?.should == true
|
253
|
-
end
|
245
|
+
it 'emulates right click of the control identified by id, returns click coords' do
|
246
|
+
with_dialog(:save) do |dialog|
|
247
|
+
point = dialog.click(id: IDCANCEL, mouse_button: :right)
|
248
|
+
point.should be_an Array
|
249
|
+
sleep 0.3
|
250
|
+
dialog.window?.should == true
|
254
251
|
end
|
252
|
+
end
|
255
253
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
end
|
254
|
+
it 'emulates right click of the control identified by title, returns click coords' do
|
255
|
+
with_dialog(:save) do |dialog|
|
256
|
+
point = dialog.click(title: "Cancel", mouse_button: :right)
|
257
|
+
point.should be_an Array
|
258
|
+
sleep 0.3
|
259
|
+
dialog.window?.should == true
|
263
260
|
end
|
261
|
+
end
|
264
262
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
end
|
263
|
+
it 'returns nil if the specified control was not found' do
|
264
|
+
with_dialog(:save) do |dialog|
|
265
|
+
dialog.click(title: "Shpancel").should == nil
|
266
|
+
dialog.click(id: 66).should == nil
|
267
|
+
sleep 0.3
|
268
|
+
dialog.window?.should == true
|
272
269
|
end
|
273
|
-
end
|
274
|
-
end
|
270
|
+
end
|
271
|
+
end # describe #click
|
275
272
|
end
|
data/tasks/spec.rake
CHANGED
@@ -2,17 +2,13 @@ desc 'Alias to spec:spec'
|
|
2
2
|
task :spec => 'spec:spec'
|
3
3
|
|
4
4
|
namespace :spec do
|
5
|
-
require '
|
5
|
+
require 'rspec/core/rake_task'
|
6
6
|
|
7
7
|
desc "Run all specs"
|
8
|
-
|
9
|
-
t.spec_opts = ['--options', %Q{"#{BASE_PATH}/spec/spec.opts"}]
|
10
|
-
t.spec_files = FileList['spec/**/*_spec.rb']
|
11
|
-
end
|
8
|
+
RSpec::Core::RakeTask.new(:spec){|task|}
|
12
9
|
|
13
10
|
desc "Run specs with RCov"
|
14
|
-
|
15
|
-
t.spec_files = FileList['spec/**/*_spec.rb']
|
11
|
+
RSpec::Core::RakeTask.new(:rcov) do |t|
|
16
12
|
t.rcov = true
|
17
13
|
t.rcov_opts = ['--exclude', 'spec']
|
18
14
|
end
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: win_gui
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
- 16
|
10
|
-
version: 0.2.16
|
4
|
+
prerelease:
|
5
|
+
version: 0.2.17
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- arvicco
|
@@ -15,7 +10,7 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date:
|
13
|
+
date: 2011-02-16 00:00:00 +03:00
|
19
14
|
default_executable:
|
20
15
|
dependencies:
|
21
16
|
- !ruby/object:Gem::Dependency
|
@@ -26,11 +21,6 @@ dependencies:
|
|
26
21
|
requirements:
|
27
22
|
- - ">="
|
28
23
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 13
|
30
|
-
segments:
|
31
|
-
- 1
|
32
|
-
- 2
|
33
|
-
- 9
|
34
24
|
version: 1.2.9
|
35
25
|
type: :development
|
36
26
|
version_requirements: *id001
|
@@ -42,9 +32,6 @@ dependencies:
|
|
42
32
|
requirements:
|
43
33
|
- - ">="
|
44
34
|
- !ruby/object:Gem::Version
|
45
|
-
hash: 3
|
46
|
-
segments:
|
47
|
-
- 0
|
48
35
|
version: "0"
|
49
36
|
type: :development
|
50
37
|
version_requirements: *id002
|
@@ -56,11 +43,6 @@ dependencies:
|
|
56
43
|
requirements:
|
57
44
|
- - ">="
|
58
45
|
- !ruby/object:Gem::Version
|
59
|
-
hash: 17
|
60
|
-
segments:
|
61
|
-
- 0
|
62
|
-
- 3
|
63
|
-
- 1
|
64
46
|
version: 0.3.1
|
65
47
|
type: :runtime
|
66
48
|
version_requirements: *id003
|
@@ -82,7 +64,6 @@ files:
|
|
82
64
|
- lib/win_gui/window.rb
|
83
65
|
- lib/win_gui.rb
|
84
66
|
- spec/extension_spec.rb
|
85
|
-
- spec/spec.opts
|
86
67
|
- spec/spec_helper.rb
|
87
68
|
- spec/win_gui/app_spec.rb
|
88
69
|
- spec/win_gui/convenience_spec.rb
|
@@ -121,29 +102,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
121
102
|
requirements:
|
122
103
|
- - ">="
|
123
104
|
- !ruby/object:Gem::Version
|
124
|
-
hash: 3
|
125
|
-
segments:
|
126
|
-
- 0
|
127
105
|
version: "0"
|
128
106
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
107
|
none: false
|
130
108
|
requirements:
|
131
109
|
- - ">="
|
132
110
|
- !ruby/object:Gem::Version
|
133
|
-
hash: 3
|
134
|
-
segments:
|
135
|
-
- 0
|
136
111
|
version: "0"
|
137
112
|
requirements: []
|
138
113
|
|
139
|
-
rubyforge_project:
|
140
|
-
rubygems_version: 1.
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 1.5.0
|
141
116
|
signing_key:
|
142
117
|
specification_version: 3
|
143
118
|
summary: Abstractions/wrappers around GUI-related Win32 API functions
|
144
119
|
test_files:
|
145
120
|
- spec/extension_spec.rb
|
146
|
-
- spec/spec.opts
|
147
121
|
- spec/spec_helper.rb
|
148
122
|
- spec/win_gui/app_spec.rb
|
149
123
|
- spec/win_gui/convenience_spec.rb
|
data/spec/spec.opts
DELETED