1
0
Fork 0
mirror of https://github.com/rubyjs/therubyrhino synced 2023-03-27 23:21:34 -04:00

avoid NativeFunction/NativeObject wrapping and rather customize Rhino's objects to feel Ruby-ish ...

This commit is contained in:
kares 2011-12-07 19:59:39 +01:00
parent 032666b074
commit 0941f932ed
11 changed files with 517 additions and 290 deletions

View file

@ -1,11 +1,27 @@
require 'java'
require 'rhino/rhino-1.7R3.jar'
module Rhino
require 'rhino/java'
require 'rhino/object'
require 'rhino/context'
require 'rhino/wormhole'
require 'rhino/ruby_object'
require 'rhino/ruby_function'
require 'rhino/native_object'
require 'rhino/native_function'
end
# This module contains all the native Rhino objects implemented in Java
# e.g. Rhino::JS::NativeObject # => org.mozilla.javascript.NativeObject
module JS
import "org.mozilla.javascript"
module Regexp
import "org.mozilla.javascript.regexp"
end
end
J = JS # (deprecated) backward compatibility
end
require 'rhino/object'
require 'rhino/context'
require 'rhino/wormhole'
require 'rhino/ruby_object'
require 'rhino/ruby_function'
require 'rhino/rhino_ext'

View file

@ -2,40 +2,38 @@ require 'stringio'
module Rhino
# ==Overview
# All Javascript must be executed in a context which represents the execution environment in
# which scripts will run. The environment consists of the standard javascript objects
# and functions like Object, String, Array, etc... as well as any objects or functions which
# have been defined in it. e.g.
#
# Context.open do |cxt|
# cxt['num'] = 5
# cxt.eval('num + 5') #=> 10
# end
#
# == Multiple Contexts.
# The same object may appear in any number of contexts, but only one context may be executing javascript code
# in any given thread. If a new context is opened in a thread in which a context is already opened, the second
# context will "mask" the old context e.g.
#
# six = 6
# Context.open do |cxt|
# cxt['num'] = 5
# cxt.eval('num') # => 5
# Context.open do |cxt|
# cxt['num'] = 10
# cxt.eval('num') # => 10
# cxt.eval('++num') # => 11
# end
# cxt.eval('num') # => 5
# end
#
# == Notes
# While there are many similarities between Rhino::Context and Java::org.mozilla.javascript.Context, they are not
# the same thing and should not be confused.
# ==Overview
# All Javascript must be executed in a context which represents the execution environment in
# which scripts will run. The environment consists of the standard javascript objects
# and functions like Object, String, Array, etc... as well as any objects or functions which
# have been defined in it. e.g.
#
# Context.open do |cxt|
# cxt['num'] = 5
# cxt.eval('num + 5') #=> 10
# end
#
# == Multiple Contexts.
# The same object may appear in any number of contexts, but only one context may be executing javascript code
# in any given thread. If a new context is opened in a thread in which a context is already opened, the second
# context will "mask" the old context e.g.
#
# six = 6
# Context.open do |cxt|
# cxt['num'] = 5
# cxt.eval('num') # => 5
# Context.open do |cxt|
# cxt['num'] = 10
# cxt.eval('num') # => 10
# cxt.eval('++num') # => 11
# end
# cxt.eval('num') # => 5
# end
#
# == Notes
# While there are many similarities between Rhino::Context and Java::org.mozilla.javascript.Context, they are not
# the same thing and should not be confused.
class Context
attr_reader :scope
class << self
@ -51,37 +49,40 @@ module Rhino
end
attr_reader :scope
# Create a new javascript environment for executing javascript and ruby code.
# * <tt>:sealed</tt> - if this is true, then the standard objects such as Object, Function, Array will not be able to be modified
# * <tt>:with</tt> - use this ruby object as the root scope for all javascript that is evaluated
# * <tt>:java</tt> - if true, java packages will be accessible from within javascript
def initialize(options = {}) #:nodoc:
ContextFactory.new.call do |native|
@native = native
@global = NativeObject.new(@native.initStandardObjects(nil, options[:sealed] == true))
@factory = ContextFactory.new
@factory.call do |context|
@native = context
@global = @native.initStandardObjects(nil, options[:sealed] == true)
if with = options[:with]
@scope = To.javascript(with)
@scope.setParentScope(@global.j)
@scope.setParentScope(@global)
else
@scope = @global
end
unless options[:java]
for package in ["Packages", "java", "javax", "org", "com", "edu", "net"]
@global.j.delete(package)
@global.delete(package)
end
end
end
end
# Read a value from the global scope of this context
def [](k)
@scope[k]
def [](key)
@scope[key]
end
# Set a value in the global scope of this context. This value will be visible to all the
# javascript that is executed in this context.
def []=(k, v)
@scope[k] = v
def []=(key, val)
@scope[key] = val
end
# Evaluate a string of javascript in this context:
@ -125,8 +126,8 @@ module Rhino
# If this instruction limit is exceeded, then a Rhino::RunawayScriptError
# will be raised
def instruction_limit=(limit)
@native.setInstructionObserverThreshold(limit);
@native.factory.instruction_limit = limit
@native.setInstructionObserverThreshold(limit)
@factory.instruction_limit = limit
end
# Set the optimization level that this context will use. This is sometimes necessary
@ -166,7 +167,7 @@ module Rhino
# fail unless this context is open
def open
begin
@native.factory.enterContext(@native)
@factory.enterContext(@native)
yield self
ensure
JS::Context.exit()
@ -197,6 +198,7 @@ module Rhino
raise java.io.IOException.new, "Failed reading from ruby IO object"
end
end
end
class ContextFactory < JS::ContextFactory # :nodoc:
@ -208,10 +210,10 @@ module Rhino
def instruction_limit=(count)
@limit = count
end
end
class ContextError < StandardError # :nodoc:
end
class JavascriptError < StandardError # :nodoc:
@ -232,4 +234,5 @@ module Rhino
class RunawayScriptError < StandardError # :nodoc:
end
end

View file

@ -1,27 +0,0 @@
require 'java'
require 'rhino/rhino-1.7R3.jar'
module Rhino
# This module contains all the native Rhino objects implemented in Java
# e.g.
# Rhino::JS::NativeObject # => org.mozilla.javascript.NativeObject
module JS
import "org.mozilla.javascript"
module Regexp
import "org.mozilla.javascript.regexp"
end
end
J = JS # (deprecated) backward compatibility
end
unless Object.method_defined?(:tap)
class Object #:nodoc:
def tap
yield self
self
end
end
end

View file

@ -1,29 +0,0 @@
module Rhino
# Wraps a function that has been defined in Javascript so that it can
# be referenced and called from javascript. e.g.
#
# plus = Rhino::Context.open do |cx|
# cx.eval('function(lhs, rhs) {return lhs + rhs}')
# end
# plus.call(5,4) # => 9
#
class NativeFunction < NativeObject
def call(*args)
cxt = JS::Context.enter()
scope = @j.getParentScope() || cxt.initStandardObjects()
@j.call(cxt, scope, scope, args.map {|o| To.javascript(o)})
ensure
JS::Context.exit()
end
def methodcall(this, *args)
cxt = JS::Context.enter()
scope = @j.getParentScope() || cxt.initStandardObjects()
@j.call(cxt, scope, To.javascript(this), args.map {|o| To.javascript(o)})
ensure
JS::Context.exit()
end
end
end

View file

@ -1,71 +0,0 @@
module Rhino
# Wraps a javascript object and makes its properties available from ruby.
class NativeObject
include Enumerable
# The native java object wrapped by this NativeObject. This will generally
# be an instance of org.mozilla.javascript.Scriptable
attr_reader :j
def initialize(j=nil) # :nodoc:
@j = j || JS::NativeObject.new
end
# get a property from this javascript object, where +k+ is a string or symbol
# corresponding to the property name
# e.g.
# jsobject = Context.open do |cxt|
# cxt.eval('({foo: 'bar', 'Take me to': 'a funky town'})')
# end
#
# jsobject[:foo] # => 'bar'
# jsobject['foo'] # => 'bar'
# jsobject['Take me to'] # => 'a funky town'
def [](k)
To.ruby JS::ScriptableObject.getProperty(@j,k.to_s)
end
# set a property on the javascript object, where +k+ is a string or symbol corresponding
# to the property name, and +v+ is the value to set. e.g.
#
# jsobject = eval_js "new Object()"
# jsobject['foo'] = 'bar'
# Context.open(:with => jsobject) do |cxt|
# cxt.eval('foo') # => 'bar'
# end
def []=(k,v)
JS::ScriptableObject.putProperty(@j, k.to_s, To.javascript(v))
end
# enumerate the key value pairs contained in this javascript object. e.g.
#
# eval_js("{foo: 'bar', baz: 'bang'}").each do |key,value|
# puts "#{key} -> #{value} "
# end
#
# outputs foo -> bar baz -> bang
def each
for id in @j.getAllIds() do
yield id,@j.get(id,@j)
end
end
# Converts the native object to a hash. This isn't really a stretch since it's
# pretty much a hash in the first place.
def to_h
{}.tap do |h|
each do |k,v|
v = To.ruby(v)
h[k] = self.class === v ? v.to_h : v
end
end
end
# Convert this javascript object into a json string.
def to_json(*args)
to_h.to_json(*args)
end
end
end

View file

@ -1,8 +1,17 @@
class Object
unless method_defined?(:tap)
def tap # :nodoc:
yield self
self
end
end
def eval_js(source, options = {})
Rhino::Context.open(options.merge(:with => self)) do |cxt|
cxt.eval(source)
end
end
end
end

134
lib/rhino/rhino_ext.rb Normal file
View file

@ -0,0 +1,134 @@
# The base class for all JavaScript objects.
class Java::OrgMozillaJavascript::ScriptableObject
import "org.mozilla.javascript"
# get a property from this javascript object, where +k+ is a string or symbol
# corresponding to the property name e.g.
#
# jsobject = Context.open do |cxt|
# cxt.eval('({foo: 'bar', 'Take me to': 'a funky town'})')
# end
# jsobject[:foo] # => 'bar'
# jsobject['foo'] # => 'bar'
# jsobject['Take me to'] # => 'a funky town'
#
def [](name)
Rhino::To.to_ruby ScriptableObject.getProperty(self, name.to_s)
end
# set a property on the javascript object, where +k+ is a string or symbol corresponding
# to the property name, and +v+ is the value to set. e.g.
#
# jsobject = eval_js "new Object()"
# jsobject['foo'] = 'bar'
# Context.open(:with => jsobject) do |cxt|
# cxt.eval('foo') # => 'bar'
# end
#
def []=(key, value)
scope = self
ScriptableObject.putProperty(self, key.to_s, Rhino::To.to_javascript(value, scope))
end
# enumerate the key value pairs contained in this javascript object. e.g.
#
# eval_js("{foo: 'bar', baz: 'bang'}").each do |key,value|
# puts "#{key} -> #{value} "
# end
#
# outputs foo -> bar baz -> bang
#
def each
getAllIds.each { |id| yield id, Rhino::To.to_ruby(get(id, self)) }
end
def each_key
getAllIds.each { |id| yield id }
end
def each_value
getAllIds.each { |id| yield Rhino::To.to_ruby(get(id, self)) }
end
def keys
keys = []
each_key { |key| keys << key }
keys
end
def values
vals = []
each_value { |val| vals << val }
vals
end
# Converts the native object to a hash. This isn't really a stretch since it's
# pretty much a hash in the first place.
def to_h
hash = {}
each do |key, val|
hash[key] = val.is_a?(ScriptableObject) ? val.to_h : val
end
hash
end
# Convert this javascript object into a json string.
def to_json(*args)
to_h.to_json(*args)
end
# Delegate methods to JS object if possible when called from Ruby.
def method_missing(name, *args)
if ScriptableObject.hasProperty(self, name.to_s)
begin
context = Context.enter
js_args = Rhino::To.args_to_javascript(args, self) # scope == self
ScriptableObject.callMethod(context, self, name.to_s, js_args)
ensure
Context.exit
end
else
super
end
end
end
class Java::OrgMozillaJavascript::NativeObject
# re-implement Map#put
def []=(key, value)
scope = self
ScriptableObject.putProperty(self, key.to_s, Rhino::To.to_javascript(value, scope))
end
end
# The base class for all JavaScript function objects.
class Java::OrgMozillaJavascript::BaseFunction
import "org.mozilla.javascript"
alias_method :__call__, :call # Rhino's Function#call(a1, a2, a3, a4)
# make JavaScript functions callable Ruby style e.g. `fn.call('42')`
def call(*args)
context = Context.enter
scope = getParentScope || context.initStandardObjects
__call__(context, scope, scope, Rhino::To.args_to_javascript(args, scope))
ensure
Context.exit
end
# use JavaScript functions constructors from Ruby as `fn.new`
def new(*args)
context = Context.enter
scope = getParentScope || context.initStandardObjects
construct(context, scope, Rhino::To.args_to_javascript(args, scope))
ensure
Context.exit
end
end

View file

@ -1,81 +1,67 @@
module Rhino
module To
JS_UNDEF = [JS::Scriptable::NOT_FOUND, JS::Undefined]
module_function
def ruby(object)
def to_ruby(object)
case object
when *JS_UNDEF then nil
when JS::Wrapper then object.unwrap
when JS::NativeArray then array(object)
when JS::NativeDate then Time.at(object.getJSTimeValue() / 1000)
when JS::Regexp::NativeRegExp then object
when JS::Function then j2r(object) {|o| NativeFunction.new(o)}
when JS::Scriptable then j2r(object) {|o| NativeObject.new(o)}
else object
when JS::Scriptable::NOT_FOUND, JS::Undefined then nil
when JS::Wrapper then object.unwrap
when JS::NativeArray then array_to_ruby(object)
when JS::NativeDate then Time.at(object.getJSTimeValue / 1000)
else object
end
end
def ruby(object); to_ruby(object); end # alias
def javascript(object)
def to_javascript(object, scope = nil)
case object
when String,Numeric then object
when TrueClass,FalseClass then object
when Array then JS::NativeArray.new(object.to_java)
when Hash then ruby_hash_to_native(object)
when Proc,Method then r2j(object, object.to_s) {|o| RubyFunction.new(o)}
when NativeObject then object.j
when NilClass then object
when String, Numeric then object
when TrueClass, FalseClass then object
when Array then array_to_javascript(object, scope)
when Hash then hash_to_javascript(object, scope)
when Proc, Method then RubyFunction.new(object)
when JS::Scriptable then object
else r2j(object) {|o| RubyObject.new(o)}
else RubyObject.new(object)
end
end
def javascript(object, scope = nil); to_javascript(object, scope); end # alias
def array(native)
native.length.times.map {|i| ruby(native.get(i,native))}
def array_to_ruby(js_array)
js_array.length.times.map { |i| to_ruby( js_array.get(i, js_array) ) }
end
def ruby_hash_to_native(ruby_object)
native_object = NativeObject.new
ruby_object.each_pair do |k, v|
native_object[k] = v
end
native_object.j
end
@@j2r = {}
def j2r(value)
key = value.object_id
if ref = @@j2r[key]
if peer = ref.get()
return peer
else
@@j2r.delete(key)
return j2r(value) {|o| yield o}
end
def args_to_javascript(args, scope = nil)
args.map { |arg| to_javascript(arg, scope) }.to_java
end
def array_to_javascript(rb_array, scope = nil)
if scope
raise "no current context" unless context = JS::Context.getCurrentContext
context.newArray(scope, rb_array.to_java)
else
yield(value).tap do |peer|
@@j2r[key] = java.lang.ref.WeakReference.new(peer)
end
JS::NativeArray.new(rb_array.to_java)
end
end
@@r2j = {}
def r2j(value, key = value.object_id)
if ref = @@r2j[key]
if peer = ref.get()
return peer
def hash_to_javascript(rb_hash, scope = nil)
js_object =
if scope
raise "no current context" unless context = JS::Context.getCurrentContext
context.newObject(scope)
else
@@r2j.delete(key)
return r2j(value, key) {|o| yield o}
end
else
yield(value).tap do |peer|
@@r2j[key] = java.lang.ref.WeakReference.new(peer)
JS::NativeObject.new
end
# JS::NativeObject implements Map put it's #put does :
# throw new UnsupportedOperationException(); thus no []=
rb_hash.each_pair do |key, val|
js_val = to_javascript(val, scope)
JS::ScriptableObject.putProperty(js_object, key.to_s, js_val)
end
js_object
end
end
end

View file

@ -1,4 +1,4 @@
require File.dirname(__FILE__) + '/../spec_helper'
require File.expand_path('../spec_helper', File.dirname(__FILE__))
describe Rhino::Context do

View file

@ -0,0 +1,182 @@
require File.expand_path('../spec_helper', File.dirname(__FILE__))
shared_examples_for 'ScriptableObject', :shared => true do
it "acts like a hash" do
@object['foo'] = 'bar'
@object['foo'].should == 'bar'
end
it "might be converted to a hash with string keys" do
@object[42] = '42'
@object[:foo] = 'bar'
expect = @object.respond_to?(:to_h_properties) ? @object.to_h_properties : {}
@object.to_h.should == expect.merge('42' => '42', 'foo' => 'bar')
end
it "yields properties with each" do
@object['1'] = 1
@object['3'] = 3
@object['2'] = 2
@object.each do |key, val|
case key
when '1' then val.should == 1
when '2' then val.should == 2
when '3' then val.should == 3
end
end
end
end
describe "NativeObject" do
before do
@object = Rhino::JS::NativeObject.new
end
it_should_behave_like 'ScriptableObject'
end
describe "FunctionObject" do
before do
factory = Rhino::JS::ContextFactory.new
context, scope = nil, nil
factory.call do |ctx|
context = ctx
scope = context.initStandardObjects(nil, false)
end
factory.enterContext(context)
to_string = java.lang.Object.new.getClass.getMethod(:toString)
@object = Rhino::JS::FunctionObject.new('to_string', to_string, scope)
@object.instance_eval do
def to_h_properties
{ "arguments"=> nil, "prototype"=> {}, "name"=> "to_string", "arity"=> 0, "length"=> 0 }
end
end
end
after do
Rhino::JS::Context.exit
end
it_should_behave_like 'ScriptableObject'
end
describe "NativeObject (scoped)" do
before do
factory = Rhino::JS::ContextFactory.new
context, scope = nil, nil
factory.call do |ctx|
context = ctx
scope = context.initStandardObjects(nil, false)
end
factory.enterContext(context)
@object = context.newObject(scope)
end
after do
Rhino::JS::Context.exit
end
it_should_behave_like 'ScriptableObject'
it 'routes rhino methods' do
@object.prototype.should == {}
@object.constructor.should == {}
end
it 'invokes JS method' do
@object.toLocaleString.should == '[object Object]'
end
it 'raises on missing method' do
lambda { @object.aMissingMethod }.should raise_error(NoMethodError)
end
end
describe "NativeFunction" do
before do
factory = Rhino::JS::ContextFactory.new
context, scope = nil, nil
factory.call do |ctx|
context = ctx
scope = context.initStandardObjects(nil, false)
end
factory.enterContext(context)
object = context.newObject(scope)
@object = Rhino::JS::ScriptableObject.getProperty(object, 'toString')
@object.instance_eval do
def to_h_properties
{ "arguments"=> nil, "prototype"=> {}, "name"=> "toString", "arity"=> 0, "length"=> 0 }
end
end
end
after do
Rhino::JS::Context.exit
end
it_should_behave_like 'ScriptableObject'
it 'is callable' do
@object.call.should == '[object Object]'
end
end
describe "NativeFunction (constructor)" do
before do
factory = Rhino::JS::ContextFactory.new
context, scope = nil, nil
factory.call do |ctx|
context = ctx
scope = context.initStandardObjects(nil, false)
end
factory.enterContext(context)
@object = Rhino::JS::ScriptableObject.getProperty(context.newObject(scope), 'constructor')
@object.instance_eval do
def to_h_properties
{
"arguments"=>nil, "prototype"=>{}, "name"=>"Object", "arity"=>1, "length"=>1,
"getPrototypeOf"=> { "arguments"=>nil, "prototype"=>{}, "name"=>"getPrototypeOf", "arity"=>1, "length"=>1},
"keys"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"keys", "arity"=>1, "length"=>1},
"getOwnPropertyNames"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"getOwnPropertyNames", "arity"=>1, "length"=>1},
"getOwnPropertyDescriptor"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"getOwnPropertyDescriptor", "arity"=>2, "length"=>2},
"defineProperty"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"defineProperty", "arity"=>3, "length"=>3},
"isExtensible"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"isExtensible", "arity"=>1, "length"=>1},
"preventExtensions"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"preventExtensions", "arity"=>1, "length"=>1},
"defineProperties"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"defineProperties", "arity"=>2, "length"=>2},
"create"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"create", "arity"=>2, "length"=>2},
"isSealed"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"isSealed", "arity"=>1, "length"=>1},
"isFrozen"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"isFrozen", "arity"=>1, "length"=>1},
"seal"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"seal", "arity"=>1, "length"=>1},
"freeze"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"freeze", "arity"=>1, "length"=>1}
}
end
end
end
after do
Rhino::JS::Context.exit
end
it_should_behave_like 'ScriptableObject'
it 'is constructable' do
@object.new.should == {}
end
end

View file

@ -1,42 +1,38 @@
require File.dirname(__FILE__) + '/../spec_helper'
require File.expand_path('../spec_helper', File.dirname(__FILE__))
describe Rhino::To do
describe "ruby translation" do
it "converts javascript NOT_FOUND to ruby nil" do
Rhino::To.ruby(Rhino::JS::Scriptable::NOT_FOUND).should be_nil
end
it "converts javascript arrays to ruby arrays" do
Rhino::JS::NativeObject.new.tap do |o|
Rhino::To.ruby(o).tap do |ruby_object|
ruby_object.should respond_to(:j)
ruby_object.j.should be(o)
it "converts javascript undefined into nil" do
Rhino::To.ruby(Rhino::JS::Undefined.instance).should be_nil
end
it "does return javascript object" do
Rhino::JS::NativeObject.new.tap do |js_obj|
Rhino::To.ruby(js_obj).tap do |rb_obj|
rb_obj.should be(js_obj)
end
end
end
it "wraps native javascript arrays into a ruby NativeArray wrapper" do
Rhino::JS::NativeArray.new([1,2,4].to_java).tap do |a|
Rhino::To.ruby(a).should == [1,2,4]
Rhino::JS::NativeArray.new([1,2,4].to_java).tap do |js_array|
Rhino::To.ruby(js_array).should == [1,2,4]
end
end
it "wraps native javascript functions into a ruby NativeFunction wrapper" do
it "does return javascript function" do
c = Class.new(Rhino::JS::BaseFunction).class_eval do
self.tap do
def call(cxt, scope, this, args)
args.join(',')
end
end
end
klass = Class.new(Rhino::JS::BaseFunction)
c.new.tap do |f|
Rhino::To.ruby(f).tap do |o|
o.should_not be_nil
o.should be_kind_of(Rhino::NativeObject)
o.should be_respond_to(:call)
o.call(1,2,3).should == "1,2,3"
klass.new.tap do |js_fn|
Rhino::To.ruby(js_fn).tap do |rb_fn|
rb_fn.should be(js_fn)
end
end
@ -51,33 +47,24 @@ describe Rhino::To do
it "it unwraps wrapped java objects" do
Rhino::Context.open do |cx|
scope = cx.scope
java.lang.String.new("Hello World").tap do |str|
Rhino::JS::NativeJavaObject.new(scope.j, str, str.getClass()).tap do |o|
Rhino::To.ruby(o).should == "Hello World"
end
j_str = java.lang.String.new("Hello World")
Rhino::JS::NativeJavaObject.new(scope, j_str, j_str.getClass()).tap do |o|
Rhino::To.ruby(o).should == "Hello World"
end
end
end
it "converts javascript undefined into nil" do
Rhino::To.ruby(Rhino::JS::Undefined.instance).should be_nil
end
end
describe "javascript translation" do
it "passes primitives through to the js layer to let jruby and rhino do he thunking" do
to(1).should be(1)
to(2.5).should == 2.5
to("foo").should == "foo"
to(true).should be(true)
to(false).should be(false)
end
it "unwraps wrapped ruby objects before passing them to the javascript runtime" do
Rhino::JS::NativeObject.new.tap do |o|
Rhino::To.javascript(Rhino::NativeObject.new(o)).should be(o)
end
Rhino::To.javascript(1).should be(1)
Rhino::To.javascript(2.5).should == 2.5
Rhino::To.javascript("foo").should == "foo"
Rhino::To.javascript(true).should be(true)
Rhino::To.javascript(false).should be(false)
Rhino::To.javascript(nil).should be_nil
end
it "leaves native javascript objects alone" do
@ -93,36 +80,76 @@ describe Rhino::To do
a.get(1,a).should be(2)
a.get(2,a).should be(3)
a.get(3,a).should be(4)
a.prototype.should be_nil # this is how Rhino works !
end
end
it "converts ruby hashes into native objects" do
Rhino::To.javascript({ :bare => true }).tap do |h|
h.should be_kind_of(Rhino::JS::NativeObject)
h.get("bare", h).should be(true)
h.prototype.should be_nil # this is how Rhino works !
end
end
describe "with a scope" do
before do
factory = Rhino::JS::ContextFactory.new
context = nil
factory.call do |ctx|
context = ctx
@scope = context.initStandardObjects(nil, false)
end
factory.enterContext(context)
end
after do
Rhino::JS::Context.exit
end
it "converts ruby arrays into javascript arrays" do
Rhino::To.javascript([1,2,3,4,5], @scope).tap do |a|
a.should be_kind_of(Rhino::JS::NativeArray)
a.get(0,a).should be(1)
a.get(1,a).should be(2)
a.get(2,a).should be(3)
a.get(3,a).should be(4)
a.prototype.should_not be_nil
end
end
it "converts ruby hashes into native objects" do
Rhino::To.javascript({ :bare => true }, @scope).tap do |h|
h.should be_kind_of(Rhino::JS::NativeObject)
h.get("bare", h).should be(true)
h.prototype.should_not be_nil
end
end
end
it "converts procs and methods into native functions" do
to(lambda {|lhs,rhs| lhs * rhs}).tap do |f|
Rhino::To.javascript(lambda {|lhs,rhs| lhs * rhs}).tap do |f|
f.should be_kind_of(Rhino::JS::Function)
f.call(nil, nil, nil, [7,6]).should be(42)
end
to("foo,bar,baz".method(:split)).tap do |m|
Rhino::To.javascript("foo,bar,baz".method(:split)).tap do |m|
m.should be_kind_of(Rhino::JS::Function)
Rhino::To.ruby(m.call(nil, nil, nil, ',')).should == ['foo', 'bar', 'baz']
end
end
it "creates a prototype for the object based on its class" do
Class.new.tap do |c|
c.class_eval do
Class.new.tap do |klass|
klass.class_eval do
def foo(one, two)
"1: #{one}, 2: #{two}"
end
end
Rhino::To.javascript(c.new).tap do |o|
Rhino::To.javascript(klass.new).tap do |o|
o.should be_kind_of(Rhino::RubyObject)
o.prototype.tap do |p|
p.should_not be_nil
@ -131,11 +158,8 @@ describe Rhino::To do
end
end
end
end
end
def to(object)
Rhino::To.javascript(object)
end
end
end