Compare commits
No commits in common. "gh-pages" and "master" have entirely different histories.
24
.gitignore
vendored
24
.gitignore
vendored
@ -1,6 +1,22 @@
|
|||||||
.sass-cache/
|
*.gem
|
||||||
|
*.rbc
|
||||||
|
.bundle
|
||||||
|
.config
|
||||||
|
.yardoc
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
|
InstalledFiles
|
||||||
|
_yardoc
|
||||||
|
coverage
|
||||||
|
doc/
|
||||||
|
lib/bundler/man
|
||||||
|
pkg
|
||||||
|
rdoc
|
||||||
|
spec/reports
|
||||||
|
test/tmp
|
||||||
|
test/version_tmp
|
||||||
|
tmp
|
||||||
|
.tmp
|
||||||
|
node_modules/
|
||||||
|
.tmp-sprockets/
|
||||||
_site/
|
_site/
|
||||||
coverage/
|
.sass-cache/
|
||||||
tmp/
|
|
||||||
pkg/
|
|
||||||
|
15
Gemfile
15
Gemfile
@ -1,10 +1,7 @@
|
|||||||
source :rubygems
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
# Specify your gem's dependencies in flowerbox.gemspec
|
||||||
|
gemspec
|
||||||
|
|
||||||
|
gem 'guard-flowerbox', :path => '../guard-flowerbox'
|
||||||
|
|
||||||
gem 'redcarpet', '~> 1'
|
|
||||||
gem 'jekyll'
|
|
||||||
gem 'compass'
|
|
||||||
gem 'foreman'
|
|
||||||
gem 'guard'
|
|
||||||
gem 'haml'
|
|
||||||
gem 'guard-haml'
|
|
||||||
gem 'guard-compass'
|
|
||||||
|
17
Guardfile
17
Guardfile
@ -1,11 +1,18 @@
|
|||||||
# A sample Guardfile
|
# A sample Guardfile
|
||||||
# More info at https://github.com/guard/guard#readme
|
# More info at https://github.com/guard/guard#readme
|
||||||
|
|
||||||
|
group :rspec do
|
||||||
guard 'compass', :configuration_file => 'config/compass.rb' do
|
guard 'rspec', :version => 2 do
|
||||||
watch(%r{^sass/(.*)\.s[ac]ss$})
|
watch(%r{^spec/.+_spec\.rb$})
|
||||||
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
||||||
|
watch('spec/spec_helper.rb') { "spec" }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
guard 'haml', :input => '_layouts/haml', :output => '_layouts' do
|
group :wip do
|
||||||
watch(/^.+(\.html\.haml)/)
|
guard 'cucumber', :cli => '-p wip' do
|
||||||
|
watch(%r{^features/.+\.feature$})
|
||||||
|
watch(%r{^features/support/.+$}) { 'features' }
|
||||||
|
watch(%r{^features/step_definitions/.*$}) { 'features' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
22
LICENSE
Normal file
22
LICENSE
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2012 John Bintz
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
29
README.md
Normal file
29
README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Flowerbox
|
||||||
|
|
||||||
|
TODO: Write a gem description
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Add this line to your application's Gemfile:
|
||||||
|
|
||||||
|
gem 'flowerbox'
|
||||||
|
|
||||||
|
And then execute:
|
||||||
|
|
||||||
|
$ bundle
|
||||||
|
|
||||||
|
Or install it yourself as:
|
||||||
|
|
||||||
|
$ gem install flowerbox
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
TODO: Write usage instructions here
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Fork it
|
||||||
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||||
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
||||||
|
4. Push to the branch (`git push origin my-new-feature`)
|
||||||
|
5. Create new Pull Request
|
@ -1,4 +0,0 @@
|
|||||||
markdown: redcarpet
|
|
||||||
auto: true
|
|
||||||
pygments: true
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Flowerbox</title>
|
|
||||||
<link href='http://fonts.googleapis.com/css?family=Pontano+Sans' rel='stylesheet' type='text/css' />
|
|
||||||
<link href='http://fonts.googleapis.com/css?family=Gorditas' rel='stylesheet' type='text/css' />
|
|
||||||
<link href='stylesheets/screen.css' rel='stylesheet' type='text/css' />
|
|
||||||
<link href='stylesheets/syntax.css' rel='stylesheet' type='text/css' />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id='container'>
|
|
||||||
{{ content }}
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,11 +0,0 @@
|
|||||||
%html
|
|
||||||
%head
|
|
||||||
%title Flowerbox
|
|
||||||
%link{:href => 'http://fonts.googleapis.com/css?family=Pontano+Sans', :rel => :stylesheet, :type => 'text/css'}/
|
|
||||||
%link{:href => 'http://fonts.googleapis.com/css?family=Gorditas', :rel => :stylesheet, :type => 'text/css'}/
|
|
||||||
%link{:rel => :stylesheet, :href => 'stylesheets/screen.css', :type => 'text/css'}/
|
|
||||||
%link{:rel => :stylesheet, :href => 'stylesheets/syntax.css', :type => 'text/css'}/
|
|
||||||
%body
|
|
||||||
#container
|
|
||||||
{{ content }}
|
|
||||||
|
|
95
bin/flowerbox
Executable file
95
bin/flowerbox
Executable file
@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
if !ENV['BUNDLE_GEMFILE'] && File.file?('Gemfile')
|
||||||
|
require 'bundler/setup'
|
||||||
|
|
||||||
|
$: << File.expand_path('../../lib', __FILE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
require 'flowerbox'
|
||||||
|
require 'thor'
|
||||||
|
|
||||||
|
class Flowerbox::CLI < Thor
|
||||||
|
include Thor::Actions
|
||||||
|
|
||||||
|
default_task :help
|
||||||
|
|
||||||
|
def help(*args)
|
||||||
|
if !args.first
|
||||||
|
puts "Flowerbox is a multi-environment, multi-runner JavaScript testing tool."
|
||||||
|
puts "It supports Jasmine and Cucumber.js, and it runs tests on Node.js and Selenium-driven browsers."
|
||||||
|
puts
|
||||||
|
end
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "test [DIR]", "Run the specs found in spec dir, loading spec_helper.rb for configuration details"
|
||||||
|
method_options :pwd => :string, [:quiet, '-q'] => false, :env_options => nil, :runners => :string, :runner => :string, :verbose_server => false
|
||||||
|
def test(dir = "spec/javascripts", *files)
|
||||||
|
Dir.chdir(pwd) do
|
||||||
|
result = Flowerbox.run(dir, options.dup.merge(:files => files))
|
||||||
|
Flowerbox.cleanup!
|
||||||
|
exit result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "debug DIR", "Start the Flowerbox server to help debug loading issues."
|
||||||
|
method_options :pwd => :string, :env_options => nil, :runners => :string, :runner => :string, :verbose_server => false
|
||||||
|
def debug(dir = "spec/javascripts")
|
||||||
|
Dir.chdir(pwd) do
|
||||||
|
Flowerbox.debug(dir, options.dup)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "transplant DIR", "Convert an existing JavaScript testing project to Flowerbox"
|
||||||
|
long_desc <<-TXT
|
||||||
|
`flowerbox transplant` converts an existing JavaScript testing project type
|
||||||
|
to Flowerbox. Currently, you can transplant the following types of projects:
|
||||||
|
|
||||||
|
* Pivotal Labs Jasmine gem-style
|
||||||
|
\x5 (also covers jasmine-headless-webkit)
|
||||||
|
|
||||||
|
These types of projects live in `spec/javascripts` (or the specified directory)
|
||||||
|
and have the file `support/jasmine.yml` that defines what files are loaded
|
||||||
|
at what parts in the test load process. `jasmine.yml` is converted to a
|
||||||
|
Flowerbox `spec_helper.rb` file and placed into `spec/javascripts`.
|
||||||
|
|
||||||
|
Flowerbox will ask before overwriting existing files.
|
||||||
|
TXT
|
||||||
|
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 TYPE [DIR]", "Start a new Flowerbox project of TYPE, potentially specifying a different DIR to install"
|
||||||
|
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
|
||||||
|
|
||||||
|
def method_missing(method, *args)
|
||||||
|
if File.directory?(method.to_s)
|
||||||
|
test(method.to_s, *args)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
no_tasks do
|
||||||
|
def pwd
|
||||||
|
options[:pwd] || Dir.pwd
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Flowerbox::CLI.start
|
||||||
|
|
@ -1,3 +0,0 @@
|
|||||||
css_dir = 'stylesheets'
|
|
||||||
sass_dir = 'sass'
|
|
||||||
|
|
2
config/cucumber.yml
Normal file
2
config/cucumber.yml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
default: -f pretty
|
||||||
|
wip: --tags @wip -f pretty
|
52
features/basic_run.feature
Normal file
52
features/basic_run.feature
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
Feature: Basic Run
|
||||||
|
Background:
|
||||||
|
Given I have the file "spec/javascripts/cat_spec.js.coffee" with the content:
|
||||||
|
"""
|
||||||
|
#= require cat
|
||||||
|
|
||||||
|
describe 'Cat', ->
|
||||||
|
describe '#meow', ->
|
||||||
|
it 'should meow', ->
|
||||||
|
cat = new Cat()
|
||||||
|
|
||||||
|
expect(cat.meow()).toEqual('meow')
|
||||||
|
"""
|
||||||
|
Given I have the file "lib/cat.js.coffee" with the content:
|
||||||
|
"""
|
||||||
|
class @Cat
|
||||||
|
meow: -> "meow"
|
||||||
|
"""
|
||||||
|
|
||||||
|
Scenario: Use the Node runner using Jasmine
|
||||||
|
Given I have the file "spec/javascripts/spec_helper.rb" with the content:
|
||||||
|
"""
|
||||||
|
Flowerbox.configure do |c|
|
||||||
|
c.test_with :jasmine
|
||||||
|
c.run_with :node
|
||||||
|
|
||||||
|
c.spec_patterns << "**/*_spec.*"
|
||||||
|
c.asset_paths << "lib"
|
||||||
|
|
||||||
|
c.reporters << :json
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
When I run Flowerbox with "spec/javascripts"
|
||||||
|
Then I should have 1 test and 0 failures
|
||||||
|
|
||||||
|
@wip
|
||||||
|
Scenario: Use the Selenium runner using Jasmine
|
||||||
|
Given I have the file "spec/javascripts/spec_helper.rb" with the content:
|
||||||
|
"""
|
||||||
|
Flowerbox.configure do |c|
|
||||||
|
c.test_with :jasmine
|
||||||
|
c.run_with :chrome
|
||||||
|
|
||||||
|
c.spec_patterns << "**/*_spec.*"
|
||||||
|
c.asset_paths << "lib"
|
||||||
|
|
||||||
|
c.reporters << :json
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
When I run Flowerbox with "spec/javascripts"
|
||||||
|
Then I should have 1 test and 0 failures
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
Given /^I have the file "([^"]*)" with the content:$/ do |file, string|
|
||||||
|
target = File.join(@root, file)
|
||||||
|
|
||||||
|
FileUtils.mkdir_p File.dirname(target)
|
||||||
|
File.open(target, 'wb') { |fh| fh.print string }
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,9 @@
|
|||||||
|
require 'json'
|
||||||
|
|
||||||
|
Then /^I should have (\d+) tests? and (\d+) failures?$/ do |tests, failures|
|
||||||
|
results = JSON.parse(@output).last.last
|
||||||
|
|
||||||
|
results['total'].should == tests.to_i
|
||||||
|
results['failures'].should == failures.to_i
|
||||||
|
end
|
||||||
|
|
8
features/step_definitions/when/i_run_flowerbox.rb
Normal file
8
features/step_definitions/when/i_run_flowerbox.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
When /^I run Flowerbox with "([^"]*)"$/ do |arguments|
|
||||||
|
command = %{bundle exec bin/flowerbox test #{arguments} -q --pwd #{@root} 2>&1}
|
||||||
|
|
||||||
|
@output = %x{#{command}}
|
||||||
|
|
||||||
|
raise StandardError.new("Flowerbox failed: #{@output}") if $?.exitstatus != 0
|
||||||
|
end
|
||||||
|
|
6
features/support/env.rb
Normal file
6
features/support/env.rb
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Before do
|
||||||
|
@root = "tmp/features"
|
||||||
|
|
||||||
|
FileUtils.rm_rf @root
|
||||||
|
end
|
||||||
|
|
38
flowerbox.gemspec
Normal file
38
flowerbox.gemspec
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# -*- encoding: utf-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{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) }
|
||||||
|
gem.files = `git ls-files`.split("\n")
|
||||||
|
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
||||||
|
gem.name = "flowerbox"
|
||||||
|
gem.require_paths = ["lib"]
|
||||||
|
gem.version = Flowerbox::VERSION
|
||||||
|
|
||||||
|
gem.add_development_dependency 'cucumber'
|
||||||
|
gem.add_development_dependency 'rspec'
|
||||||
|
gem.add_development_dependency 'simplecov'
|
||||||
|
gem.add_development_dependency 'mocha'
|
||||||
|
gem.add_development_dependency 'fakefs'
|
||||||
|
gem.add_development_dependency 'guard'
|
||||||
|
gem.add_development_dependency 'guard-flowerbox'
|
||||||
|
gem.add_development_dependency 'guard-rspec'
|
||||||
|
gem.add_development_dependency 'guard-cucumber'
|
||||||
|
|
||||||
|
gem.add_dependency 'jasmine-core'
|
||||||
|
gem.add_dependency 'thor'
|
||||||
|
gem.add_dependency 'selenium-webdriver'
|
||||||
|
gem.add_dependency 'rainbow'
|
||||||
|
gem.add_dependency 'sprockets'
|
||||||
|
gem.add_dependency 'sprockets-vendor_gems'
|
||||||
|
gem.add_dependency 'thin'
|
||||||
|
gem.add_dependency 'em-websocket'
|
||||||
|
gem.add_dependency 'coffee-script'
|
||||||
|
end
|
||||||
|
|
237
index.md
237
index.md
@ -1,237 +0,0 @@
|
|||||||
---
|
|
||||||
title: Flowerbox -- a multi-environment, multi-runner, super-easy JavaScript testing framework framework.
|
|
||||||
layout: default
|
|
||||||
---
|
|
||||||
# Flowerbox
|
|
||||||
## A multi-environment, multi-runner, super-easy JavaScript testing framework framework.
|
|
||||||
---
|
|
||||||
## Super-fast unit testing getting started super guide!
|
|
||||||
### (you need Firefox at the very least)
|
|
||||||
|
|
||||||
{% highlight sh %}
|
|
||||||
gem install flowerbox
|
|
||||||
flowerbox plant jasmine
|
|
||||||
flowerbox spec/javascripts
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
## ...and what about integration testing?
|
|
||||||
### (whoa nelly, pure JS Cucumber?)
|
|
||||||
|
|
||||||
{% highlight sh %}
|
|
||||||
flowerbox plant cucumber
|
|
||||||
flowerbox js-features
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
## Hey, I'm already using `jasmine-gem` or `jasmine-headless-webkit`
|
|
||||||
### (you're smart!)
|
|
||||||
|
|
||||||
{% highlight sh %}
|
|
||||||
flowerbox transplant spec/javascripts
|
|
||||||
flowerbox spec/javascripts
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
## I use Sprockets and need to configure asset paths and templates and stuff!
|
|
||||||
### (...may the force be with you...)
|
|
||||||
|
|
||||||
{% highlight ruby %}
|
|
||||||
# in spec/javascripts/spec_helper.rb
|
|
||||||
|
|
||||||
require 'haml-sprockets'
|
|
||||||
|
|
||||||
Flowerbox.configure do |c|
|
|
||||||
c.asset_paths << "app/assets/javascripts"
|
|
||||||
c.asset_paths << "vendor/assets/javascripts"
|
|
||||||
|
|
||||||
c.additional_files << "applications.js.coffee"
|
|
||||||
|
|
||||||
# ... other stuff ...
|
|
||||||
end
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
## Firefox is so 2008. Can I run my tests on Chrome?
|
|
||||||
### (we like Selenium here!)
|
|
||||||
|
|
||||||
{% highlight ruby %}
|
|
||||||
Flowerbox.configure do |c|
|
|
||||||
c.run_with :chrome
|
|
||||||
end
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
## Chrome is so 2010. Can I run my tests on node.js?
|
|
||||||
### (evented hipness! jsdom will give you a fake DOM to work with.)
|
|
||||||
|
|
||||||
{% highlight ruby %}
|
|
||||||
# make sure node and npm are in your path
|
|
||||||
# jsdom and ws get installed in your project under node_modules
|
|
||||||
|
|
||||||
Flowerbox.configure do |c|
|
|
||||||
c.run_with :node
|
|
||||||
end
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
{% highlight coffeescript %}
|
|
||||||
# make sure you bind your global things to this
|
|
||||||
|
|
||||||
class @MyCoolClass extends @MyBaseClass
|
|
||||||
cool: -> "yeah man"
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
## Where'd headless support go?!
|
|
||||||
|
|
||||||
No, I knew you'd ask. Read the long bit of text below. _tl;dr_: Selenium with Chrome is
|
|
||||||
pretty much the same setup, just as fast, and gives you stack traces and other debugging assistance.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Plant some Jasmine
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Plant a Cucumber or two
|
|
||||||
|
|
||||||
## Make a feature like in big boy Cucumber
|
|
||||||
### (that's 'cause it *is* big boy Cucumber, just in JS form)
|
|
||||||
|
|
||||||
{% highlight gherkin %}
|
|
||||||
# js-features/features/my_cool_thing.feature
|
|
||||||
|
|
||||||
Feature: My Cool Thing
|
|
||||||
Scenario: Show a cool thing
|
|
||||||
Given I have a cool thing
|
|
||||||
And I have a cool thing viewer
|
|
||||||
When I render the viewer
|
|
||||||
Then I should see the cool thing in the viewer
|
|
||||||
|
|
||||||
@wip # <= tags work, too!
|
|
||||||
Scenario: Maybe do something else
|
|
||||||
Given something
|
|
||||||
When yadda
|
|
||||||
Then BAM
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
## Write some steps
|
|
||||||
### (Flowerbox gives you a donut-full of syntactic sugar that wraps around Cucumber.js. Be sure to drink lots of water!)
|
|
||||||
|
|
||||||
{% highlight coffeescript %}
|
|
||||||
# js-features/steps/given/i_have_a_cool_thing.js.coffee
|
|
||||||
|
|
||||||
Flowerbox.Given /^I have a cool thing$/, ->
|
|
||||||
@data =
|
|
||||||
one: 'two'
|
|
||||||
three: 'four'
|
|
||||||
|
|
||||||
@coolThing = new CoolThing(@data)
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
{% highlight coffeescript %}
|
|
||||||
# js-features/steps/given/i_have_a_cool_thing_viewer.js.coffee
|
|
||||||
|
|
||||||
Flowerbox.Given /^I have a cool thing viewer$/, ->
|
|
||||||
@coolThingViewer = new CoolThingViewer(@coolThing)
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
{% highlight coffeescript %}
|
|
||||||
# js-features/steps/when/i_render_the_viewer.js.coffee
|
|
||||||
|
|
||||||
Flowerbox.When /^I render the viewer$/, ->
|
|
||||||
@coolThingViewer.render()
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
{% highlight coffeescript %}
|
|
||||||
# js-features/steps/then/i_should_see_the_cool_thing.js.coffee
|
|
||||||
|
|
||||||
Flowerbox.Then /^I should see the cool thing in the viewer$/, ->
|
|
||||||
@expect(
|
|
||||||
@coolThingViewer.$('input[name="one"]').val()
|
|
||||||
).toEqual(@data.one)
|
|
||||||
|
|
||||||
@expect(
|
|
||||||
@coolThingViewer.$('input[name="three"]').val()
|
|
||||||
).toEqual(@data.three)
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
## Get some hooks up in here!
|
|
||||||
### (and other fun environment stuff)
|
|
||||||
|
|
||||||
{% highlight coffeescript %}
|
|
||||||
# js-features/support/env.js.coffee
|
|
||||||
#
|
|
||||||
#= require ../steps
|
|
||||||
|
|
||||||
Flowerbox.World ->
|
|
||||||
@Before (callback) ->
|
|
||||||
@something = 'is available everywhere'
|
|
||||||
callback()
|
|
||||||
|
|
||||||
@After (callback) ->
|
|
||||||
Put = 'some stuff back'
|
|
||||||
callback()
|
|
||||||
{% endhighlight %}
|
|
||||||
|
|
||||||
## Auto-generate missing steps
|
|
||||||
### (see [cucumber-step_writer](http://github.com/johnbintz/cucumber-step_writer) for my philosophy)
|
|
||||||
|
|
||||||
{% highlight ruby %}
|
|
||||||
# js-features/spec_helper.rb
|
|
||||||
|
|
||||||
Flowerbox.configure do |c|
|
|
||||||
c.report_with :verbose
|
|
||||||
c.reporters.add(
|
|
||||||
:step_writer,
|
|
||||||
:target => 'js-features/step_definitions',
|
|
||||||
:on_finish => lambda { |dir| system %{open #{dir}} }
|
|
||||||
)
|
|
||||||
end
|
|
||||||
{% endhighlight %}
|
|
||||||
---
|
|
||||||
|
|
||||||
# Flowerbox! Yea...wha?
|
|
||||||
|
|
||||||
So I (John) have been in this JavaScript testing game for a while, and, after about
|
|
||||||
three years of doing JS testing for Web apps, decided to finally take all the knowledge I had
|
|
||||||
and make something that was dead-simple to use.
|
|
||||||
|
|
||||||
## You already did once...
|
|
||||||
|
|
||||||
`jasmine-headless-webkit` was my first attempt, but it's a C++/Qt mess. Downloading all of
|
|
||||||
Qt is a total pain. Also, Qt4's JavaScript engine, JavaScriptCore, doesn't have stack trace
|
|
||||||
support, so it got pretty useless for me for bigger, messy projects (looking at you, Backbone).
|
|
||||||
Qt5 has V8, but whatever, I just decided to ditch the whole
|
|
||||||
compiling software thing and replace it with running tests in Selenium and/or node.js.
|
|
||||||
|
|
||||||
## Selenium faster than headless? But CI servers--
|
|
||||||
|
|
||||||
Sure, spinning up that instance of Firefox or Chrome is slower than starting up a small
|
|
||||||
QtWebKit widget, but there's a lot less that can go wrong when using a full-blown
|
|
||||||
browser. This also means your CI server config stays leaner. You may already have Qt installed
|
|
||||||
for capybara-webkit, but one less thing depending on Qt just makes your life (and mine) easier.
|
|
||||||
|
|
||||||
Also, when running Flowerbox with Guard, your browser stays open between test runs, so you only
|
|
||||||
pay the startup penalty once.
|
|
||||||
|
|
||||||
And, if you write everything correctly, you can just run your tests on node.js and not even have
|
|
||||||
to worry about a browser.
|
|
||||||
|
|
||||||
## But you said fake browsers are teh bad--
|
|
||||||
|
|
||||||
I changed my mind. As long as I'm not testing for browser-only things, like CSS interaction
|
|
||||||
or position elements, running tests on node.js with a browser-like DOM provided by jsdom
|
|
||||||
really is good enough. I use full-blown Ruby Cucumber if I need to test more complex browser
|
|
||||||
things.
|
|
||||||
|
|
||||||
## Cucumber.js? Full-stack testing is the only way!
|
|
||||||
|
|
||||||
Yeah, and it's slow. I'll carve out what I can in Cucumber.js while I work on Ruby Cucumber
|
|
||||||
features. Makes the process of getting those complex integration tests written a lot faster.
|
|
||||||
|
|
||||||
Also, I've changed my views on unit testing as a process. Unit testing for me is now about
|
|
||||||
two things:
|
|
||||||
|
|
||||||
* Code design through a liberal use of stubs and mocks.
|
|
||||||
* Really quickly running through blg blobs of inputs/outputs (parsers and such).
|
|
||||||
|
|
||||||
Treating unit testing this way taught me how to better organize and design my code, and I
|
|
||||||
only use it for really testing code when I need to test those big blobs of things.
|
|
||||||
This is my opinion, YMMV, Flowerbox lets you do what you want. Never use Cucumber.js support
|
|
||||||
if you don't want to. Write your integration bits in Jasmine. I don't mind.
|
|
||||||
|
|
65
lib/assets/javascripts/flowerbox.js.coffee
Normal file
65
lib/assets/javascripts/flowerbox.js.coffee
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
Flowerbox =
|
||||||
|
debug: false
|
||||||
|
ping: ->
|
||||||
|
Flowerbox.contact('ping')
|
||||||
|
|
||||||
|
messageQueue: []
|
||||||
|
pulling: false
|
||||||
|
|
||||||
|
contact: (url, data...) ->
|
||||||
|
Flowerbox.started = true
|
||||||
|
|
||||||
|
if !Flowerbox.debug
|
||||||
|
message = [ url, data ]
|
||||||
|
Flowerbox.messageQueue.push(message)
|
||||||
|
|
||||||
|
if Flowerbox.socket && !Flowerbox.pulling
|
||||||
|
Flowerbox.queuePuller()
|
||||||
|
|
||||||
|
started: false
|
||||||
|
done: false
|
||||||
|
|
||||||
|
queuePuller: ->
|
||||||
|
Flowerbox.pulling = true
|
||||||
|
|
||||||
|
if Flowerbox.messageQueue.length > 0
|
||||||
|
message = Flowerbox.messageQueue.shift()
|
||||||
|
|
||||||
|
Flowerbox.socket.onmessage = (data) ->
|
||||||
|
if message[0] == 'results'
|
||||||
|
if __$instrument?
|
||||||
|
Flowerbox.socket.send JSON.stringify(['instrument', __$instrument]) {}, ->
|
||||||
|
Flowerbox.done = true
|
||||||
|
else
|
||||||
|
Flowerbox.done = true
|
||||||
|
|
||||||
|
Flowerbox.queuePuller()
|
||||||
|
|
||||||
|
Flowerbox.socket.send JSON.stringify(message)
|
||||||
|
else
|
||||||
|
setTimeout(Flowerbox.queuePuller, 10) if !Flowerbox.done
|
||||||
|
|
||||||
|
fail: ->
|
||||||
|
|
||||||
|
class Flowerbox.Exception
|
||||||
|
constructor: (@source, @name, @stack) ->
|
||||||
|
|
||||||
|
toJSON: ->
|
||||||
|
{ status: Flowerbox.Result.FAILURE, source: @source, name: @name, trace: { stack: @stack } }
|
||||||
|
|
||||||
|
class Flowerbox.Result
|
||||||
|
@SUCCESS = 'success'
|
||||||
|
@PENDING = 'pending'
|
||||||
|
@UNDEFINED = 'undefined'
|
||||||
|
@FAILURE = 'failure'
|
||||||
|
@SKIPPED = 'skipped'
|
||||||
|
|
||||||
|
constructor: (data) ->
|
||||||
|
for key, value of data
|
||||||
|
this[key] = value
|
||||||
|
|
||||||
|
this.status ||= Flowerbox.Result.SKIPPED
|
||||||
|
this.failures ||= []
|
||||||
|
|
||||||
|
toJSON: => this
|
||||||
|
|
119
lib/assets/javascripts/flowerbox/cucumber.js.coffee
Normal file
119
lib/assets/javascripts/flowerbox/cucumber.js.coffee
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
#= require flowerbox/cucumber/reporter
|
||||||
|
|
||||||
|
Flowerbox.Cucumber ||= {}
|
||||||
|
Flowerbox.Cucumber.features = ->
|
||||||
|
Flowerbox.Cucumber.Features ||= []
|
||||||
|
Flowerbox.Cucumber.Features.join("\n")
|
||||||
|
|
||||||
|
Flowerbox.World = (code = null) ->
|
||||||
|
if code
|
||||||
|
Flowerbox.World.Code ||= []
|
||||||
|
Flowerbox.World.Code.push(code)
|
||||||
|
else
|
||||||
|
->
|
||||||
|
for code in (Flowerbox.World.Code || [])
|
||||||
|
code.apply(this)
|
||||||
|
|
||||||
|
Flowerbox.Matchers =
|
||||||
|
toEqual: (expected) ->
|
||||||
|
@message = "Expected #{@actual} #{@notMessage} equal #{expected}"
|
||||||
|
result = null
|
||||||
|
|
||||||
|
if @actual? && expected?
|
||||||
|
switch (typeof @actual)
|
||||||
|
when 'object'
|
||||||
|
result = true
|
||||||
|
for key, value of @actual
|
||||||
|
result = false if expected[key] != value
|
||||||
|
|
||||||
|
for key, value of expected
|
||||||
|
result = false if @actual[key] != value
|
||||||
|
|
||||||
|
if result == null
|
||||||
|
result = (@actual == expected)
|
||||||
|
|
||||||
|
result
|
||||||
|
|
||||||
|
Flowerbox.World ->
|
||||||
|
@assert = (what, message = 'failed') ->
|
||||||
|
throw new Error(message) if !what
|
||||||
|
|
||||||
|
@expect = (what) -> new Flowerbox.Matcher(what)
|
||||||
|
|
||||||
|
@addMatchers = (data) -> Flowerbox.Matcher.addMatchers(data)
|
||||||
|
|
||||||
|
Flowerbox.Matcher.matchers = {}
|
||||||
|
@addMatchers(Flowerbox.Matchers)
|
||||||
|
|
||||||
|
class Flowerbox.Matcher
|
||||||
|
@addMatchers: (data) ->
|
||||||
|
for method, code of data
|
||||||
|
Flowerbox.Matcher.matchers[method] = code
|
||||||
|
|
||||||
|
constructor: (@actual, @_negated = false) ->
|
||||||
|
@not = this.negated() if !@_negated
|
||||||
|
@notMessage = if @_negated then "to not" else "to"
|
||||||
|
|
||||||
|
generateMethod = (method) ->
|
||||||
|
(args...) -> @fail() if !@maybeNegate(Flowerbox.Matcher.matchers[method].apply(this, args))
|
||||||
|
|
||||||
|
for method, code of Flowerbox.Matcher.matchers
|
||||||
|
this[method] = generateMethod(method)
|
||||||
|
|
||||||
|
negated: ->
|
||||||
|
new Flowerbox.Matcher(@actual, true)
|
||||||
|
|
||||||
|
fail: ->
|
||||||
|
throw new Error(@message)
|
||||||
|
|
||||||
|
maybeNegate: (result) ->
|
||||||
|
result = !result if @_negated
|
||||||
|
result
|
||||||
|
|
||||||
|
Flowerbox.Step = (type, match, code) ->
|
||||||
|
Flowerbox.World.Code ||= []
|
||||||
|
Flowerbox.World.Code.push (args..., callback) ->
|
||||||
|
this[type] match, (args..., callback) =>
|
||||||
|
_pending = false
|
||||||
|
@pending = -> _pending = true
|
||||||
|
|
||||||
|
result = code.apply(this, args)
|
||||||
|
|
||||||
|
if result? and result.__prototype__ == Error
|
||||||
|
callback.fail(result)
|
||||||
|
else
|
||||||
|
if _pending then callback.pending("pending") else callback()
|
||||||
|
|
||||||
|
null
|
||||||
|
|
||||||
|
Flowerbox.Step.files ||= {}
|
||||||
|
Flowerbox.Step.matchFile = (name) ->
|
||||||
|
for key, data of Flowerbox.Step.files
|
||||||
|
[ regexp, filename ] = data
|
||||||
|
if name.match(regexp)
|
||||||
|
return filename
|
||||||
|
|
||||||
|
null
|
||||||
|
|
||||||
|
stepGenerator = (type) ->
|
||||||
|
Flowerbox[type] = (match, code) ->
|
||||||
|
if !Flowerbox.Step.files[match.toString()]
|
||||||
|
nextLine = false
|
||||||
|
if stack = (new Error()).stack
|
||||||
|
for line in stack.split('\n')
|
||||||
|
if nextLine
|
||||||
|
result = if line.match(/__F__/)
|
||||||
|
line.replace(/^.*__F__\/([^:]+:\d+).*$/, '$1')
|
||||||
|
else
|
||||||
|
line.replace(/^.*(\(| )([^:]+:\d+).*$/, '$2')
|
||||||
|
|
||||||
|
Flowerbox.Step.files[match.toString()] = [ match, result ]
|
||||||
|
break
|
||||||
|
|
||||||
|
if line.match(/(Given|When|Then)/)
|
||||||
|
nextLine = true
|
||||||
|
|
||||||
|
Flowerbox.Step(type, match, code)
|
||||||
|
|
||||||
|
stepGenerator(type) for type in [ 'Given', 'When', 'Then' ]
|
||||||
|
|
67
lib/assets/javascripts/flowerbox/cucumber/reporter.js.coffee
Normal file
67
lib/assets/javascripts/flowerbox/cucumber/reporter.js.coffee
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
Flowerbox = Flowerbox || {}
|
||||||
|
Flowerbox.Cucumber = Flowerbox.Cucumber || {}
|
||||||
|
|
||||||
|
class Flowerbox.Cucumber.Reporter
|
||||||
|
nameParts: ->
|
||||||
|
[ @feature.getName(), @scenario.getName(), "#{this.type()} #{@step.getName()}" ]
|
||||||
|
|
||||||
|
type: ->
|
||||||
|
type = "Given"
|
||||||
|
|
||||||
|
if @step.isOutcomeStep()
|
||||||
|
type = "Then"
|
||||||
|
else if @step.isEventStep()
|
||||||
|
type = "When"
|
||||||
|
|
||||||
|
type
|
||||||
|
|
||||||
|
hear: (event, callback) ->
|
||||||
|
switch event.getName()
|
||||||
|
when 'BeforeFeatures'
|
||||||
|
@time = (new Date()).getTime()
|
||||||
|
|
||||||
|
Flowerbox.contact("starting")
|
||||||
|
|
||||||
|
when 'AfterFeatures'
|
||||||
|
Flowerbox.contact("results", (new Date()).getTime() - @time)
|
||||||
|
|
||||||
|
when 'BeforeFeature'
|
||||||
|
@feature = event.getPayloadItem('feature')
|
||||||
|
|
||||||
|
when 'BeforeScenario'
|
||||||
|
@scenario = event.getPayloadItem('scenario')
|
||||||
|
|
||||||
|
when 'BeforeStep'
|
||||||
|
@step = event.getPayloadItem('step')
|
||||||
|
|
||||||
|
Flowerbox.contact("start_test", @step.getName())
|
||||||
|
|
||||||
|
when 'StepResult'
|
||||||
|
stepResult = event.getPayloadItem('stepResult')
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
if stepResult.isSuccessful()
|
||||||
|
result.status = Flowerbox.Result.SUCCESS
|
||||||
|
else if stepResult.isPending()
|
||||||
|
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
|
||||||
|
|
||||||
|
error = stepResult.getFailureException()
|
||||||
|
stack = (error.stack || "message\n#{file}:0").split("\n")
|
||||||
|
|
||||||
|
failure = { runner: Flowerbox.environment, message: error.message, stack: stack }
|
||||||
|
|
||||||
|
result.failures.push(failure)
|
||||||
|
|
||||||
|
Flowerbox.contact("finish_test", result.toJSON())
|
||||||
|
|
||||||
|
callback()
|
||||||
|
|
@ -0,0 +1,4 @@
|
|||||||
|
window.onerror = (message, file, line) ->
|
||||||
|
Flowerbox.contact("log", message)
|
||||||
|
Flowerbox.contact("log", " #{file}:#{line}")
|
||||||
|
|
57
lib/assets/javascripts/flowerbox/jasmine.js.coffee
Normal file
57
lib/assets/javascripts/flowerbox/jasmine.js.coffee
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#= require flowerbox/jasmine/reporter
|
||||||
|
|
||||||
|
# because why not?
|
||||||
|
@context = @describe
|
||||||
|
|
||||||
|
getSplitName = (parts) ->
|
||||||
|
parts.push(String(@description).replace(/[\n\r]/g, ' '))
|
||||||
|
parts
|
||||||
|
|
||||||
|
jasmine.Suite.prototype.getSuiteSplitName = ->
|
||||||
|
this.getSplitName(if @parentSuite then @parentSuite.getSuiteSplitName() else [])
|
||||||
|
|
||||||
|
jasmine.Spec.prototype.getSpecSplitName = ->
|
||||||
|
this.getSplitName(@suite.getSuiteSplitName())
|
||||||
|
|
||||||
|
jasmine.Suite.prototype.getSplitName = getSplitName
|
||||||
|
jasmine.Spec.prototype.getSplitName = getSplitName
|
||||||
|
|
||||||
|
jasmine.Spec.prototype.addMatcherResult = (result) ->
|
||||||
|
for method in jasmine.Spec.beforeAddMatcherResult()
|
||||||
|
method.apply(result, [ this ])
|
||||||
|
|
||||||
|
@results_.addResult(result)
|
||||||
|
|
||||||
|
jasmine.Spec.beforeAddMatcherResult = ->
|
||||||
|
@_beforeAddMatcherResult ||= []
|
||||||
|
|
||||||
|
jasmine.Spec.beforeAddMatcherResult().push (spec) ->
|
||||||
|
@splitName = spec.getSpecSplitName()
|
||||||
|
|
||||||
|
Flowerbox.only = (envs..., code) ->
|
||||||
|
for env in envs
|
||||||
|
if Flowerbox.environment == env
|
||||||
|
describe("only in #{envs.join(', ')}", code)
|
||||||
|
|
||||||
|
jasmine.WaitsBlock.prototype._execute = jasmine.WaitsBlock.prototype.execute
|
||||||
|
jasmine.WaitsForBlock.prototype._execute = jasmine.WaitsForBlock.prototype.execute
|
||||||
|
|
||||||
|
pauseAndRun = (onComplete) ->
|
||||||
|
jasmine.getEnv().reporter.reportSpecWaiting()
|
||||||
|
|
||||||
|
this._execute ->
|
||||||
|
jasmine.getEnv().reporter.reportSpecRunning()
|
||||||
|
onComplete()
|
||||||
|
|
||||||
|
jasmine.WaitsBlock.prototype.execute = pauseAndRun
|
||||||
|
jasmine.WaitsForBlock.prototype.execute = pauseAndRun
|
||||||
|
|
||||||
|
for method in [ "reportSpecWaiting", "reportSpecRunning" ]
|
||||||
|
generator = (method) ->
|
||||||
|
(args...) ->
|
||||||
|
for reporter in @subReporters_
|
||||||
|
if reporter[method]?
|
||||||
|
reporter[method](args...)
|
||||||
|
|
||||||
|
jasmine.MultiReporter.prototype[method] = generator(method)
|
||||||
|
|
5
lib/assets/javascripts/flowerbox/jasmine/node.js.coffee
Normal file
5
lib/assets/javascripts/flowerbox/jasmine/node.js.coffee
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
jasmine.Spec.beforeAddMatcherResult().push ->
|
||||||
|
if !@passed_
|
||||||
|
@trace = { stack: new Error().stack }
|
||||||
|
|
||||||
|
Flowerbox.fail = -> process.exit(1)
|
53
lib/assets/javascripts/flowerbox/jasmine/reporter.js.coffee
Normal file
53
lib/assets/javascripts/flowerbox/jasmine/reporter.js.coffee
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
class jasmine.FlowerboxReporter
|
||||||
|
buildResult: (spec, overrides = {}) ->
|
||||||
|
data =
|
||||||
|
status: Flowerbox.Result.SUCCESS
|
||||||
|
source: 'jasmine'
|
||||||
|
name: spec.getSpecSplitName()
|
||||||
|
file: "#{Flowerbox.UNKNOWN}:0"
|
||||||
|
|
||||||
|
for key, value of overrides
|
||||||
|
data[key] = value
|
||||||
|
|
||||||
|
new Flowerbox.Result(data)
|
||||||
|
|
||||||
|
reportRunnerStarting: (runner) ->
|
||||||
|
@time = (new Date()).getTime()
|
||||||
|
|
||||||
|
Flowerbox.contact("starting")
|
||||||
|
|
||||||
|
reportSpecStarting: (spec) ->
|
||||||
|
Flowerbox.contact("start_test", spec.getSpecSplitName().join(" "))
|
||||||
|
|
||||||
|
if spec.description == 'encountered a declaration exception'
|
||||||
|
Flowerbox.contact("finish_test", this.buildResult(spec, status: Flowerbox.Result.FAILURE))
|
||||||
|
|
||||||
|
reportSpecWaiting: ->
|
||||||
|
@paused = true
|
||||||
|
Flowerbox.contact('pause_timer')
|
||||||
|
|
||||||
|
reportSpecRunning: ->
|
||||||
|
@paused = false
|
||||||
|
Flowerbox.contact('unpause_timer')
|
||||||
|
|
||||||
|
reportSpecResults: (spec) ->
|
||||||
|
this.reportSpecRunning() if @paused
|
||||||
|
|
||||||
|
result = this.buildResult(spec)
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
failure = { runner: Flowerbox.environment, message: item.message, stack: stack }
|
||||||
|
|
||||||
|
result.failures.push(failure)
|
||||||
|
|
||||||
|
Flowerbox.contact("finish_test", result)
|
||||||
|
reportRunnerResults: (runner) ->
|
||||||
|
Flowerbox.contact("results", (new Date().getTime()) - @time)
|
||||||
|
|
17
lib/assets/javascripts/flowerbox/jasmine/selenium.js.coffee
Normal file
17
lib/assets/javascripts/flowerbox/jasmine/selenium.js.coffee
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
jasmine.Spec.beforeAddMatcherResult().push ->
|
||||||
|
if !@passed_
|
||||||
|
@trace = { stack: [] }
|
||||||
|
try
|
||||||
|
lol.wut
|
||||||
|
catch e
|
||||||
|
if e.stack
|
||||||
|
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: [ @message, file ] }
|
||||||
|
|
109
lib/flowerbox.rb
Normal file
109
lib/flowerbox.rb
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
require "flowerbox/version"
|
||||||
|
require 'rainbow'
|
||||||
|
require 'forwardable'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
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'
|
||||||
|
require 'flowerbox/configuration'
|
||||||
|
|
||||||
|
if defined?(Rails::Engine)
|
||||||
|
require 'flowerbox/rails/engine'
|
||||||
|
end
|
||||||
|
|
||||||
|
CACHE_DIR = 'tmp/sprockets'
|
||||||
|
|
||||||
|
class << self
|
||||||
|
attr_writer :quiet
|
||||||
|
attr_accessor :server
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@configuration = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def configuration
|
||||||
|
@configuration ||= Flowerbox::Configuration.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def path
|
||||||
|
Pathname(File.expand_path('../..', __FILE__))
|
||||||
|
end
|
||||||
|
|
||||||
|
def configure(&block)
|
||||||
|
configuration.configure(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def bare_coffeescript
|
||||||
|
@bare_coffeescript ||= true
|
||||||
|
end
|
||||||
|
|
||||||
|
def debug(dir, options = {})
|
||||||
|
Flowerbox::Run::Debug.execute(dir, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(dir, options = {})
|
||||||
|
Flowerbox.quiet = options[:quiet]
|
||||||
|
|
||||||
|
Flowerbox::Run::Test.execute(dir, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def quiet?
|
||||||
|
@quiet == true
|
||||||
|
end
|
||||||
|
|
||||||
|
def notify(msg)
|
||||||
|
if !Flowerbox.quiet?
|
||||||
|
puts msg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def browsers
|
||||||
|
@browsers ||= {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def cleanup!
|
||||||
|
browsers.values.each do |browser|
|
||||||
|
begin
|
||||||
|
browser.close
|
||||||
|
rescue Errno::ECONNREFUSED => e
|
||||||
|
puts "Browser already closed.".foreground(:yellow)
|
||||||
|
rescue ::Selenium::WebDriver::Error::UnknownError => e
|
||||||
|
puts "Unknown browser error, pushing past it to close...".foreground(:yellow)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@browsers = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def transplant(dir)
|
||||||
|
Flowerbox::TestEnvironment.transplantable_environments.each do |env|
|
||||||
|
break if env.transplant(dir)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_dir
|
||||||
|
File.join(CACHE_DIR, Flowerbox.test_environment.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(method, *args)
|
||||||
|
if configuration.respond_to?(method)
|
||||||
|
configuration.send(method, *args)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
66
lib/flowerbox/configuration.rb
Normal file
66
lib/flowerbox/configuration.rb
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
require 'flowerbox/reporter_list'
|
||||||
|
require 'flowerbox/instrumented_files_list'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
class Configuration
|
||||||
|
attr_writer :reporters, :backtrace_filter, :instrument_js
|
||||||
|
attr_accessor :port
|
||||||
|
|
||||||
|
attr_accessor :test_environment, :runner_environment, :bare_coffeescript
|
||||||
|
|
||||||
|
def spec_patterns
|
||||||
|
@spec_patterns ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def asset_paths
|
||||||
|
@asset_paths ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def reporters
|
||||||
|
@reporters ||= Flowerbox::ReporterList.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def additional_files
|
||||||
|
@additional_files ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def backtrace_filter
|
||||||
|
@backtrace_filter ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def instrument_files
|
||||||
|
@instrument_files ||= Flowerbox::InstrumentedFilesList.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def instrument_js?
|
||||||
|
!instrument_files.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_with(what)
|
||||||
|
self.test_environment = Flowerbox::TestEnvironment.for(what)
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_with(*whats)
|
||||||
|
self.runner_environment = whats.flatten.collect { |what| Flowerbox::Runner.for(what.to_s) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def report_with(*whats)
|
||||||
|
self.reporters.clear!
|
||||||
|
whats.each { |what| self.reporters << what }
|
||||||
|
end
|
||||||
|
|
||||||
|
def configure
|
||||||
|
yield self
|
||||||
|
|
||||||
|
if spec_patterns.empty?
|
||||||
|
spec_patterns << "**/*_spec*"
|
||||||
|
spec_patterns << "*/*_spec*"
|
||||||
|
end
|
||||||
|
|
||||||
|
if reporters.empty?
|
||||||
|
reporters << :progress
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
14
lib/flowerbox/core_ext/module.rb
Normal file
14
lib/flowerbox/core_ext/module.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module Flowerbox
|
||||||
|
module CoreExt
|
||||||
|
module Module
|
||||||
|
def find_constant(string)
|
||||||
|
const_get(constants.find { |f| f.to_s.downcase == string.to_s.downcase.gsub('_', '') })
|
||||||
|
end
|
||||||
|
|
||||||
|
def for(env)
|
||||||
|
find_constant(env).new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
31
lib/flowerbox/failure.rb
Normal file
31
lib/flowerbox/failure.rb
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
module Flowerbox
|
||||||
|
class Failure < BaseResult
|
||||||
|
def pending?
|
||||||
|
message == "pending"
|
||||||
|
end
|
||||||
|
|
||||||
|
def skipped?
|
||||||
|
message == "skipped"
|
||||||
|
end
|
||||||
|
|
||||||
|
def undefined?
|
||||||
|
message[%r{^Step not defined}]
|
||||||
|
end
|
||||||
|
|
||||||
|
def print_progress
|
||||||
|
case message
|
||||||
|
when "pending"
|
||||||
|
print "P".foreground(:yellow)
|
||||||
|
when "skipped"
|
||||||
|
print "-".foreground(:cyan)
|
||||||
|
else
|
||||||
|
if undefined?
|
||||||
|
print "U".foreground(:yellow)
|
||||||
|
else
|
||||||
|
print "F".foreground(:red)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
30
lib/flowerbox/gathered_result.rb
Normal file
30
lib/flowerbox/gathered_result.rb
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
module Flowerbox
|
||||||
|
class GatheredResult
|
||||||
|
attr_reader :name
|
||||||
|
|
||||||
|
def initialize(name)
|
||||||
|
@name = name
|
||||||
|
end
|
||||||
|
|
||||||
|
def <<(result)
|
||||||
|
results << result
|
||||||
|
end
|
||||||
|
|
||||||
|
def results
|
||||||
|
@results ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def successes
|
||||||
|
results.find_all(&:success?)
|
||||||
|
end
|
||||||
|
|
||||||
|
def failures
|
||||||
|
results.reject(&:success?)
|
||||||
|
end
|
||||||
|
|
||||||
|
def success?
|
||||||
|
@results.all?(&:success?)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
22
lib/flowerbox/instrumented_files_list.rb
Normal file
22
lib/flowerbox/instrumented_files_list.rb
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
require 'forwardable'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
class InstrumentedFilesList
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
def_delegators :@files_list, :empty?
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@files_list = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def <<(pattern)
|
||||||
|
@files_list << pattern
|
||||||
|
end
|
||||||
|
|
||||||
|
def include?(filename)
|
||||||
|
@files_list.any? { |pattern| filename[pattern] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
37
lib/flowerbox/rack.rb
Normal file
37
lib/flowerbox/rack.rb
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
module Flowerbox
|
||||||
|
class Rack
|
||||||
|
attr_accessor :runner, :sprockets
|
||||||
|
|
||||||
|
def initialize(runner, sprockets)
|
||||||
|
@runner, @sprockets = runner, sprockets
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
dup._call(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
def _call(env)
|
||||||
|
if sprockets_file = env['PATH_INFO'][%r{/__F__(.*)$}, 1]
|
||||||
|
result = sprockets.call(env.merge('QUERY_STRING' => 'body=1', 'PATH_INFO' => sprockets_file))
|
||||||
|
result[1]['Cache-Control'] = 'max-age: 0; must-revalidate; no-store'
|
||||||
|
result[1].delete('ETag')
|
||||||
|
|
||||||
|
result
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
template = runner.template
|
||||||
|
|
||||||
|
[ 200, { 'Content-type' => 'text/html' }, [ template ] ]
|
||||||
|
rescue => e
|
||||||
|
$stderr.puts
|
||||||
|
$stderr.puts e.message
|
||||||
|
$stderr.puts e.backtrace.join("\n")
|
||||||
|
$stderr.puts
|
||||||
|
|
||||||
|
[ 500, {}, [] ]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
15
lib/flowerbox/rails/engine.rb
Normal file
15
lib/flowerbox/rails/engine.rb
Normal file
@ -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
|
||||||
|
|
14
lib/flowerbox/reporter.rb
Normal file
14
lib/flowerbox/reporter.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module Flowerbox
|
||||||
|
module Reporter
|
||||||
|
extend Flowerbox::CoreExt::Module
|
||||||
|
|
||||||
|
require 'flowerbox/reporter/file_display'
|
||||||
|
|
||||||
|
def self.for(env)
|
||||||
|
require "flowerbox/reporter/#{env}"
|
||||||
|
|
||||||
|
find_constant(env).new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
67
lib/flowerbox/reporter/base.rb
Normal file
67
lib/flowerbox/reporter/base.rb
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
require 'forwardable'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
module Reporter
|
||||||
|
class Base
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
def_delegators :$stdout, :puts, :print
|
||||||
|
|
||||||
|
attr_accessor :options
|
||||||
|
|
||||||
|
def post_report_data
|
||||||
|
@post_report_data ||= {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def report(gathered_results, data = {})
|
||||||
|
gathered_results.each do |result|
|
||||||
|
self.send("report_#{result.type}", result)
|
||||||
|
end
|
||||||
|
|
||||||
|
report_numeric_results(gathered_results, data)
|
||||||
|
|
||||||
|
post_report_data.each do |type, data|
|
||||||
|
puts
|
||||||
|
self.send("post_report_#{type}", data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def report_success(result); end
|
||||||
|
def report_skipped(result) ; end
|
||||||
|
def report_failure(result) ; end
|
||||||
|
def report_pending(result) ; end
|
||||||
|
def report_undefined(result) ; end
|
||||||
|
|
||||||
|
def report_progress(result); end
|
||||||
|
|
||||||
|
def post_report_success(data) ; end
|
||||||
|
def post_report_skipped(data) ; end
|
||||||
|
def post_report_pending(data) ; end
|
||||||
|
def post_report_undefined(data)
|
||||||
|
self.puts "Some steps were not defined. Define them using the following code:".foreground(:yellow)
|
||||||
|
self.puts
|
||||||
|
|
||||||
|
data.each do |code|
|
||||||
|
self.puts code.foreground(:yellow)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def post_report_failed(data) ; end
|
||||||
|
|
||||||
|
def report_numeric_results(gathered_results, data = {}) ; end
|
||||||
|
|
||||||
|
def start(message) ; end
|
||||||
|
def log(message) ; end
|
||||||
|
|
||||||
|
protected
|
||||||
|
def numeric_results_for(gathered_results)
|
||||||
|
{
|
||||||
|
:total => gathered_results.length,
|
||||||
|
:failures => gathered_results.find_all(&:failure?).length,
|
||||||
|
:pending => gathered_results.find_all(&:pending?).length
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
48
lib/flowerbox/reporter/console_base.rb
Normal file
48
lib/flowerbox/reporter/console_base.rb
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
require 'flowerbox/reporter/base'
|
||||||
|
|
||||||
|
module Flowerbox::Reporter
|
||||||
|
class ConsoleBase < Base
|
||||||
|
include FileDisplay
|
||||||
|
|
||||||
|
def start(message)
|
||||||
|
self.puts message
|
||||||
|
end
|
||||||
|
|
||||||
|
def report_success(result) ; end
|
||||||
|
def report_skipped(result) ; end
|
||||||
|
|
||||||
|
def report_pending(result)
|
||||||
|
puts
|
||||||
|
puts result.name.join(" - ").foreground(:yellow)
|
||||||
|
puts " Pending (finish defining the test)".foreground(:yellow) + ' ' + path_for(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
def report_undefined(result)
|
||||||
|
(post_report_data[:undefined] ||= []) << result.test_environment.obtain_test_definition_for(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
def report_failure(result)
|
||||||
|
puts
|
||||||
|
puts result.name.join(" - ").foreground(:red)
|
||||||
|
result.failures.each do |failure|
|
||||||
|
puts " " + failure.message.foreground(:red) + " [" + failure.runners.join(',') + "] " + path_for(failure)
|
||||||
|
puts failure.filtered_stack.join("\n").foreground(:red) if failure.exception?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def report_numeric_results(gathered_results, data = {})
|
||||||
|
results = numeric_results_for(gathered_results)
|
||||||
|
|
||||||
|
output = "#{results[:total]} total, #{results[:failures]} failed, #{results[:pending]} pending, #{data[:time].to_f / 1000} (#{data[:realtime]}.0) secs."
|
||||||
|
color = :green
|
||||||
|
|
||||||
|
color = :yellow if results[:pending] > 0
|
||||||
|
color = :red if results[:failures] > 0
|
||||||
|
|
||||||
|
puts
|
||||||
|
puts output.foreground(color)
|
||||||
|
puts
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
13
lib/flowerbox/reporter/file_display.rb
Normal file
13
lib/flowerbox/reporter/file_display.rb
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module Flowerbox::Reporter
|
||||||
|
module FileDisplay
|
||||||
|
private
|
||||||
|
def path_for(result)
|
||||||
|
if result.line.gsub(%r{[^0-9]}, '').to_i == 0
|
||||||
|
''
|
||||||
|
else
|
||||||
|
result.translated_file_and_line.foreground(:cyan)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
21
lib/flowerbox/reporter/json.rb
Normal file
21
lib/flowerbox/reporter/json.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
require 'flowerbox/reporter/base'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
|
module Flowerbox::Reporter
|
||||||
|
class JSON < Base
|
||||||
|
def output
|
||||||
|
@output ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def log(message)
|
||||||
|
@output << [ :log, message ]
|
||||||
|
end
|
||||||
|
|
||||||
|
def report_numeric_results(gathered_results, data = {})
|
||||||
|
output << [ :results, numeric_results_for(gathered_results) ]
|
||||||
|
|
||||||
|
self.puts ::JSON.dump(output)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
31
lib/flowerbox/reporter/progress.rb
Normal file
31
lib/flowerbox/reporter/progress.rb
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
require 'flowerbox/reporter/console_base'
|
||||||
|
|
||||||
|
module Flowerbox::Reporter
|
||||||
|
class Progress < ConsoleBase
|
||||||
|
def report_progress(result)
|
||||||
|
print self.send("progress_#{result.type}")
|
||||||
|
$stdout.flush
|
||||||
|
end
|
||||||
|
|
||||||
|
def progress_skipped
|
||||||
|
"-".foreground(:blue)
|
||||||
|
end
|
||||||
|
|
||||||
|
def progress_success
|
||||||
|
".".foreground(:green)
|
||||||
|
end
|
||||||
|
|
||||||
|
def progress_failure
|
||||||
|
"F".foreground(:red)
|
||||||
|
end
|
||||||
|
|
||||||
|
def progress_pending
|
||||||
|
"P".foreground(:yellow)
|
||||||
|
end
|
||||||
|
|
||||||
|
def progress_undefined
|
||||||
|
"U".foreground(:yellow)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
28
lib/flowerbox/reporter/step_writer.rb
Normal file
28
lib/flowerbox/reporter/step_writer.rb
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
require 'flowerbox/reporter/base'
|
||||||
|
|
||||||
|
module Flowerbox::Reporter
|
||||||
|
class StepWriter < Base
|
||||||
|
def report_undefined(result)
|
||||||
|
target = File.join(options[:target], result.step_type.downcase, result.original_name.strip.downcase.gsub(%r{[^\w]+}, '_')) + '.js'
|
||||||
|
|
||||||
|
if result.test_environment.primarily_coffeescript?
|
||||||
|
target << '.coffee'
|
||||||
|
end
|
||||||
|
|
||||||
|
if !File.file?(target)
|
||||||
|
self.puts "Writing #{target}..."
|
||||||
|
|
||||||
|
File.open(target, 'wb') { |fh| fh.print result.test_environment.obtain_test_definition_for(result) }
|
||||||
|
|
||||||
|
post_report_data[:undefined] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def post_report_undefined(data)
|
||||||
|
if post_report_data[:undefined] && options[:on_finish]
|
||||||
|
options[:on_finish].call(options[:target])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
59
lib/flowerbox/reporter/verbose.rb
Normal file
59
lib/flowerbox/reporter/verbose.rb
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
require 'flowerbox/reporter/console_base'
|
||||||
|
|
||||||
|
module Flowerbox::Reporter
|
||||||
|
class Verbose < ConsoleBase
|
||||||
|
def initialize
|
||||||
|
@name_stack = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def indent(text, count)
|
||||||
|
" " * count + text
|
||||||
|
end
|
||||||
|
|
||||||
|
def report_progress(result)
|
||||||
|
if result.name
|
||||||
|
result.name.each_with_index do |name, index|
|
||||||
|
if @name_stack[index] != name
|
||||||
|
@name_stack = []
|
||||||
|
|
||||||
|
if name != result.name.last
|
||||||
|
message = name
|
||||||
|
else
|
||||||
|
message = self.send("progress_#{result.type}", result)
|
||||||
|
if !(file_display = path_for(result)).empty?
|
||||||
|
message << " # #{file_display}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
puts indent(message, index)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@name_stack = result.name.dup
|
||||||
|
else
|
||||||
|
puts result.data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def progress_skipped(result)
|
||||||
|
result.name.last.foreground(:blue)
|
||||||
|
end
|
||||||
|
|
||||||
|
def progress_success(result)
|
||||||
|
result.name.last.foreground(:green)
|
||||||
|
end
|
||||||
|
|
||||||
|
def progress_failure(result)
|
||||||
|
result.name.last.foreground(:red)
|
||||||
|
end
|
||||||
|
|
||||||
|
def progress_pending(result)
|
||||||
|
"#{result.name.last} (Pending)".foreground(:yellow)
|
||||||
|
end
|
||||||
|
|
||||||
|
def progress_undefined(result)
|
||||||
|
"#{result.name.last} (Undefined)".foreground(:yellow)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
46
lib/flowerbox/reporter_list.rb
Normal file
46
lib/flowerbox/reporter_list.rb
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
require 'forwardable'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
class ReporterList
|
||||||
|
include Enumerable
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
def_delegators :@reporters, :empty?
|
||||||
|
|
||||||
|
def each(&block)
|
||||||
|
@reporters.each(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
clear!
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear!
|
||||||
|
@reporters = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def <<(reporter)
|
||||||
|
@reporters << Flowerbox::Reporter.for(reporter)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add(reporter, options)
|
||||||
|
reporter_obj = Flowerbox::Reporter.for(reporter)
|
||||||
|
reporter_obj.options = options
|
||||||
|
@reporters << reporter_obj
|
||||||
|
end
|
||||||
|
|
||||||
|
def start(message)
|
||||||
|
on_each_reporter(:start, message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def log(message)
|
||||||
|
on_each_reporter(:log, message)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def on_each_reporter(method, message)
|
||||||
|
@reporters.each { |reporter| reporter.send(method, message) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
30
lib/flowerbox/result.rb
Normal file
30
lib/flowerbox/result.rb
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
module Flowerbox
|
||||||
|
module Result
|
||||||
|
extend Flowerbox::CoreExt::Module
|
||||||
|
|
||||||
|
module Cucumber
|
||||||
|
extend Flowerbox::CoreExt::Module
|
||||||
|
end
|
||||||
|
|
||||||
|
module Jasmine
|
||||||
|
extend Flowerbox::CoreExt::Module
|
||||||
|
end
|
||||||
|
|
||||||
|
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)
|
||||||
|
require "flowerbox/result/#{test_env}/#{type}"
|
||||||
|
|
||||||
|
test_group = find_constant(test_env)
|
||||||
|
test_group.find_constant(type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
74
lib/flowerbox/result/base.rb
Normal file
74
lib/flowerbox/result/base.rb
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
require 'forwardable'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
module Result
|
||||||
|
class Base
|
||||||
|
require 'flowerbox/result/file_info'
|
||||||
|
include Flowerbox::Result::FileInfo
|
||||||
|
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
attr_reader :data
|
||||||
|
|
||||||
|
def_delegators :data, :[]
|
||||||
|
|
||||||
|
def type
|
||||||
|
@type ||= self.class.name.split("::").last.downcase.to_sym
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(data)
|
||||||
|
@data = data
|
||||||
|
end
|
||||||
|
|
||||||
|
def name
|
||||||
|
data['name']
|
||||||
|
end
|
||||||
|
|
||||||
|
def message
|
||||||
|
data['message']
|
||||||
|
end
|
||||||
|
|
||||||
|
def runners
|
||||||
|
data['runners']
|
||||||
|
end
|
||||||
|
|
||||||
|
def file
|
||||||
|
data['file']
|
||||||
|
end
|
||||||
|
|
||||||
|
def ==(other)
|
||||||
|
name == other.name
|
||||||
|
end
|
||||||
|
|
||||||
|
def <=>(other)
|
||||||
|
self.name.length <=> other.name.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def runners
|
||||||
|
@runners ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def success?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def failure?
|
||||||
|
!success?
|
||||||
|
end
|
||||||
|
|
||||||
|
def pending?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def <<(other)
|
||||||
|
runners
|
||||||
|
@runners += other.runners
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_environment
|
||||||
|
Flowerbox.test_environment
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
0
lib/flowerbox/result/cucumber/base.rb
Normal file
0
lib/flowerbox/result/cucumber/base.rb
Normal file
7
lib/flowerbox/result/cucumber/failure.rb
Normal file
7
lib/flowerbox/result/cucumber/failure.rb
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module Flowerbox::Result
|
||||||
|
module Cucumber
|
||||||
|
class Failure < Flowerbox::Result::Failure
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
8
lib/flowerbox/result/cucumber/pending.rb
Normal file
8
lib/flowerbox/result/cucumber/pending.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module Flowerbox::Result
|
||||||
|
module Cucumber
|
||||||
|
class Pending < Flowerbox::Result::Pending
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
7
lib/flowerbox/result/cucumber/skipped.rb
Normal file
7
lib/flowerbox/result/cucumber/skipped.rb
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module Flowerbox::Result
|
||||||
|
module Cucumber
|
||||||
|
class Skipped < Flowerbox::Result::Pending
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
10
lib/flowerbox/result/cucumber/success.rb
Normal file
10
lib/flowerbox/result/cucumber/success.rb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module Flowerbox::Result
|
||||||
|
module Cucumber
|
||||||
|
class Success < Flowerbox::Result::Base
|
||||||
|
def success?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
14
lib/flowerbox/result/cucumber/undefined.rb
Normal file
14
lib/flowerbox/result/cucumber/undefined.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module Flowerbox::Result
|
||||||
|
module Cucumber
|
||||||
|
class Undefined < Flowerbox::Result::Pending
|
||||||
|
def step_type
|
||||||
|
@data['step_type']
|
||||||
|
end
|
||||||
|
|
||||||
|
def original_name
|
||||||
|
@data['original_name']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
6
lib/flowerbox/result/exception.rb
Normal file
6
lib/flowerbox/result/exception.rb
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module Flowerbox::Result
|
||||||
|
class Exception < Base
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
20
lib/flowerbox/result/failure.rb
Normal file
20
lib/flowerbox/result/failure.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module Flowerbox::Result
|
||||||
|
class Failure < Flowerbox::Result::Base
|
||||||
|
def failures
|
||||||
|
@failures ||= @data['failures'].collect { |fail| FailureMessage.new(fail) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def <<(other)
|
||||||
|
super
|
||||||
|
|
||||||
|
other.failures.each do |failure|
|
||||||
|
if existing_failure = failures.find { |f| f.message == failure.message }
|
||||||
|
existing_failure.runners << failure.runner
|
||||||
|
else
|
||||||
|
failures << failure
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
56
lib/flowerbox/result/failure_message.rb
Normal file
56
lib/flowerbox/result/failure_message.rb
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
module Flowerbox::Result
|
||||||
|
class FailureMessage
|
||||||
|
include Flowerbox::Result::FileInfo
|
||||||
|
|
||||||
|
attr_reader :data
|
||||||
|
|
||||||
|
def initialize(data)
|
||||||
|
@data = data
|
||||||
|
end
|
||||||
|
|
||||||
|
def message
|
||||||
|
@data['message']
|
||||||
|
end
|
||||||
|
|
||||||
|
def file
|
||||||
|
file = first_local_stack
|
||||||
|
|
||||||
|
if file['__F__']
|
||||||
|
file[%r{^.*__F__/([^:]+:\d+)}, 1]
|
||||||
|
else
|
||||||
|
file[%r{\(([^:]+\:\d+)}, 1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def runner
|
||||||
|
@data['runner']
|
||||||
|
end
|
||||||
|
|
||||||
|
def runners
|
||||||
|
@runners ||= [ runner ]
|
||||||
|
end
|
||||||
|
|
||||||
|
def stack
|
||||||
|
@data['stack'] || []
|
||||||
|
end
|
||||||
|
|
||||||
|
def filtered_stack
|
||||||
|
filtered_stack = stack.reject { |line| Flowerbox.backtrace_filter.any? { |filter| line[filter] } }
|
||||||
|
|
||||||
|
filtered_stack.shift if exception?
|
||||||
|
|
||||||
|
filter_coffeescript_lines(filtered_stack)
|
||||||
|
end
|
||||||
|
|
||||||
|
def first_local_stack
|
||||||
|
@first_local_stack ||= (stack[1..-1] || []).find do |line|
|
||||||
|
!system_files.any? { |file| line[%r{\(#{file}}] }
|
||||||
|
end || stack[1] || ''
|
||||||
|
end
|
||||||
|
|
||||||
|
def exception?
|
||||||
|
(stack[0] || '')[%r{^.+Error: }]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
52
lib/flowerbox/result/file_info.rb
Normal file
52
lib/flowerbox/result/file_info.rb
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
module Flowerbox::Result::FileInfo
|
||||||
|
UNKNOWN = '__unknown__'
|
||||||
|
|
||||||
|
def translated_file
|
||||||
|
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[%r{\.coffee$}]
|
||||||
|
end
|
||||||
|
|
||||||
|
def filename
|
||||||
|
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 = "~#{coffeescript_line_to_js_line(@line_number)}" if file_translated?
|
||||||
|
@line_number ||= "0"
|
||||||
|
end
|
||||||
|
|
||||||
|
alias :line :line_number
|
||||||
|
|
||||||
|
def translated_file_and_line
|
||||||
|
"#{translated_file.gsub(%r{^#{Dir.pwd}/}, '')}:#{line_number}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def system_files
|
||||||
|
Flowerbox.test_environment.system_files
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_coffeescript_lines(lines)
|
||||||
|
[ lines ].flatten.collect { |line|
|
||||||
|
line.gsub(%r{\.coffee:(\d+)}) { |_| ".coffee:~#{coffeescript_line_to_js_line($1)}" }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def coffeescript_line_to_js_line(number)
|
||||||
|
(number.to_i * 0.67 + 1).to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
7
lib/flowerbox/result/jasmine/failure.rb
Normal file
7
lib/flowerbox/result/jasmine/failure.rb
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module Flowerbox::Result
|
||||||
|
module Jasmine
|
||||||
|
class Failure < Flowerbox::Result::Failure
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
8
lib/flowerbox/result/jasmine/success.rb
Normal file
8
lib/flowerbox/result/jasmine/success.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module Flowerbox::Result
|
||||||
|
module Jasmine
|
||||||
|
class Success < Flowerbox::Result::Base
|
||||||
|
def success? ; true ; end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
14
lib/flowerbox/result/pending.rb
Normal file
14
lib/flowerbox/result/pending.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module Flowerbox::Result
|
||||||
|
class Pending < Base
|
||||||
|
def pending?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def failure?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def failures; [] ; end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
102
lib/flowerbox/result_set.rb
Normal file
102
lib/flowerbox/result_set.rb
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
module Flowerbox
|
||||||
|
class ResultSet
|
||||||
|
attr_reader :results, :options
|
||||||
|
|
||||||
|
attr_accessor :time
|
||||||
|
|
||||||
|
def self.from_results(results, options)
|
||||||
|
results = results.collect do |result|
|
||||||
|
result['runner'] = options[:runner]
|
||||||
|
|
||||||
|
if name = result['name']
|
||||||
|
Flowerbox::Result.for(result['source'], result['status']).new(result)
|
||||||
|
else
|
||||||
|
Flowerbox::Result::Exception.new(result)
|
||||||
|
end
|
||||||
|
end.flatten
|
||||||
|
|
||||||
|
new(results, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.for(runner)
|
||||||
|
new(results, :runner => runner)
|
||||||
|
end
|
||||||
|
|
||||||
|
def reporters
|
||||||
|
Flowerbox.reporters
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(results = [], options = {})
|
||||||
|
@results, @options = results, options
|
||||||
|
end
|
||||||
|
|
||||||
|
def <<(other)
|
||||||
|
other.results.each do |other_result|
|
||||||
|
if existing_result = results.find { |result| result == other_result }
|
||||||
|
existing_result << other_result
|
||||||
|
else
|
||||||
|
results << other_result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def exitstatus
|
||||||
|
if results.any?(&:failure?)
|
||||||
|
1
|
||||||
|
elsif results.any?(&:pending?)
|
||||||
|
2
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def print(data = {})
|
||||||
|
reporters.each { |reporter| reporter.report(flattened_gathered_results, data) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_tests
|
||||||
|
results.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_failures
|
||||||
|
results.reject(&:success?).length
|
||||||
|
end
|
||||||
|
|
||||||
|
def print_progress
|
||||||
|
@results.each do |result|
|
||||||
|
reporters.each do |reporter|
|
||||||
|
begin
|
||||||
|
reporter.report_progress(result)
|
||||||
|
rescue => e
|
||||||
|
puts e.message
|
||||||
|
puts e.backtrace.join("\n")
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def gathered_results
|
||||||
|
return @gathered_results if @gathered_results
|
||||||
|
|
||||||
|
@gathered_results = []
|
||||||
|
|
||||||
|
results.each do |result|
|
||||||
|
if !(gathered_result = @gathered_results.find { |g| g.name == result.name })
|
||||||
|
gathered_result = GatheredResult.new(result.name)
|
||||||
|
|
||||||
|
@gathered_results << gathered_result
|
||||||
|
end
|
||||||
|
|
||||||
|
gathered_result << result
|
||||||
|
end
|
||||||
|
|
||||||
|
@gathered_results
|
||||||
|
end
|
||||||
|
|
||||||
|
def flattened_gathered_results
|
||||||
|
gathered_results.collect(&:results).flatten
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
79
lib/flowerbox/run/base.rb
Normal file
79
lib/flowerbox/run/base.rb
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
module Flowerbox::Run
|
||||||
|
class Base
|
||||||
|
attr_reader :dir, :options
|
||||||
|
|
||||||
|
def self.execute(dir, options)
|
||||||
|
new(dir, options).execute
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(dir, options)
|
||||||
|
@dir, @options = dir, options
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute
|
||||||
|
raise StandardError.new("override in subclass")
|
||||||
|
end
|
||||||
|
|
||||||
|
def prep!
|
||||||
|
Flowerbox.reset!
|
||||||
|
|
||||||
|
load File.join(dir, 'spec_helper.rb')
|
||||||
|
|
||||||
|
require 'coffee_script'
|
||||||
|
require 'tilt/coffee'
|
||||||
|
|
||||||
|
Tilt::CoffeeScriptTemplate.default_bare = Flowerbox.bare_coffeescript
|
||||||
|
|
||||||
|
if runners = options[:runners] || options[:runner]
|
||||||
|
Flowerbox.run_with(runners.split(','))
|
||||||
|
end
|
||||||
|
|
||||||
|
Flowerbox.test_environment.run = self
|
||||||
|
Flowerbox.test_environment.set_additional_options(options[:env_options])
|
||||||
|
end
|
||||||
|
|
||||||
|
def sprockets
|
||||||
|
require 'flowerbox/sprockets_handler'
|
||||||
|
|
||||||
|
Flowerbox::SprocketsHandler.new(:asset_paths => asset_paths)
|
||||||
|
end
|
||||||
|
|
||||||
|
def asset_paths
|
||||||
|
[
|
||||||
|
Flowerbox.path.join("lib/assets/javascripts"),
|
||||||
|
Flowerbox.path.join("vendor/assets/javascripts"),
|
||||||
|
@dir,
|
||||||
|
Flowerbox.asset_paths
|
||||||
|
].flatten
|
||||||
|
end
|
||||||
|
|
||||||
|
def spec_files
|
||||||
|
return @spec_files if @spec_files
|
||||||
|
|
||||||
|
@spec_files = []
|
||||||
|
|
||||||
|
Flowerbox.spec_patterns.each do |pattern|
|
||||||
|
Dir[File.join(dir, pattern)].each do |file|
|
||||||
|
if !only || only.find { |match| file[%r{^#{match}}] }
|
||||||
|
@spec_files << File.expand_path(file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec_files
|
||||||
|
end
|
||||||
|
|
||||||
|
def only
|
||||||
|
return @only if @only
|
||||||
|
|
||||||
|
@only = options[:files] || []
|
||||||
|
@only = nil if only.empty?
|
||||||
|
@only
|
||||||
|
end
|
||||||
|
|
||||||
|
def system_files
|
||||||
|
%w{flowerbox json2}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
43
lib/flowerbox/run/debug.rb
Normal file
43
lib/flowerbox/run/debug.rb
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
module Flowerbox::Run
|
||||||
|
class Debug < Base
|
||||||
|
def execute
|
||||||
|
prep!
|
||||||
|
|
||||||
|
env = Flowerbox.runner_environment.first
|
||||||
|
env.setup(sprockets, spec_files, options)
|
||||||
|
|
||||||
|
Flowerbox.reporters.clear!
|
||||||
|
|
||||||
|
puts "Flowerbox debug server running test prepared for #{env.console_name} on #{env.server.address}"
|
||||||
|
|
||||||
|
env.server.start
|
||||||
|
|
||||||
|
trap('INT') do
|
||||||
|
env.server.stop
|
||||||
|
end
|
||||||
|
|
||||||
|
@restart = false
|
||||||
|
|
||||||
|
trap('QUIT') do
|
||||||
|
puts "Restarting Flowerbox server..."
|
||||||
|
@restart = true
|
||||||
|
env.server.stop
|
||||||
|
end
|
||||||
|
|
||||||
|
while env.server.alive?
|
||||||
|
sleep 0.25
|
||||||
|
end
|
||||||
|
|
||||||
|
if @restart
|
||||||
|
debug(dir, options)
|
||||||
|
else
|
||||||
|
puts "Flowerbox finished."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def options
|
||||||
|
@options.dup.merge(:debug => true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
35
lib/flowerbox/run/test.rb
Normal file
35
lib/flowerbox/run/test.rb
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
module Flowerbox::Run
|
||||||
|
class Test < Base
|
||||||
|
require 'flowerbox/runner/base'
|
||||||
|
|
||||||
|
def execute
|
||||||
|
prep!
|
||||||
|
|
||||||
|
result_set = Flowerbox::ResultSet.new
|
||||||
|
|
||||||
|
time = 0
|
||||||
|
realtime = Time.now.to_i
|
||||||
|
failed = false
|
||||||
|
|
||||||
|
runner_envs = Flowerbox.runner_environment.collect do |env|
|
||||||
|
env.ensure_configured!
|
||||||
|
|
||||||
|
result_set << env.run(sprockets, spec_files, options)
|
||||||
|
failed = true if !env.started?
|
||||||
|
|
||||||
|
time += env.time
|
||||||
|
|
||||||
|
env
|
||||||
|
end
|
||||||
|
|
||||||
|
result_set.print(:time => time, :realtime => Time.now.to_i - realtime)
|
||||||
|
|
||||||
|
runner_envs.each(&:cleanup)
|
||||||
|
|
||||||
|
failed ? 1 : result_set.exitstatus
|
||||||
|
rescue Flowerbox::Runner::Base::RunnerDiedError
|
||||||
|
255
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
12
lib/flowerbox/runner.rb
Normal file
12
lib/flowerbox/runner.rb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module Flowerbox
|
||||||
|
module Runner
|
||||||
|
extend Flowerbox::CoreExt::Module
|
||||||
|
|
||||||
|
def self.find_constant(string)
|
||||||
|
require "flowerbox/runner/#{string}"
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
247
lib/flowerbox/runner/base.rb
Normal file
247
lib/flowerbox/runner/base.rb
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
require 'thread'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
module Runner
|
||||||
|
class Base
|
||||||
|
attr_reader :sprockets, :spec_files, :options, :time
|
||||||
|
|
||||||
|
attr_accessor :results
|
||||||
|
|
||||||
|
MAX_COUNT = 50
|
||||||
|
|
||||||
|
class RunnerDiedError < StandardError ; end
|
||||||
|
|
||||||
|
def self.mutex
|
||||||
|
@mutex ||= Mutex.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@results = ResultSet.new
|
||||||
|
@started = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def started?
|
||||||
|
@started
|
||||||
|
end
|
||||||
|
|
||||||
|
def starting(*args)
|
||||||
|
@started = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def reporters
|
||||||
|
Flowerbox.reporters
|
||||||
|
end
|
||||||
|
|
||||||
|
def load(file)
|
||||||
|
sprockets.find_asset(file.first, :bundle => false).body
|
||||||
|
end
|
||||||
|
|
||||||
|
def instrument(data)
|
||||||
|
results = []
|
||||||
|
|
||||||
|
data.flatten.first.each do |filename, lines|
|
||||||
|
results += lines.reject { |line| line == nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
visited = results.find_all { |result| result == 1 }.length
|
||||||
|
|
||||||
|
Flowerbox.notify "Coverage: %d/%d lines (%0.2f%% of instrumented code)" % [ visited, results.length, (visited.to_f / results.length.to_f) * 100 ]
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_alive
|
||||||
|
while @count < MAX_COUNT && !finished?
|
||||||
|
@count += 1 if @timer_running
|
||||||
|
sleep 0.1
|
||||||
|
end
|
||||||
|
|
||||||
|
if !finished?
|
||||||
|
Flowerbox.notify "Something died hard. Here are the tests that did get run before Flowerbox died.".foreground(:red)
|
||||||
|
Flowerbox.notify tests.flatten.join("\n").foreground(:red)
|
||||||
|
server.stop
|
||||||
|
Flowerbox.server = nil
|
||||||
|
|
||||||
|
raise RunnerDiedError.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup(sprockets, spec_files, options)
|
||||||
|
@sprockets, @spec_files, @options = sprockets, spec_files, options
|
||||||
|
|
||||||
|
Flowerbox.test_environment.runner = self
|
||||||
|
Flowerbox.test_environment.inject_into(sprockets)
|
||||||
|
Flowerbox.additional_files.each { |file| sprockets.add(file) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(*args)
|
||||||
|
self.class.mutex.synchronize do
|
||||||
|
setup(*args)
|
||||||
|
|
||||||
|
@count = 0
|
||||||
|
@timer_running = true
|
||||||
|
|
||||||
|
reporters.start("Flowerbox running your #{Flowerbox.test_environment.name} tests on #{console_name}...")
|
||||||
|
|
||||||
|
attempts = 3
|
||||||
|
|
||||||
|
require 'flowerbox/server'
|
||||||
|
|
||||||
|
while true
|
||||||
|
begin
|
||||||
|
server.start
|
||||||
|
|
||||||
|
break
|
||||||
|
rescue Flowerbox::Server::ServerDiedError
|
||||||
|
attempts -= 1
|
||||||
|
raise RunnerDiedError.new if attempts == 0
|
||||||
|
|
||||||
|
Flowerbox.server = nil
|
||||||
|
ensure
|
||||||
|
@sprockets.reset!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
server.stop
|
||||||
|
end
|
||||||
|
|
||||||
|
@results
|
||||||
|
rescue => e
|
||||||
|
case e
|
||||||
|
when ExecJS::RuntimeError, ExecJS::ProgramError, Sprockets::FileNotFound, Sprockets::CircularDependencyError
|
||||||
|
handle_coffeescript_compilation_error(e)
|
||||||
|
else
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def configured?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def configure ; end
|
||||||
|
|
||||||
|
def pause_timer(*args)
|
||||||
|
@timer_running = false
|
||||||
|
@count = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def unpause_timer(*args)
|
||||||
|
@timer_running = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def debug?
|
||||||
|
options[:debug] == true
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_configured!
|
||||||
|
if !configured?
|
||||||
|
Flowerbox.notify "#{console_name} is not configured for this project, configuring now..."
|
||||||
|
configure
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def type
|
||||||
|
self.class.name.to_s.split('::').last.downcase.to_sym
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_test_environment
|
||||||
|
Flowerbox.test_environment.start
|
||||||
|
rescue ExecJS::ProgramError => e
|
||||||
|
handle_coffeescript_compilation_error(e)
|
||||||
|
end
|
||||||
|
|
||||||
|
def time=(time)
|
||||||
|
@results.time = time
|
||||||
|
end
|
||||||
|
|
||||||
|
def server
|
||||||
|
require 'flowerbox/rack'
|
||||||
|
|
||||||
|
if Flowerbox.server
|
||||||
|
Flowerbox.server.runner = self
|
||||||
|
Flowerbox.server.app.runner = self
|
||||||
|
Flowerbox.server.app.sprockets = sprockets
|
||||||
|
return Flowerbox.server
|
||||||
|
end
|
||||||
|
|
||||||
|
require 'flowerbox/server'
|
||||||
|
|
||||||
|
server_options = { :app => Flowerbox::Rack.new(self, sprockets) }
|
||||||
|
server_options[:logging] = true if options[:verbose_server]
|
||||||
|
server_options[:port] = Flowerbox.port
|
||||||
|
|
||||||
|
Flowerbox.server = Flowerbox::Server.new(self, server_options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def log(message)
|
||||||
|
reporters.log(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def tests
|
||||||
|
@tests ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_test(new_tests)
|
||||||
|
tests << new_tests
|
||||||
|
|
||||||
|
Flowerbox.notify(new_tests.flatten) if options[:verbose_server]
|
||||||
|
|
||||||
|
@count = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def failures
|
||||||
|
@failures ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def finish_test(test_results)
|
||||||
|
results = result_set_from_test_results(test_results)
|
||||||
|
|
||||||
|
results.print_progress
|
||||||
|
|
||||||
|
@count = 0
|
||||||
|
|
||||||
|
@results << results
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_count
|
||||||
|
@tests.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def failure_count
|
||||||
|
@failures.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def time
|
||||||
|
@time ||= 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def results(time)
|
||||||
|
@time = time.first
|
||||||
|
|
||||||
|
@finished = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def finished?
|
||||||
|
@finished
|
||||||
|
end
|
||||||
|
|
||||||
|
def ping
|
||||||
|
@count = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_coffeescript_compilation_error(exception)
|
||||||
|
Flowerbox.notify(exception.message.foreground(:red))
|
||||||
|
@finished = true
|
||||||
|
|
||||||
|
raise RunnerDiedError.new(exception.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def result_set_from_test_results(test_results)
|
||||||
|
ResultSet.from_results(test_results, options.merge(:runner => name))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
22
lib/flowerbox/runner/chrome.rb
Normal file
22
lib/flowerbox/runner/chrome.rb
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
require 'flowerbox/runner/selenium'
|
||||||
|
|
||||||
|
class Flowerbox::Runner::Chrome < Flowerbox::Runner::Selenium
|
||||||
|
|
||||||
|
def name
|
||||||
|
"Chrome"
|
||||||
|
end
|
||||||
|
|
||||||
|
def console_name
|
||||||
|
"C".foreground('#4f97d1') +
|
||||||
|
"h".foreground('#ec5244') +
|
||||||
|
"r".foreground('#fdd901') +
|
||||||
|
"o".foreground('#4f97d1') +
|
||||||
|
"m".foreground('#5cb15b') +
|
||||||
|
"e".foreground('#ec5244')
|
||||||
|
end
|
||||||
|
|
||||||
|
def browser
|
||||||
|
Flowerbox.browsers[:chrome] ||= ::Selenium::WebDriver.for(:chrome)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
16
lib/flowerbox/runner/firefox.rb
Normal file
16
lib/flowerbox/runner/firefox.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
require 'flowerbox/runner/selenium'
|
||||||
|
|
||||||
|
class Flowerbox::Runner::Firefox < Flowerbox::Runner::Selenium
|
||||||
|
def name
|
||||||
|
"Firefox"
|
||||||
|
end
|
||||||
|
|
||||||
|
def console_name
|
||||||
|
"Firefox".foreground('#d0450b')
|
||||||
|
end
|
||||||
|
|
||||||
|
def browser
|
||||||
|
Flowerbox.browsers[:firefox] ||= ::Selenium::WebDriver.for(:firefox)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
132
lib/flowerbox/runner/node.rb
Normal file
132
lib/flowerbox/runner/node.rb
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
require 'tempfile'
|
||||||
|
require 'json'
|
||||||
|
require 'flowerbox/runner/base'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
module Runner
|
||||||
|
class Node < Base
|
||||||
|
def name
|
||||||
|
"node.js"
|
||||||
|
end
|
||||||
|
|
||||||
|
def console_name
|
||||||
|
"n".foreground(:white) +
|
||||||
|
"o".foreground('#8cc84b') +
|
||||||
|
"de".foreground(:white) +
|
||||||
|
".js".foreground('#8cc84b')
|
||||||
|
end
|
||||||
|
|
||||||
|
def type
|
||||||
|
:node
|
||||||
|
end
|
||||||
|
|
||||||
|
def configured?
|
||||||
|
File.directory?(File.join(Dir.pwd, 'node_modules/jsdom')) &&
|
||||||
|
File.directory?(File.join(Dir.pwd, 'node_modules/ws'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def cleanup ; end
|
||||||
|
|
||||||
|
def configure
|
||||||
|
Flowerbox.notify %x{bash -c "mkdir -p node_modules && npm link jsdom && npm link ws"}
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(sprockets, spec_files, options)
|
||||||
|
super do
|
||||||
|
begin
|
||||||
|
file = File.join(Dir.pwd, ".node-tmp.#{Time.now.to_i}.js")
|
||||||
|
File.open(file, 'wb') { |fh| fh.print template }
|
||||||
|
|
||||||
|
system %{node #{file}}
|
||||||
|
|
||||||
|
if $?.exitstatus == 0
|
||||||
|
count = 20
|
||||||
|
while !finished? && count > 0
|
||||||
|
sleep 0.1
|
||||||
|
|
||||||
|
count -= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
File.unlink(file) if file
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def template
|
||||||
|
env = start_test_environment
|
||||||
|
|
||||||
|
<<-JS
|
||||||
|
// whoa node
|
||||||
|
var fs = require('fs'),
|
||||||
|
vm = require('vm'),
|
||||||
|
http = require('http'),
|
||||||
|
jsdom = require('jsdom'),
|
||||||
|
ws = require('ws')
|
||||||
|
|
||||||
|
// expand the sandbox a bit
|
||||||
|
var context = function() {};
|
||||||
|
for (method in global) { context[method] = global[method]; }
|
||||||
|
|
||||||
|
jsdom.env(
|
||||||
|
"<html><head><title></title></head><body></body></html>", [], function(errors, window) {
|
||||||
|
context.window = window;
|
||||||
|
context.WebSocket = ws;
|
||||||
|
|
||||||
|
var gotFlowerbox = false;
|
||||||
|
|
||||||
|
console._log = console.log;
|
||||||
|
|
||||||
|
var socket = new ws('ws://localhost:#{server.port + 1}/');
|
||||||
|
socket.onopen = function() {
|
||||||
|
var files = #{sprockets.files.to_json};
|
||||||
|
|
||||||
|
var fileLoader = function() {
|
||||||
|
if (files.length > 0) {
|
||||||
|
var file = files.shift();
|
||||||
|
|
||||||
|
socket.onmessage = function(data) {
|
||||||
|
vm.runInNewContext(data.data, context, file);
|
||||||
|
|
||||||
|
for (thing in window) {
|
||||||
|
if (!context[thing]) { context[thing] = window[thing] }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gotFlowerbox && context.Flowerbox) {
|
||||||
|
context.Flowerbox.environment = 'node';
|
||||||
|
context.Flowerbox.UNKNOWN = '#{Flowerbox::Result::FileInfo::UNKNOWN}';
|
||||||
|
context.Flowerbox.socket = socket;
|
||||||
|
|
||||||
|
gotFlowerbox = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileLoader();
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.send(JSON.stringify(['load', file]));
|
||||||
|
} else {
|
||||||
|
socket.onmessage = null;
|
||||||
|
|
||||||
|
#{env}
|
||||||
|
|
||||||
|
var waitForFinish;
|
||||||
|
waitForFinish = function() {
|
||||||
|
if (!context.Flowerbox.started || !context.Flowerbox.done) {
|
||||||
|
process.nextTick(waitForFinish);
|
||||||
|
} else {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
waitForFinish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fileLoader();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
JS
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
91
lib/flowerbox/runner/selenium.rb
Normal file
91
lib/flowerbox/runner/selenium.rb
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
require 'selenium-webdriver'
|
||||||
|
require 'flowerbox/runner/base'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
module Runner
|
||||||
|
class Selenium < Base
|
||||||
|
def name
|
||||||
|
raise StandardError.new("Override me")
|
||||||
|
end
|
||||||
|
|
||||||
|
def type
|
||||||
|
:selenium
|
||||||
|
end
|
||||||
|
|
||||||
|
def cleanup
|
||||||
|
end
|
||||||
|
|
||||||
|
def browser
|
||||||
|
raise StandardError.new("Define a browser")
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(sprockets, spec_files, options)
|
||||||
|
super do
|
||||||
|
navigate = Proc.new { browser.navigate.to "http://localhost:#{server.port}/?#{Time.now.to_f}" }
|
||||||
|
|
||||||
|
begin
|
||||||
|
navigate.call
|
||||||
|
rescue ::Selenium::WebDriver::Error::UnknownError => e
|
||||||
|
puts "Browser communication error, reopening all browsers...".foreground(:yellow)
|
||||||
|
Flowerbox.cleanup!
|
||||||
|
|
||||||
|
navigate.call
|
||||||
|
end
|
||||||
|
|
||||||
|
ensure_alive
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def log(msg)
|
||||||
|
puts msg
|
||||||
|
end
|
||||||
|
|
||||||
|
def page_title
|
||||||
|
"Flowerbox - #{Flowerbox.test_environment.name} Runner"
|
||||||
|
end
|
||||||
|
|
||||||
|
def template
|
||||||
|
env = start_test_environment
|
||||||
|
|
||||||
|
<<-HTML
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>#{page_title}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>#{page_title}</h1>
|
||||||
|
<pre id="queue"></pre>
|
||||||
|
<script type="text/javascript">
|
||||||
|
console._log = console.log;
|
||||||
|
|
||||||
|
console.log = function(msg) {
|
||||||
|
console._log(msg);
|
||||||
|
Flowerbox.contact("log", msg);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
#{template_files.join("\n")}
|
||||||
|
<script type="text/javascript">
|
||||||
|
Flowerbox.environment = '#{name}';
|
||||||
|
Flowerbox.UNKNOWN = '#{Flowerbox::Result::FileInfo::UNKNOWN}';
|
||||||
|
|
||||||
|
var context = this;
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
Flowerbox.socket = new WebSocket('ws://localhost:#{server.port + 1}/');
|
||||||
|
Flowerbox.socket.onopen = function() {
|
||||||
|
#{env}
|
||||||
|
};
|
||||||
|
}, false);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
HTML
|
||||||
|
end
|
||||||
|
|
||||||
|
def template_files
|
||||||
|
sprockets.files.collect { |file| %{<script type="text/javascript" src="/__F__/#{sprockets.logical_path_for(file)}?#{Time.now.to_f}"></script>} }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
176
lib/flowerbox/server.rb
Normal file
176
lib/flowerbox/server.rb
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
require 'rack'
|
||||||
|
require 'net/http'
|
||||||
|
require 'socket'
|
||||||
|
require 'rack/builder'
|
||||||
|
require 'thin'
|
||||||
|
require 'em-websocket'
|
||||||
|
require 'json'
|
||||||
|
require 'forwardable'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
class Server
|
||||||
|
attr_reader :options
|
||||||
|
attr_accessor :runner
|
||||||
|
|
||||||
|
class MissingRackApp < StandardError ; end
|
||||||
|
class ServerDiedError < StandardError ; end
|
||||||
|
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
def_delegators :@server_thread, :alive?
|
||||||
|
|
||||||
|
def initialize(runner, options = {})
|
||||||
|
@runner = runner
|
||||||
|
@options = { :logging => false }.merge(options || {})
|
||||||
|
end
|
||||||
|
|
||||||
|
def app
|
||||||
|
options[:app] || raise(MissingRackApp.new)
|
||||||
|
end
|
||||||
|
|
||||||
|
def rack_app
|
||||||
|
Thin::Logging.silent = !options[:logging]
|
||||||
|
|
||||||
|
rack_app = app
|
||||||
|
|
||||||
|
if options[:logging]
|
||||||
|
rack_app = ::Rack::Builder.new do
|
||||||
|
use ::Rack::CommonLogger, STDOUT
|
||||||
|
run app
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rack_app
|
||||||
|
end
|
||||||
|
|
||||||
|
def websocket_app(ws)
|
||||||
|
ws.onmessage { |message|
|
||||||
|
command, data = JSON.parse(message)
|
||||||
|
|
||||||
|
begin
|
||||||
|
output = runner.send(command, [ data ].flatten)
|
||||||
|
|
||||||
|
if command == 'load'
|
||||||
|
ws.send(output)
|
||||||
|
else
|
||||||
|
ws.send("ok")
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
$stderr.puts e.message
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
@server_thread = Thread.new do
|
||||||
|
begin
|
||||||
|
server_options = { :Port => port, :Host => interface }
|
||||||
|
|
||||||
|
EventMachine.run do
|
||||||
|
EventMachine::WebSocket.start(:host => interface, :port => port + 1, &method(:websocket_app))
|
||||||
|
|
||||||
|
::Rack::Handler::Thin.run(rack_app, server_options) do |server|
|
||||||
|
Thread.current[:server] = server
|
||||||
|
|
||||||
|
trap('QUIT') { server.stop }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
@server_thread[:exception] = e
|
||||||
|
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
while !@server_thread[:server] && alive?
|
||||||
|
sleep 0.1
|
||||||
|
end
|
||||||
|
|
||||||
|
if @server_thread[:exception]
|
||||||
|
raise @server_thread[:exception]
|
||||||
|
else
|
||||||
|
raise ServerDiedError.new if !alive?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def stop
|
||||||
|
if @server_thread
|
||||||
|
@server_thread[:server].stop rescue nil
|
||||||
|
EventMachine.stop_event_loop
|
||||||
|
@server_thread.kill
|
||||||
|
|
||||||
|
wait_for_server_to_stop
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def interface
|
||||||
|
options[:interface] || '0.0.0.0'
|
||||||
|
end
|
||||||
|
|
||||||
|
def port
|
||||||
|
return @port if @port
|
||||||
|
|
||||||
|
if options[:port]
|
||||||
|
return @port = options[:port]
|
||||||
|
end
|
||||||
|
|
||||||
|
attempts = 20
|
||||||
|
|
||||||
|
begin
|
||||||
|
try_server_to_something(nil, Proc.new { |port| @port = port }, (random_port / 2).floor * 2)
|
||||||
|
|
||||||
|
attempts -= 1
|
||||||
|
end while !@port and attempts != 0
|
||||||
|
|
||||||
|
raise StandardError.new("can't start server") if attempts == 0
|
||||||
|
|
||||||
|
@port
|
||||||
|
end
|
||||||
|
|
||||||
|
def address
|
||||||
|
@address ||= "http://#{interface}:#{port}/"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def wait_for_server_to_start
|
||||||
|
started = false
|
||||||
|
|
||||||
|
while !started do
|
||||||
|
try_server_to_something(Proc.new { started = true })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def wait_for_server_to_stop
|
||||||
|
while alive? do
|
||||||
|
try_server_to_something(nil, Proc.new { return })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_server_to_something(success, failure = nil, current_port = port)
|
||||||
|
begin
|
||||||
|
connect_interface = '127.0.0.1' if interface == '0.0.0.0'
|
||||||
|
|
||||||
|
socket = TCPSocket.new(connect_interface, current_port)
|
||||||
|
socket.close
|
||||||
|
|
||||||
|
success.call(current_port) if success
|
||||||
|
rescue => e
|
||||||
|
case e
|
||||||
|
when Errno::ECONNREFUSED, Errno::ECONNRESET
|
||||||
|
failure.call(current_port) if failure
|
||||||
|
else
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sleep 0.1
|
||||||
|
end
|
||||||
|
|
||||||
|
def random_port
|
||||||
|
25000 + Kernel.rand(1000)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
78
lib/flowerbox/sprockets_handler.rb
Normal file
78
lib/flowerbox/sprockets_handler.rb
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
require 'sprockets'
|
||||||
|
require 'sprockets/engines'
|
||||||
|
require 'forwardable'
|
||||||
|
require 'sprockets-vendor_gems'
|
||||||
|
require 'fileutils'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
class SprocketsHandler
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
class LogicalPathNotFoundError < StandardError ; end
|
||||||
|
|
||||||
|
attr_reader :files, :options
|
||||||
|
|
||||||
|
def_delegators :environment, :append_path, :register_engine, :[], :call, :find_asset, :paths
|
||||||
|
|
||||||
|
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)
|
||||||
|
assets_for(asset).each { |dependent_asset| files.add(dependent_asset) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def assets_for(asset)
|
||||||
|
find_asset(asset, :bundle => true).to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def environment
|
||||||
|
return @environment if @environment
|
||||||
|
|
||||||
|
@environment = Sprockets::Environment.new
|
||||||
|
|
||||||
|
if Flowerbox.instrument_js?
|
||||||
|
require 'flowerbox/tilt/instrument_js'
|
||||||
|
|
||||||
|
@environment.register_postprocessor 'application/javascript', Flowerbox::Tilt::InstrumentJS
|
||||||
|
end
|
||||||
|
|
||||||
|
default_asset_paths.each { |path| @environment.append_path(path) }
|
||||||
|
|
||||||
|
@environment
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_asset_paths
|
||||||
|
self.class.gem_asset_paths + options[:asset_paths]
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache
|
||||||
|
Sprockets::Cache::FileStore.new(Flowerbox.cache_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
def logical_path_for(asset)
|
||||||
|
asset_path = asset.pathname.to_s
|
||||||
|
|
||||||
|
paths.each do |path|
|
||||||
|
if result = asset_path[%r{^#{path}/(.*)}, 1]
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
raise LogicalPathNotFoundError.new("Could not find logical path for #{asset_path}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@environment.send(:expire_index!)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
12
lib/flowerbox/success.rb
Normal file
12
lib/flowerbox/success.rb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module Flowerbox
|
||||||
|
class Success < BaseResult
|
||||||
|
def print_progress
|
||||||
|
print ".".foreground(:green)
|
||||||
|
end
|
||||||
|
|
||||||
|
def success?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
27
lib/flowerbox/task.rb
Normal file
27
lib/flowerbox/task.rb
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
module Flowerbox
|
||||||
|
class Task
|
||||||
|
include Rake::DSL if defined?(Rake::DSL)
|
||||||
|
|
||||||
|
def self.create(*args)
|
||||||
|
new(*args).add
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :name, :options
|
||||||
|
|
||||||
|
def initialize(name = "flowerbox", options = nil)
|
||||||
|
@name = name
|
||||||
|
@options = options || {}
|
||||||
|
|
||||||
|
@options = { :dir => 'spec/javascripts' }.merge(@options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add
|
||||||
|
desc "Run Flowerbox for the tests in #{options[:dir]}"
|
||||||
|
task(name) do
|
||||||
|
raise StandardError.new("Flowerbox tests failed") if Flowerbox.run(@options[:dir], @options) != 0
|
||||||
|
Flowerbox.cleanup!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
20
lib/flowerbox/test_environment.rb
Normal file
20
lib/flowerbox/test_environment.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module Flowerbox
|
||||||
|
module TestEnvironment
|
||||||
|
extend Flowerbox::CoreExt::Module
|
||||||
|
|
||||||
|
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
|
||||||
|
|
55
lib/flowerbox/test_environment/base.rb
Normal file
55
lib/flowerbox/test_environment/base.rb
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
require 'yaml'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
module TestEnvironment
|
||||||
|
class Base
|
||||||
|
attr_accessor :runner, :run
|
||||||
|
|
||||||
|
def name
|
||||||
|
self.class.name.split("::").last
|
||||||
|
end
|
||||||
|
|
||||||
|
def reporters
|
||||||
|
@reporters ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.transplantable?
|
||||||
|
respond_to?(:transplant)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_additional_options(opts = nil)
|
||||||
|
@options = {}
|
||||||
|
|
||||||
|
if opts
|
||||||
|
case opts
|
||||||
|
when String
|
||||||
|
@options = Hash[YAML.load(opts).collect { |k, v| [ k.to_sym, v ] }]
|
||||||
|
when Hash
|
||||||
|
@options = opts
|
||||||
|
end
|
||||||
|
|
||||||
|
@options[:tags] = [ @options[:tags] ].flatten(1) if @options[:tags]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def inject_into(sprockets)
|
||||||
|
@sprockets = sprockets
|
||||||
|
|
||||||
|
system_files.each { |file| @sprockets.add(file) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def system_files
|
||||||
|
run.system_files + global_system_files + runner_system_files
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
runner.spec_files.each { |file| @sprockets.add(file) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def actual_path_for(file)
|
||||||
|
@sprockets.find_asset(file.gsub(%r{\?.*$}, ''), :bundle => false).pathname.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
112
lib/flowerbox/test_environment/cucumber.rb
Normal file
112
lib/flowerbox/test_environment/cucumber.rb
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
require 'flowerbox/test_environment/base'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
module TestEnvironment
|
||||||
|
class Cucumber < Base
|
||||||
|
def initialize
|
||||||
|
@step_language = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def prefer_step_language(language)
|
||||||
|
@step_language = language
|
||||||
|
end
|
||||||
|
|
||||||
|
def inject_into(sprockets)
|
||||||
|
super
|
||||||
|
|
||||||
|
require 'flowerbox/tilt/feature_template'
|
||||||
|
|
||||||
|
@sprockets.register_engine('.feature', Flowerbox::Tilt::FeatureTemplate)
|
||||||
|
end
|
||||||
|
|
||||||
|
def global_system_files
|
||||||
|
%w{cucumber.js flowerbox/cucumber}
|
||||||
|
end
|
||||||
|
|
||||||
|
def runner_system_files
|
||||||
|
[ "flowerbox/cucumber/#{@runner.type}" ]
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
super
|
||||||
|
|
||||||
|
<<-JS
|
||||||
|
context.Cucumber = context.require('./cucumber');
|
||||||
|
|
||||||
|
options = {}
|
||||||
|
#{maybe_tags}
|
||||||
|
|
||||||
|
context.cucumber = context.Cucumber(context.Flowerbox.Cucumber.features(), context.Flowerbox.World(), options);
|
||||||
|
context.cucumber.attachListener(new context.Flowerbox.Cucumber.Reporter());
|
||||||
|
context.cucumber.start(function() {});
|
||||||
|
JS
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_tags
|
||||||
|
"options.tags = #{@options[:tags].to_json};" if @options[:tags]
|
||||||
|
end
|
||||||
|
|
||||||
|
def obtain_test_definition_for(result)
|
||||||
|
matcher = result.original_name
|
||||||
|
args = []
|
||||||
|
|
||||||
|
matcher.gsub!(%r{"[^"]+"}) do |_, match|
|
||||||
|
args << "arg#{args.length + 1}"
|
||||||
|
'"([^"]+)"'
|
||||||
|
end
|
||||||
|
|
||||||
|
matcher.gsub!(%r{ \d+ }) do |_, match|
|
||||||
|
args << "arg#{args.length + 1}"
|
||||||
|
" (\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?
|
||||||
|
output << %{Flowerbox.#{result.step_type} /^#{matcher}$/, #{"(#{args_string}) " if !args_string.empty?}->}
|
||||||
|
output += messages.collect { |msg| " # #{msg}" }
|
||||||
|
output << %{ @pending() # add your code here}
|
||||||
|
else
|
||||||
|
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?
|
||||||
|
return true if @step_language == :coffeescript
|
||||||
|
|
||||||
|
coffee_count = @runner.spec_files.inject(0) { |s, n| s += 1 if n[%r{.coffee$}]; s }
|
||||||
|
js_count = @runner.spec_files.inject(0) { |s, n| s += 1 if n[%r{.js$}]; s }
|
||||||
|
|
||||||
|
coffee_count > js_count
|
||||||
|
end
|
||||||
|
|
||||||
|
def plant_source
|
||||||
|
"skel/cucumber"
|
||||||
|
end
|
||||||
|
|
||||||
|
def plant_target
|
||||||
|
"js-features"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
84
lib/flowerbox/test_environment/jasmine.rb
Normal file
84
lib/flowerbox/test_environment/jasmine.rb
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def global_system_files
|
||||||
|
%w{jasmine.js flowerbox/jasmine.js}
|
||||||
|
end
|
||||||
|
|
||||||
|
def runner_system_files
|
||||||
|
[ "flowerbox/jasmine/#{@runner.type}.js" ]
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
super
|
||||||
|
|
||||||
|
<<-JS
|
||||||
|
if (typeof context != 'undefined' && typeof jasmine == 'undefined') {
|
||||||
|
jasmine = context.jasmine;
|
||||||
|
}
|
||||||
|
|
||||||
|
jasmine.getEnv().addReporter(new jasmine.FlowerboxReporter());
|
||||||
|
|
||||||
|
jasmine.getEnv().execute();
|
||||||
|
JS
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
24
lib/flowerbox/tilt/feature_template.rb
Normal file
24
lib/flowerbox/tilt/feature_template.rb
Normal file
@ -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
|
||||||
|
|
50
lib/flowerbox/tilt/instrument_js.rb
Normal file
50
lib/flowerbox/tilt/instrument_js.rb
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
require 'tilt'
|
||||||
|
|
||||||
|
module Flowerbox
|
||||||
|
module Tilt
|
||||||
|
class InstrumentJS < ::Tilt::Template
|
||||||
|
def prepare ; end
|
||||||
|
|
||||||
|
def evaluate(scope, locals, &block)
|
||||||
|
if Flowerbox.instrument_files.include?(file)
|
||||||
|
block_comment = false
|
||||||
|
|
||||||
|
lines = data.lines.to_a
|
||||||
|
|
||||||
|
output = [
|
||||||
|
'if (typeof __$instrument == "undefined") { __$instrument = {} }',
|
||||||
|
"__$instrument['#{file}'] = [];",
|
||||||
|
"__$instrument['#{file}'][#{lines.length - 1}] = null;"
|
||||||
|
]
|
||||||
|
|
||||||
|
prior_comma_end = comma_end = false
|
||||||
|
|
||||||
|
lines_instrumented = []
|
||||||
|
|
||||||
|
code_output = []
|
||||||
|
lines.each_with_index do |line, index|
|
||||||
|
line.rstrip!
|
||||||
|
|
||||||
|
instrument = "__$instrument['#{file}'][#{index}] = 1;"
|
||||||
|
|
||||||
|
if line[%r{; *$}]
|
||||||
|
line = line + instrument
|
||||||
|
lines_instrumented << index
|
||||||
|
end
|
||||||
|
|
||||||
|
code_output << line
|
||||||
|
end
|
||||||
|
|
||||||
|
lines_instrumented.each do |line|
|
||||||
|
output << "__$instrument['#{file}'][#{line}] = 0;"
|
||||||
|
end
|
||||||
|
|
||||||
|
(output + code_output).join("\n")
|
||||||
|
else
|
||||||
|
data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
34
lib/flowerbox/unique_asset_list.rb
Normal file
34
lib/flowerbox/unique_asset_list.rb
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
module Flowerbox
|
||||||
|
class UniqueAssetList < ::Array
|
||||||
|
attr_reader :sprockets
|
||||||
|
|
||||||
|
def initialize(sprockets)
|
||||||
|
super([])
|
||||||
|
|
||||||
|
@sprockets = sprockets
|
||||||
|
|
||||||
|
@included = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def add(files)
|
||||||
|
[ files ].flatten.each do |file|
|
||||||
|
self << file if !included?(file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def <<(file)
|
||||||
|
super(file)
|
||||||
|
|
||||||
|
@included[file.pathname.to_s] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_json
|
||||||
|
collect { |file| sprockets.logical_path_for(file) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def included?(file)
|
||||||
|
@included[file.pathname.to_s]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
3
lib/flowerbox/version.rb
Normal file
3
lib/flowerbox/version.rb
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module Flowerbox
|
||||||
|
VERSION = "0.0.1"
|
||||||
|
end
|
@ -1,5 +0,0 @@
|
|||||||
/* Welcome to Compass. Use this file to write IE specific override styles.
|
|
||||||
* Import this file using the following HTML or equivalent:
|
|
||||||
* <!--[if IE]>
|
|
||||||
* <link href="/stylesheets/ie.css" media="screen, projection" rel="stylesheet" type="text/css" />
|
|
||||||
* <![endif]--> */
|
|
@ -1,3 +0,0 @@
|
|||||||
/* Welcome to Compass. Use this file to define print styles.
|
|
||||||
* Import this file using the following HTML or equivalent:
|
|
||||||
* <link href="/stylesheets/print.css" media="print" rel="stylesheet" type="text/css" /> */
|
|
@ -1,82 +0,0 @@
|
|||||||
/* Welcome to Compass.
|
|
||||||
* In this file you should write your main styles. (or centralize your imports)
|
|
||||||
* Import this file using the following HTML or equivalent:
|
|
||||||
* <link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css" /> */
|
|
||||||
|
|
||||||
@import "compass/reset";
|
|
||||||
@import "compass/css3";
|
|
||||||
|
|
||||||
$base_color: #d9b741;
|
|
||||||
$header_color: #000;
|
|
||||||
$header_blend_color: #3a60e5;
|
|
||||||
|
|
||||||
$code_border_color: $header_blend_color;
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: $base_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container {
|
|
||||||
width: 550px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 1.75em;
|
|
||||||
margin: 0.35em 0;
|
|
||||||
color: mix($header_color, $header_blend_color, 90);
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 1.45em;
|
|
||||||
margin: 0.25em 0;
|
|
||||||
color: mix($header_color, $header_blend_color, 70);
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.1em;
|
|
||||||
margin: 0.2em 0;
|
|
||||||
color: mix($header_color, $header_blend_color, 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3 {
|
|
||||||
text-align: center;
|
|
||||||
font-family: 'Gorditas', 'Helvetica Neue', 'Helvetica';
|
|
||||||
}
|
|
||||||
|
|
||||||
p, li {
|
|
||||||
font-family: 'Pontano Sans', 'Helvetica Neue', 'Helvetica';
|
|
||||||
line-height: 120%;
|
|
||||||
}
|
|
||||||
|
|
||||||
code, pre {
|
|
||||||
font-family: 'Courier New';
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight {
|
|
||||||
font-size: 0.9em;
|
|
||||||
padding: 1em;
|
|
||||||
border: solid $code_border_color 1px;
|
|
||||||
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
em {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0.7em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
list-style-type: disc;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
padding-left: 2em;
|
|
||||||
}
|
|
7
skel/cucumber/features/my_first_feature.feature
Normal file
7
skel/cucumber/features/my_first_feature.feature
Normal file
@ -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 |
|
||||||
|
|
10
skel/cucumber/spec_helper.rb
Normal file
10
skel/cucumber/spec_helper.rb
Normal file
@ -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
|
||||||
|
|
@ -0,0 +1,8 @@
|
|||||||
|
Flowerbox.Given /^I have a flowerbox$/, ->
|
||||||
|
@flowerbox =
|
||||||
|
plantSeed: (type) ->
|
||||||
|
@types ||= []
|
||||||
|
@types.push(type)
|
||||||
|
pick: ->
|
||||||
|
@types
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -0,0 +1,3 @@
|
|||||||
|
Flowerbox.When /^I plant a "([^"]+)" seed$/, (type) ->
|
||||||
|
@flowerbox.plantSeed(type)
|
||||||
|
|
1
skel/cucumber/support/env.js.coffee
Normal file
1
skel/cucumber/support/env.js.coffee
Normal file
@ -0,0 +1 @@
|
|||||||
|
#= require_tree ../step_definitions
|
22
spec/flowerbox/reporter/json_spec.rb
Normal file
22
spec/flowerbox/reporter/json_spec.rb
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
require 'flowerbox/reporter/json'
|
||||||
|
|
||||||
|
describe Flowerbox::Reporter::JSON do
|
||||||
|
let(:json) { described_class.new }
|
||||||
|
|
||||||
|
describe '#report_numeric_results' do
|
||||||
|
let(:results) { 'results' }
|
||||||
|
let(:numeric_results_for) { 'numeric results for' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
json.expects(:numeric_results_for).with(results).returns(numeric_results_for)
|
||||||
|
json.expects(:puts)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should add numeric results' do
|
||||||
|
json.report_numeric_results(results)
|
||||||
|
|
||||||
|
json.output.should == [ [ :results, numeric_results_for ] ]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
131
spec/flowerbox/server_spec.rb
Normal file
131
spec/flowerbox/server_spec.rb
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
require 'socket'
|
||||||
|
require 'thread'
|
||||||
|
|
||||||
|
require 'flowerbox/server'
|
||||||
|
|
||||||
|
describe Flowerbox::Server do
|
||||||
|
let(:server) { described_class.new(runner, options) }
|
||||||
|
let(:options) { nil }
|
||||||
|
let(:runner) { 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 '#app' do
|
||||||
|
subject { server.app }
|
||||||
|
|
||||||
|
context 'no app defined' do
|
||||||
|
before do
|
||||||
|
server.stubs(:options).returns({})
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should raise an error' do
|
||||||
|
expect { server.app }.to raise_error(Flowerbox::Server::MissingRackApp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'app defined' do
|
||||||
|
let(:app) { 'app' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
server.stubs(:options).returns(:app => app)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { should == app }
|
||||||
|
end
|
||||||
|
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)
|
||||||
|
server.stubs(:app).returns(lambda { |env| [ 200, {}, [] ] })
|
||||||
|
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 + 2)
|
||||||
|
|
||||||
|
count = 10
|
||||||
|
while count > 0
|
||||||
|
begin
|
||||||
|
TCPSocket.new(interface, initial)
|
||||||
|
break
|
||||||
|
rescue Errno::ECONNREFUSED
|
||||||
|
count -= 1
|
||||||
|
sleep 0.1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it { should == initial + 2 }
|
||||||
|
|
||||||
|
after do
|
||||||
|
@server.kill
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
96
spec/flowerbox/sprockets_handler_spec.rb
Normal file
96
spec/flowerbox/sprockets_handler_spec.rb
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
require 'flowerbox/sprockets_handler'
|
||||||
|
|
||||||
|
describe Flowerbox::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(:dependent_asset) { 'dependent' }
|
||||||
|
|
||||||
|
let(:pathname_path) { 'pathname path' }
|
||||||
|
let(:files) { stub }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sprockets_handler.expects(:assets_for).with(asset).returns([ dependent_asset ])
|
||||||
|
sprockets_handler.stubs(:files).returns(files)
|
||||||
|
files.expects(:add).with(dependent_asset)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should add the asset to the list of ones to work with' do
|
||||||
|
sprockets_handler.add(asset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#environment' do
|
||||||
|
let(:cache) { stub }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sprockets_handler.stubs(:cache).returns(cache)
|
||||||
|
sprockets_handler.stubs(:default_asset_paths).returns([])
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { sprockets_handler.environment }
|
||||||
|
|
||||||
|
it { should be_a_kind_of(Sprockets::Environment) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#default_asset_paths' do
|
||||||
|
let(:gem) { 'gem' }
|
||||||
|
let(:asset) { 'asset' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
described_class.stubs(:gem_asset_paths).returns([ gem ])
|
||||||
|
sprockets_handler.stubs(:options).returns(:asset_paths => [ asset ])
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { sprockets_handler.default_asset_paths }
|
||||||
|
|
||||||
|
it { should == [ gem, asset ] }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#assets_for' do
|
||||||
|
subject { sprockets_handler.assets_for(asset) }
|
||||||
|
|
||||||
|
let(:asset) { 'asset' }
|
||||||
|
let(:found_asset) { 'found asset' }
|
||||||
|
let(:other_asset) { 'other asset' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sprockets_handler.stubs(:find_asset).returns(found_asset)
|
||||||
|
found_asset.stubs(:to_a).returns(other_asset)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { should == other_asset }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#logical_path_for' do
|
||||||
|
subject { sprockets_handler.logical_path_for(asset) }
|
||||||
|
|
||||||
|
let(:path) { 'path' }
|
||||||
|
let(:result) { 'result' }
|
||||||
|
|
||||||
|
let(:asset) { stub(:pathname => Pathname("#{path}/#{result}")) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sprockets_handler.stubs(:paths).returns(paths)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'found' do
|
||||||
|
let(:paths) { [ path ] }
|
||||||
|
|
||||||
|
it { should == result }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'not found' do
|
||||||
|
let(:paths) { [] }
|
||||||
|
|
||||||
|
it 'should raise an exception' do
|
||||||
|
expect { subject }.to raise_error(Flowerbox::SprocketsHandler::LogicalPathNotFoundError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
47
spec/flowerbox/unique_asset_list_spec.rb
Normal file
47
spec/flowerbox/unique_asset_list_spec.rb
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
require 'flowerbox/unique_asset_list'
|
||||||
|
|
||||||
|
describe Flowerbox::UniqueAssetList do
|
||||||
|
let(:unique_asset_list) { described_class.new(sprockets) }
|
||||||
|
let(:sprockets) { stub }
|
||||||
|
|
||||||
|
describe "#add" do
|
||||||
|
let(:first) { stub(:pathname => Pathname.new('one')) }
|
||||||
|
let(:second) { stub(:pathname => Pathname.new('one')) }
|
||||||
|
let(:third) { stub(:pathname => 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
|
||||||
|
|
||||||
|
describe '#to_json' do
|
||||||
|
subject { unique_asset_list.to_json }
|
||||||
|
|
||||||
|
let(:file) { 'file' }
|
||||||
|
let(:path) { 'path' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
unique_asset_list.replace([ file ])
|
||||||
|
sprockets.expects(:logical_path_for).with(file).returns(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { should == [ path ] }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#<<' do
|
||||||
|
let(:asset) { stub(:pathname => Pathname(path)) }
|
||||||
|
let(:path) { 'path' }
|
||||||
|
|
||||||
|
it 'should add the asset and mark it included' do
|
||||||
|
unique_asset_list << asset
|
||||||
|
|
||||||
|
unique_asset_list.should == [ asset ]
|
||||||
|
unique_asset_list.should be_included(asset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
10
spec/javascripts/spec_helper.rb
Normal file
10
spec/javascripts/spec_helper.rb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Flowerbox.configure do |c|
|
||||||
|
c.test_with :jasmine
|
||||||
|
c.run_with :node
|
||||||
|
|
||||||
|
c.spec_patterns << "*_spec.*"
|
||||||
|
c.spec_patterns << "**/*_spec.*"
|
||||||
|
|
||||||
|
c.test_environment.reporters << "SimpleNodeReporter"
|
||||||
|
end
|
||||||
|
|
6
spec/javascripts/test_spec.js
Normal file
6
spec/javascripts/test_spec.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
describe("cats", function() {
|
||||||
|
it("should hiss", function() {
|
||||||
|
expect("hiss").toEqual("hiss");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
11
spec/spec_helper.rb
Normal file
11
spec/spec_helper.rb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
require 'simplecov'
|
||||||
|
SimpleCov.start
|
||||||
|
|
||||||
|
require 'mocha'
|
||||||
|
require 'fakefs/spec_helpers'
|
||||||
|
|
||||||
|
require 'flowerbox'
|
||||||
|
|
||||||
|
RSpec.configure do |c|
|
||||||
|
c.mock_with :mocha
|
||||||
|
end
|
@ -1,5 +0,0 @@
|
|||||||
/* Welcome to Compass. Use this file to write IE specific override styles.
|
|
||||||
* Import this file using the following HTML or equivalent:
|
|
||||||
* <!--[if IE]>
|
|
||||||
* <link href="/stylesheets/ie.css" media="screen, projection" rel="stylesheet" type="text/css" />
|
|
||||||
* <![endif]--> */
|
|
@ -1,3 +0,0 @@
|
|||||||
/* Welcome to Compass. Use this file to define print styles.
|
|
||||||
* Import this file using the following HTML or equivalent:
|
|
||||||
* <link href="/stylesheets/print.css" media="print" rel="stylesheet" type="text/css" /> */
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user