nobbie-wx-preview 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/KNOWN ISSUES +27 -0
- data/README +149 -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 +36 -0
- data/lib/nobbie/wx/command/click_on.rb +33 -0
- data/lib/nobbie/wx/command/get_component.rb +17 -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 +89 -0
- data/lib/nobbie/wx/command/type_into.rb +79 -0
- data/lib/nobbie/wx/command_executor.rb +25 -0
- data/lib/nobbie/wx/command_factory.rb +48 -0
- data/lib/nobbie/wx/driven.rb +7 -0
- data/lib/nobbie/wx/impl/element/element_path_builder.rb +41 -0
- data/lib/nobbie/wx/impl/operation/choosable.rb +31 -0
- data/lib/nobbie/wx/impl/operation/select.rb +31 -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 +6 -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 +107 -0
- data/test/suite/test_type.rb +59 -0
- metadata +102 -0
@@ -0,0 +1,79 @@
|
|
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 #{@value}"
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def handle_text_ctrl_or_combo_box
|
28
|
+
ensure_enabled
|
29
|
+
|
30
|
+
Kernel.raise(ComponentReadOnlyException,
|
31
|
+
"cannot type, component is readonly") if readonly
|
32
|
+
|
33
|
+
#todo: resolve issue with events when value does not change
|
34
|
+
#this is required because windows raises the event when the value is the same, OSX does not.
|
35
|
+
#return if component.value == @value
|
36
|
+
|
37
|
+
#todo: consider using 'clear' .. but symantics different for ComboBox (ControlWithItems)
|
38
|
+
type('')
|
39
|
+
|
40
|
+
@value.length.times {|n| type(@value[0..n]) }
|
41
|
+
|
42
|
+
#todo: this looks like a windows wx issue .. the docs state that set_value does nothing if the combo is readonly
|
43
|
+
Kernel.raise(ComponentReadOnlyException,
|
44
|
+
"cannot type, component is readonly") if component.value != @value && component.is_a?(ComboBox)
|
45
|
+
end
|
46
|
+
|
47
|
+
def readonly
|
48
|
+
if component.is_a?(TextCtrl)
|
49
|
+
return !component.is_editable
|
50
|
+
end
|
51
|
+
|
52
|
+
if component.is_a?(ComboBox)
|
53
|
+
#todo: fix ComboBox readonly
|
54
|
+
#there doesnt seem to be a method of determining when a combobox is readonly ...
|
55
|
+
#in theory you can try setting it to a value thats not in the list and see if that changes
|
56
|
+
#which doesnt work as expected on windows (see above))
|
57
|
+
return false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def type(text)
|
62
|
+
#todo: this isnt quite right .. should really use append (but combo's don't support it)
|
63
|
+
|
64
|
+
#todo: find a nicer way to handle OS specific bits ...
|
65
|
+
if Platform.windows?
|
66
|
+
event = CommandEvent.new(EVT_COMMAND_TEXT_UPDATED, component.get_id)
|
67
|
+
event.string = text
|
68
|
+
event.event_object = component
|
69
|
+
component.command(event) if component.is_a?(ComboBox)
|
70
|
+
end
|
71
|
+
component.value = text
|
72
|
+
component.refresh
|
73
|
+
component.update
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
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
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,48 @@
|
|
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
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,41 @@
|
|
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
|
+
component = Window.find_window_by_name(@name)
|
22
|
+
return component unless component.nil?
|
23
|
+
|
24
|
+
menu_bar = APPLICATION_UNDER_TEST.get_top_window.get_menu_bar
|
25
|
+
unless menu_bar.nil?
|
26
|
+
component = menu_bar.get_menu(menu_bar.find_menu(@name))
|
27
|
+
end
|
28
|
+
|
29
|
+
#todo: pull this up ...
|
30
|
+
Kernel.raise(ComponentNotFoundException, "cannot find component with name: #{@name}") if component.nil?
|
31
|
+
|
32
|
+
component
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
"#{@name}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
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,31 @@
|
|
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
|
+
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,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
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'nobbie/wx/platform'
|
2
|
+
require 'nobbie/wx/command_factory'
|
3
|
+
require 'nobbie/wx/launcher'
|
4
|
+
|
5
|
+
impl = File.dirname(__FILE__) + File::SEPARATOR + 'impl'
|
6
|
+
Dir.glob("#{impl}/**/*.rb") {|f| require "#{f}" }
|
7
|
+
|
8
|
+
module Nobbie
|
9
|
+
module Wx
|
10
|
+
|
11
|
+
module Operations
|
12
|
+
|
13
|
+
# Types text into the component specified in the path.
|
14
|
+
# Supported components: TextCtrl, ComboBox
|
15
|
+
def type(text, path)
|
16
|
+
execute(command_factory.create_type_into_command(coerce_path(path), text))
|
17
|
+
end
|
18
|
+
|
19
|
+
# Clicks the component specified in the path.
|
20
|
+
# Supported components: Button
|
21
|
+
def click(path)
|
22
|
+
execute(command_factory.create_click_on_command(coerce_path(path)))
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the component specified in the path. Useful for performing operations that Nobbie does not
|
26
|
+
# currently support.
|
27
|
+
# Supported components: Any
|
28
|
+
def component(path)
|
29
|
+
execute(command_factory.create_get_component_command(coerce_path(path)))
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a SelectionOperations[link:classes/Nobbie/Wx/SelectOperations.html] for interacting with
|
33
|
+
# the component specified in the path
|
34
|
+
def selection(path)
|
35
|
+
SelectOperations.new(self, coerce_path(path))
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns a ChoosableOperations[link:classes/Nobbie/Wx/ChoosableOperations.html] for interacting with
|
39
|
+
# the component specified in the path
|
40
|
+
def choosable(path)
|
41
|
+
ChoosableOperations.new(self, coerce_path((path)))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Determines if the component specified in the path is currently enabled.
|
45
|
+
# Supported components: Any
|
46
|
+
def enabled?(path)
|
47
|
+
execute(command_factory.create_is_enabled_command(coerce_path(path)))
|
48
|
+
end
|
49
|
+
|
50
|
+
# Creates an instance of the default {path builder}[link:classes/Nobbie/Wx/ElementPathBuilder.html].
|
51
|
+
# Override this method if you wish to provide an alternative default path builder (because coerce_path
|
52
|
+
# uses in_() internally).
|
53
|
+
def in_(name)
|
54
|
+
ElementPathBuilder.new(name)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Creates an instance of the default {command factory}[link:classes/Nobbie/Wx/CommandFactory.html].
|
58
|
+
# Override this method if you wish to provide an alternative command factory.
|
59
|
+
def command_factory
|
60
|
+
Command::Factory.new
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def coerce_path(path)
|
66
|
+
return path if path.respond_to?(:find_component)
|
67
|
+
|
68
|
+
if path.is_a?(Hash) && path.has_key?(:in)
|
69
|
+
return path[:in].is_a?(String) ? in_(path[:in]) : path[:in]
|
70
|
+
end
|
71
|
+
|
72
|
+
return in_(path.id2name) if path.is_a?(Symbol)
|
73
|
+
return in_(path) if path.is_a?(String)
|
74
|
+
|
75
|
+
Kernel.raise("Unable to coerce path: #{path}")
|
76
|
+
end
|
77
|
+
|
78
|
+
#todo: pull up
|
79
|
+
def execute(command)
|
80
|
+
Command::Executor.new.execute(command)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'wx'
|
3
|
+
|
4
|
+
#todo: move this into Nobbie::Wx namespace ... once name clash is fixed
|
5
|
+
class Platform #:nodoc:
|
6
|
+
include Wx
|
7
|
+
|
8
|
+
WINDOWS = 'WXMSW'
|
9
|
+
MAC = 'WXMAC'
|
10
|
+
SUPPORTED_PLATFORMS = [WINDOWS, MAC]
|
11
|
+
|
12
|
+
def self.windows?
|
13
|
+
name == WINDOWS
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.ensure_supported
|
17
|
+
Kernel.raise "Sorry '#{name}' is not currently supported, Nobbie-Wx is only available for the following platforms: [#{SUPPORTED_PLATFORMS.join(',')}]" unless supported?
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def self.supported?
|
23
|
+
SUPPORTED_PLATFORMS.include?(name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.name
|
27
|
+
Wx::PLATFORM
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Platform.ensure_supported
|