melomel 0.1.0

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.md ADDED
File without changes
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Ben Johnson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
data/README.md ADDED
@@ -0,0 +1,141 @@
1
+ melomel.rb -- A Ruby interface to Melomel
2
+ =========================================
3
+
4
+ ## DESCRIPTION
5
+
6
+ Melomel.rb is a library that allows Ruby to communicate with an application
7
+ running within the Flash virtual machine. For more information on Melomel,
8
+ visit the Melomel repository:
9
+
10
+ http://github.com/benbjohnson/melomel
11
+
12
+ Melomel.rb follows the rules of [Semantic Versioning](http://semver.org/) and
13
+ uses [TomDoc](http://tomdoc.org/) for inline documentation.
14
+
15
+
16
+ ## INSTALLATION
17
+
18
+ To install Melomel.rb simply use RubyGems:
19
+
20
+ $ [sudo] gem install melomel
21
+
22
+ Melomel needs to be embedded and running in the application you're trying to
23
+ connect to. Please see the Melomel repository for instructions on installation.
24
+
25
+
26
+ ## GETTING STARTED
27
+
28
+ The first step to using Melomel is to install the Flash SWC in your application.
29
+ Once the SWC is in your application, setup in your Ruby project is simple.
30
+
31
+ In your Ruby file, simply call the `connect()` method on `Melomel`:
32
+
33
+ require 'melomel'
34
+ Melomel.connect()
35
+
36
+ The `connect()` method is a blocking method so it won't proceed until it's done.
37
+ After it's connected, there are several actions you can perform:
38
+
39
+ 1. Create Object
40
+ 1. Get Class
41
+ 1. Get Property
42
+ 1. Set Property
43
+ 1. Invoke Method
44
+
45
+ ## API
46
+
47
+ ### Overview
48
+
49
+ Melomel communicates to the Flash virtual machine over a socket connection
50
+ using XML. The protocol is simple and is meant to proxy all data access calls
51
+ to the Flash virtual machine. This means that only primitives (strings, numbers
52
+ and booleans) are copied but all objects are accessed by reference. By proxying
53
+ objects, all data stays in the Flash virtual machine and there are no syncing
54
+ issues.
55
+
56
+ ### Create Object
57
+
58
+ To create an object, use the `create_object()` method on `Melomel`:
59
+
60
+ point = Melomel.create_object('flash.geom.Point')
61
+
62
+ The object returned is a proxy object so any actions performed on it in Ruby
63
+ will be performed on the ActionScript object in the Flash virtual machine.
64
+
65
+ ### Get Class
66
+
67
+ You can retrieve a class to call static methods and properties. Since classes
68
+ are objects in ActionScript, they work identically in Melomel.
69
+
70
+ app = Melomel.get_class('mx.core.FlexGlobals')
71
+ app.topLevelApplication.name = 'Melomel App!'
72
+
73
+ This Flex 4 example updates the name of the application to "Melomel App!".
74
+
75
+ ### Get Property & Set Property
76
+
77
+ Getting and setting properties is handled transparently when using the object
78
+ proxies.
79
+
80
+ point = Melomel.create_object('flash.geom.Point')
81
+ point.x = 30
82
+ point.set_property('y') = 40
83
+ p "pos: #{point.x}, #{point.y}"
84
+ p "length: #{point.get_property('length')}"
85
+
86
+ Property accessors on an object proxy are automatically used to retrieve the
87
+ property value of the Flash object. You can also use the `get_property()`
88
+ method when accessing properties and the `set_property()` method when mutating
89
+ properties.
90
+
91
+ ### Invoke Method
92
+
93
+ Invoking methods is also handled transparently when using object proxies.
94
+
95
+ _IMPORTANT: There a catch! Methods without any parameters must have a bang
96
+ (`!`) character appended to their method name when calling directly on an
97
+ object proxy._
98
+
99
+ clipboard = Melomel.get_class('flash.desktop.Clipboard').generalClipboard
100
+ data = clipboard.getData('air:text')
101
+ data = clipboard.invoke_method('getData', 'air:text')
102
+ clipboard.clear!()
103
+ clipboard.invoke_method('clear')
104
+
105
+ In this example, the `getData` method could be called on the object directly
106
+ because it had more than one argument. The `clear` method, however, required
107
+ that a bang character be appended to the name or that the `invoke_method` be
108
+ used.
109
+
110
+
111
+ ## HACKING
112
+
113
+ To get started with working with the source, start by running Bundler to get all
114
+ your dependencies:
115
+
116
+ [sudo] bundle install
117
+
118
+ If you are adding new features, please add test coverage to all code that you
119
+ write. Run the test suite with `rake`:
120
+
121
+ rake test
122
+
123
+
124
+ ## CONTRIBUTE
125
+
126
+ If you'd like to contribute to Melomel.rb, please fork the repo on GitHub:
127
+
128
+ http://github.com/benbjohnson/melomel.rb
129
+
130
+ To get all of the dependencies, install the gem first. The best way to get
131
+ your changes merged back into core is as follows:
132
+
133
+ 1. Clone your fork locally
134
+ 1. Create a named topic branch to contain your change
135
+ 1. Code
136
+ 1. All code must have RSpec test coverage. Run `rake` to ensure everything
137
+ passes.
138
+ 1. If you are adding new functionality, document it in the README
139
+ 1. If necessary, rebase your commits into logical chunks, without errors
140
+ 1. Push the branch up to GitHub
141
+ 1. Send me a pull request for your branch
@@ -0,0 +1,112 @@
1
+ require 'nokogiri'
2
+
3
+ # These add-on methods enable the message encoding and decoding.
4
+ module Melomel
5
+ class Bridge
6
+ # Creates an object in the Flash virtual machine and returns the reference
7
+ # to it.
8
+ def create_object(class_name)
9
+ send("<create class=\"#{class_name}\"/>")
10
+ parse_message_value(Nokogiri::XML(receive()).root)
11
+ end
12
+
13
+ # Retrieves a reference to a class in the Flash virtual machine.
14
+ def get_class(class_name)
15
+ send("<get-class name=\"#{class_name}\"/>")
16
+ parse_message_value(Nokogiri::XML(receive()).root)
17
+ end
18
+
19
+ # Retrieves a property of an object in the Flash virtual machine
20
+ def get_property(proxy_id, property)
21
+ send("<get object=\"#{proxy_id}\" property=\"#{property}\"/>")
22
+ parse_message_value(Nokogiri::XML(receive()).root)
23
+ end
24
+
25
+ # Sets a property on an object in the Flash virtual machine
26
+ def set_property(proxy_id, property, value)
27
+ # Create message and format value to set
28
+ xml = Nokogiri::XML("<set object=\"#{proxy_id}\" property=\"#{property}\"><arg/></set>")
29
+ format_message_value(xml.at_xpath('/set/arg'), value)
30
+
31
+ # Send & Receive
32
+ send(xml.root.to_xml(:indent => 0))
33
+ parse_message_value(Nokogiri::XML(receive()).root)
34
+ end
35
+
36
+ # Invokes a method on an object in the Flash virtual machine
37
+ def invoke_method(proxy_id, method_name, *args)
38
+ xml = Nokogiri::XML("<invoke object=\"#{proxy_id}\" method=\"#{method_name}\"><args/></invoke>")
39
+
40
+ # Loop over and add arguments to call
41
+ args_node = xml.at_xpath('invoke/args')
42
+ args.each do |arg|
43
+ arg_node = Nokogiri::XML::Node.new('arg', xml)
44
+ format_message_value(arg_node, arg)
45
+ args_node.add_child(arg_node)
46
+ end
47
+
48
+ # Send and receive
49
+ send(xml.root.to_xml(:indent => 0))
50
+ parse_message_value(Nokogiri::XML(receive()).root)
51
+ end
52
+
53
+ # Creates an object proxy from a hash
54
+ def create_hash(hash)
55
+ proxy = create_object('Object')
56
+ hash.each_pair do |k,v|
57
+ v = create_hash(v) if !v.nil? && v.is_a?(Hash)
58
+ proxy.set_property(k, v)
59
+ end
60
+ return proxy
61
+ end
62
+
63
+ # Formats a Ruby value into an XML message
64
+ def format_message_value(xml, value)
65
+ # Automatically convert simple hashes to objects
66
+ if(!value.nil? && value.is_a?(Hash))
67
+ value = create_hash(value)
68
+ end
69
+
70
+ if value.nil?
71
+ xml['dataType'] = 'null'
72
+ elsif value.class == Fixnum || value.class == Bignum
73
+ xml['value'] = value.to_s
74
+ xml['dataType'] = 'int'
75
+ elsif value.class == Float
76
+ xml['value'] = value.to_s
77
+ xml['dataType'] = 'float'
78
+ elsif value == true || value == false
79
+ xml['value'] = value.to_s
80
+ xml['dataType'] = 'boolean'
81
+ elsif value.class == Melomel::ObjectProxy
82
+ xml['value'] = value.proxy_id.to_s
83
+ xml['dataType'] = 'object'
84
+ elsif value.class == String
85
+ xml['value'] = value
86
+ xml['dataType'] = 'string'
87
+ end
88
+ end
89
+
90
+ # Parses a return message and converts it into an appropriate type
91
+ def parse_message_value(xml)
92
+ value = xml['value']
93
+ data_type = xml['dataType']
94
+
95
+ if data_type == 'null'
96
+ return nil
97
+ elsif data_type == 'int'
98
+ return value.to_i
99
+ elsif data_type == 'float'
100
+ return value.to_f
101
+ elsif data_type == 'boolean'
102
+ return value == 'true'
103
+ elsif data_type == 'object'
104
+ return Melomel::ObjectProxy.new(self, value.to_i)
105
+ elsif data_type == 'string' || data_type.nil?
106
+ return value
107
+ else
108
+ raise UnrecognizedTypeError, "Unknown type: #{data_type}"
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,55 @@
1
+ # These add-on methods provide ease of use utility methods.
2
+ module Melomel
3
+ class Bridge
4
+ # Finds a list of display objects matching a class and hash of properties.
5
+ def find_all(class_name, root={}, properties={})
6
+ # Merge hashes if no root is specified
7
+ if root.is_a?(Hash)
8
+ properties.merge!(root)
9
+ root = nil
10
+ end
11
+
12
+ # Retrieve object
13
+ get_class('melomel.core.UI').findAll(class_name, root, properties)
14
+ end
15
+
16
+ # Finds a display object by class and properties.
17
+ def find(class_name, root={}, properties={})
18
+ # Merge hashes if no root is specified
19
+ if root.is_a?(Hash)
20
+ properties.merge!(root)
21
+ root = nil
22
+ end
23
+
24
+ # Retrieve object
25
+ get_class('melomel.core.UI').find(class_name, root, properties)
26
+ end
27
+
28
+
29
+ # Imitates a click on a component
30
+ def click(component, properties={})
31
+ get_class('melomel.core.UI').click(component, properties)
32
+ end
33
+
34
+ # Imitates a double click on a component
35
+ def double_click(component, properties={})
36
+ get_class('melomel.core.UI').doubleClick(component, properties)
37
+ end
38
+
39
+
40
+ # Imitates a key down on a component
41
+ def key_down(component, char, properties={})
42
+ get_class('melomel.core.UI').keyDown(component, char, properties)
43
+ end
44
+
45
+ # Imitates a key up on a component
46
+ def key_up(component, char, properties={})
47
+ get_class('melomel.core.UI').keyUp(component, char, properties)
48
+ end
49
+
50
+ # Imitates a key press on a component
51
+ def key_press(component, char, properties={})
52
+ get_class('melomel.core.UI').keyPress(component, char, properties)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,76 @@
1
+ require 'socket'
2
+ require 'melomel/bridge/messaging'
3
+ require 'melomel/bridge/ui'
4
+
5
+ # The bridge manages the connection to the Flash virtual machine. All messages
6
+ # sent to the virtual machine are passed through this object.
7
+ module Melomel
8
+ class Bridge
9
+ attr_accessor :host, :port
10
+
11
+ def initialize(host='localhost', port=10101)
12
+ self.host = host
13
+ self.port = port
14
+ server, @socket = nil, nil
15
+ end
16
+
17
+ # Opens a socket connection to listen for incoming bridge connections. This
18
+ # method automatically handles requests for the policy file.
19
+ def connect()
20
+ disconnect()
21
+
22
+ # Listen for connections
23
+ server = TCPServer.open(host, port)
24
+
25
+ # Retrieve socket and check for initial handshake
26
+ while(@socket.nil?) do
27
+ socket = server.accept()
28
+ data = socket.gets("\x00").chomp("\x00")
29
+
30
+ # Send policy file if requested.
31
+ if(data == '<policy-file-request/>')
32
+ send_policy_file(socket)
33
+ # Otherwise open connection and continue
34
+ elsif(data == '<connect/>')
35
+ @socket = socket
36
+ end
37
+ end
38
+
39
+ server.close();
40
+ end
41
+
42
+ # Closes any open connection to a Flash virtual machine.
43
+ def disconnect()
44
+ begin
45
+ @socket.close()
46
+ rescue
47
+ end
48
+
49
+ @socket = nil
50
+ end
51
+
52
+ # Sends a message over to the Flash bridge.
53
+ def send(message)
54
+ @socket.puts("#{message}\x00")
55
+ end
56
+
57
+ # Receives a message from the Flash bridge. This is a blocking call.
58
+ def receive()
59
+ @socket.gets("\x00").chomp("\x00")
60
+ end
61
+
62
+
63
+ private
64
+ def send_policy_file(socket)
65
+ policy = ''
66
+ policy << '<?xml version="1.0"?>'
67
+ policy << '<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">'
68
+ policy << '<cross-domain-policy>'
69
+ policy << "<allow-access-from domain=\"#{host}\" to-ports=\"#{port}\"/>"
70
+ policy << '</cross-domain-policy>'
71
+ socket.send(policy)
72
+ socket.flush()
73
+ socket.close()
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,58 @@
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
+ # Sets the value of a property for the proxied object.
26
+ def set_property(name, value)
27
+ @bridge.set_property(@proxy_id, name, value)
28
+ end
29
+
30
+ # Invokes a method on the proxied object. Arguments passed into the method
31
+ # are passed through to the invoked method
32
+ def invoke_method(method_name, *args)
33
+ @bridge.invoke_method(@proxy_id, method_name, *args)
34
+ end
35
+
36
+ alias :invoke :invoke_method
37
+
38
+ # Proxies all methods to the appropriate Flash objects.
39
+ def method_missing(symbol, *args)
40
+ method_name = symbol.to_s
41
+ last_char = method_name.to_s[-1,1]
42
+
43
+ # Methods ending in "=" are aliased to set_property
44
+ if last_char == '='
45
+ return set_property(method_name.chop, *args)
46
+ # Methods with arguments are methods
47
+ elsif args.length > 0
48
+ return invoke_method(method_name, *args)
49
+ # Methods ending in '!' are methods
50
+ elsif last_char == '!'
51
+ return invoke_method(method_name.chop, *args)
52
+ # Methods with no arguments are aliased to get_property
53
+ else
54
+ return get_property(method_name)
55
+ end
56
+ end
57
+ end
58
+ end
data/lib/melomel/ui.rb ADDED
@@ -0,0 +1,44 @@
1
+ # This class provides ease of use utility methods for finding display objects
2
+ # and interacting with them.
3
+ module Melomel
4
+ class UI
5
+ class << self
6
+ # Finds all display objects matching a class and hash of properties.
7
+ def find_all(class_name, root={}, properties={})
8
+ Melomel.bridge.find_all(class_name, root, properties)
9
+ end
10
+
11
+ # Finds a display object by class and properties.
12
+ def find(class_name, root={}, properties={})
13
+ Melomel.bridge.find(class_name, root, properties)
14
+ end
15
+
16
+
17
+ # Imitates a click on a component
18
+ def click(component, properties={})
19
+ Melomel.bridge.click(component, properties)
20
+ end
21
+
22
+ # Imitates a double click on a component
23
+ def double_click(component, properties={})
24
+ Melomel.bridge.double_click(component, properties)
25
+ end
26
+
27
+
28
+ # Imitates a key down on a component
29
+ def key_down(component, char, properties={})
30
+ Melomel.bridge.key_down(component, char, properties)
31
+ end
32
+
33
+ # Imitates a key up on a component
34
+ def key_up(component, char, properties={})
35
+ Melomel.bridge.key_up(component, char, properties)
36
+ end
37
+
38
+ # Imitates a key press on a component
39
+ def key_press(component, char, properties={})
40
+ Melomel.bridge.key_press(component, char, properties)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module Melomel
2
+ VERSION = "0.1.0"
3
+ end
data/lib/melomel.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'melomel/bridge'
2
+ require 'melomel/object_proxy'
3
+ require 'melomel/ui'
4
+ require 'melomel/version'
5
+
6
+ # This class acts as a singleton instance of the bridge. This is typically the
7
+ # only class you'll need to use. If multiple instances of the bridge are needed
8
+ # (to connect to multiple SWF files), you will need to instantiate and manage
9
+ # the bridge objects manually.
10
+ module Melomel
11
+ class MelomelError < StandardError; end
12
+ class UnrecognizedTypeError < MelomelError; end
13
+
14
+ class << self
15
+ attr_reader :bridge
16
+
17
+ # Opens a bridge connection to the SWF file. This is a blocking call and
18
+ # will wait until a SWF connects before continuing.
19
+ def connect(host='localhost', port=10101)
20
+ @bridge = Melomel::Bridge.new(host, port)
21
+ @bridge.connect();
22
+ end
23
+
24
+ # Retrieves a reference to a class
25
+ def get_class(class_name)
26
+ @bridge.get_class(class_name)
27
+ end
28
+
29
+ # Creates an object in the virtual machine.
30
+ def create_object(class_name)
31
+ @bridge.create_object(class_name)
32
+ end
33
+ end
34
+ 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
@@ -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)
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
@@ -0,0 +1,41 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), *%w[helper])
2
+
3
+ class IntegrationTestCase < RunnerTestCase
4
+ def setup
5
+ start_runner
6
+ Melomel.connect()
7
+ end
8
+
9
+ def teardown
10
+ stop_runner
11
+ end
12
+
13
+ def test_should_get_application_name
14
+ app = Melomel.get_class('mx.core.FlexGlobals')
15
+ assert_equal 'Melomel Runner', app.topLevelApplication.name
16
+ end
17
+
18
+ def test_should_get_property
19
+ runner = Melomel.get_class('MelomelRunner')
20
+ assert_equal 'bar', runner.foo
21
+ end
22
+
23
+ def test_should_set_property
24
+ runner = Melomel.get_class('MelomelRunner')
25
+ runner.name = 'Susy'
26
+ assert_equal 'Susy', runner.name
27
+ runner.name = 'John' # TODO: Do not make other tests dependent on this
28
+ end
29
+
30
+ def test_should_invoke_method
31
+ runner = Melomel.get_class('MelomelRunner')
32
+ assert_equal 'Hello, John', runner.hello('John')
33
+ end
34
+
35
+ def test_should_create_object
36
+ point = Melomel.create_object('flash.geom.Point')
37
+ point.x = 30
38
+ point.y = 40
39
+ assert_equal 50, point.length
40
+ end
41
+ end
@@ -0,0 +1,109 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), *%w[helper])
2
+
3
+ class MessagingTestCase < MiniTest::Unit::TestCase
4
+ def setup
5
+ @bridge = Melomel::Bridge.new('localhost', 10101)
6
+ end
7
+
8
+
9
+ #############################################################################
10
+ #
11
+ # Message Parsing
12
+ #
13
+ #############################################################################
14
+
15
+ def test_should_parse_null
16
+ message = '<return dataType="null"/>'
17
+ assert_nil @bridge.parse_message_value(Nokogiri::XML(message).root)
18
+ end
19
+
20
+ def test_should_parse_integer
21
+ message = '<return value="12" dataType="int"/>'
22
+ assert_equal 12, @bridge.parse_message_value(Nokogiri::XML(message).root)
23
+ end
24
+
25
+ def test_should_parse_float
26
+ message = '<return value="100.12" dataType="float"/>'
27
+ assert_equal 100.12, @bridge.parse_message_value(Nokogiri::XML(message).root)
28
+ end
29
+
30
+ def test_should_parse_boolean_true
31
+ message = '<return value="true" dataType="boolean"/>'
32
+ assert_equal true, @bridge.parse_message_value(Nokogiri::XML(message).root)
33
+ end
34
+
35
+ def test_should_parse_boolean_false
36
+ message = '<return value="false" dataType="boolean"/>'
37
+ assert_equal false, @bridge.parse_message_value(Nokogiri::XML(message).root)
38
+ end
39
+
40
+ def test_should_parse_string
41
+ message = '<return value="foo" dataType="string"/>'
42
+ assert_equal 'foo', @bridge.parse_message_value(Nokogiri::XML(message).root)
43
+ end
44
+
45
+ def test_should_parse_object_proxy
46
+ message = '<return value="123" dataType="object"/>'
47
+ proxy = @bridge.parse_message_value(Nokogiri::XML(message).root)
48
+ assert_instance_of Melomel::ObjectProxy, proxy
49
+ assert_equal 123, proxy.proxy_id
50
+ assert_equal @bridge, proxy.bridge
51
+ end
52
+
53
+ def test_should_throw_error_parsing_unknown_type
54
+ message = '<return value="foo" dataType="unknown_type"/>'
55
+ assert_raises Melomel::UnrecognizedTypeError do
56
+ @bridge.parse_message_value(Nokogiri::XML(message).root)
57
+ end
58
+ end
59
+
60
+
61
+ #############################################################################
62
+ #
63
+ # Message Formatting
64
+ #
65
+ #############################################################################
66
+
67
+ def test_should_format_nil
68
+ xml = Nokogiri::XML('<root/>').root
69
+ @bridge.format_message_value(xml, nil)
70
+ assert_equal '<root dataType="null"/>', xml.to_s
71
+ end
72
+
73
+ def test_should_format_integer
74
+ xml = Nokogiri::XML('<root/>').root
75
+ @bridge.format_message_value(xml, 12)
76
+ assert_equal '<root value="12" dataType="int"/>', xml.to_s
77
+ end
78
+
79
+ def should_format_float
80
+ xml = Nokogiri::XML('<root/>').root
81
+ @bridge.format_message_value(xml, 100.12)
82
+ assert_equal '<root value="100.12" dataType="float"/>', xml.to_s
83
+ end
84
+
85
+ def test_should_format_boolean_true
86
+ xml = Nokogiri::XML('<root/>').root
87
+ @bridge.format_message_value(xml, true)
88
+ assert_equal '<root value="true" dataType="boolean"/>', xml.to_s
89
+ end
90
+
91
+ def test_should_format_boolean_false
92
+ xml = Nokogiri::XML('<root/>').root
93
+ @bridge.format_message_value(xml, false)
94
+ assert_equal '<root value="false" dataType="boolean"/>', xml.to_s
95
+ end
96
+
97
+ def test_should_format_string
98
+ xml = Nokogiri::XML('<root/>').root
99
+ @bridge.format_message_value(xml, 'foo')
100
+ assert_equal '<root value="foo" dataType="string"/>', xml.to_s
101
+ end
102
+
103
+ def test_should_format_object
104
+ proxy = Melomel::ObjectProxy.new(@bridge, 123)
105
+ xml = Nokogiri::XML('<root/>').root
106
+ @bridge.format_message_value(xml, proxy)
107
+ assert_equal '<root value="123" dataType="object"/>', xml.to_s
108
+ end
109
+ end
@@ -0,0 +1,48 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), *%w[helper])
2
+
3
+ class ObjectProxyTestCase < MiniTest::Unit::TestCase
4
+ def setup
5
+ @bridge = mock()
6
+ @proxy = Melomel::ObjectProxy.new(@bridge, 123)
7
+ end
8
+
9
+ def test_should_delegate_to_bridge_to_retrieve_property
10
+ @bridge.expects(:get_property).with(123, 'foo')
11
+ @proxy.get_property('foo')
12
+ end
13
+
14
+ def test_should_delegate_to_bridge_to_set_property
15
+ @bridge.expects(:set_property).with(123, 'foo', 'bar')
16
+ @proxy.set_property('foo', 'bar')
17
+ end
18
+
19
+ def test_should_delegate_to_bridge_to_invoke_method
20
+ @bridge.expects(:invoke_method).with(123, 'foo', 'bar', 'baz')
21
+ @proxy.invoke_method('foo', 'bar', 'baz')
22
+ end
23
+
24
+ def test_should_alias_invoke_to_invoke_method
25
+ @bridge.expects(:invoke_method).with(123, 'foo')
26
+ @proxy.invoke('foo')
27
+ end
28
+
29
+ def test_should_alias_accessors_to_get_property_method
30
+ @proxy.expects(:get_property).with('foo')
31
+ @proxy.foo
32
+ end
33
+
34
+ def test_should_alias_mutators_to_set_property_method
35
+ @proxy.expects(:set_property).with('foo', 'bar')
36
+ @proxy.foo = 'bar'
37
+ end
38
+
39
+ def test_should_alias_methods_with_arguments_to_invoke_method
40
+ @proxy.expects(:invoke_method).with('foo', 'bar', 'baz')
41
+ @proxy.foo('bar', 'baz')
42
+ end
43
+
44
+ def test_should_alias_methods_ending_in_bang_to_invoke_method
45
+ @proxy.expects(:invoke_method).with('foo')
46
+ @proxy.foo!()
47
+ end
48
+ end
data/test/test_ui.rb ADDED
@@ -0,0 +1,59 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), *%w[helper])
2
+
3
+ class IntegrationTestCase < RunnerTestCase
4
+ def setup
5
+ start_runner
6
+ Melomel.connect()
7
+ end
8
+
9
+ def teardown
10
+ stop_runner
11
+ end
12
+
13
+ def test_should_find_list_of_labels_named_foo
14
+ labels = Melomel::UI.find_all('spark.components.Label', :name => 'foo')
15
+ assert_equal 3, labels.length
16
+ end
17
+
18
+ def should_find_text_input
19
+ text_input = Melomel::UI.find('spark.components.TextInput', :id => 'nameTextInput')
20
+ assert_equal 'nameTextField', text_input.name
21
+ end
22
+
23
+
24
+ def test_should_click_button
25
+ button = Melomel::UI.find('spark.components.Button', :id => 'clickButton')
26
+ label = Melomel::UI.find('spark.components.Label', :id => 'clickLabel')
27
+ Melomel::UI.click(button)
28
+ assert_equal 'Hello!', label.text
29
+ end
30
+
31
+ def test_should_double_click_the_button
32
+ button = Melomel::UI.find('spark.components.Button', :id => 'doubleClickButton')
33
+ label = Melomel::UI.find('spark.components.Label', :id => 'doubleClickLabel')
34
+ Melomel::UI.double_click(button)
35
+ assert_equal 'Hello Hello!', label.text
36
+ end
37
+
38
+
39
+ def test_should_press_key_down
40
+ text_input = Melomel::UI.find('spark.components.TextInput', :id => 'keyDownTextInput')
41
+ label = Melomel::UI.find('spark.components.Label', :id => 'keyDownLabel')
42
+ Melomel::UI.key_down(text_input, 'a')
43
+ assert_equal 'a', label.text
44
+ end
45
+
46
+ def test_should_release_key_up
47
+ text_input = Melomel::UI.find('spark.components.TextInput', :id => 'keyUpTextInput')
48
+ label = Melomel::UI.find('spark.components.Label', :id => 'keyUpLabel')
49
+ Melomel::UI.key_up(text_input, 'b')
50
+ assert_equal 'b', label.text
51
+ end
52
+
53
+ def test_should_press_key
54
+ text_input = Melomel::UI.find('spark.components.TextInput', :id => 'keyPressTextInput')
55
+ label = Melomel::UI.find('spark.components.Label', :id => 'keyPressLabel')
56
+ Melomel::UI.key_press(text_input, 'a')
57
+ assert_equal 'du', label.text
58
+ end
59
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: melomel
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Ben Johnson
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-08-26 00:00:00 -06:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: nokogiri
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 4
30
+ - 3
31
+ version: 1.4.3
32
+ type: :runtime
33
+ prerelease: false
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: minitest
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 1
44
+ - 7
45
+ - 0
46
+ version: 1.7.0
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: mocha
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 0
59
+ - 9
60
+ - 8
61
+ version: 0.9.8
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: *id003
65
+ description: Melomel allows Rubyist to communicate with Flash and Flex applications easily
66
+ email:
67
+ - benbjohnson@yahoo.com
68
+ executables: []
69
+
70
+ extensions: []
71
+
72
+ extra_rdoc_files: []
73
+
74
+ files:
75
+ - lib/melomel/bridge/messaging.rb
76
+ - lib/melomel/bridge/ui.rb
77
+ - lib/melomel/bridge.rb
78
+ - lib/melomel/object_proxy.rb
79
+ - lib/melomel/ui.rb
80
+ - lib/melomel/version.rb
81
+ - lib/melomel.rb
82
+ - LICENSE
83
+ - README.md
84
+ - CHANGELOG.md
85
+ - test/helper.rb
86
+ - test/test_bridge.rb
87
+ - test/test_integration.rb
88
+ - test/test_messaging.rb
89
+ - test/test_object_proxy.rb
90
+ - test/test_ui.rb
91
+ has_rdoc: true
92
+ homepage: http://github.com/benbjohnson/melomel.rb
93
+ licenses: []
94
+
95
+ post_install_message:
96
+ rdoc_options: []
97
+
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ requirements: []
117
+
118
+ rubyforge_project:
119
+ rubygems_version: 1.3.7
120
+ signing_key:
121
+ specification_version: 3
122
+ summary: A Ruby interface to Melomel
123
+ test_files:
124
+ - test/helper.rb
125
+ - test/test_bridge.rb
126
+ - test/test_integration.rb
127
+ - test/test_messaging.rb
128
+ - test/test_object_proxy.rb
129
+ - test/test_ui.rb