blob: a6bfb6ba4cfa5c8024ebf544bbaa21e8881671dd [file] [log] [blame]
#
# This class spawns a io.js process to run a HTTP server which accepts
# POST requests containing Vue/jsdom scripts and responds with
# HTML results. It provides a Rack interface, enabling this server to
# be run with Capybara/RackTest.
#
require 'ruby2js'
require 'net/http'
require 'stringio'
require 'capybara/rspec'
require 'ruby2js/filter/vue'
class VueServer
@@pid = nil
@@port = nil
# start a new server
def self.start
return if @@pid
# select an available port
server = TCPServer.new('127.0.0.1', 0)
@@port = server.addr[1]
server.close
# spawn a server process
nodejs = (`which nodejs`.empty? ? 'node' : 'nodejs')
@@pid = spawn(nodejs, '-e',
Ruby2JS.convert(@@server, {ivars: {:@port => @@port}}))
# wait for server to start
(0..10).each do |i|
begin
response = new.call('rack.input' => StringIO.new("response.end('hi')"))
return if response.first == '200' and response.last == 'hi'
STDERR.puts response
raise RuntimeError('Invalid VueServer response received')
rescue Errno::ECONNREFUSED
sleep i * 0.1
end
end
end
# rack compatible interface
def call(env)
http = Net::HTTP.new('localhost', @@port)
request = Net::HTTP::Post.new('/', {})
request.body = env['rack.input'].read
response = http.request(request)
[response.code, response.to_hash(), response.body]
end
# stop server
def self.stop
return unless @@pid
begin
http = Net::HTTP.new('localhost', @@port)
request = Net::HTTP::Post.new('/', {})
request.body = "response.end('bye'); process.exit(0)"
response = http.request(request)
rescue Errno::ECONNREFUSED, EOFError
nil
ensure
Process.wait(@@pid)
@@pid = nil
end
end
# the server itself
@@server = proc do
cleanup = require("jsdom-global/register")
delete global.XMLHttpRequest
process.env.VUE_ENV = 'server'
Vue = require('vue')
Vue.config.productionTip = false
# render a response, using server side rendering
def Vue.renderResponse(component, response)
renderer = require('vue-server-renderer').createRenderer()
app = Vue.new(render: proc {|h| return h(component)})
renderer.renderToString(app) do |err, html|
if err
response.end(err.toString() + "\n" + err.stack)
else
response.end(html)
end
end
end
# render a element, using client side rendering
def Vue.renderElement(component)
outer = document.createElement('div')
inner = document.createElement('span')
outer.appendChild(inner);
Vue.new(el: inner, render: proc {|h| return h(component)})
return outer.firstChild
end
# render an app, using client side rendering. Convenience methods are
# provided to querySelector, and to extract outerHTML.
def Vue.renderApp(component)
outer = document.createElement('div')
inner = document.createElement('span')
outer.appendChild(inner);
app = Vue.new(el: inner, render: proc {|h| return h(component)})
inner = outer.firstChild
def app.outerHTML
return inner.outerHTML
end
def app.querySelector(selector)
return outer.querySelector(selector)
end
return app
end
jQuery = require('jquery')
http = require('http')
server = http.createServer do |request, response|
data = ''
request.on('data') do |chunk|
data += chunk
end
request.on 'error' do |error|
console.log "VueServer error: #{error.message}"
end
request.on 'end' do
response.writeHead(200, 'Content-Type' => 'text/plain')
begin
eval(data)
rescue => error
response.end(error.toString());
end
end
end
server.listen(@port)
end
end
shared_context "vue_server", server: :vue do
#
# administrivia
#
before :all do
VueServer.start
Dir.chdir File.expand_path('../../views', __FILE__) do
@_script = Ruby2JS.convert(File.read('app.js.rb'), file: 'app.js.rb')
end
end
before :each do
@_app, Capybara.app = Capybara.app, VueServer.new
end
def on_vue_server(&block)
locals = {}
instance_variables.each do |ivar|
next if ivar.to_s.start_with? '@_'
locals[ivar] = instance_variable_get(ivar)
end
page.driver.post('/', @_script + ';' +
Ruby2JS.convert(block, vue: true, ivars: locals).to_s)
end
after :each do
Capybara.app = @_app
end
at_exit do
VueServer.stop
end
end