Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge pull request #4 from kares/gemified

gemified + remastered some version history
  • Loading branch information...
commit f4b1ba71274e2edfbc6c9bba5708d26cee0df01f 2 parents 1ebcd2a + 19bfa1c
Charles Lowell authored April 13, 2012
64  README.md
Source Rendered
... ...
@@ -0,0 +1,64 @@
  1
+# RedJS
  2
+
  3
+https://github.com/cowboyd/redjs
  4
+
  5
+Shared specs for a Ruby interface to JavaScript.
  6
+
  7
+
  8
+Useful for different JavaScript interpreters wanting to present a unified 
  9
+interface to Ruby.
  10
+
  11
+Currently used by :
  12
+
  13
+* The Ruby Rhino: http://github.com/cowboyd/therubyrhino
  14
+* The Ruby Racer: http://github.com/cowboyd/therubyracer
  15
+
  16
+## Usage
  17
+
  18
+You'll need [RSpec](http://rspec.info/) to use the specs RedJS provides.
  19
+
  20
+add this your *spec_helper.rb* :
  21
+
  22
+```ruby
  23
+require 'redjs'
  24
+module RedJS
  25
+  Context = MyJS::Context # e.g. V8::Context or Rhino::Context
  26
+end
  27
+```
  28
+
  29
+add *spec/redjs_spec.rb* similar to this :
  30
+
  31
+```ruby
  32
+require File.expand_path('../spec_helper', File.dirname(__FILE__))
  33
+
  34
+require 'redjs/load_specs'
  35
+
  36
+describe MyJS::Context do
  37
+  
  38
+  it_behaves_like 'RedJS::Context'
  39
+  
  40
+end
  41
+```
  42
+
  43
+## API Requirements
  44
+
  45
+### RedJS::Context
  46
+
  47
+* should be instantiable using `new` without arguments or with a hash of options
  48
+* desired options passed to `Context.new` :
  49
+  * `:with` emulates JavaScript's with statement (provides a shorthand for writing 
  50
+    recurring accesses to the passed objects)
  51
+* if a block is provided should yield the context instance
  52
+* contexts provide an `eval` method for evaluating JavaScript code
  53
+* context instances implement [] for exposing Ruby instances to JavaScript
  54
+
  55
+```ruby
  56
+MyJS::Context.new do |context|
  57
+  context["math"] = MyMath.new
  58
+  context.eval("math.plus(20, 22)") #=> 42
  59
+end
  60
+```
  61
+
  62
+### RedJS::Error
  63
+
  64
+* common error class for propagating errors that occur in JS into the Ruby side
8  README.txt
... ...
@@ -1,8 +0,0 @@
1  
-Rspecs for a Ruby interface to javascript.
2  
-
3  
-This is useful for different javascript interpreters wanting to present a unified interface to ruby.
4  
-
5  
-Currently used by 
6  
- The Ruby Rhino: http://github.com/cowboyd/therubyrhino
7  
- The Ruby Racer: http://github.com/cowboyd/therubyracer
8  
- 
4  lib/redjs.rb
... ...
@@ -0,0 +1,4 @@
  1
+# -*- encoding: utf-8 -*-
  2
+module RedJS
  3
+  VERSION = "0.4.1".freeze
  4
+end
1  lib/redjs/load_specs.rb
... ...
@@ -0,0 +1 @@
  1
+load 'redjs/spec/context_spec.rb'
431  jsapi_spec.rb → lib/redjs/spec/context_spec.rb
... ...
@@ -1,12 +1,11 @@
1 1
 # -*- encoding: utf-8 -*-
2  
-require "#{File.dirname(__FILE__)}/../redjs_helper.rb"
3 2
 
4  
-describe "Ruby Javascript API" do
  3
+shared_examples_for "RedJS::Context", :shared => true do
5 4
 
6 5
   describe "Basic Evaluation" do
7 6
 
8 7
     before do
9  
-      @cxt = Context.new
  8
+      @cxt = RedJS::Context.new
10 9
     end
11 10
 
12 11
     it "can evaluate some javascript" do
@@ -84,6 +83,16 @@
84 83
       end
85 84
     end
86 85
 
  86
+    it "can pass int properties to ruby" do
  87
+      @cxt.eval("({ 4: '4', 5: 5, '6': true })").tap do |object|
  88
+        object[ 4 ].should == '4'
  89
+        object['4'].should == '4'
  90
+        object[ 5 ].should == 5
  91
+        object['5'].should == 5
  92
+        object['6'].should == true
  93
+      end
  94
+    end
  95
+    
87 96
     it "unwraps ruby objects returned by embedded ruby code to maintain referential integrity" do
88 97
       Object.new.tap do |o|
89 98
         @cxt['get'] = lambda {o}
@@ -129,25 +138,18 @@
129 138
     before(:each) do
130 139
       @class = Class.new
131 140
       @instance = @class.new
132  
-      @cxt = Context.new
133  
-      @cxt['puts'] = lambda {|o| puts o.inspect}
  141
+      @cxt = RedJS::Context.new
  142
+      @cxt['puts'] = lambda { |o| puts o.inspect }
134 143
       @cxt['o'] = @instance
135 144
     end
136 145
 
137 146
     it "can embed a closure into a context and call it" do
138  
-      @cxt["say"] = lambda {|this, word, times| word * times}
  147
+      @cxt["say"] = lambda { |word, times| word * times }
139 148
       @cxt.eval("say('Hello',2)").should == "HelloHello"
140 149
     end
141 150
 
142  
-    it "truncates the arguments passed in to match the arity of the function" do
143  
-      @cxt['testing'] = lambda {|this|}
144  
-      expect{@cxt.eval('testing(1,2,3)')}.should_not raise_error
145  
-      @cxt['testing'] = lambda {}
146  
-      expect{@cxt.eval('testing(1,2,3)')}.should_not raise_error
147  
-    end
148  
-
149 151
     it "recognizes the same closure embedded into the same context as the same function object" do
150  
-      @cxt['say'] = @cxt['declare'] = lambda {|word, times| word * times}
  152
+      @cxt['say'] = @cxt['declare'] = lambda { |*args| args }
151 153
       @cxt.eval('say == declare').should be(true)
152 154
       @cxt.eval('say === declare').should be(true)
153 155
     end
@@ -203,26 +205,46 @@ def say_hello(to)
203 205
       end
204 206
       evaljs('o.say_hello("Gracie")').should == "Hello Gracie!"
205 207
     end
206  
-
207  
-    it "recognizes object method as the same." do |variable|
208  
-      class_eval do
209  
-        def foo(*a);end
  208
+    
  209
+    it "recognizes object method as the same function" do
  210
+      @class.class_eval do
  211
+        def foo(*args); args; end
  212
+      end
  213
+      @cxt['obj'] = @class.new
  214
+      @cxt.eval('obj.foo === obj.foo').should be(true)
  215
+    end
  216
+    
  217
+    it "recognizes functions on objects of the same class being equal" do
  218
+      @class.class_eval do
  219
+        def foo(*args); args; end
  220
+        self
210 221
       end
211  
-      @cxt.eval('o.foo == o.foo').should be(true)
  222
+      @cxt['one'] = @class.new
  223
+      @cxt['two'] = @class.new
  224
+      @cxt.eval('one.foo == two.foo').should be(true)
212 225
     end
213 226
 
214  
-    it "recognizes functions on objects of the same class as being the same function" do
215  
-      cls = class_eval do
216  
-        def foo(*a);end
  227
+    it "recognizes functions on objects of the same class being the same" do
  228
+      @class.class_eval do
  229
+        def foo(*args); args; end
217 230
         self
218 231
       end
219  
-      @cxt['one'] = cls.new
220  
-      @cxt['two'] = cls.new
  232
+      @cxt['one'] = @class.new
  233
+      @cxt['two'] = @class.new
221 234
       @cxt.eval('one.foo === two.foo').should be(true)
222 235
       #TODO: nice to have, but a bit tricky.
223 236
       # @cxt.eval('one.foo === one.constructor.prototype.foo').should be(true)
224 237
     end
225  
-
  238
+    
  239
+    it "fails without the correct context passed to an object function" do
  240
+      @class.class_eval do
  241
+        def foo(*args); args; end
  242
+      end
  243
+      @cxt['obj'] = @class.new
  244
+      @cxt.eval('var foo = obj.foo;')
  245
+      lambda { @cxt.eval('foo()') }.should raise_error
  246
+    end
  247
+    
226 248
     it "can call a bound ruby method" do
227 249
       five = class_eval do
228 250
         def initialize(lhs)
@@ -238,6 +260,22 @@ def times(rhs)
238 260
       @cxt.eval('timesfive(3)').should == 15
239 261
     end
240 262
 
  263
+    it "reports object's type being object" do
  264
+      @cxt.eval('typeof( o )').should == 'object'
  265
+    end
  266
+    
  267
+    it "reports object method type as function" do
  268
+      @class.class_eval do
  269
+        def foo(*args); args; end
  270
+      end
  271
+      @cxt.eval('typeof o.foo ').should == 'function'
  272
+    end
  273
+    
  274
+    it "reports wrapped class as of type function" do
  275
+      @cxt['RObject'] = Object
  276
+      @cxt.eval('typeof(RObject)').should == 'function'
  277
+    end
  278
+    
241 279
     describe "Default Ruby Object Access" do
242 280
 
243 281
       it "can call public locally defined ruby methods" do
@@ -250,8 +288,8 @@ def voo(str)
250 288
       end
251 289
 
252 290
       it "reports ruby methods that do not exist as undefined" do
253  
-        Context.new(:with => Object.new) do |cxt|
254  
-          cxt.eval('this.foobar').should be_nil
  291
+        RedJS::Context.new(:with => Object.new) do |cxt|
  292
+          cxt.eval('this.foobar').should be nil
255 293
         end
256 294
       end
257 295
 
@@ -263,7 +301,7 @@ def foo
263 301
           end
264 302
           Class.new(self)
265 303
         end.new
266  
-        Context.new(:with => o) do |cxt|
  304
+        RedJS::Context.new(:with => o) do |cxt|
267 305
           cxt.eval('this.foo').should == 'FOO'
268 306
           cxt.eval('this.foo = "bar!"')
269 307
           cxt.eval('this.foo').should == "bar!"
@@ -275,7 +313,7 @@ def foo
275 313
           def [](val)
276 314
             "FOO"
277 315
           end
278  
-          Context.new(:with => new) do |cxt|
  316
+          RedJS::Context.new(:with => new) do |cxt|
279 317
             cxt.eval('this.foo').should == "FOO"
280 318
             cxt.eval('this.bar').should == "FOO"
281 319
           end
@@ -294,7 +332,7 @@ def []=(name, value)
294 332
             @properties[name] = value
295 333
           end
296 334
 
297  
-          Context.new(:with => new) do |cxt|
  335
+          RedJS::Context.new(:with => new) do |cxt|
298 336
             cxt.eval('this.foo = "bar"').should == "bar"
299 337
             cxt.eval('this.foo').should == "bar"
300 338
           end
@@ -302,7 +340,7 @@ def []=(name, value)
302 340
       end
303 341
 
304 342
       it "allows a ruby object which intercepts property access to take a pass on intercepting the property" do
305  
-        Class.new.class_eval do
  343
+        klass = Class.new do
306 344
           def initialize
307 345
             @attrs = {}
308 346
           end
@@ -312,70 +350,70 @@ def [](name)
312 350
           def []=(name, value)
313 351
             name =~ /foo/ ? @attrs[name] = "#{value}-diddly" : yield
314 352
           end
315  
-          Context.new do |cxt|
316  
-            cxt['foo'] = new
317  
-            cxt.eval('typeof foo.bar').should == 'undefined'
318  
-            cxt.eval('foo.bar = "baz"')
319  
-            cxt.eval('foo.bar').should == 'baz'
320  
-            cxt.eval('foo.foobar').should == nil
321  
-            cxt.eval('foo.foobar = "baz"')
322  
-            cxt.eval('foo.foobar').should == "baz-diddly"
323  
-          end
  353
+        end
  354
+
  355
+        RedJS::Context.new do |cxt|
  356
+          cxt['foo'] = klass.new
  357
+          cxt.eval('typeof foo.bar').should == 'undefined'
  358
+          cxt.eval('foo.bar = "baz"')
  359
+          cxt.eval('foo.bar').should == 'baz'
  360
+          cxt.eval('foo.foobar').should == nil
  361
+          cxt.eval('foo.foobar = "baz"')
  362
+          cxt.eval('foo.foobar').should == "baz-diddly"
324 363
         end
325 364
       end
326 365
 
327 366
       it "allows a ruby object to take a pass on intercepting an indexed property" do
328  
-        Class.new.class_eval do
  367
+        klass = Class.new do
329 368
           def initialize
330  
-            @a = []
  369
+            @arr = []
331 370
           end
332 371
           def [](i)
333  
-            i >= 5 ? @a[i] : yield
  372
+            i >= 5 ? @arr[i] : yield
334 373
           end
335 374
           def []=(i, value)
336  
-            i >= 5 ? @a[i] = "#{value}-diddly" : yield
337  
-          end
338  
-          Context.new do |cxt|
339  
-            cxt['obj'] = new
340  
-            cxt.eval('typeof obj[1]').should == 'undefined'
341  
-            cxt.eval('obj[1] = "foo"')
342  
-            cxt.eval('obj[1]').should == "foo"
343  
-            cxt.eval('obj[5] = "foo"').should == "foo"
344  
-            cxt.eval('obj[5]').should == "foo-diddly"
  375
+            i >= 5 ? @arr[i] = "#{value}-diddly" : yield
345 376
           end
346 377
         end
  378
+        
  379
+        RedJS::Context.new do |cxt|
  380
+          cxt['obj'] = klass.new
  381
+          cxt.eval('typeof obj[1]').should == 'undefined'
  382
+          cxt.eval('obj[1] = "foo"')
  383
+          cxt.eval('obj[1]').should == "foo"
  384
+          cxt.eval('obj[5] = "foo"').should == "foo"
  385
+          cxt.eval('obj[5]').should == "foo-diddly"
  386
+        end
347 387
       end
348  
-
  388
+      
349 389
       it "does not make the [] and []= methods visible or enumerable by default" do
350  
-        Class.new.class_eval do
351  
-          def [](name)
352  
-          end
353  
-          def []=(name, value)
354  
-          end
355  
-          def bar=(value)
356  
-          end
357  
-          Context.new do |cxt|
358  
-            cxt['o'] = new
359  
-            cxt.eval('o["[]"]').should == nil
360  
-            cxt.eval('o["[]="]').should == nil
361  
-            cxt.eval('a = new Array(); for (var i in o) {a.push(i)}')
362  
-            cxt['a'].length.should == 0
363  
-          end
  390
+        klass = Class.new do
  391
+          def [](name); name; end
  392
+          def []=(name, value); name && value; end
  393
+          def bar=(value); value; end
  394
+        end
  395
+        
  396
+        RedJS::Context.new do |cxt|
  397
+          cxt['o'] = klass.new
  398
+          cxt.eval('o["[]"]').should == nil
  399
+          cxt.eval('o["[]="]').should == nil
  400
+          cxt.eval('a = new Array(); for (var i in o) a.push(i);')
  401
+          cxt['a'].length.should == 0
364 402
         end
365 403
       end
366  
-
  404
+      
367 405
       it "doesn't kill the whole process if a dynamic interceptor or setter throws an exception" do
368  
-        cls = Class.new.class_eval do
  406
+        klass = Class.new do
369 407
           def [](name)
370  
-            raise "BOOM!"
  408
+            raise "BOOM #{name}!"
371 409
           end
372  
-          def []=(name, val)
373  
-            raise "Bam!"
  410
+          def []=(name, value)
  411
+            raise "Bam #{name} = #{value} !"
374 412
           end
375  
-          self
376 413
         end
377  
-        Context.new do |cxt|
378  
-          cxt['foo'] = cls.new
  414
+        
  415
+        RedJS::Context.new do |cxt|
  416
+          cxt['foo'] = klass.new
379 417
           lambda {
380 418
             cxt.eval('foo.bar')
381 419
           }.should raise_error
@@ -386,15 +424,16 @@ def []=(name, val)
386 424
       end
387 425
 
388 426
       it "doesn't kill the whole process if reader or accessor throws an exception" do
389  
-        cxt = Class.new.class_eval do
  427
+        klass = Class.new do
390 428
           def foo
391 429
             raise "NO GET 4 U!"
392 430
           end
393 431
           def foo=(val)
394 432
             raise "NO SET 4 U!"
395 433
           end
396  
-          Context.new(:with => new)
397 434
         end
  435
+        
  436
+        cxt = RedJS::Context.new(:with => klass.new)
398 437
         lambda {
399 438
           cxt.eval(this.foo)
400 439
         }.should raise_error
@@ -404,18 +443,17 @@ def foo=(val)
404 443
       end
405 444
 
406 445
       it "allows access to methods defined on an objects included/extended modules (class)" do
407  
-        m = Module.new.module_eval do
  446
+        modul = Module.new do
408 447
           attr_accessor :foo
409 448
           def foo
410 449
             @foo ||= "FOO"
411 450
           end
412  
-          self
413 451
         end
414  
-        o = Class.new.class_eval do
415  
-          include m
416  
-          new
  452
+        klass = Class.new do
  453
+          include modul
417 454
         end
418  
-        Context.new(:with => o) do |cxt|
  455
+        
  456
+        RedJS::Context.new(:with => klass.new) do |cxt|
419 457
           cxt.eval('this.foo').should == "FOO"
420 458
           cxt.eval('this.foo = "bar!"')
421 459
           cxt.eval('this.foo').should == "bar!"
@@ -423,62 +461,36 @@ def foo
423 461
       end
424 462
 
425 463
       it "allows access to methods defined on an objects included/extended modules (instance)" do
426  
-        m = Module.new.module_eval do
  464
+        modul = Module.new do
427 465
           attr_accessor :foo
428 466
           def foo
429 467
             @foo ||= "FOO"
430 468
           end
431 469
           self
432 470
         end
433  
-        Object.new.tap do |o|
434  
-          o.extend(m)
435  
-          Context.new(:with => o) do |cxt|
436  
-            cxt.eval('this.foo').should == "FOO"
437  
-            cxt.eval('this.foo = "bar!"')
438  
-            cxt.eval('this.foo').should == "bar!"
439  
-          end
  471
+        
  472
+        obj = Object.new; obj.extend(modul)
  473
+        RedJS::Context.new(:with => obj) do |cxt|
  474
+          cxt.eval('this.foo').should == "FOO"
  475
+          cxt.eval('this.foo = "bar!"')
  476
+          cxt.eval('this.foo').should == "bar!"
440 477
         end
441 478
       end
442 479
 
443 480
       it "allows access to public singleton methods" do
444  
-        Object.new.tap do |o|
445  
-          class << o
446  
-            attr_accessor :foo
447  
-          end
448  
-          def o.foo
449  
-            @foo ||= "FOO"
450  
-          end
451  
-          Context.new(:with => o) do |cxt|
452  
-            cxt.eval("this.foo").should == "FOO"
453  
-            cxt.eval('this.foo = "bar!"')
454  
-            cxt.eval('this.foo').should == "bar!"
455  
-          end
456  
-        end
457  
-      end
458  
-
459  
-      it "does not allow access to methods defined on Object and above" do
460  
-        o = Class.new.class_eval do
461  
-          def foo
462  
-            "FOO"
463  
-          end
464  
-          self.new
465  
-        end
466  
-        Context.new(:with => o) do |cxt|
467  
-          for method in Object.public_instance_methods
468  
-            cxt.eval("this['#{method}']").should be_nil
469  
-          end
470  
-        end
471  
-      end
472  
-
473  
-      it "hides methods derived from Object, Kernel, etc..." do
474  
-        class_eval do
475  
-          def bar
476  
-          end
  481
+        obj = Object.new
  482
+        class << obj; attr_accessor :foo; end
  483
+        def obj.foo; @foo ||= "FOO"; end
  484
+        
  485
+        RedJS::Context.new(:with => obj) do |cxt|
  486
+          cxt.eval("this.foo").should == "FOO"
  487
+          cxt.eval('this.foo = "bar!"')
  488
+          cxt.eval('this.foo').should == "bar!"
477 489
         end
478  
-        evaljs("o.to_s").should be_nil
479 490
       end
480 491
 
481 492
       describe "with an integer index" do
  493
+        
482 494
         it "allows accessing indexed properties via the []() method" do
483 495
           class_eval do
484 496
             def [](i)
@@ -487,6 +499,7 @@ def [](i)
487 499
           end
488 500
           evaljs("o[3]").should == "foofoofoo"
489 501
         end
  502
+        
490 503
         it "allows setting indexed properties via the []=() method" do
491 504
           class_eval do
492 505
             def [](i)
@@ -505,10 +518,10 @@ def []=(i, val)
505 518
         it "doesn't kill the whole process if indexed interceptors throw exceptions" do
506 519
           class_eval do
507 520
             def [](idx)
508  
-              raise "No Indexed Get For You!"
  521
+              raise "No Indexed [#{idx}] For You!"
509 522
             end
510 523
             def []=(idx, value)
511  
-              raise "No Indexed Set For You!"
  524
+              raise "No Indexed [#{idx}] = #{value} For You!"
512 525
             end
513 526
           end
514 527
           lambda {
@@ -516,7 +529,8 @@ def []=(idx, value)
516 529
           }.should raise_error
517 530
           lambda {
518 531
             evaljs("o[1]")
519  
-          }.should raise_error        end
  532
+          }.should raise_error
  533
+        end
520 534
 
521 535
         #TODO: I'm not sure this is warranted
522 536
         #it "will enumerate indexed properties if a length property is provided"
@@ -583,7 +597,7 @@ def class_eval(&body)
583 597
   describe "Calling JavaScript Code From Within Ruby" do
584 598
 
585 599
     before(:each) do
586  
-      @cxt = Context.new
  600
+      @cxt = RedJS::Context.new
587 601
     end
588 602
 
589 603
     it "allows you to capture a reference to a javascript function and call it" do
@@ -615,13 +629,13 @@ def class_eval(&body)
615 629
     end
616 630
 
617 631
     it "can access properties defined on a javascript object through ruby" do
618  
-      obj = @cxt.eval('({str: "bar", num: 5})')
  632
+      obj = @cxt.eval('({ str: "bar", num: 5 })')
619 633
       obj.str.should == "bar"
620 634
       obj.num.should == 5
621 635
     end
622 636
 
623 637
     it "can set properties on the javascript object via ruby setter methods" do
624  
-      obj = @cxt.eval('({str: "bar", num: 5})')
  638
+      obj = @cxt.eval('({ str: "bar", num: 5 })')
625 639
       obj.str = "baz"
626 640
       obj.str.should == "baz"
627 641
       obj.double = proc {|i| i * 2}
@@ -641,8 +655,9 @@ def class_eval(&body)
641 655
   end
642 656
 
643 657
   describe "Setting up the Host Environment" do
  658
+    
644 659
     before(:each) do
645  
-      @cxt = Context.new
  660
+      @cxt = RedJS::Context.new
646 661
     end
647 662
 
648 663
     it "can eval javascript with a given ruby object as the scope." do
@@ -658,7 +673,7 @@ def minus(lhs, rhs)
658 673
         new
659 674
       end
660 675
 
661  
-      Context.new(:with => scope) do |cxt|
  676
+      RedJS::Context.new(:with => scope) do |cxt|
662 677
         cxt.eval("plus(1,2)", "test").should == 3
663 678
         cxt.eval("minus(10, 20)", "test").should == -10
664 679
         cxt.eval("this").should be(scope)
@@ -671,7 +686,7 @@ def minus(lhs, rhs)
671 686
       @cxt['num'] = 3.14
672 687
       @cxt['trU'] = true
673 688
       @cxt['falls'] = false
674  
-      @cxt.eval("bar + 10").should be(19)
  689
+      @cxt.eval("bar + 10").should == 19
675 690
       @cxt.eval('foo').should == "bar"
676 691
       @cxt.eval('num').should == 3.14
677 692
       @cxt.eval('trU').should be(true)
@@ -680,18 +695,16 @@ def minus(lhs, rhs)
680 695
 
681 696
     it "has the global object available as a javascript value" do
682 697
       @cxt['foo'] = 'bar'
683  
-      @cxt.scope.should be_kind_of(V8::Object)
  698
+      @cxt.scope.should_not be(nil)
  699
+      @cxt.scope.should respond_to :'[]'
684 700
       @cxt.scope['foo'].should == 'bar'
685 701
     end
686 702
 
687 703
     it "will treat class objects as constructors by default" do
688  
-      @cxt[:MyClass] = Class.new.tap do |cls|
689  
-        cls.class_eval do
690  
-          attr_reader :one, :two
691  
-          def initialize(one, two)
692  
-            @one, @two = one, two
693  
-            # rputs "one: #{@one}, two: #{@two}"
694  
-          end
  704
+      @cxt[:MyClass] = Class.new do
  705
+        attr_reader :one, :two
  706
+        def initialize(one, two)
  707
+          @one, @two = one, two
695 708
         end
696 709
       end
697 710
       @cxt.eval('new MyClass(1,2).one').should == 1
@@ -699,12 +712,13 @@ def initialize(one, two)
699 712
     end
700 713
 
701 714
     it "exposes class properties as javascript properties on the corresponding constructor" do
702  
-      @cxt[:MyClass] = Class.new.tap do |cls|
703  
-        def cls.foo
704  
-          "bar"
  715
+      @cxt[:MyClass] = Class.new do
  716
+        def self.foo(*args)
  717
+          args.inspect
705 718
         end
706 719
       end
707  
-      @cxt.eval('MyClass.foo').should == "bar"
  720
+      @cxt.eval('MyClass.foo').should_not be nil
  721
+      @cxt.eval('MyClass.foo()').should == "[]"
708 722
     end
709 723
 
710 724
     it "unwraps reflected ruby constructor objects into their underlying ruby classes" do
@@ -714,35 +728,39 @@ def cls.foo
714 728
 
715 729
     it "honors the instanceof operator for ruby instances when compared to their reflected constructors" do
716 730
       @cxt['RubyObject'] = Object
717  
-      @cxt['one'] = Object.new
718  
-      @cxt['two'] = Object.new
719  
-      @cxt.eval('one instanceof RubyObject')
720  
-      @cxt.eval('two instanceof RubyObject')
721  
-      @cxt.eval('RubyObject instanceof Function').should be(true)
  731
+      @cxt['rb_object'] = Object.new
  732
+      @cxt.eval('rb_object instanceof RubyObject').should be(true)
722 733
       @cxt.eval('new RubyObject() instanceof RubyObject').should be(true)
723 734
       @cxt.eval('new RubyObject() instanceof Array').should be(false)
724 735
       @cxt.eval('new RubyObject() instanceof Object').should be(true)
725 736
     end
726 737
 
  738
+    it "reports constructor function as being an instance of Function" do
  739
+      @cxt['RubyObject'] = Object
  740
+      @cxt.eval('RubyObject instanceof Function').should be(true)
  741
+    end
  742
+   
  743
+    it "respects ruby's inheritance chain with the instanceof operator" do
  744
+      @cxt['Class1'] = klass1 = Class.new(Object)
  745
+      @cxt['Class2'] = klass2 = Class.new(klass1)
  746
+      @cxt['obj1'] = klass1.new
  747
+      @cxt['obj2'] = klass2.new
  748
+      
  749
+      @cxt.eval('obj1 instanceof Class1').should == true
  750
+      @cxt.eval('obj1 instanceof Class2').should == false
  751
+      @cxt.eval('obj2 instanceof Class2').should == true
  752
+      @cxt.eval('obj2 instanceof Class1').should == true
  753
+    end
  754
+    
727 755
     it "unwraps instances created by a native constructor when passing them back to ruby" do
728  
-      cls = Class.new.tap do |c|
729  
-        c.class_eval do
730  
-          def definitely_a_product_of_this_one_off_class?
731  
-            true
732  
-          end
  756
+      @cxt['RubyClass'] = Class.new do
  757
+        def definitely_a_product_of_this_one_off_class?
  758
+          true
733 759
         end
734 760
       end
735  
-      @cxt['RubyClass'] = cls
736 761
       @cxt.eval('new RubyClass()').should be_definitely_a_product_of_this_one_off_class
737 762
     end
738 763
 
739  
-    it "does not allow you to call a native ruby constructor, unless that constructor has been directly embedded" do
740  
-      @cxt['o'] = Class.new.new
741  
-      lambda {
742  
-        @cxt.eval('new (o.constructor)()')
743  
-      }.should raise_error(JSError)
744  
-    end
745  
-
746 764
     it "extends object to allow for the arbitrary execution of javascript with any object as the scope" do
747 765
       Class.new.class_eval do
748 766
 
@@ -758,17 +776,6 @@ def timesfive(rhs)
758 776
       end
759 777
     end
760 778
 
761  
-    # it "can limit the number of instructions that are executed in the context" do
762  
-    #   pending "haven't figured out how to constrain resources in V8"
763  
-    #   lambda {
764  
-    #     Context.new do |cxt|
765  
-    #       cxt.instruction_limit = 100 * 1000
766  
-    #       timeout(1) do
767  
-    #         cxt.eval('while (true);')
768  
-    #       end
769  
-    #     end
770  
-    #   }.should raise_error(RunawayScriptError)
771  
-    # end
772 779
   end
773 780
 
774 781
   describe "Loading javascript source into the interpreter" do
@@ -788,23 +795,22 @@ def timesfive(rhs)
788 795
   foo = 'bar'
789 796
   five();
790 797
       EOJS
791  
-      Context.new do |cxt|
  798
+      RedJS::Context.new do |cxt|
792 799
         cxt.eval(source, "StringIO").should == 5
793 800
         cxt['foo'].should == "bar"
794 801
       end
795 802
     end
796 803
 
797 804
     it "can load a file into the runtime" do
798  
-      Context.new do |cxt|
799  
-        cxt.load(Pathname(__FILE__).dirname.join("loadme.js")).should == "I am Legend"
800  
-      end
  805
+      filename = Pathname(__FILE__).dirname.join("fixtures/loadme.js").to_s
  806
+      RedJS::Context.new.load(filename).should == "I am Legend"
801 807
     end
802 808
   end
803 809
 
804 810
   describe "A Javascript Object Reflected Into Ruby" do
805 811
 
806 812
     before(:each) do
807  
-      @cxt = Context.new
  813
+      @cxt = RedJS::Context.new
808 814
       @o = @cxt.eval("o = new Object(); o")
809 815
     end
810 816
 
@@ -831,7 +837,7 @@ def evaljs(js)
831 837
     end
832 838
 
833 839
     it "traverses the prototype chain when hash accessing properties from the ruby object" do
834  
-      Context.new do |cxt|
  840
+      RedJS::Context.new do |cxt|
835 841
         cxt.eval(<<EOJS)['bar'].should == "baz"
836 842
 function Foo() {}
837 843
 Foo.prototype.bar = 'baz'
@@ -852,30 +858,21 @@ def evaljs(js)
852 858
   end
853 859
 
854 860
   describe "Exception Handling" do
  861
+    
855 862
     it "raises javascript exceptions as ruby exceptions" do
856 863
       lambda {
857  
-        Context.new.eval('foo')
858  
-      }.should raise_error(JSError)
  864
+        RedJS::Context.new.eval('foo')
  865
+      }.should raise_js_error
859 866
     end
860 867
 
861 868
     it "can handle syntax errors" do
862 869
       lambda {
863  
-        Context.eval('does not compiles')
  870
+        RedJS::Context.eval('does not compiles')
864 871
       }.should raise_error
865 872
     end
866 873
 
867  
-    it "translates ruby exceptions into javascript exceptions if they are thrown from code called it javascript" do
868  
-      Context.new do |cxt|
869  
-        cxt['rputs'] = lambda {|this, msg| rputs msg}
870  
-        cxt['boom'] = lambda do |this|
871  
-          raise "BOOM!"
872  
-        end
873  
-        cxt.eval('var msg;try {boom()} catch (e) {msg = e.message};msg').should == 'BOOM!'
874  
-      end
875  
-    end
876  
-
877 874
     it "will allow exceptions to pass through multiple languages boundaries (i.e. js -> rb -> js -> rb)" do
878  
-      Context.new do |cxt|
  875
+      RedJS::Context.new do |cxt|
879 876
         cxt['one'] = lambda do
880 877
           cxt.eval('two()', 'one.js')
881 878
         end
@@ -893,30 +890,30 @@ def evaljs(js)
893 890
         }
894 891
       end
895 892
     end
896  
-  end
897  
-
898  
-  describe "A Ruby class reflected into JavaScript" do
899  
-    it "will extend instances of the class when properties are added to the corresponding JavaScript constructor's prototype" do
900  
-      Class.new.tap do |cls|
901  
-        Context.new do |cxt|
902  
-          cxt['RubyObject'] = cls
903  
-          cxt.eval('RubyObject.prototype.foo = function() {return "bar"}')
904  
-          cxt['o'] = cls.new
905  
-          cxt.eval('o.foo()').should == "bar"
906  
-        end
  893
+    
  894
+    it "translates ruby exceptions into javascript exceptions if they are thrown from code called it javascript" do
  895
+      RedJS::Context.new do |cxt|
  896
+        cxt['boom'] = lambda { raise "BOOM!" }
  897
+        cxt.eval('( function() { try { boom() } catch (e) { return e.message } } )()').should == 'BOOM!'
907 898
       end
908 899
     end
909  
-
910  
-    it "will extend instances of subclasses when properties are added to the corresponding JavaScript constructor's prototype" do
911  
-      superclass = Class.new
912  
-      subclass = Class.new(superclass)
913  
-      Context.new do |cxt|
914  
-        cxt['SuperClass'] = superclass
915  
-        cxt['SubClass'] = subclass
916  
-        cxt['o'] = subclass.new
917  
-        cxt.eval('SuperClass.prototype.foo = function() {return "bar"}')
918  
-        cxt.eval('o.foo()').should == "bar"
919  
-      end
  900
+    
  901
+  end
  902
+  
  903
+  private
  904
+  
  905
+  before :all do # check setup
  906
+    unless defined?(RedJS::Context)
  907
+      raise "missing RedJS::Context - please expose your context e.g. `RedJS.const_set :Context, V8::Context`"
920 908
     end
  909
+    unless defined?(RedJS::Error)
  910
+      warn "RedJS::Error not exposed, specs will only check that a generic error is raised, " + 
  911
+           "if you have a JSError please set it e.g. `RedJS.const_set :Error, Rhnino::JSError`"
  912
+    end
  913
+  end
  914
+  
  915
+  def raise_js_error
  916
+    defined?(RedJS::Error) ? raise_error(RedJS::Error) : raise_error
921 917
   end
  918
+  
922 919
 end
0  loadme.js → lib/redjs/spec/fixtures/loadme.js
File renamed without changes
22  redjs.gemspec
... ...
@@ -0,0 +1,22 @@
  1
+# -*- encoding: utf-8 -*-
  2
+$:.push File.expand_path("../lib", __FILE__)
  3
+require "redjs"
  4
+
  5
+Gem::Specification.new do |s|
  6
+  s.name = %q{redjs}
  7
+  s.version = RedJS::VERSION
  8
+  
  9
+  s.authors = ["Charles Lowell"]
  10
+  s.summary = %q{JavaScript compatibility specs for Ruby.}
  11
+  s.description = %q{An interface compatibility suite for Ruby embeddings of Javascript.}
  12
+  s.email = %q{cowboyd@thefrontside.net}
  13
+  
  14
+  s.homepage = %q{http://github.com/cowboyd/redjs}
  15
+  s.require_paths = ["lib"]
  16
+  
  17
+  s.extra_rdoc_files = ["README.md"]
  18
+  
  19
+  s.files = `git ls-files`.split("\n")
  20
+
  21
+  s.add_development_dependency "rspec", ">= 2.7"
  22
+end

0 notes on commit f4b1ba7

Please sign in to comment.
Something went wrong with that request. Please try again.