win_gui 0.2.16 → 0.2.17
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.
- 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