diff --git a/flowerbox.gemspec b/flowerbox.gemspec
index 81675f9..85fc657 100644
--- a/flowerbox.gemspec
+++ b/flowerbox.gemspec
@@ -4,8 +4,8 @@ require File.expand_path('../lib/flowerbox/version', __FILE__)
Gem::Specification.new do |gem|
gem.authors = ["John Bintz"]
gem.email = ["john@coswellproductions.com"]
- gem.description = %q{TODO: Write a gem description}
- gem.summary = %q{TODO: Write a gem summary}
+ gem.description = %q{No-nonsense JavaScript testing solution.}
+ gem.summary = %q{No-nonsense JavaScript testing solution.}
gem.homepage = ""
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -19,9 +19,9 @@ Gem::Specification.new do |gem|
gem.add_development_dependency 'rspec'
gem.add_dependency 'jasmine-core'
- gem.add_dependency 'flowerbox-delivery'
gem.add_dependency 'thor'
gem.add_dependency 'selenium-webdriver'
gem.add_dependency 'sinatra'
gem.add_dependency 'rainbow'
+ gem.add_dependency 'sprockets-vendor_gems'
end
diff --git a/lib/flowerbox.rb b/lib/flowerbox.rb
index fdc870a..f94f5b8 100644
--- a/lib/flowerbox.rb
+++ b/lib/flowerbox.rb
@@ -1,5 +1,4 @@
require "flowerbox/version"
-require 'flowerbox-delivery'
require 'rainbow'
module Flowerbox
@@ -40,6 +39,10 @@ module Flowerbox
autoload :Reporter, 'flowerbox/reporter'
+ autoload :Server, 'flowerbox/server'
+ autoload :UniqueAssetList, 'flowerbox/unique_asset_list'
+ autoload :SprocketsHandler, 'flowerbox/sprockets_handler'
+
class << self
attr_writer :reporters
attr_accessor :port
diff --git a/lib/flowerbox/run/base.rb b/lib/flowerbox/run/base.rb
index 189fde5..3f1fea1 100644
--- a/lib/flowerbox/run/base.rb
+++ b/lib/flowerbox/run/base.rb
@@ -33,7 +33,7 @@ module Flowerbox::Run
end
def sprockets
- Flowerbox::Delivery::SprocketsHandler.new(
+ Flowerbox::SprocketsHandler.new(
:asset_paths => [
Flowerbox.path.join("lib/assets/javascripts"),
Flowerbox.path.join("vendor/assets/javascripts"),
diff --git a/lib/flowerbox/runner/base.rb b/lib/flowerbox/runner/base.rb
index 43365c5..aeb8848 100644
--- a/lib/flowerbox/runner/base.rb
+++ b/lib/flowerbox/runner/base.rb
@@ -106,7 +106,7 @@ module Flowerbox
server_options[:logging] = true if options[:verbose_server]
server_options[:port] = Flowerbox.port
- @server = Flowerbox::Delivery::Server.new(server_options)
+ @server = Flowerbox::Server.new(server_options)
Flowerbox::Rack.runner = self
Flowerbox::Rack.sprockets = @sprockets
diff --git a/lib/flowerbox/server.rb b/lib/flowerbox/server.rb
new file mode 100644
index 0000000..2e64e27
--- /dev/null
+++ b/lib/flowerbox/server.rb
@@ -0,0 +1,123 @@
+require 'rack'
+require 'net/http'
+require 'socket'
+require 'rack/builder'
+require 'thin'
+
+module Flowerbox
+ class Server
+ attr_reader :options
+
+ def initialize(options = {})
+ @options = { :logging => false }.merge(options || {})
+ end
+
+ def start
+ @server_thread = Thread.new do
+ server_options = { :Port => port, :Host => interface }
+
+ app = options[:app]
+
+ Thin::Logging.silent = !options[:logging]
+
+ if options[:logging]
+ real_app = app
+ app = ::Rack::Builder.new do
+ use ::Rack::CommonLogger, STDOUT
+ run real_app
+ end
+ end
+
+ ::Rack::Handler::Thin.run(app, server_options) do |server|
+ Thread.current[:server] = server
+
+ trap('QUIT') { server.stop }
+ end
+ end
+
+ while !@server_thread[:server] && @server_thread.alive?
+ sleep 0.1
+ end
+
+ raise StandardError.new("Server died") if !@server_thread[:server].running?
+ end
+
+ def stop
+ if @server_thread
+ @server_thread[:server].stop
+
+ wait_for_server_to_stop
+ end
+ end
+
+ def interface
+ options[:interface] || '0.0.0.0'
+ end
+
+ def port
+ return @port if @port ||= options[:port]
+
+ attempts = 20
+
+ begin
+ attempts -= 1
+
+ current_port = random_port
+
+ begin
+ socket = TCPSocket.new(interface, current_port)
+ socket.close
+ rescue Errno::ECONNREFUSED => e
+ @port = current_port
+ end
+ end while !@port and attempts > 0
+
+ raise StandardError.new("can't start server") if attempts == 0
+
+ @port
+ end
+
+ def address
+ "http://#{interface}:#{port}/"
+ end
+
+ def alive?
+ @server_thread.alive?
+ end
+
+ private
+ def wait_for_server_to_start
+ while true do
+ begin
+ connect_interface = '127.0.0.1' if interface == '0.0.0.0'
+
+ TCPSocket.new(connect_interface, port)
+ break
+ rescue Errno::ECONNREFUSED => e
+ end
+
+ sleep 0.1
+ end
+ end
+
+ def wait_for_server_to_stop
+ while alive? do
+ begin
+ connect_interface = '127.0.0.1' if interface == '0.0.0.0'
+
+ socket = TCPSocket.new(connect_interface, port)
+ socket.close
+ rescue Errno::ECONNREFUSED => e
+ return
+ end
+
+ sleep 0.1
+ end
+ end
+
+ def random_port
+ 25000 + Kernel.rand(1000)
+ end
+ end
+end
+
diff --git a/lib/flowerbox/sprockets_handler.rb b/lib/flowerbox/sprockets_handler.rb
new file mode 100644
index 0000000..0666f40
--- /dev/null
+++ b/lib/flowerbox/sprockets_handler.rb
@@ -0,0 +1,57 @@
+require 'sprockets'
+require 'sprockets/engines'
+require 'forwardable'
+require 'sprockets-vendor_gems'
+
+module Flowerbox
+ class SprocketsHandler
+ extend Forwardable
+
+ attr_reader :files, :options
+
+ def_delegators :environment, :append_path, :register_engine, :[]
+
+ def self.gem_asset_paths
+ @gem_asset_paths ||= Sprockets.find_gem_vendor_paths
+ end
+
+ def initialize(options)
+ @options = options
+
+ @files = Flowerbox::UniqueAssetList.new
+ end
+
+ def add(asset)
+ paths_for(asset).each { |path| add_paths_for_compiled_asset(path) }
+ end
+
+ def paths_for(asset)
+ environment.find_asset(asset).to_a.collect(&:pathname)
+ end
+
+ def expire_index!
+ @environment.send(:expire_index!)
+ end
+
+ def environment
+ return @environment if @environment
+
+ @environment = Sprockets::Environment.new
+ @environment.cache = Sprockets::Cache::FileStore.new(".tmp")
+
+ self.class.gem_asset_paths.each { |path| append_path(path) }
+ options[:asset_paths].each { |path| append_path(path) }
+
+ @environment
+ end
+
+ def asset_for(*args)
+ environment.find_asset(*args)
+ end
+
+ def add_paths_for_compiled_asset(path)
+ asset_for(path, :bundle => false).to_a.each { |file_path| @files.add(file_path) }
+ end
+ end
+end
+
diff --git a/lib/flowerbox/unique_asset_list.rb b/lib/flowerbox/unique_asset_list.rb
new file mode 100644
index 0000000..8011b9d
--- /dev/null
+++ b/lib/flowerbox/unique_asset_list.rb
@@ -0,0 +1,17 @@
+module Flowerbox
+ class UniqueAssetList < ::Array
+ def add(files)
+ [ files ].flatten.each { |file| self << file if !include?(file) }
+ end
+
+ def to_json
+ collect(&:logical_path)
+ end
+
+ private
+ def include?(file)
+ any? { |other_file| other_file.pathname.to_s == file.pathname.to_s }
+ end
+ end
+end
+
diff --git a/spec/flowerbox/delivery/template_renderer_spec.rb b/spec/flowerbox/delivery/template_renderer_spec.rb
new file mode 100644
index 0000000..7c790d0
--- /dev/null
+++ b/spec/flowerbox/delivery/template_renderer_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+
+describe Flowerbox::Delivery::TemplateRenderer do
+ let(:template_renderer) { described_class.new(:template => template, :files => files) }
+ let(:template) { 'template' }
+ let(:files) { 'files' }
+
+ let(:rendered_template) { 'rendered template' }
+ let(:erb_template) { "#{rendered_template} <%= resource_tags %>" }
+
+ describe '#render' do
+ subject { template_renderer.render }
+
+ let(:rendered_files) { 'with files' }
+ let(:result) { "#{rendered_template} #{rendered_files}" }
+
+ before do
+ template_renderer.expects(:resource_tags).returns(rendered_files)
+ template_renderer.expects(:template).returns(erb_template)
+ end
+
+ it { should == result }
+ end
+
+ describe '#template' do
+ include FakeFS::SpecHelpers
+
+ before do
+ File.open(template, 'wb') { |fh| fh.print erb_template }
+ end
+
+ subject { template_renderer.template }
+
+ it { should == erb_template }
+ end
+
+ describe '#resource_tags' do
+ subject { template_renderer.resource_tags }
+
+ context 'success' do
+ let(:files) { [ js, css ] }
+ let(:js) { 'file.js' }
+ let(:css) { 'file.css' }
+
+ it { should == [
+ %{},
+ %{}
+ ].join }
+ end
+
+ context 'failure' do
+ let(:files) { [ 'what.ever' ] }
+
+ it 'should raise error' do
+ expect { subject }.to raise_error(Flowerbox::Delivery::TemplateRenderer::FileTypeError)
+ end
+ end
+ end
+end
diff --git a/spec/flowerbox/delivery/tilt/js_template_spec.rb b/spec/flowerbox/delivery/tilt/js_template_spec.rb
new file mode 100644
index 0000000..41a680b
--- /dev/null
+++ b/spec/flowerbox/delivery/tilt/js_template_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+describe Flowerbox::Delivery::Tilt::JSTemplate do
+ let(:js_template) { described_class.new { '' } }
+
+ describe '#evaluate' do
+ subject { js_template.evaluate(Object.new, {}) }
+
+ before do
+ js_template.stubs(:file).returns(file)
+ end
+
+ context '.js' do
+ let(:file) { 'file.js' }
+
+ it { should == file }
+ end
+
+ context 'other extension' do
+ let(:file) { 'file.coffee' }
+ let(:temp_file) { 'temp file' }
+
+ before do
+ js_template.expects(:save).returns(temp_file)
+ end
+
+ it { should == temp_file }
+ end
+ end
+
+ describe '#save' do
+ include FakeFS::SpecHelpers
+
+ let(:temp_file) { 'dir/temp file' }
+ let(:data) { 'data' }
+
+ before do
+ js_template.stubs(:temp_file).returns(temp_file)
+ js_template.stubs(:data).returns(data)
+ end
+
+ it 'should save the file to disk and return the temp path' do
+ js_template.save.should == temp_file
+
+ File.read(temp_file).should == data
+ end
+ end
+
+ describe '#temp_file' do
+ subject { js_template.temp_file }
+
+ let(:filename) { "#{root_filename}.ext" }
+ let(:root_filename) { "dir/file.js" }
+
+ before do
+ js_template.stubs(:file).returns(filename)
+ end
+
+ it { should == File.join(Dir.pwd, '.tmp/sprockets', root_filename) }
+ end
+end
+
diff --git a/spec/flowerbox/server_spec.rb b/spec/flowerbox/server_spec.rb
new file mode 100644
index 0000000..5a73362
--- /dev/null
+++ b/spec/flowerbox/server_spec.rb
@@ -0,0 +1,100 @@
+require 'spec_helper'
+require 'socket'
+require 'thread'
+
+describe Flowerbox::Delivery::Server do
+ let(:server) { described_class.new(options) }
+ let(:options) { nil }
+
+ subject { server }
+
+ describe '#initialize' do
+ let(:options) { { :port => port, :interface => interface } }
+ let(:port) { 'port' }
+ let(:interface) { 'interface' }
+
+ its(:port) { should == port }
+ its(:interface) { should == interface }
+ end
+
+ describe '#start' do
+ let(:port) { 12345 }
+ let(:interface) { '127.0.0.1' }
+
+ before do
+ server.stubs(:port).returns(port)
+ server.stubs(:interface).returns(interface)
+ end
+
+ it 'should start a Rack server' do
+ server.start
+
+ TCPSocket.new(server.interface, server.port)
+ end
+ end
+
+ describe '#interface' do
+ subject { server.interface }
+
+ it { should == '0.0.0.0' }
+ end
+
+ describe '#port' do
+ let(:interface) { '127.0.0.1' }
+ let(:base) { 25000 }
+ let(:initial) { base + @offset }
+
+ before do
+ server.stubs(:interface).returns(interface)
+
+ @offset = 0
+ ok = true
+
+ begin
+ [ 0, 1 ].each do |index|
+ begin
+ TCPSocket.new(interface, base + @offset + index)
+ @offset += 1
+ ok = false
+ rescue Errno::ECONNREFUSED => e
+ end
+ end
+ end while !ok
+ end
+
+ subject { server.port }
+
+ context 'no running service' do
+ before do
+ Kernel.stubs(:rand).returns(@offset)
+ end
+
+ it { should == initial }
+ end
+
+ context 'running service' do
+ before do
+ @server = Thread.new do
+ TCPServer.new(interface, initial)
+ end
+
+ server.stubs(:random_port).returns(initial, initial + 1)
+
+ while true
+ begin
+ TCPSocket.new(interface, initial)
+ break
+ rescue Errno::ECONNREFUSED
+ end
+ end
+ end
+
+ it { should == initial + 1 }
+
+ after do
+ @server.kill
+ end
+ end
+ end
+end
+
diff --git a/spec/flowerbox/sprockets_handler_spec.rb b/spec/flowerbox/sprockets_handler_spec.rb
new file mode 100644
index 0000000..2635a7a
--- /dev/null
+++ b/spec/flowerbox/sprockets_handler_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe Flowerbox::Delivery::SprocketsHandler do
+ let(:sprockets_handler) { described_class.new(options) }
+ let(:options) { { :asset_paths => asset_paths } }
+ let(:asset_paths) { [ File.expand_path('asset path') ] }
+
+ describe '#add' do
+ let(:asset) { 'asset' }
+ let(:path) { 'path' }
+ let(:paths) { [ path ] }
+
+ let(:pathname_path) { 'pathname path' }
+
+ before do
+ sprockets_handler.expects(:paths_for).with(asset).returns(paths)
+ sprockets_handler.expects(:path_for_compiled_asset).with(path).returns(pathname_path)
+ end
+
+ it 'should add the asset to the list of ones to work with' do
+ sprockets_handler.add(asset)
+
+ sprockets_handler.files.should == [ pathname_path ]
+ end
+ end
+
+ describe '#paths_for' do
+ subject { sprockets_handler.paths_for(asset) }
+
+ let(:asset) { 'asset' }
+ let(:environment) { stub }
+ let(:bundled_asset) { stub(:to_a => [ processed_asset ]) }
+ let(:processed_asset) { stub(:pathname => path) }
+
+ let(:path) { 'path' }
+
+ before do
+ sprockets_handler.stubs(:environment).returns(environment)
+ environment.expects(:find_asset).with(asset).returns(bundled_asset)
+ end
+
+ it { should == [ path ] }
+ end
+
+ describe '#environment' do
+ subject { sprockets_handler.environment }
+
+ it { should be_a_kind_of(Sprockets::Environment) }
+ its(:paths) { should == asset_paths }
+ end
+end
+
diff --git a/spec/flowerbox/unique_asset_list_spec.rb b/spec/flowerbox/unique_asset_list_spec.rb
new file mode 100644
index 0000000..1def2c1
--- /dev/null
+++ b/spec/flowerbox/unique_asset_list_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe Flowerbox::Delivery::UniqueAssetList do
+ let(:unique_asset_list) { described_class.new }
+
+ describe "#add" do
+ let(:first) { Pathname.new('one') }
+ let(:second) { Pathname.new('one') }
+ let(:third) { Pathname.new('two') }
+
+ it 'should not add assets already added' do
+ unique_asset_list.add(first)
+ unique_asset_list.add([ second, third ])
+
+ unique_asset_list.should == [ first, third ]
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..fe4f8b5
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,7 @@
+require 'flowerbox-delivery'
+require 'mocha'
+require 'fakefs/spec_helpers'
+
+RSpec.configure do |c|
+ c.mock_with :mocha
+end