kolach-melomel 0.6.4
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/CHANGELOG +35 -0
- data/README.md +58 -0
- data/lib/melomel.rb +48 -0
- data/lib/melomel/bridge.rb +76 -0
- data/lib/melomel/bridge/messaging.rb +252 -0
- data/lib/melomel/bridge/ui.rb +155 -0
- data/lib/melomel/cucumber.rb +150 -0
- data/lib/melomel/cucumber/alert_steps.rb +40 -0
- data/lib/melomel/cucumber/button_steps.rb +17 -0
- data/lib/melomel/cucumber/color_picker_steps.rb +18 -0
- data/lib/melomel/cucumber/data_grid_steps.rb +83 -0
- data/lib/melomel/cucumber/date_steps.rb +22 -0
- data/lib/melomel/cucumber/list_steps.rb +30 -0
- data/lib/melomel/cucumber/slider_steps.rb +20 -0
- data/lib/melomel/cucumber/text_steps.rb +26 -0
- data/lib/melomel/date.rb +19 -0
- data/lib/melomel/error.rb +43 -0
- data/lib/melomel/flex.rb +46 -0
- data/lib/melomel/object_proxy.rb +94 -0
- data/lib/melomel/version.rb +3 -0
- data/lib/object.rb +30 -0
- data/test/helper.rb +28 -0
- data/test/sandbox.rb +14 -0
- data/test/test_bridge.rb +61 -0
- data/test/test_integration.rb +71 -0
- data/test/test_messaging.rb +109 -0
- data/test/test_object_proxy.rb +43 -0
- data/test/test_ui.rb +59 -0
- metadata +148 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
When /^I set the "([^"]*)" (date chooser|date field) to "(\d{1,2}\/\d{1,2}\/\d{4})"$/ do |name, type, date_string|
|
2
|
+
Melomel::Cucumber.run! do
|
3
|
+
classes = Melomel::Flex.get_component_classes(type)
|
4
|
+
component = Melomel::Cucumber.find_labeled!(classes, name)
|
5
|
+
component.setFocus()
|
6
|
+
component.selectedDate = Melomel::Date.parse(date_string)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
Then /^I should see the "([^"]*)" (date chooser|date field) set to "(\d{1,2}\/\d{1,2}\/\d{4})"$/ do |name, type, date_string|
|
11
|
+
Melomel::Cucumber.run! do
|
12
|
+
classes = Melomel::Flex.get_component_classes(type)
|
13
|
+
component = Melomel::Cucumber.find_labeled!(classes, name)
|
14
|
+
date = Melomel::Date.parse(date_string)
|
15
|
+
component.setFocus()
|
16
|
+
|
17
|
+
component.selectedDate.should_not be_nil
|
18
|
+
date.should_not be_nil
|
19
|
+
component.selectedDate.toLocaleDateString().should == date.toLocaleDateString()
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
When /^I select "([^"]*)" on the "([^"]*)" (combo box|list)$/ do |value, name, type|
|
2
|
+
Melomel::Cucumber.run! do
|
3
|
+
classes = Melomel::Flex.get_component_classes(type)
|
4
|
+
list = Melomel::Cucumber.find_labeled!(classes, name)
|
5
|
+
list.setFocus()
|
6
|
+
labels = Melomel.items_to_labels!(list, list.dataProvider)
|
7
|
+
|
8
|
+
# Loop over labels and set the selected index when we find a match
|
9
|
+
index = nil
|
10
|
+
labels.length.times do |i|
|
11
|
+
if labels[i] == value
|
12
|
+
index = i
|
13
|
+
end
|
14
|
+
end
|
15
|
+
raise "Cannot find '#{value}' on #{type}" if index.nil?
|
16
|
+
|
17
|
+
list.selectedIndex = index
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Then /^I should see "([^"]*)" selected on the "([^"]*)" (combo box|list)$/ do |value, name, type|
|
22
|
+
Melomel::Cucumber.run! do
|
23
|
+
classes = Melomel::Flex.get_component_classes(type)
|
24
|
+
list = Melomel::Cucumber.find_labeled!(classes, name)
|
25
|
+
list.setFocus()
|
26
|
+
label = list.itemToLabel(list.selectedItem)
|
27
|
+
label.should == value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
When /^I set the "([^"]*)" (slider) to "([^"]*)"$/ do |name, type, value|
|
2
|
+
Melomel::Cucumber.run! do
|
3
|
+
classes = Melomel::Flex.get_component_classes(type)
|
4
|
+
slider = Melomel::Cucumber.find_labeled!(classes, name)
|
5
|
+
slider.setFocus()
|
6
|
+
value = value.index('.') ? value.to_f : value.to_i
|
7
|
+
slider.value = value
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
Then /^I should see the "([^"]*)" (slider) set to "([^"]*)"$/ do |name, type, value|
|
12
|
+
Melomel::Cucumber.run! do
|
13
|
+
classes = Melomel::Flex.get_component_classes(type)
|
14
|
+
slider = Melomel::Cucumber.find_labeled!(classes, name)
|
15
|
+
slider.setFocus()
|
16
|
+
value = value.index('.') ? value.to_f : value.to_i
|
17
|
+
slider.value.should == value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
When /^I type "([^"]*)" in the "([^"]*)" (text field|text area)$/ do |text, name, type|
|
2
|
+
Melomel::Cucumber.run! do
|
3
|
+
classes = Melomel::Flex.get_component_classes(type)
|
4
|
+
component = Melomel::Cucumber.find_labeled!(classes, name)
|
5
|
+
component.setFocus()
|
6
|
+
component.text = text
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
Then /^I should see "([^"]*)" in the "([^"]*)" (text field|text area|label)$/ do |text, name, type|
|
11
|
+
Melomel::Cucumber.run! do
|
12
|
+
classes = Melomel::Flex.get_component_classes(type)
|
13
|
+
component = Melomel::Cucumber.find_labeled!(classes, name)
|
14
|
+
component.setFocus()
|
15
|
+
component.text.should == text
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Then /^I should not see "([^"]*)" in the "([^"]*)" (text field|text area|label)$/ do |text, name, type|
|
20
|
+
Melomel::Cucumber.run! do
|
21
|
+
classes = Melomel::Flex.get_component_classes(type)
|
22
|
+
component = Melomel::Cucumber.find_labeled!(classes, name)
|
23
|
+
component.setFocus()
|
24
|
+
component.text.should_not == text
|
25
|
+
end
|
26
|
+
end
|
data/lib/melomel/date.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Melomel
|
2
|
+
# This class contains helper methods for working with Flash dates.
|
3
|
+
class Date
|
4
|
+
# Parses a date.
|
5
|
+
#
|
6
|
+
# text - The date string to parse.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# Melomel::Date.parse('02/04/2010') # => <Melomel::ObjectProxy>
|
11
|
+
#
|
12
|
+
# Returns a proxy to a date object in Flash.
|
13
|
+
def self.parse(text)
|
14
|
+
date = Melomel.create_object!('Date')
|
15
|
+
date.time = Melomel.get_class!('Date').parse(text)
|
16
|
+
date
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# This class is used for all errors returned from the Flash virtual machine.
|
2
|
+
module Melomel
|
3
|
+
class Error < StandardError
|
4
|
+
############################################################################
|
5
|
+
#
|
6
|
+
# Constructor
|
7
|
+
#
|
8
|
+
############################################################################
|
9
|
+
|
10
|
+
def initialize(object, error_id, message, name, stack_trace)
|
11
|
+
@object = object
|
12
|
+
@error_id = error_id
|
13
|
+
@message = message
|
14
|
+
@name = name
|
15
|
+
@stack_trace = stack_trace
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
############################################################################
|
20
|
+
#
|
21
|
+
# Public Properties
|
22
|
+
#
|
23
|
+
############################################################################
|
24
|
+
|
25
|
+
# A proxied reference to the original Flash error object.
|
26
|
+
#
|
27
|
+
# Returns a Melomel::ObjectProxy.
|
28
|
+
attr_reader :object
|
29
|
+
|
30
|
+
# The error identifier of the Flash error.
|
31
|
+
attr_reader :error_id
|
32
|
+
|
33
|
+
# The Flash error message.
|
34
|
+
attr_reader :message
|
35
|
+
|
36
|
+
# The name of the Flash error.
|
37
|
+
attr_reader :name
|
38
|
+
|
39
|
+
# The Flash stack trace. This is only available when using the Flash debug
|
40
|
+
# player or the AIR Debug Launcher (ADL).
|
41
|
+
attr_reader :stack_trace
|
42
|
+
end
|
43
|
+
end
|
data/lib/melomel/flex.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Melomel
|
2
|
+
# This class contains helper methods for working with Flex components.
|
3
|
+
class Flex
|
4
|
+
# Retrieves a list of classes associated with a commonly named component.
|
5
|
+
#
|
6
|
+
# name - The common name of the component.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# Melomel.get_component_classes('button')
|
11
|
+
# # => ['mx.controls.Button', 'spark.components.Button']
|
12
|
+
#
|
13
|
+
# Returns a list of classes associated with a component's common name.
|
14
|
+
def self.get_component_classes(name)
|
15
|
+
case name.downcase
|
16
|
+
when 'alert' then ['mx.controls.Alert']
|
17
|
+
when 'button' then ['mx.controls.Button', 'spark.components.supportClasses.ButtonBase']
|
18
|
+
when 'check box' then ['mx.controls.CheckBox', 'spark.components.CheckBox']
|
19
|
+
when 'color picker' then ['mx.controls.ColorPicker']
|
20
|
+
when 'combo box' then ['mx.controls.ComboBox', 'spark.components.ComboBox']
|
21
|
+
when 'data grid' then ['mx.controls.DataGrid', 'mx.controls.AdvancedDataGrid']
|
22
|
+
when 'date chooser' then ['mx.controls.DateChooser']
|
23
|
+
when 'date field' then ['mx.controls.DateField']
|
24
|
+
when 'scroll bar' then ['mx.controls.HScrollBar', 'mx.controls.VScrollBar', 'spark.components.HScrollBar', 'spark.components.VScrollBar']
|
25
|
+
when 'slider' then ['mx.controls.HSlider', 'mx.controls.VSlider', 'spark.components.HSlider', 'spark.components.VSlider']
|
26
|
+
when 'image' then ['mx.controls.Image']
|
27
|
+
when 'label' then ['mx.controls.Label', 'spark.components.Label', 'spark.components.RichText']
|
28
|
+
when 'list' then ['mx.controls.List', 'spark.components.List']
|
29
|
+
when 'menu' then ['mx.controls.Menu']
|
30
|
+
when 'menu bar' then ['mx.controls.MenuBar']
|
31
|
+
when 'panel' then ['mx.containers.Panel', 'spark.components.Panel']
|
32
|
+
when 'stepper' then ['mx.controls.NumericStepper', 'spark.components.Spinner']
|
33
|
+
when 'pop up button' then ['mx.controls.PopUpButton']
|
34
|
+
when 'pop up menu button' then ['mx.controls.PopUpMenuButton']
|
35
|
+
when 'progress bar' then ['mx.controls.ProgressBar']
|
36
|
+
when 'radio button' then ['mx.controls.RadioButton', 'spark.components.RadioButton']
|
37
|
+
when 'rich text area' then ['mx.controls.RichTextEditor', 'spark.components.RichEditableText']
|
38
|
+
when 'tab' then ['mx.controls.tabBarClasses.Tab']
|
39
|
+
when 'text field' then ['mx.controls.TextInput', 'spark.components.TextInput']
|
40
|
+
when 'text area' then ['mx.controls.TextArea', 'spark.components.TextArea']
|
41
|
+
when 'tool tip' then ['mx.controls.ToolTip']
|
42
|
+
when 'tree' then ['mx.controls.Tree']
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# This class as a proxy to an object in the Flash virtual machine. Invoking
|
2
|
+
# methods, accessing properties or changing properties on this object will
|
3
|
+
# result in a command being sent to Flash to change or access the state of the
|
4
|
+
# object within the virtual machine. The Ruby object proxy holds no state.
|
5
|
+
#
|
6
|
+
# The object proxy works exactly as if the Flash object was a local Ruby object.
|
7
|
+
# To do this, the following aliases are made:
|
8
|
+
# * Method calls with method names ending in "=" are aliased to #set_property
|
9
|
+
# * Method calls without arguments are aliased to #get_property.
|
10
|
+
# * Method calls with arguments are aliased to #invoke_method
|
11
|
+
module Melomel
|
12
|
+
class ObjectProxy
|
13
|
+
attr_reader :bridge, :proxy_id
|
14
|
+
|
15
|
+
def initialize(bridge, proxy_id)
|
16
|
+
@bridge = bridge
|
17
|
+
@proxy_id = proxy_id
|
18
|
+
end
|
19
|
+
|
20
|
+
# Retrieves the value of a property for the proxied object.
|
21
|
+
def get_property(name)
|
22
|
+
@bridge.get_property(@proxy_id, name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_property!(name)
|
26
|
+
@bridge.get_property!(@proxy_id, name)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Sets the value of a property for the proxied object.
|
30
|
+
def set_property(name, value)
|
31
|
+
@bridge.set_property(@proxy_id, name, value)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets the value of a property for the proxied object.
|
35
|
+
def set_property!(name, value)
|
36
|
+
@bridge.set_property!(@proxy_id, name, value)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Invokes a method on the proxied object. Arguments passed into the method
|
40
|
+
# are passed through to the invoked method
|
41
|
+
def invoke_method(method_name, *args)
|
42
|
+
@bridge.invoke_method(@proxy_id, method_name, *args)
|
43
|
+
end
|
44
|
+
|
45
|
+
def invoke_method!(method_name, *args)
|
46
|
+
@bridge.invoke_method!(@proxy_id, method_name, *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
alias :invoke :invoke_method
|
50
|
+
|
51
|
+
# Proxies all methods to the appropriate Flash objects.
|
52
|
+
def method_missing(symbol, *args)
|
53
|
+
method_name = symbol.to_s
|
54
|
+
last_char = method_name.to_s[-1,1]
|
55
|
+
|
56
|
+
# Methods ending in "=" are aliased to set_property
|
57
|
+
if last_char == '='
|
58
|
+
return set_property(method_name.chop, *args)
|
59
|
+
# Methods with arguments are methods
|
60
|
+
elsif args.length > 0
|
61
|
+
if last_char == '!'
|
62
|
+
return invoke_method!(method_name.chop, *args)
|
63
|
+
else
|
64
|
+
return invoke_method(method_name, *args)
|
65
|
+
end
|
66
|
+
# Methods with no arguments are aliased to get_property
|
67
|
+
else
|
68
|
+
if last_char == '!'
|
69
|
+
return get_property!(method_name.chop)
|
70
|
+
else
|
71
|
+
return get_property(method_name)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Array accessor.
|
77
|
+
def [](index)
|
78
|
+
if index.is_a?(Fixnum)
|
79
|
+
get_property("[#{index}]")
|
80
|
+
else
|
81
|
+
get_property(index.to_s)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Array mutator.
|
86
|
+
def []=(index, value)
|
87
|
+
if index.is_a?(Fixnum)
|
88
|
+
set_property("[#{index}]", value)
|
89
|
+
else
|
90
|
+
set_property(index.to_s, value)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/object.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
class Object
|
2
|
+
# Recursively generates an object proxy for the object if it is a Hash or
|
3
|
+
# and Array.
|
4
|
+
#
|
5
|
+
# bridge - The bridge to use when generating a proxy.
|
6
|
+
#
|
7
|
+
# Returns a Melomel::ObjectProxy if it is a Hash or an Array. Otherwise
|
8
|
+
# returns the object itself.
|
9
|
+
def to_object_proxy(bridge)
|
10
|
+
proxy = self
|
11
|
+
|
12
|
+
# Convert each key/value pair in a Hash
|
13
|
+
if self.is_a?(Hash)
|
14
|
+
proxy = bridge.create_object('Object')
|
15
|
+
each_pair do |k,v|
|
16
|
+
v = v.to_object_proxy(bridge) unless v.nil?
|
17
|
+
proxy.set_property!(k, v)
|
18
|
+
end
|
19
|
+
# Convert each item in an Array.
|
20
|
+
elsif is_a?(Array)
|
21
|
+
proxy = bridge.create_object('Array')
|
22
|
+
each do |item|
|
23
|
+
item = item.to_object_proxy(bridge) unless item.nil?
|
24
|
+
proxy.push!(item)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
return proxy
|
29
|
+
end
|
30
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require "bundler"
|
3
|
+
Bundler.setup
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'mocha'
|
6
|
+
|
7
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
8
|
+
$LOAD_PATH.unshift(File.join(dir, '..', 'lib'))
|
9
|
+
$LOAD_PATH.unshift(dir)
|
10
|
+
|
11
|
+
require 'melomel'
|
12
|
+
|
13
|
+
class RunnerTestCase < MiniTest::Unit::TestCase
|
14
|
+
def start_runner
|
15
|
+
# Make sure FLEX_HOME is defined
|
16
|
+
raise 'FLEX_HOME environment variable must be set' if ENV['FLEX_HOME'].nil?
|
17
|
+
|
18
|
+
# Open up the sandbox
|
19
|
+
@pid = fork do
|
20
|
+
exec("#{ENV['FLEX_HOME']}/bin/adl target/MelomelRunner-app.xml")
|
21
|
+
end
|
22
|
+
Process.detach(@pid)
|
23
|
+
end
|
24
|
+
|
25
|
+
def stop_runner
|
26
|
+
Process.kill('KILL', @pid)
|
27
|
+
end
|
28
|
+
end
|
data/test/sandbox.rb
ADDED
data/test/test_bridge.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), *%w[helper])
|
2
|
+
|
3
|
+
class BridgeTestCase < MiniTest::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@bridge = Melomel::Bridge.new('localhost', 10101)
|
6
|
+
end
|
7
|
+
|
8
|
+
def connect
|
9
|
+
# Mock server
|
10
|
+
@server = mock('server')
|
11
|
+
TCPServer.stubs(:open).returns(@server)
|
12
|
+
TCPServer.any_instance.stubs(:close)
|
13
|
+
|
14
|
+
# Mock sockets
|
15
|
+
@socket = mock('socket')
|
16
|
+
@socket.expects(:gets).returns("<connect/>\x00")
|
17
|
+
@server.expects(:accept).returns(@socket)
|
18
|
+
@server.expects(:close)
|
19
|
+
|
20
|
+
# Attempt connection
|
21
|
+
@bridge.connect()
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_should_send_messages_over_socket_connection
|
25
|
+
connect()
|
26
|
+
@socket.expects(:puts).with("<message/>\x00")
|
27
|
+
@bridge.send('<message/>')
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_should_receive_messages_from_socket_connection
|
31
|
+
connect()
|
32
|
+
@socket.expects(:gets).returns("<message/>\x00")
|
33
|
+
assert_equal '<message/>', @bridge.receive()
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_should_send_policy_file_and_connect
|
37
|
+
policy = '<?xml version="1.0"?><!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"><cross-domain-policy><allow-access-from domain="localhost" to-ports="10101"/></cross-domain-policy>';
|
38
|
+
|
39
|
+
# Mock server
|
40
|
+
server = mock('server')
|
41
|
+
server.expects(:close)
|
42
|
+
TCPServer.expects(:open).returns(server)
|
43
|
+
|
44
|
+
# Mock policy file
|
45
|
+
policy_socket = mock('policy_socket')
|
46
|
+
policy_socket.expects(:gets).returns("<policy-file-request/>\x00")
|
47
|
+
policy_socket.expects(:send).with(policy, 0)
|
48
|
+
policy_socket.expects(:flush)
|
49
|
+
policy_socket.expects(:close)
|
50
|
+
|
51
|
+
# Mock regular socket
|
52
|
+
socket = mock('socket')
|
53
|
+
socket.expects(:gets).returns("<connect/>\x00")
|
54
|
+
|
55
|
+
# Server should return policy socket first and then regular socket
|
56
|
+
server.stubs(:accept).returns(policy_socket, socket, nil)
|
57
|
+
|
58
|
+
# Attempt connection
|
59
|
+
@bridge.connect()
|
60
|
+
end
|
61
|
+
end
|