├── .gitignore ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── javascript.gemspec ├── lib ├── javascript.rb └── javascript │ ├── no_conflict.rb │ └── version.rb └── test ├── javascript_test.rb ├── no_conflict_test.rb └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | *.gem 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.5 4 | - ruby-head 5 | 6 | notifications: 7 | email: false -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in javascript.gemspec 4 | gemspec 5 | 6 | gem "rake", "~> 10.0" 7 | gem "minitest", "~> 5.11" 8 | gem "activesupport", "~> 5.2" -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Godfrey Chan 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript 2 | 3 | [![Build Status](https://travis-ci.org/chancancode/javascript.svg?branch=master)](https://travis-ci.org/chancancode/javascript) 4 | 5 | As much as we love writing Ruby, when you need to *get really close to the 6 | metal*, you have no choice but to use JavaScript. With this gem, Rubyists can 7 | finally harness the raw power of their machines by programming in JavaScript 8 | syntax *right inside their Ruby applications*. 9 | 10 | ## Installation 11 | 12 | Add this line to your application's Gemfile: 13 | 14 | ```ruby 15 | gem 'javascript' 16 | ``` 17 | 18 | And then execute: 19 | 20 | $ bundle 21 | 22 | Or install it yourself as: 23 | 24 | $ gem install javascript 25 | 26 | ## Usage 27 | 28 | ```ruby 29 | require "javascript" 30 | 31 | puts "This is totally Ruby" 32 | 33 | javascript { 34 | 35 | console.log("ZOMG JavaScript"); 36 | 37 | let a = 1; 38 | 39 | console.log(a); 40 | 41 | a = a + 1; 42 | 43 | console.log(a); 44 | 45 | let b = function(x) { 46 | console.log(x + 1); 47 | }; 48 | 49 | b(3); 50 | 51 | function c(x) { 52 | console.log(x + 2); 53 | } 54 | 55 | c(4); 56 | 57 | function inspectArguments() { 58 | let args = Array.prototype.join.call(arguments, ", "); 59 | console.log(`Arguments: ${args}`); 60 | } 61 | 62 | inspectArguments("a", "b", "c"); 63 | 64 | inspectArguments(1, 2, 3, 4, 5); 65 | 66 | } 67 | 68 | puts "This is Ruby again" 69 | ``` 70 | 71 | Output: 72 | 73 | ``` 74 | % ruby test.rb 75 | This is totally Ruby 76 | ZOMG JavaScript 77 | 1 78 | 2 79 | 4 80 | 6 81 | Arguments: a, b, c 82 | Arguments: 1, 2, 3, 4, 5 83 | This is Ruby again 84 | % 85 | ``` 86 | 87 | ### JavaScript + Rails = <3 88 | 89 | Because Rails embraces callbacks, this gem is the perfectly compliment for your 90 | Rails application. For example: 91 | 92 | ```ruby 93 | require "javascript" 94 | 95 | class Post < ActiveRecord::Base 96 | before_create &javascript { 97 | function(post) { 98 | post.slug = post.title.parameterize(); 99 | } 100 | } 101 | end 102 | ``` 103 | 104 | Alternatively: 105 | 106 | ```ruby 107 | require "javascript" 108 | 109 | class Post < ActiveRecord::Base 110 | before_create &javascript { 111 | function() { 112 | this.slug = this.title.parameterize(); 113 | } 114 | } 115 | end 116 | ``` 117 | 118 | ### No Conflict Mode 119 | 120 | If the `javascript` helper conflicts with an existing method, you use this gem 121 | in the "no conflict" mode: 122 | 123 | ```ruby 124 | require "javascript/no_conflict" 125 | 126 | # Or add this to your Gemfile: 127 | # 128 | # gem "javascript", require: "javascript/no_conflict" 129 | # 130 | 131 | JavaScript.eval { 132 | console.log("JavaScript here"); 133 | } 134 | 135 | # You can also define your own helper method 136 | 137 | module Kernel 138 | private def metal(&block) 139 | JavaScript.eval(&block) 140 | end 141 | end 142 | 143 | metal { 144 | console.log("JavaScript here"); 145 | } 146 | ``` 147 | 148 | ## Pros 149 | 150 | * Gives you the illusion of programming in a *closer to the metal* syntax. 151 | * The examples in this README [actually work](/test/javascript_test.rb). 152 | 153 | ## Cons 154 | 155 | * Things that aren't covered in the examples probably won't ever work. 156 | * [Not enough jQuery](http://meta.stackexchange.com/questions/45176/when-is-use-jquery-not-a-valid-answer-to-a-javascript-question). 157 | 158 | ## Contributing 159 | 160 | 1. Fork it ( https://github.com/chancancode/javascript/fork ) 161 | 2. Create your feature branch (`git checkout -b my-new-feature`) 162 | 3. Commit your changes (`git commit -am 'Add some feature'`) 163 | 4. Push to the branch (`git push origin my-new-feature`) 164 | 5. Create a new Pull Request 165 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | desc 'Run tests' 5 | test_task = Rake::TestTask.new(:test) do |t| 6 | t.libs << 'test' 7 | t.pattern = 'test/**/*_test.rb' 8 | t.verbose = true 9 | end 10 | 11 | task default: :test 12 | -------------------------------------------------------------------------------- /javascript.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path("../lib", __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require "javascript/version" 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "javascript" 8 | spec.version = JavaScript::VERSION 9 | spec.authors = ["Godfrey Chan"] 10 | spec.email = ["godfreykfc@gmail.com"] 11 | spec.summary = "Harness the raw power of your machine with JavaScript" 12 | spec.description = "With this gem, Rubyists can finally get really close " \ 13 | "to the metal by programming in JavaScript syntax right " \ 14 | "within their Ruby applications." 15 | spec.homepage = "https://github.com/chancancode/javascript" 16 | spec.license = "MIT" 17 | 18 | spec.files = `git ls-files -z`.split("\x0") 19 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 20 | spec.test_files = spec.files.grep(%r{^test/}) 21 | spec.require_paths = ["lib"] 22 | 23 | spec.required_ruby_version = ">= 2.1.0" 24 | 25 | spec.add_dependency "binding_of_caller", "~> 0.7.0" 26 | 27 | spec.add_development_dependency "bundler", "~> 1.16" 28 | end 29 | -------------------------------------------------------------------------------- /lib/javascript.rb: -------------------------------------------------------------------------------- 1 | require "javascript/version" 2 | require "binding_of_caller" 3 | 4 | def supress_warnings 5 | verbose, $VERBOSE = $VERBOSE, nil 6 | yield 7 | ensure 8 | $VERBOSE = verbose 9 | end 10 | 11 | module JavaScript 12 | def self.eval(&block) 13 | Scope.new.__eval__(&block) 14 | end 15 | 16 | def self.current_scope 17 | @scope 18 | end 19 | 20 | def self.current_scope=(scope) 21 | @scope = scope 22 | end 23 | 24 | # Make it an `Object` instead of `BasicObject` so that we can bind 25 | # any instance methods from `Object` and call them here 26 | class BlankObject 27 | supress_warnings do 28 | instance_methods(true).each { |meth| undef_method(meth) } 29 | private_instance_methods(true).each { |meth| undef_method(meth) } 30 | end 31 | end 32 | 33 | module Internals 34 | supress_warnings do 35 | def __send__(meth, *args, &block) 36 | __method__(:public_send).call(meth, *args, &block) 37 | end 38 | end 39 | 40 | private 41 | def __method__(name) 42 | ::Object.instance_method(name).bind(self) 43 | end 44 | 45 | def __binding__ 46 | __method__(:binding).call.of_caller(1) 47 | end 48 | 49 | def __caller__ 50 | __binding__.of_caller(2) 51 | end 52 | end 53 | 54 | class Object < BlankObject 55 | include Internals 56 | 57 | def initialize(proto = nil) 58 | @hash = { __proto__: proto } 59 | end 60 | 61 | def [](key) 62 | if @hash.key?(key) 63 | @hash[key] 64 | elsif __proto__ 65 | __proto__[key] 66 | else 67 | nil 68 | end 69 | end 70 | 71 | def []=(key, value) 72 | @hash[key.to_sym] = value 73 | end 74 | 75 | def ==(other) 76 | __method__(:==).call(other) 77 | end 78 | 79 | def ===(other) 80 | self == other 81 | end 82 | 83 | def !=(other) 84 | !(self == other) 85 | end 86 | 87 | def __hash__ 88 | @hash 89 | end 90 | 91 | def __proto__ 92 | @hash[:__proto__] 93 | end 94 | 95 | def __defined__?(key) 96 | @hash.key?(key) || (__proto__ && __proto__.__defined__?(key)) 97 | end 98 | 99 | private 100 | def method_missing(name, *args, &block) 101 | if name =~ /=\z/ 102 | self[name[0...-1].to_sym] = args[0] 103 | elsif Function === self[name] 104 | self[name].apply(self, args) 105 | else 106 | self[name] 107 | end 108 | end 109 | end 110 | 111 | class GlobalObject < Object 112 | def initialize 113 | super 114 | @hash[:console] = Console.new 115 | end 116 | end 117 | 118 | module Syntax 119 | private 120 | def window 121 | @global 122 | end 123 | 124 | def global 125 | @global 126 | end 127 | 128 | def this 129 | @target 130 | end 131 | 132 | def undefined 133 | nil 134 | end 135 | 136 | def let(*identifiers) 137 | end 138 | 139 | def var(*identifiers) 140 | end 141 | 142 | def `(string) 143 | __caller__.eval(string.inspect.gsub(/\$(?={)/, '#')) 144 | end 145 | 146 | def new(identifier) 147 | ::Object.const_get(identifier.name).new(*identifier.args) 148 | end 149 | 150 | def function(*args, &block) 151 | if args.length == 1 && Function === args[0] 152 | hash = @parent ? @locals : global.__hash__ 153 | hash[args[0].name] = args[0] 154 | else 155 | Function.new(nil, args, block) 156 | end 157 | end 158 | end 159 | 160 | class Scope < BlankObject 161 | include Internals 162 | include Syntax 163 | 164 | def initialize(parent = nil, target = nil, locals = {}) 165 | @parent = parent 166 | @global = parent ? parent.__global__ : GlobalObject.new 167 | @locals = locals 168 | 169 | if Scope === target 170 | @target = target.__target__ 171 | else 172 | @target = target || global 173 | end 174 | end 175 | 176 | def __global__ 177 | @global 178 | end 179 | 180 | def __target__ 181 | @target 182 | end 183 | 184 | def __spawn__(target = nil, locals = {}) 185 | Scope.new(self, target, locals) 186 | end 187 | 188 | def __eval__(*args, &block) 189 | JavaScript.current_scope = self 190 | 191 | # convert the block to a lambda, so that +return+ works correctly 192 | temp = :"block_#{Time.now.to_i}" 193 | metaclass = __method__(:singleton_class).call 194 | 195 | metaclass.send(:define_method, temp, &block) 196 | 197 | if block.arity.zero? 198 | __method__(:send).call(temp) 199 | else 200 | __method__(:send).call(temp, *args) 201 | end 202 | ensure 203 | JavaScript.current_scope = @parent 204 | metaclass.send(:remove_method, temp) 205 | end 206 | 207 | private 208 | def method_missing(name, *args, &block) 209 | found = false 210 | value = nil 211 | defined = __caller__.local_variable_defined?(name) rescue nil 212 | 213 | if defined 214 | found = true 215 | value = __caller__.local_variable_get(name) 216 | elsif @locals.key?(name) 217 | found = true 218 | value = @locals[name] 219 | elsif !@parent && @global.__defined__?(name) 220 | found = true 221 | value = @global[name] 222 | end 223 | 224 | if found && Function === value 225 | value.apply(nil, args) 226 | elsif found 227 | value 228 | elsif @parent 229 | @parent.__send__(name, *args, &block) 230 | elsif block.nil? 231 | Identifier.new(name, args) 232 | else 233 | Function.new(name, args, block) 234 | end 235 | end 236 | end 237 | 238 | class Identifier < Struct.new(:name, :args); end 239 | 240 | class Function < Struct.new(:name, :args, :body) 241 | def initialize(*) 242 | @scope = JavaScript.current_scope 243 | super 244 | end 245 | 246 | def arity 247 | args.length 248 | end 249 | 250 | def call(target = nil, *arg_values) 251 | ::Object.instance_method(:instance_exec).bind(target).call(*arg_values, &self) 252 | end 253 | 254 | def apply(target = nil, arg_values) 255 | call(target, *arg_values) 256 | end 257 | 258 | def bind(target) 259 | BoundFunction.new(target, name, args, body) 260 | end 261 | 262 | def to_proc 263 | parent_scope = @scope 264 | arg_names = args.map(&:name) 265 | unwrapped = body 266 | 267 | FunctionWrapper.new(arg_names) do |*arg_values| 268 | locals = Hash[ arg_names.zip(arg_values) ] 269 | locals[:arguments] = arg_values 270 | parent_scope.__spawn__(self, locals).__eval__(*arg_values, &unwrapped) 271 | end 272 | end 273 | end 274 | 275 | class BoundFunction < Function 276 | def initialize(target, name, args, body) 277 | super(name, args, body) 278 | @target = target 279 | end 280 | 281 | def call(_, *arg_values) 282 | super(@target, *arg_values) 283 | end 284 | 285 | def apply(_, arg_values) 286 | super(@target, arg_values) 287 | end 288 | 289 | def bind(_) 290 | self 291 | end 292 | end 293 | 294 | class FunctionWrapper < Proc 295 | def initialize(args, &block) 296 | @args = args 297 | super(&block) 298 | end 299 | 300 | def arity 301 | @args.length 302 | end 303 | 304 | def parameters 305 | @args.map { |arg| [:required, arg.to_sym] } 306 | end 307 | end 308 | 309 | class Prototype < Object 310 | def initialize(klass) 311 | super(nil) 312 | @klass = klass 313 | end 314 | 315 | private 316 | def method_missing(name, *args, &block) 317 | meth = @klass.instance_method(name) 318 | args = meth.parameters.map { |_, name| name && Identifier.new(name) }.compact 319 | body = ->(*args) { meth.bind(this).call(*args) } 320 | Function.new(name, args, body) 321 | rescue NameError 322 | super 323 | end 324 | end 325 | 326 | class Console 327 | def log(*args) 328 | puts(*args) 329 | end 330 | end 331 | end 332 | 333 | unless defined?(JavaScript::NO_CONFLICT) 334 | module Kernel 335 | def javascript(&block) 336 | JavaScript.eval(&block) 337 | end 338 | end 339 | 340 | class Class 341 | def prototype 342 | JavaScript::Prototype.new(self) 343 | end 344 | end 345 | end 346 | -------------------------------------------------------------------------------- /lib/javascript/no_conflict.rb: -------------------------------------------------------------------------------- 1 | module JavaScript 2 | NO_CONFLICT = true 3 | end 4 | 5 | require "javascript" -------------------------------------------------------------------------------- /lib/javascript/version.rb: -------------------------------------------------------------------------------- 1 | module JavaScript 2 | VERSION = "0.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /test/javascript_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | $messages = [] 4 | 5 | module JavaScript 6 | class Console 7 | private def puts(message) 8 | $messages << message 9 | end 10 | end 11 | end 12 | 13 | class JavaScriptTest < TestCase 14 | teardown do 15 | $messages.clear 16 | end 17 | 18 | test "undefined" do 19 | require "javascript" 20 | assert_nil javascript { undefined } 21 | end 22 | 23 | test "this" do 24 | require "javascript" 25 | 26 | javascript { 27 | console.log(this === window); 28 | console.log(this === global); 29 | } 30 | 31 | assert_messages true, true 32 | end 33 | 34 | test "Local variables (let)" do 35 | require "javascript" 36 | 37 | javascript { 38 | let a = 1, b = 2; 39 | 40 | console.log(a); 41 | console.log(b); 42 | 43 | a = a + 1; 44 | b = a + b; 45 | 46 | console.log(a); 47 | console.log(b); 48 | } 49 | 50 | assert_messages 1, 2, 2, 4 51 | end 52 | 53 | test "Local variables (var)" do 54 | require "javascript" 55 | 56 | javascript { 57 | var a = 1, b = 2; 58 | 59 | console.log(a); 60 | console.log(b); 61 | 62 | a = a + 1; 63 | b = a + b; 64 | 65 | console.log(a); 66 | console.log(b); 67 | } 68 | 69 | assert_messages 1, 2, 2, 4 70 | end 71 | 72 | test "Global variables" do 73 | require "javascript" 74 | 75 | javascript { 76 | window.a = 1; 77 | global.b = 2; 78 | 79 | console.log(a); 80 | console.log(b); 81 | 82 | console.log(a === window.a); 83 | console.log(a === global.a); 84 | 85 | console.log(b === window.b); 86 | console.log(b === global.b); 87 | } 88 | 89 | assert_messages 1, 2, true, true, true, true 90 | end 91 | 92 | test "Global object is not shared between different javascript blocks" do 93 | require "javascript" 94 | 95 | javascript { window.a = 1; } 96 | assert_nil javascript { window.a; } 97 | end 98 | 99 | test "String interpolation" do 100 | require "javascript" 101 | 102 | javascript { 103 | console.log(`hello world...`); 104 | 105 | let name = "Godfrey"; 106 | 107 | console.log(`hello ${name}!`); 108 | } 109 | 110 | assert_messages "hello world...", "hello Godfrey!" 111 | end 112 | 113 | test "Functions" do 114 | require "javascript" 115 | 116 | javascript { 117 | var a = function(msg) { 118 | console.log(`a: ${msg}`); 119 | }; 120 | 121 | function b(msg) { 122 | console.log(`b: ${msg}`); 123 | } 124 | 125 | var c = "c"; 126 | 127 | a("hello"); 128 | b("hello"); 129 | 130 | a(c); 131 | b(c); 132 | } 133 | 134 | assert_messages "a: hello", "b: hello", "a: c", "b: c" 135 | end 136 | 137 | test "Functions can return a value" do 138 | require "javascript" 139 | 140 | javascript { 141 | function identity(x) { 142 | return x; 143 | } 144 | 145 | function square(x) { 146 | return x * x; 147 | } 148 | 149 | console.log(identity("Hello world!")); 150 | console.log(square(2)); 151 | } 152 | 153 | assert_messages "Hello world!", 4 154 | end 155 | 156 | test "closure (let variables)" do 157 | require "javascript" 158 | 159 | javascript { 160 | let a = 1; 161 | 162 | function outer(b) { 163 | let c = 3; 164 | 165 | function inner(d) { 166 | let e = 5; 167 | 168 | console.log(a); 169 | console.log(b); 170 | console.log(c); 171 | console.log(d); 172 | console.log(e); 173 | }; 174 | 175 | inner(4); 176 | } 177 | 178 | outer(2); 179 | } 180 | 181 | assert_messages 1, 2, 3, 4, 5 182 | end 183 | 184 | test "closure (var variables)" do 185 | require "javascript" 186 | 187 | javascript { 188 | var a = 1; 189 | 190 | function outer(b) { 191 | var c = 3; 192 | 193 | function inner(d) { 194 | var e = 5; 195 | 196 | console.log(a); 197 | console.log(b); 198 | console.log(c); 199 | console.log(d); 200 | console.log(e); 201 | }; 202 | 203 | inner(4); 204 | } 205 | 206 | outer(2); 207 | } 208 | 209 | assert_messages 1, 2, 3, 4, 5 210 | end 211 | 212 | test "closure (functions)" do 213 | require "javascript" 214 | 215 | javascript { 216 | function a() { 217 | return 1; 218 | }; 219 | 220 | function outer() { 221 | function b() { 222 | return 2; 223 | } 224 | 225 | function inner() { 226 | function c() { 227 | return 3; 228 | } 229 | 230 | console.log(a()); 231 | console.log(b()); 232 | console.log(c()); 233 | }; 234 | 235 | inner(); 236 | } 237 | 238 | outer(); 239 | } 240 | 241 | assert_messages 1, 2, 3 242 | end 243 | 244 | test "arguments" do 245 | require "javascript" 246 | 247 | javascript { 248 | function inspect() { 249 | console.log(arguments.length); 250 | console.log(arguments); 251 | } 252 | 253 | inspect("a", "b", "c"); 254 | inspect(1, 2, 3, 4, 5); 255 | inspect(); 256 | } 257 | 258 | assert_messages 3, ["a", "b", "c"], 5, [1, 2, 3, 4, 5], 0, [] 259 | end 260 | 261 | test "Function#call" do 262 | require "javascript" 263 | 264 | javascript do 265 | var thisCheck = function(expected) { 266 | console.log(this === expected); 267 | }; 268 | 269 | thisCheck(this); 270 | thisCheck("abc"); 271 | 272 | thisCheck.call(this, this); 273 | thisCheck.call(this, "abc"); 274 | 275 | thisCheck.call("abc", "abc"); 276 | thisCheck.call("abc", this); 277 | 278 | var argsCheck = function() { 279 | console.log(this == arguments); 280 | }; 281 | 282 | argsCheck.call([1, 2, 3], 1, 2, 3); 283 | argsCheck.call([1, 2, 3], 4, 5, 6); 284 | argsCheck.call([1, 2, 3], [1, 2, 3]); 285 | end 286 | 287 | assert_messages true, false, true, false, true, false, true, false, false 288 | end 289 | 290 | test "Function#apply" do 291 | require "javascript" 292 | 293 | javascript do 294 | var thisCheck = function(expected) { 295 | console.log(this === expected); 296 | }; 297 | 298 | thisCheck(this); 299 | thisCheck("abc"); 300 | 301 | thisCheck.apply(this, [this]); 302 | thisCheck.apply(this, ["abc"]); 303 | 304 | thisCheck.apply("abc", ["abc"]); 305 | thisCheck.apply("abc", [this]); 306 | 307 | var argsCheck = function() { 308 | console.log(this == arguments); 309 | }; 310 | 311 | argsCheck.apply([1, 2, 3], [1, 2, 3]); 312 | argsCheck.apply([1, 2, 3], [4, 5, 6]); 313 | end 314 | 315 | assert_messages true, false, true, false, true, false, true, false 316 | end 317 | 318 | test "Function#bind" do 319 | require "javascript" 320 | 321 | javascript do 322 | var thisCheck = function(expected) { 323 | console.log(this === expected); 324 | }; 325 | 326 | thisCheck = thisCheck.bind("abc"); 327 | 328 | thisCheck(this); 329 | thisCheck("abc"); 330 | 331 | thisCheck.call(this, this); 332 | thisCheck.call(this, "abc"); 333 | thisCheck.call("abc", "abc"); 334 | 335 | var argsCheck = function() { 336 | console.log(this == arguments); 337 | }; 338 | 339 | argsCheck = argsCheck.bind([1, 2, 3]); 340 | 341 | argsCheck.call("abc", 1, 2, 3); 342 | argsCheck.apply("abc", [1, 2, 3]); 343 | 344 | argsCheck.call("abc", 4, 5, 6); 345 | argsCheck.apply("abc", [4, 5, 6]); 346 | end 347 | 348 | assert_messages false, true, false, true, true, true, true, false, false 349 | end 350 | 351 | test "Prototype" do 352 | require "javascript" 353 | 354 | javascript { 355 | let join = Array.prototype.join; 356 | 357 | console.log(join.call(["a", "b", "c"], "+")); 358 | console.log(join.call([1, 2, 3, 4, 5], "-")); 359 | } 360 | 361 | assert_messages "a+b+c", "1-2-3-4-5" 362 | end 363 | 364 | test "Callback" do 365 | require "javascript" 366 | require "active_support/callbacks" 367 | require "active_support/core_ext/string/inflections" 368 | 369 | class Post < Struct.new(:title, :slug) 370 | include ActiveSupport::Callbacks 371 | 372 | define_callbacks :create, :destroy 373 | 374 | set_callback :create, :before, &javascript { 375 | function(post) { 376 | console.log("Before"); 377 | post.slug = post.title.parameterize(); 378 | } 379 | } 380 | 381 | set_callback :destroy, :after, &javascript { 382 | function() { 383 | console.log("After"); 384 | this.slug = undefined; 385 | } 386 | } 387 | 388 | def create 389 | run_callbacks(:create) 390 | end 391 | 392 | def destroy 393 | run_callbacks(:destroy) 394 | end 395 | end 396 | 397 | post = Post.new("Hello world!") 398 | 399 | assert_nil post.slug 400 | 401 | post.create 402 | 403 | assert_equal "hello-world", post.slug 404 | 405 | post.destroy 406 | 407 | assert_nil post.slug 408 | 409 | assert_messages "Before", "After" 410 | end 411 | 412 | private 413 | def assert_messages(*messages) 414 | assert_equal messages, $messages 415 | end 416 | end 417 | -------------------------------------------------------------------------------- /test/no_conflict_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class NoConflictTest < TestCase 4 | test "Global `javascript` helper is not available in no-conflict mode" do 5 | require "javascript/no_conflict" 6 | assert_raises(NoMethodError) { javascript { 1 } } 7 | end 8 | 9 | test "Global monkey-patch is not available in no-conflict mode" do 10 | require "javascript/no_conflict" 11 | assert_raises(NoMethodError) { JavaScript.eval { Array.prototype } } 12 | end 13 | 14 | test "Defining custom helper in no-conflict mode" do 15 | require "javascript/no_conflict" 16 | 17 | def metal(&block) 18 | JavaScript.eval(&block) 19 | end 20 | 21 | assert_equal 1, metal { 1 } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | require "minitest/autorun" 3 | require "minitest/pride" 4 | require "active_support" 5 | require "active_support/testing/declarative" 6 | require "active_support/testing/setup_and_teardown" 7 | require "active_support/testing/isolation" 8 | 9 | class TestCase < Minitest::Test 10 | extend ActiveSupport::Testing::Declarative 11 | prepend ActiveSupport::Testing::SetupAndTeardown 12 | include ActiveSupport::Testing::Isolation 13 | end 14 | --------------------------------------------------------------------------------