bryan-ash-wx-nobbie 0.0.3.2
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/KNOWN_ISSUES +24 -0
- data/README +153 -0
- data/lib/nobbie/wx/acceptance_test.rb +19 -0
- data/lib/nobbie/wx/command.rb +63 -0
- data/lib/nobbie/wx/command/choose.rb +38 -0
- data/lib/nobbie/wx/command/click_on.rb +35 -0
- data/lib/nobbie/wx/command/get_component.rb +17 -0
- data/lib/nobbie/wx/command/get_options.rb +27 -0
- data/lib/nobbie/wx/command/get_selected_values.rb +25 -0
- data/lib/nobbie/wx/command/is_chosen.rb +21 -0
- data/lib/nobbie/wx/command/is_enabled.rb +18 -0
- data/lib/nobbie/wx/command/select.rb +97 -0
- data/lib/nobbie/wx/command/type_into.rb +73 -0
- data/lib/nobbie/wx/command_executor.rb +25 -0
- data/lib/nobbie/wx/command_factory.rb +52 -0
- data/lib/nobbie/wx/driven.rb +7 -0
- data/lib/nobbie/wx/impl/element/element_path_builder.rb +42 -0
- data/lib/nobbie/wx/impl/operation/choosable.rb +31 -0
- data/lib/nobbie/wx/impl/operation/select.rb +37 -0
- data/lib/nobbie/wx/launcher.rb +75 -0
- data/lib/nobbie/wx/operations.rb +85 -0
- data/lib/nobbie/wx/platform.rb +31 -0
- data/test/all_tests.rb +8 -0
- data/test/suite/app.rb +144 -0
- data/test/suite/nobbie_test_case.rb +46 -0
- data/test/suite/test_choose.rb +52 -0
- data/test/suite/test_click.rb +28 -0
- data/test/suite/test_enabled.rb +13 -0
- data/test/suite/test_launcher.rb +22 -0
- data/test/suite/test_operations.rb +27 -0
- data/test/suite/test_selection.rb +138 -0
- data/test/suite/test_type.rb +58 -0
- metadata +105 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
module Command
|
4
|
+
|
5
|
+
class SelectCommand < ComponentAwareCommand #:nodoc:
|
6
|
+
def initialize(path, value)
|
7
|
+
super(path)
|
8
|
+
@value = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
if component.is_a?(Menu)
|
13
|
+
return handle_menu
|
14
|
+
end
|
15
|
+
|
16
|
+
ensure_enabled
|
17
|
+
|
18
|
+
if component.is_a?(Notebook)
|
19
|
+
handle_notebook
|
20
|
+
elsif component.is_a?(ComboBox)
|
21
|
+
handle_combo_box
|
22
|
+
elsif component.is_a?(ListBox) || component.is_a?(Choice)
|
23
|
+
handle_list_box_or_choice
|
24
|
+
else
|
25
|
+
handle_unsupported_operation_for_component
|
26
|
+
end
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def describe
|
31
|
+
"Select '#{@value}' in #{@path}"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def handle_menu
|
37
|
+
id = component.find_item(@value)
|
38
|
+
handle_value_not_found unless id > -1
|
39
|
+
ensure_enabled(id)
|
40
|
+
|
41
|
+
#todo: should this be a MenuEvent?
|
42
|
+
#todo: find a way to avoid using APPLICATION_UNDER_TEST
|
43
|
+
APPLICATION_UNDER_TEST.get_top_window.process_event(CommandEvent.new(EVT_COMMAND_MENU_SELECTED, id))
|
44
|
+
|
45
|
+
return ''
|
46
|
+
end
|
47
|
+
|
48
|
+
def handle_notebook
|
49
|
+
component.page_count.times {|i|
|
50
|
+
if component.page(i).name == @value
|
51
|
+
highlight(component.page(i)) {
|
52
|
+
component.selection = i
|
53
|
+
}
|
54
|
+
return ''
|
55
|
+
end
|
56
|
+
}
|
57
|
+
|
58
|
+
handle_value_not_found
|
59
|
+
end
|
60
|
+
|
61
|
+
#todo: remove duplication for event raising
|
62
|
+
def handle_combo_box
|
63
|
+
highlight {
|
64
|
+
index = component.find_string(@value)
|
65
|
+
handle_value_not_found unless index > -1
|
66
|
+
|
67
|
+
event = CommandEvent.new(EVT_COMMAND_COMBOBOX_SELECTED, component.get_id)
|
68
|
+
component.selection = index
|
69
|
+
event.event_object = component
|
70
|
+
|
71
|
+
#todo: should this use process_event
|
72
|
+
component.command(event)
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
#todo: remove duplication for event raising
|
77
|
+
def handle_list_box_or_choice
|
78
|
+
highlight {
|
79
|
+
index = component.find_string(@value)
|
80
|
+
handle_value_not_found unless index > -1
|
81
|
+
|
82
|
+
event_type = (component.is_a?(ListBox) ? EVT_COMMAND_LISTBOX_SELECTED : EVT_COMMAND_CHOICE_SELECTED)
|
83
|
+
|
84
|
+
event = CommandEvent.new(event_type, component.get_id)
|
85
|
+
event.int = 1 #no idea why this works .. but it is needed
|
86
|
+
event.string = @value
|
87
|
+
component.selection = index
|
88
|
+
event.event_object = component
|
89
|
+
|
90
|
+
component.process_event(event)
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
module Command
|
4
|
+
|
5
|
+
class TypeIntoCommand < ComponentAwareCommand #:nodoc:
|
6
|
+
def initialize(path, value)
|
7
|
+
super(path)
|
8
|
+
@value = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
highlight {
|
13
|
+
if component.is_a?(TextCtrl) || component.is_a?(ComboBox)
|
14
|
+
handle_text_ctrl_or_combo_box
|
15
|
+
else
|
16
|
+
handle_unsupported_operation_for_component
|
17
|
+
end
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def describe
|
22
|
+
"Type '#{@value}' into #{@path}"
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def handle_text_ctrl_or_combo_box
|
28
|
+
ensure_enabled
|
29
|
+
|
30
|
+
Kernel.raise(ComponentReadOnlyException, "cannot type, component is readonly") if readonly
|
31
|
+
|
32
|
+
#todo: resolve issue with events when value does not change
|
33
|
+
#this is required because windows raises the event when the value is the same, OSX does not.
|
34
|
+
#return if component.value == @value
|
35
|
+
|
36
|
+
#todo: consider using 'clear' .. but symantics different for ComboBox (ControlWithItems)
|
37
|
+
type('')
|
38
|
+
|
39
|
+
@value.length.times {|n| type(@value[0..n]) }
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def readonly
|
44
|
+
if component.is_a?(TextCtrl)
|
45
|
+
return !component.is_editable
|
46
|
+
end
|
47
|
+
|
48
|
+
if component.is_a?(ComboBox)
|
49
|
+
return (component.get_window_style & CB_READONLY).nonzero?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def type(text)
|
54
|
+
#todo: this isnt quite right .. should really use append (but combo's don't support it)
|
55
|
+
|
56
|
+
#todo: find a nicer way to handle OS specific bits ...
|
57
|
+
if Platform.windows?
|
58
|
+
event = CommandEvent.new(EVT_COMMAND_TEXT_UPDATED, component.get_id)
|
59
|
+
event.string = text
|
60
|
+
event.event_object = component
|
61
|
+
|
62
|
+
#todo: should this use process_event
|
63
|
+
component.command(event) if component.is_a?(ComboBox)
|
64
|
+
end
|
65
|
+
component.value = text
|
66
|
+
component.refresh
|
67
|
+
component.update
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
module Command
|
4
|
+
|
5
|
+
class Executor #:nodoc:
|
6
|
+
def execute(command)
|
7
|
+
puts "\n> #{command.describe}"
|
8
|
+
result = command.execute
|
9
|
+
puts "< #{render(result)}"
|
10
|
+
result
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def render(component)
|
16
|
+
#todo: this needs improving to support other components
|
17
|
+
return '' if component.nil?
|
18
|
+
return component.value if component.respond_to?(:get_value)
|
19
|
+
return component.inspect
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'nobbie/wx/command'
|
2
|
+
|
3
|
+
command = File.dirname(__FILE__) + File::SEPARATOR + 'command'
|
4
|
+
Dir.glob("#{command}/**/*.rb") {|f| require "#{f}" }
|
5
|
+
|
6
|
+
require 'nobbie/wx/command_executor'
|
7
|
+
|
8
|
+
module Nobbie
|
9
|
+
module Wx
|
10
|
+
module Command
|
11
|
+
|
12
|
+
class Factory
|
13
|
+
def create_type_into_command(path, value)
|
14
|
+
TypeIntoCommand.new(path, value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_get_component_command(path)
|
18
|
+
GetComponentCommand.new(path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_click_on_command(path)
|
22
|
+
ClickOnCommand.new(path)
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_get_selected_values_command(path)
|
26
|
+
GetSelectedValuesCommand.new(path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_select_command(path, value)
|
30
|
+
SelectCommand.new(path, value)
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_is_chosen_command(path)
|
34
|
+
IsChosenCommand.new(path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_choose_command(path)
|
38
|
+
ChooseCommand.new(path)
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_is_enabled_command(path)
|
42
|
+
IsEnabledCommand.new(path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_get_options_command(path)
|
46
|
+
GetOptionsCommand.new(path)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
|
4
|
+
class ComponentNotFoundException < RuntimeError; end
|
5
|
+
class ValueNotFoundException < RuntimeError; end
|
6
|
+
class ComponentDisabledException < RuntimeError; end
|
7
|
+
class UnsupportedOperationForComponentException < RuntimeError; end
|
8
|
+
class ComponentReadOnlyException < RuntimeError; end
|
9
|
+
|
10
|
+
class ElementPathBuilder
|
11
|
+
def initialize(name)
|
12
|
+
@name = name
|
13
|
+
end
|
14
|
+
|
15
|
+
# Finds the component specified in the path. This implementation is about as dumb as its gets, but does
|
16
|
+
# handle named components and menus.
|
17
|
+
def find_component
|
18
|
+
#todo: make me properly navigate component tree
|
19
|
+
#todo: I should blow up if multiple windows with the same name are found ....
|
20
|
+
|
21
|
+
#todo: shouldn''t need to pass AUT.gtw here ... nil should search all
|
22
|
+
component = Window.find_window_by_name(@name, APPLICATION_UNDER_TEST.get_top_window)
|
23
|
+
return component unless component.nil?
|
24
|
+
|
25
|
+
menu_bar = APPLICATION_UNDER_TEST.get_top_window.get_menu_bar
|
26
|
+
unless menu_bar.nil?
|
27
|
+
component = menu_bar.get_menu(menu_bar.find_menu(@name))
|
28
|
+
end
|
29
|
+
|
30
|
+
#todo: pull this up ...
|
31
|
+
Kernel.raise(ComponentNotFoundException, "cannot find component with name: #{to_s}") if component.nil?
|
32
|
+
|
33
|
+
component
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
"'#{@name}'"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
|
4
|
+
class ChoosableOperations
|
5
|
+
def initialize(operations, path)
|
6
|
+
@operations = operations
|
7
|
+
@path = path
|
8
|
+
end
|
9
|
+
|
10
|
+
# Chooses the component specified in the path.
|
11
|
+
# Supported components: RadioButton, CheckBox
|
12
|
+
def choose
|
13
|
+
execute(@operations.command_factory.create_choose_command(@path))
|
14
|
+
end
|
15
|
+
|
16
|
+
# Determines if the component specified in the path is chosen.
|
17
|
+
# Supported components: RadioButton, CheckBox
|
18
|
+
def chosen?
|
19
|
+
execute(@operations.command_factory.create_is_chosen_command(@path))
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
#todo: pullup execute
|
25
|
+
def execute(command)
|
26
|
+
Command::Executor.new.execute(command)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
|
4
|
+
class SelectOperations
|
5
|
+
def initialize(operations, path)
|
6
|
+
@operations = operations
|
7
|
+
@path = path
|
8
|
+
end
|
9
|
+
|
10
|
+
# Retrieves the currently selected value for the component specified in the path.
|
11
|
+
# Supported components: Notebook, ComboBox, ListBox, Choice
|
12
|
+
def selected_value
|
13
|
+
execute(@operations.command_factory.create_get_selected_values_command(@path))
|
14
|
+
end
|
15
|
+
|
16
|
+
# Selects the given value for the component specified in the path.
|
17
|
+
# Supported components: Notebook, Menu, ComboBox, ListBox, Choice
|
18
|
+
def choose(value)
|
19
|
+
execute(@operations.command_factory.create_select_command(@path, value))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Retrieves available options for the component specified in the path.
|
23
|
+
# Supported components: Notebook, ComboBox, ListBox, Choice
|
24
|
+
def options
|
25
|
+
execute(@operations.command_factory.create_get_options_command(@path))
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
#todo: pullup execute
|
31
|
+
def execute(command)
|
32
|
+
Command::Executor.new.execute(command)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Nobbie
|
2
|
+
module Wx
|
3
|
+
|
4
|
+
module ApplicationUnderTest #:nodoc:
|
5
|
+
def init_timer
|
6
|
+
@aut_timer = Timer.new(self, -1)
|
7
|
+
@aut_timer.start(10)
|
8
|
+
evt_timer(@aut_timer.object_id) {|e| self.yield; Thread.pass }
|
9
|
+
end
|
10
|
+
|
11
|
+
#todo: think about adding evt_idle here as well.
|
12
|
+
end
|
13
|
+
|
14
|
+
class ApplicationLauncher #:nodoc:
|
15
|
+
|
16
|
+
AUT_NOT_WX_APP = "APPLICATION_UNDER_TEST must be an instance of a Wx::App"
|
17
|
+
AUT_NOT_DEFINED = "APPLICATION_UNDER_TEST must be set to be an instance of the application you wish to test"
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
begin
|
21
|
+
app = get_application
|
22
|
+
unless app.is_a?(Wxruby2::App)
|
23
|
+
handle(AUT_NOT_WX_APP)
|
24
|
+
end
|
25
|
+
rescue NameError => e
|
26
|
+
handle(AUT_NOT_DEFINED)
|
27
|
+
end
|
28
|
+
@app = app
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_application
|
32
|
+
start
|
33
|
+
result = yield
|
34
|
+
stop
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
def start
|
39
|
+
puts "\n>> Starting application: #{@app.class}"
|
40
|
+
start = Time.now
|
41
|
+
|
42
|
+
@app_thread = Thread.new {
|
43
|
+
@app.extend(ApplicationUnderTest)
|
44
|
+
@app.init_timer
|
45
|
+
@app.main_loop
|
46
|
+
}
|
47
|
+
|
48
|
+
@app_thread.priority = -1
|
49
|
+
|
50
|
+
sleep 1
|
51
|
+
finish = Time.now
|
52
|
+
puts "\n>> Took #{finish-start} seconds to start application"
|
53
|
+
Thread.pass
|
54
|
+
end
|
55
|
+
|
56
|
+
def stop
|
57
|
+
puts "\n>> Stopping application: #{@app.class}\n"
|
58
|
+
|
59
|
+
#todo: tbis would seem a polite way to exit .. but causes Bus/Segmentation Errors on OSX.
|
60
|
+
#@app.top_window.destroy
|
61
|
+
#@app.exit_main_loop
|
62
|
+
#@app = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def handle(message)
|
66
|
+
Kernel.raise message
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_application
|
70
|
+
APPLICATION_UNDER_TEST
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|