diff --git a/.gitignore b/.gitignore
index 5875e07..44a1b60 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,5 @@ test/version_tmp
tmp
.tmp
node_modules/
+.tmp-sprockets/
+
diff --git a/bin/flowerbox b/bin/flowerbox
index 1ffc87b..aa8ecf7 100755
--- a/bin/flowerbox
+++ b/bin/flowerbox
@@ -4,7 +4,9 @@ require 'flowerbox'
require 'thor'
class Flowerbox::CLI < Thor
- desc "test DIR FILES...", "Run the specs found in spec dir, loading spec_helper.rb for configuration details"
+ include Thor::Actions
+
+ desc "test", "Run the specs found in spec dir, loading spec_helper.rb for configuration details"
method_options :pwd => :string, :env_options => nil, :runners => :string, :runner => :string, :verbose_server => false
def test(dir = "spec/javascripts", *files)
Dir.chdir(pwd) do
@@ -22,6 +24,26 @@ class Flowerbox::CLI < Thor
end
end
+ desc "transplant DIR", "Convert an existing Jasmine gem-style project to Flowerbox"
+ def transplant(dir)
+ Flowerbox.transplant(dir)
+ end
+
+ desc "clean", "Clean the Sprockets cache"
+ def clean
+ FileUtils.rm_rf(Flowerbox::CACHE_DIR)
+ puts "Sprockets cache cleaned."
+ end
+
+ desc "plant", "Start a new Flowerbox project"
+ def plant(type, dir = nil)
+ env = Flowerbox::TestEnvironment.for(type)
+
+ self.class.source_root(Flowerbox.path.join(env.plant_source))
+
+ directory('.', dir || env.plant_target)
+ end
+
default_task :test
no_tasks do
diff --git a/flowerbox.gemspec b/flowerbox.gemspec
index aabfdec..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) }
@@ -23,4 +23,5 @@ Gem::Specification.new do |gem|
gem.add_dependency 'selenium-webdriver'
gem.add_dependency 'sinatra'
gem.add_dependency 'rainbow'
+ gem.add_dependency 'sprockets-vendor_gems'
end
diff --git a/lib/assets/javascripts/flowerbox.js.coffee b/lib/assets/javascripts/flowerbox.js.coffee
index a72b7a3..cfe100c 100644
--- a/lib/assets/javascripts/flowerbox.js.coffee
+++ b/lib/assets/javascripts/flowerbox.js.coffee
@@ -21,7 +21,7 @@ Flowerbox =
onQueueStateChange: ->
- queueRunner: ->
+ queueRunner: (failsafe = 5) ->
Flowerbox.onQueueStateChange("checking queue")
if Flowerbox.contactQueue.length > 0
Flowerbox.started = true
@@ -55,7 +55,10 @@ Flowerbox =
xhr.abort()
Flowerbox.onQueueStateChange("aborted #{url}, rerunning")
Flowerbox.contactQueue.unshift(info)
- Flowerbox.queueRunner()
+
+ if failsafe > 0
+ failsafe -= 1
+ Flowerbox.queueRunner(failsafe)
, Flowerbox.delay * 5
)
diff --git a/lib/assets/javascripts/flowerbox/cucumber.js.coffee b/lib/assets/javascripts/flowerbox/cucumber.js.coffee
index 4de13eb..688b14c 100644
--- a/lib/assets/javascripts/flowerbox/cucumber.js.coffee
+++ b/lib/assets/javascripts/flowerbox/cucumber.js.coffee
@@ -17,16 +17,22 @@ Flowerbox.World = (code = null) ->
Flowerbox.Matchers =
toEqual: (expected) ->
@message = "Expected #{@actual} #{@notMessage} equal #{expected}"
- if typeof @actual == 'object'
- for key, value of @actual
- return false if expected[key] != value
+ result = null
- for key, value of expected
- return false if @actual[key] != value
+ if @actual? && expected?
+ switch (typeof @actual)
+ when 'object'
+ result = true
+ for key, value of @actual
+ result = false if expected[key] != value
- true
- else
- @actual == expected
+ for key, value of expected
+ result = false if @actual[key] != value
+
+ if result == null
+ result = (@actual == expected)
+
+ result
Flowerbox.World ->
@assert = (what, message = 'failed') ->
diff --git a/lib/assets/javascripts/flowerbox/cucumber/reporter.js.coffee b/lib/assets/javascripts/flowerbox/cucumber/reporter.js.coffee
index 52bb46a..6966442 100644
--- a/lib/assets/javascripts/flowerbox/cucumber/reporter.js.coffee
+++ b/lib/assets/javascripts/flowerbox/cucumber/reporter.js.coffee
@@ -39,7 +39,7 @@ class Flowerbox.Cucumber.Reporter
when 'StepResult'
stepResult = event.getPayloadItem('stepResult')
- file = Flowerbox.Step.matchFile(@step.getName()) || "unknown:0"
+ file = Flowerbox.Step.matchFile(@step.getName()) || "#{Flowerbox.UNKNOWN}:0"
result = new Flowerbox.Result(step_type: this.type(), source: 'cucumber', original_name: @step.getName(), name: this.nameParts(), file: file)
@@ -49,6 +49,8 @@ class Flowerbox.Cucumber.Reporter
result.status = Flowerbox.Result.PENDING
else if stepResult.isUndefined()
result.status = Flowerbox.Result.UNDEFINED
+ result.hasDataTable = @step.hasDataTable()
+ result.hasDocString = @step.hasDocString()
else if stepResult.isFailed()
result.status = Flowerbox.Result.FAILURE
diff --git a/lib/assets/javascripts/flowerbox/jasmine/reporter.js.coffee b/lib/assets/javascripts/flowerbox/jasmine/reporter.js.coffee
index df098e8..3bb6a9d 100644
--- a/lib/assets/javascripts/flowerbox/jasmine/reporter.js.coffee
+++ b/lib/assets/javascripts/flowerbox/jasmine/reporter.js.coffee
@@ -4,7 +4,7 @@ class jasmine.FlowerboxReporter
status: Flowerbox.Result.SUCCESS
source: 'jasmine'
name: spec.getSpecSplitName()
- file: 'unknown:0'
+ file: "#{Flowerbox.UNKNOWN}:0"
for key, value of overrides
data[key] = value
@@ -38,6 +38,7 @@ class jasmine.FlowerboxReporter
for item in spec.results().items_
if !item.passed_
result.status = Flowerbox.Result.FAILURE
+
stack = item.trace.stack || []
if stack.constructor != Array
stack = stack.split("\n")
diff --git a/lib/assets/javascripts/flowerbox/jasmine/selenium.js.coffee b/lib/assets/javascripts/flowerbox/jasmine/selenium.js.coffee
index 473db0f..fc6db02 100644
--- a/lib/assets/javascripts/flowerbox/jasmine/selenium.js.coffee
+++ b/lib/assets/javascripts/flowerbox/jasmine/selenium.js.coffee
@@ -5,11 +5,13 @@ jasmine.Spec.beforeAddMatcherResult().push ->
lol.wut
catch e
if e.stack
- file = switch Flowerbox.environment
+ file = switch Flowerbox.environment.toLowerCase()
when 'firefox'
e.stack.split("\n")[3]
when 'chrome'
e.stack.split("\n")[4]
+ else
+ "#{Flowerbox.UNKNOWN}:0"
- @trace = { stack: [ file ] }
+ @trace = { stack: [ @message, file ] }
diff --git a/lib/flowerbox.rb b/lib/flowerbox.rb
index fdc870a..8c9caa2 100644
--- a/lib/flowerbox.rb
+++ b/lib/flowerbox.rb
@@ -1,44 +1,27 @@
require "flowerbox/version"
-require 'flowerbox-delivery'
require 'rainbow'
module Flowerbox
- module CoreExt
- autoload :Module, 'flowerbox/core_ext/module'
+ require 'flowerbox/core_ext/module'
+
+ require 'flowerbox/runner'
+
+ require 'flowerbox/run/base'
+ require 'flowerbox/run/test'
+ require 'flowerbox/run/debug'
+
+ require 'flowerbox/test_environment'
+ require 'flowerbox/result'
+ require 'flowerbox/result_set'
+ require 'flowerbox/gathered_result'
+
+ require 'flowerbox/reporter'
+
+ if defined?(Rails::Engine)
+ require 'flowerbox/rails/engine'
end
- autoload :Runner, 'flowerbox/runner'
- autoload :Task, 'flowerbox/task'
-
- module Run
- autoload :Base, 'flowerbox/run/base'
- autoload :Test, 'flowerbox/run/test'
- autoload :Debug, 'flowerbox/run/debug'
- end
-
- module Runner
- autoload :Node, 'flowerbox/runner/node'
- autoload :Selenium, 'flowerbox/runner/selenium'
- autoload :Firefox, 'flowerbox/runner/firefox'
- autoload :Chrome, 'flowerbox/runner/chrome'
- autoload :Base, 'flowerbox/runner/base'
- end
-
- autoload :TestEnvironment, 'flowerbox/test_environment'
-
- module TestEnvironment
- autoload :Base, 'flowerbox/test_environment/base'
- autoload :Jasmine, 'flowerbox/test_environment/jasmine'
- autoload :Cucumber, 'flowerbox/test_environment/cucumber'
- end
-
- autoload :Rack, 'flowerbox/rack'
-
- autoload :ResultSet, 'flowerbox/result_set'
- autoload :GatheredResult, 'flowerbox/gathered_result'
- autoload :Result, 'flowerbox/result'
-
- autoload :Reporter, 'flowerbox/reporter'
+ CACHE_DIR = '.tmp-sprockets'
class << self
attr_writer :reporters
@@ -84,7 +67,7 @@ module Flowerbox
Pathname(File.expand_path('../..', __FILE__))
end
- attr_accessor :test_environment, :runner_environment, :bare_coffeescript
+ attr_accessor :test_environment, :runner_environment, :bare_coffeescript, :server
def configure
yield self
@@ -126,6 +109,12 @@ module Flowerbox
@browsers = {}
end
+
+ def transplant(dir)
+ Flowerbox::TestEnvironment.transplantable_environments.each do |env|
+ break if env.transplant(dir)
+ end
+ end
end
end
diff --git a/lib/flowerbox/delivery/tilt/feature_template.rb b/lib/flowerbox/delivery/tilt/feature_template.rb
deleted file mode 100644
index 12cf32f..0000000
--- a/lib/flowerbox/delivery/tilt/feature_template.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'tilt'
-
-module Flowerbox::Delivery::Tilt
- class FeatureTemplate < Tilt::Template
- self.default_mime_type = 'application/javascript'
-
- def prepare; end
-
- def evaluate(scope, locals, &block)
- <<-JS
-Flowerbox.Cucumber.Features = Flowerbox.Cucumber.Features || [];
-
-Flowerbox.Cucumber.Features.push("#{escaped_data}");
-JS
- end
-
- def escaped_data
- data.gsub("\n", "\\n").gsub('"', '\\"')
- end
- end
-end
-
diff --git a/lib/flowerbox/rack.rb b/lib/flowerbox/rack.rb
index d0dcf95..060bf80 100644
--- a/lib/flowerbox/rack.rb
+++ b/lib/flowerbox/rack.rb
@@ -66,6 +66,8 @@ module Flowerbox
get %r{^/__F__/(.*)$} do |file|
asset = sprockets.asset_for(file, :bundle => false)
+ halt(404) if !asset
+
content_type asset.content_type
asset.body
end
@@ -75,12 +77,6 @@ module Flowerbox
runner.template
end
-
- class << self
- private
- def setup_protetion(builder)
- end
- end
end
end
diff --git a/lib/flowerbox/rails/engine.rb b/lib/flowerbox/rails/engine.rb
new file mode 100644
index 0000000..37d4c11
--- /dev/null
+++ b/lib/flowerbox/rails/engine.rb
@@ -0,0 +1,15 @@
+require 'rails/engine'
+
+module Flowerbox
+ module Rails
+ class Engine < ::Rails::Engine
+ rake_tasks do
+ require 'flowerbox/task'
+
+ Flowerbox::Task.create("flowerbox:spec", :dir => "spec/javascripts")
+ Flowerbox::Task.create("flowerbox:integration", :dir => "js-features")
+ end
+ end
+ end
+end
+
diff --git a/lib/flowerbox/reporter.rb b/lib/flowerbox/reporter.rb
index f6a00c8..3c3a49d 100644
--- a/lib/flowerbox/reporter.rb
+++ b/lib/flowerbox/reporter.rb
@@ -2,11 +2,12 @@ module Flowerbox
module Reporter
extend Flowerbox::CoreExt::Module
- autoload :Base, 'flowerbox/reporter/base'
- autoload :ConsoleBase, 'flowerbox/reporter/console_base'
- autoload :Verbose, 'flowerbox/reporter/verbose'
- autoload :Progress, 'flowerbox/reporter/progress'
- autoload :FileDisplay, 'flowerbox/reporter/file_display'
+ require 'flowerbox/reporter/file_display'
+
+ require 'flowerbox/reporter/base'
+ require 'flowerbox/reporter/console_base'
+ require 'flowerbox/reporter/verbose'
+ require 'flowerbox/reporter/progress'
end
end
diff --git a/lib/flowerbox/result.rb b/lib/flowerbox/result.rb
index cd9588d..4437c4b 100644
--- a/lib/flowerbox/result.rb
+++ b/lib/flowerbox/result.rb
@@ -10,12 +10,12 @@ module Flowerbox
extend Flowerbox::CoreExt::Module
end
- autoload :Base, 'flowerbox/result/base'
- autoload :Exception, 'flowerbox/result/exception'
- autoload :Failure, 'flowerbox/result/failure'
- autoload :Pending, 'flowerbox/result/pending'
- autoload :FailureMessage, 'flowerbox/result/failure_message'
- autoload :FileInfo, 'flowerbox/result/file_info'
+ require 'flowerbox/result/base'
+ require 'flowerbox/result/exception'
+ require 'flowerbox/result/failure'
+ require 'flowerbox/result/pending'
+ require 'flowerbox/result/failure_message'
+ require 'flowerbox/result/file_info'
class << self
def for(test_env, type)
diff --git a/lib/flowerbox/result/base.rb b/lib/flowerbox/result/base.rb
index 1eaf7e9..b2653f4 100644
--- a/lib/flowerbox/result/base.rb
+++ b/lib/flowerbox/result/base.rb
@@ -3,6 +3,7 @@ require 'forwardable'
module Flowerbox
module Result
class Base
+ require 'flowerbox/result/file_info'
include Flowerbox::Result::FileInfo
extend Forwardable
diff --git a/lib/flowerbox/result/failure_message.rb b/lib/flowerbox/result/failure_message.rb
index 80b19bc..6dc5c2a 100644
--- a/lib/flowerbox/result/failure_message.rb
+++ b/lib/flowerbox/result/failure_message.rb
@@ -31,7 +31,7 @@ module Flowerbox::Result
end
def stack
- @data['stack']
+ @data['stack'] || []
end
def first_local_stack
diff --git a/lib/flowerbox/result/file_info.rb b/lib/flowerbox/result/file_info.rb
index d41aafd..c988fee 100644
--- a/lib/flowerbox/result/file_info.rb
+++ b/lib/flowerbox/result/file_info.rb
@@ -1,28 +1,38 @@
module Flowerbox::Result::FileInfo
+ UNKNOWN = '__unknown__'
+
def translated_file
- @translated_file ||= filename
+ return @translated_file if @translated_file
+
+ if filename == UNKNOWN
+ @translated_file = UNKNOWN
+ else
+ @translated_file = Flowerbox.test_environment.actual_path_for(filename)
+ end
+
+ @translated_file
end
def file_translated?
- translated_file != filename
+ translated_file[%r{\.coffee$}]
end
def filename
- file.to_s.split(":").first || 'unknown'
+ file.to_s.split(":").first || UNKNOWN
end
def line_number
return @line_number if @line_number
@line_number = file.to_s.split(":")[1]
- @line_number = "~#{@line_number}" if file_translated?
+ @line_number = "~#{(@line_number.to_i * 0.67).to_i}" if file_translated?
@line_number ||= "0"
end
alias :line :line_number
def translated_file_and_line
- "#{translated_file.gsub(%r{^/}, '')}:#{line_number}"
+ "#{translated_file.gsub(%r{^#{Dir.pwd}/}, '')}:#{line_number}"
end
def system_files
diff --git a/lib/flowerbox/result_set.rb b/lib/flowerbox/result_set.rb
index f403754..a933b1e 100644
--- a/lib/flowerbox/result_set.rb
+++ b/lib/flowerbox/result_set.rb
@@ -41,7 +41,13 @@ module Flowerbox
end
def exitstatus
- results.any?(&:failure?) ? 1 : 0
+ if results.any?(&:failure?)
+ 1
+ elsif results.any?(&:pending?)
+ 2
+ else
+ 0
+ end
end
def print(data = {})
diff --git a/lib/flowerbox/run/base.rb b/lib/flowerbox/run/base.rb
index 189fde5..fe2e134 100644
--- a/lib/flowerbox/run/base.rb
+++ b/lib/flowerbox/run/base.rb
@@ -33,7 +33,9 @@ module Flowerbox::Run
end
def sprockets
- Flowerbox::Delivery::SprocketsHandler.new(
+ require 'flowerbox/sprockets_handler'
+
+ Flowerbox::SprocketsHandler.new(
:asset_paths => [
Flowerbox.path.join("lib/assets/javascripts"),
Flowerbox.path.join("vendor/assets/javascripts"),
diff --git a/lib/flowerbox/run/test.rb b/lib/flowerbox/run/test.rb
index 3c9954b..21bdd8f 100644
--- a/lib/flowerbox/run/test.rb
+++ b/lib/flowerbox/run/test.rb
@@ -1,5 +1,7 @@
module Flowerbox::Run
class Test < Base
+ require 'flowerbox/runner/base'
+
def execute
prep!
diff --git a/lib/flowerbox/runner.rb b/lib/flowerbox/runner.rb
index af69eb0..5506272 100644
--- a/lib/flowerbox/runner.rb
+++ b/lib/flowerbox/runner.rb
@@ -1,6 +1,12 @@
module Flowerbox
module Runner
extend Flowerbox::CoreExt::Module
+
+ def self.find_constant(string)
+ require "flowerbox/runner/#{string}"
+
+ super
+ end
end
end
diff --git a/lib/flowerbox/runner/base.rb b/lib/flowerbox/runner/base.rb
index 43365c5..4ccedfc 100644
--- a/lib/flowerbox/runner/base.rb
+++ b/lib/flowerbox/runner/base.rb
@@ -1,3 +1,5 @@
+require 'thread'
+
module Flowerbox
module Runner
class Base
@@ -9,6 +11,10 @@ module Flowerbox
class RunnerDiedError < StandardError ; end
+ def self.mutex
+ @mutex ||= Mutex.new
+ end
+
def initialize
@results = ResultSet.new
@started = false
@@ -27,6 +33,9 @@ module Flowerbox
if !finished?
puts "Something died hard. Here are the tests that did get run before Flowerbox died.".foreground(:red)
puts tests.flatten.join("\n").foreground(:red)
+ server.stop
+ Flowerbox.server = nil
+
raise RunnerDiedError.new
end
end
@@ -40,18 +49,20 @@ module Flowerbox
end
def run(*args)
- setup(*args)
+ self.class.mutex.synchronize do
+ setup(*args)
- @count = 0
- @timer_running = true
+ @count = 0
+ @timer_running = true
- puts "Flowerbox running your #{Flowerbox.test_environment.name} tests on #{console_name}..."
+ puts "Flowerbox running your #{Flowerbox.test_environment.name} tests on #{console_name}..."
- server.start
+ server.start
- yield
+ yield
- server.stop
+ server.stop
+ end
@results
end
@@ -60,8 +71,7 @@ module Flowerbox
true
end
- def configure
- end
+ def configure ; end
def pause_timer
@timer_running = false
@@ -100,17 +110,20 @@ module Flowerbox
end
def server
- return @server if @server
+ require 'flowerbox/rack'
+
+ Flowerbox::Rack.runner = self
+ Flowerbox::Rack.sprockets = @sprockets
+
+ return Flowerbox.server if Flowerbox.server
+
+ require 'flowerbox/server'
server_options = { :app => Flowerbox::Rack }
server_options[:logging] = true if options[:verbose_server]
server_options[:port] = Flowerbox.port
- @server = Flowerbox::Delivery::Server.new(server_options)
- Flowerbox::Rack.runner = self
- Flowerbox::Rack.sprockets = @sprockets
-
- @server
+ Flowerbox.server = Flowerbox::Server.new(server_options)
end
def log(message)
diff --git a/lib/flowerbox/runner/chrome.rb b/lib/flowerbox/runner/chrome.rb
index e681f64..c9483b4 100644
--- a/lib/flowerbox/runner/chrome.rb
+++ b/lib/flowerbox/runner/chrome.rb
@@ -1,4 +1,7 @@
+require 'flowerbox/runner/selenium'
+
class Flowerbox::Runner::Chrome < Flowerbox::Runner::Selenium
+
def name
"Chrome"
end
diff --git a/lib/flowerbox/runner/firefox.rb b/lib/flowerbox/runner/firefox.rb
index a86cf3f..27a49d6 100644
--- a/lib/flowerbox/runner/firefox.rb
+++ b/lib/flowerbox/runner/firefox.rb
@@ -1,3 +1,5 @@
+require 'flowerbox/runner/selenium'
+
class Flowerbox::Runner::Firefox < Flowerbox::Runner::Selenium
def name
"Firefox"
@@ -8,7 +10,7 @@ class Flowerbox::Runner::Firefox < Flowerbox::Runner::Selenium
end
def browser
- @browser ||= ::Selenium::WebDriver.for(:firefox)
+ Flowerbox.browsers[:firefox] ||= ::Selenium::WebDriver.for(:firefox)
end
end
diff --git a/lib/flowerbox/runner/node.rb b/lib/flowerbox/runner/node.rb
index 99464d0..f0fc59b 100644
--- a/lib/flowerbox/runner/node.rb
+++ b/lib/flowerbox/runner/node.rb
@@ -1,6 +1,6 @@
require 'tempfile'
-require 'flowerbox/delivery/server'
require 'json'
+require 'flowerbox/runner/base'
module Flowerbox
module Runner
@@ -22,7 +22,7 @@ module Flowerbox
def configured?
File.directory?(File.join(Dir.pwd, 'node_modules/jsdom')) &&
- File.directory?(File.join(Dir.pwd, 'node_modules/XMLHttpRequest'))
+ File.directory?(File.join(Dir.pwd, 'node_modules/xmlhttprequest'))
end
def cleanup ; end
@@ -95,6 +95,7 @@ jsdom.env(
if (!gotFlowerbox && context.Flowerbox) {
context.Flowerbox.baseUrl = "http://localhost:#{server.port}/";
context.Flowerbox.environment = 'node';
+ context.Flowerbox.UNKNOWN = '#{Flowerbox::Result::FileInfo::UNKNOWN}';
gotFlowerbox = true;
}
diff --git a/lib/flowerbox/runner/selenium.rb b/lib/flowerbox/runner/selenium.rb
index 6d67203..770389c 100644
--- a/lib/flowerbox/runner/selenium.rb
+++ b/lib/flowerbox/runner/selenium.rb
@@ -1,4 +1,5 @@
require 'selenium-webdriver'
+require 'flowerbox/runner/base'
module Flowerbox
module Runner
@@ -55,6 +56,7 @@ console.log = function(msg) {
Flowerbox.onQueueStateChange = function(msg) {
//document.getElementById('queue').innerHTML = document.getElementById('queue').innerHTML + "\\n" + msg;
};
+ Flowerbox.UNKNOWN = '#{Flowerbox::Result::FileInfo::UNKNOWN}';
var context = this;
@@ -69,7 +71,7 @@ HTML
end
def template_files
- sprockets.files.collect { |file| %{} }
+ sprockets.files.collect { |file| %{} }
end
end
end
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..a0bb219
--- /dev/null
+++ b/lib/flowerbox/sprockets_handler.rb
@@ -0,0 +1,71 @@
+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
+
+ require 'flowerbox/unique_asset_list'
+
+ @files = Flowerbox::UniqueAssetList.new(self)
+ 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(Flowerbox::CACHE_DIR)
+
+ 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
+
+ def logical_path_for(asset)
+ asset_path = asset.pathname.to_s
+
+ environment.paths.each do |path|
+ if asset_path[%r{^#{path}}]
+ return asset_path.gsub(%r{^#{path}/}, '')
+ end
+ end
+
+ raise StandardError.new("Could not find logical path for #{asset_path}")
+ end
+ end
+end
+
diff --git a/lib/flowerbox/test_environment.rb b/lib/flowerbox/test_environment.rb
index 1f56e88..5362178 100644
--- a/lib/flowerbox/test_environment.rb
+++ b/lib/flowerbox/test_environment.rb
@@ -2,10 +2,18 @@ module Flowerbox
module TestEnvironment
extend Flowerbox::CoreExt::Module
- class << self
- def for(env)
- find_constant(env).new
+ def self.for(env)
+ require "flowerbox/test_environment/#{env}"
+
+ find_constant(env).new
+ end
+
+ def self.transplantable_environments
+ Dir[File.expand_path('../test_environment/*.rb', __FILE__)].each do |file|
+ require file
end
+
+ constants.collect { |k| const_get(k) }.find_all(&:transplantable?)
end
end
end
diff --git a/lib/flowerbox/test_environment/base.rb b/lib/flowerbox/test_environment/base.rb
index bc8cc3a..b361d14 100644
--- a/lib/flowerbox/test_environment/base.rb
+++ b/lib/flowerbox/test_environment/base.rb
@@ -13,6 +13,10 @@ module Flowerbox
@reporters ||= []
end
+ def self.transplantable?
+ respond_to?(:transplant)
+ end
+
def set_additional_options(opts = nil)
@options = {}
@@ -41,6 +45,10 @@ module Flowerbox
def start
runner.spec_files.each { |file| @sprockets.add(file) }
end
+
+ def actual_path_for(file)
+ @sprockets.asset_for(file, :bundle => false).pathname.to_s
+ end
end
end
end
diff --git a/lib/flowerbox/test_environment/cucumber.rb b/lib/flowerbox/test_environment/cucumber.rb
index c0ba320..b614882 100644
--- a/lib/flowerbox/test_environment/cucumber.rb
+++ b/lib/flowerbox/test_environment/cucumber.rb
@@ -1,3 +1,5 @@
+require 'flowerbox/test_environment/base'
+
module Flowerbox
module TestEnvironment
class Cucumber < Base
@@ -12,7 +14,9 @@ module Flowerbox
def inject_into(sprockets)
super
- @sprockets.register_engine('.feature', Flowerbox::Delivery::Tilt::FeatureTemplate)
+ require 'flowerbox/tilt/feature_template'
+
+ @sprockets.register_engine('.feature', Flowerbox::Tilt::FeatureTemplate)
end
def global_system_files
@@ -56,20 +60,34 @@ JS
" (\d+) "
end
+ messages = []
+
+ if result['hasDataTable']
+ args << "table"
+ messages << "table is a Cucumber AST data table"
+ end
+
+ if result['hasDocString']
+ args << "string"
+ messages << "string is a doc string"
+ end
+
args_string = args.join(', ')
+ output = []
+
if primarily_coffeescript?
- <<-COFFEE
-Flowerbox.#{result.step_type} /^#{matcher}$/, #{"(#{args_string}) " if !args_string.empty?}->
- @pending() # add your code here
-COFFEE
+ output << %{Flowerbox.#{result.step_type} /^#{matcher}$/, #{"(#{args_string}) " if !args_string.empty?}->}
+ output += messages.collect { |msg| " # #{msg}" }
+ output << %{ @pending() # add your code here}
else
- <<-JS
-Flowerbox.#{result.step_type}(/^#{matcher}$/, function(#{args_string}) {
- this.pending(); // add your code here
-});
-JS
+ output << "Flowerbox.#{result.step_type}(/^#{matcher}$/, function(#{args_string}) {"
+ output += messages.collect { |msg| " // #{msg}" }
+ output << %{ this.pending(); // add your code here}
+ output << "});"
end
+
+ output.collect { |line| "#{line}\n" }.join
end
def primarily_coffeescript?
@@ -80,11 +98,15 @@ JS
coffee_count > js_count
end
+
+ def plant_source
+ "skel/cucumber"
+ end
+
+ def plant_target
+ "js-features"
+ end
end
end
end
-module Flowerbox::Delivery::Tilt
- autoload :FeatureTemplate, 'flowerbox/delivery/tilt/feature_template'
-end
-
diff --git a/lib/flowerbox/test_environment/jasmine.rb b/lib/flowerbox/test_environment/jasmine.rb
index 23b987e..e171e12 100644
--- a/lib/flowerbox/test_environment/jasmine.rb
+++ b/lib/flowerbox/test_environment/jasmine.rb
@@ -1,8 +1,56 @@
+require 'flowerbox/test_environment/base'
require 'jasmine-core'
+require 'yaml'
module Flowerbox
module TestEnvironment
class Jasmine < Base
+ def self.transplant(dir)
+ if File.file?(jasmine_yml = File.join(dir, 'support/jasmine.yml'))
+ puts "Transplanting #{jasmine_yml} into Flowerbox..."
+
+ config = [
+ %{f.test_with :jasmine},
+ %{f.run_with :firefox},
+ %{f.report_with :verbose}
+ ]
+
+ YAML.load_file(jasmine_yml).each do |key, value|
+ case key
+ when 'src_dir'
+ [ value ].flatten.each do |dir|
+ config << %{f.asset_paths << "#{dir}"}
+ end
+ when 'src_files'
+ [ value ].flatten.each do |file|
+ config << %{f.additional_files << "#{file}"}
+ end
+ when 'helpers'
+ [ value ].flatten.each do |pattern|
+ Dir[File.join(dir, pattern)].each do |file|
+ config << %{f.additional_files << "#{file.gsub("#{dir}/", '')}"}
+ end
+ end
+ when 'spec_files'
+ [ value ].flatten.each do |pattern|
+ config << %{f.spec_patterns << "#{pattern}"}
+ end
+ end
+ end
+
+ File.open(target = File.join(dir, 'spec_helper.rb'), 'wb') do |fh|
+ fh.puts "Flowerbox.configure do |f|"
+ config.each do |line|
+ fh.puts " #{line}"
+ end
+ fh.puts "end"
+ end
+
+ puts "#{target} created. Run your tests with:"
+ puts " flowerbox test #{dir}"
+ end
+ end
+
def inject_into(sprockets)
sprockets.append_path(::Jasmine::Core.path)
diff --git a/lib/flowerbox/tilt/feature_template.rb b/lib/flowerbox/tilt/feature_template.rb
new file mode 100644
index 0000000..b6586bf
--- /dev/null
+++ b/lib/flowerbox/tilt/feature_template.rb
@@ -0,0 +1,24 @@
+require 'tilt'
+
+module Flowerbox
+ module Tilt
+ class FeatureTemplate < ::Tilt::Template
+ self.default_mime_type = 'application/javascript'
+
+ def prepare; end
+
+ def evaluate(scope, locals, &block)
+ <<-JS
+Flowerbox.Cucumber.Features = Flowerbox.Cucumber.Features || [];
+
+Flowerbox.Cucumber.Features.push("#{escaped_data}");
+JS
+ end
+
+ def escaped_data
+ data.gsub("\n", "\\n").gsub('"', '\\"')
+ end
+ 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..03353a7
--- /dev/null
+++ b/lib/flowerbox/unique_asset_list.rb
@@ -0,0 +1,25 @@
+module Flowerbox
+ class UniqueAssetList < ::Array
+ attr_reader :sprockets
+
+ def initialize(sprockets)
+ super([])
+
+ @sprockets = sprockets
+ end
+
+ def add(files)
+ [ files ].flatten.each { |file| self << file if !include?(file) }
+ end
+
+ def to_json
+ collect { |file| sprockets.logical_path_for(file) }
+ end
+
+ private
+ def include?(file)
+ any? { |other_file| other_file.pathname.to_s == file.pathname.to_s }
+ end
+ end
+end
+
diff --git a/skel/cucumber/features/my_first_feature.feature b/skel/cucumber/features/my_first_feature.feature
new file mode 100644
index 0000000..1dd0d81
--- /dev/null
+++ b/skel/cucumber/features/my_first_feature.feature
@@ -0,0 +1,7 @@
+Feature: My First Feature
+ Scenario: Do Something
+ Given I have a flowerbox
+ When I plant a "cucumber" seed
+ Then I should get the following when I pick:
+ | cucumber |
+
diff --git a/skel/cucumber/spec_helper.rb b/skel/cucumber/spec_helper.rb
new file mode 100644
index 0000000..ebf8442
--- /dev/null
+++ b/skel/cucumber/spec_helper.rb
@@ -0,0 +1,10 @@
+Flowerbox.configure do |f|
+ f.test_with :cucumber
+ f.run_with :firefox
+
+ f.additional_files << "support/env.js.coffee"
+ f.spec_patterns << "features/**/*.feature"
+
+ f.test_environment.prefer_step_language :coffeescript
+end
+
diff --git a/skel/cucumber/step_definitions/given/i_have_a_flowerbox.js.coffee b/skel/cucumber/step_definitions/given/i_have_a_flowerbox.js.coffee
new file mode 100644
index 0000000..2221119
--- /dev/null
+++ b/skel/cucumber/step_definitions/given/i_have_a_flowerbox.js.coffee
@@ -0,0 +1,8 @@
+Flowerbox.Given /^I have a flowerbox$/, ->
+ @flowerbox =
+ plantSeed: (type) ->
+ @types ||= []
+ @types.push(type)
+ pick: ->
+ @types
+
diff --git a/skel/cucumber/step_definitions/then/i_should_get_the_following_when_i_pick.js.coffee b/skel/cucumber/step_definitions/then/i_should_get_the_following_when_i_pick.js.coffee
new file mode 100644
index 0000000..b6d7f1b
--- /dev/null
+++ b/skel/cucumber/step_definitions/then/i_should_get_the_following_when_i_pick.js.coffee
@@ -0,0 +1,5 @@
+Flowerbox.Then /^I should get the following when I pick:$/, (table) ->
+ # table is a Cucumber AST data table
+ data = (row[0] for row in table.raw())
+ @expect(@flowerbox.pick()).toEqual(data)
+
diff --git a/skel/cucumber/step_definitions/when/i_plant_a_seed.js.coffee b/skel/cucumber/step_definitions/when/i_plant_a_seed.js.coffee
new file mode 100644
index 0000000..9d98279
--- /dev/null
+++ b/skel/cucumber/step_definitions/when/i_plant_a_seed.js.coffee
@@ -0,0 +1,3 @@
+Flowerbox.When /^I plant a "([^"]+)" seed$/, (type) ->
+ @flowerbox.plantSeed(type)
+
diff --git a/skel/cucumber/support/env.js.coffee b/skel/cucumber/support/env.js.coffee
new file mode 100644
index 0000000..624ee96
--- /dev/null
+++ b/skel/cucumber/support/env.js.coffee
@@ -0,0 +1 @@
+#= require_tree ../step_definitions
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