nobbie-wx-preview 0.0.1
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 +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
|